Repository: arcbit/arcbit-ios Branch: master Commit: 1ac2f8a22c2c Files: 611 Total size: 52.4 MB Directory structure: gitextract_uro7xozv/ ├── .gitignore ├── ArcBit/ │ ├── APIs/ │ │ ├── TLBitcoinListener.swift │ │ ├── TLBlockExplorerAPI.swift │ │ ├── TLBlockchainAPI.swift │ │ ├── TLBlockrAPI.swift │ │ ├── TLExchangeRate.swift │ │ ├── TLInsightAPI.swift │ │ ├── TLNetworking.swift │ │ ├── TLPushTxAPI.swift │ │ ├── TLStealthServerAPI.swift │ │ ├── TLStealthServerConfig.swift │ │ ├── TLStealthWebSocket.swift │ │ └── TLTxFeeAPI.swift │ ├── AppDelegate.swift │ ├── ArcBit-Bridging-Header.h │ ├── ArcBit.entitlements │ ├── Assets/ │ │ └── certs/ │ │ └── live.cer │ ├── Base.lproj/ │ │ ├── LaunchScreen.xib │ │ ├── Localizable.strings │ │ └── Main.storyboard │ ├── External/ │ │ ├── BreadWalletClassesV0.5/ │ │ │ ├── BRKey+BIP38.h │ │ │ ├── BRKey+BIP38.m │ │ │ ├── BRKey.h │ │ │ ├── BRKey.m │ │ │ ├── BRTransaction.h │ │ │ ├── BRTransaction.m │ │ │ ├── NSData+Bitcoin.h │ │ │ ├── NSData+Bitcoin.m │ │ │ ├── NSData+Hash.h │ │ │ ├── NSData+Hash.m │ │ │ ├── NSMutableData+Bitcoin.h │ │ │ ├── NSMutableData+Bitcoin.m │ │ │ ├── NSString+Base58.h │ │ │ ├── NSString+Base58.m │ │ │ └── ccMemory.h │ │ ├── CustomIOS7AlertView/ │ │ │ ├── CustomIOS7AlertView.h │ │ │ └── CustomIOS7AlertView.m │ │ ├── InAppSettingsKit/ │ │ │ ├── Controllers/ │ │ │ │ ├── IASKAppSettingsViewController.h │ │ │ │ ├── IASKAppSettingsViewController.m │ │ │ │ ├── IASKAppSettingsWebViewController.h │ │ │ │ ├── IASKAppSettingsWebViewController.m │ │ │ │ ├── IASKMultipleValueSelection.h │ │ │ │ ├── IASKMultipleValueSelection.m │ │ │ │ ├── IASKSpecifierValuesViewController.h │ │ │ │ ├── IASKSpecifierValuesViewController.m │ │ │ │ └── IASKViewController.h │ │ │ ├── Models/ │ │ │ │ ├── IASKSettingsReader.h │ │ │ │ ├── IASKSettingsReader.m │ │ │ │ ├── IASKSettingsStore.h │ │ │ │ ├── IASKSettingsStore.m │ │ │ │ ├── IASKSettingsStoreFile.h │ │ │ │ ├── IASKSettingsStoreFile.m │ │ │ │ ├── IASKSettingsStoreUserDefaults.h │ │ │ │ ├── IASKSettingsStoreUserDefaults.m │ │ │ │ ├── IASKSpecifier.h │ │ │ │ └── IASKSpecifier.m │ │ │ ├── Resources/ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ └── IASKLocalizable.strings │ │ │ │ ├── de.lproj/ │ │ │ │ │ └── IASKLocalizable.strings │ │ │ │ ├── el.lproj/ │ │ │ │ │ └── IASKLocalizable.strings │ │ │ │ ├── en.lproj/ │ │ │ │ │ └── IASKLocalizable.strings │ │ │ │ ├── es.lproj/ │ │ │ │ │ └── IASKLocalizable.strings │ │ │ │ ├── fr.lproj/ │ │ │ │ │ └── IASKLocalizable.strings │ │ │ │ ├── it.lproj/ │ │ │ │ │ └── IASKLocalizable.strings │ │ │ │ ├── ja.lproj/ │ │ │ │ │ └── IASKLocalizable.strings │ │ │ │ ├── nl.lproj/ │ │ │ │ │ └── IASKLocalizable.strings │ │ │ │ ├── pt-PT.lproj/ │ │ │ │ │ └── IASKLocalizable.strings │ │ │ │ ├── pt.lproj/ │ │ │ │ │ └── IASKLocalizable.strings │ │ │ │ ├── ru.lproj/ │ │ │ │ │ └── IASKLocalizable.strings │ │ │ │ ├── sv.lproj/ │ │ │ │ │ └── IASKLocalizable.strings │ │ │ │ ├── th.lproj/ │ │ │ │ │ └── IASKLocalizable.strings │ │ │ │ ├── tr.lproj/ │ │ │ │ │ └── IASKLocalizable.strings │ │ │ │ └── zh-Hant.lproj/ │ │ │ │ └── IASKLocalizable.strings │ │ │ └── Views/ │ │ │ ├── IASKPSSliderSpecifierViewCell.h │ │ │ ├── IASKPSSliderSpecifierViewCell.m │ │ │ ├── IASKPSTextFieldSpecifierViewCell.h │ │ │ ├── IASKPSTextFieldSpecifierViewCell.m │ │ │ ├── IASKSlider.h │ │ │ ├── IASKSlider.m │ │ │ ├── IASKSwitch.h │ │ │ ├── IASKSwitch.m │ │ │ ├── IASKTextField.h │ │ │ ├── IASKTextField.m │ │ │ ├── IASKTextView.h │ │ │ ├── IASKTextView.m │ │ │ ├── IASKTextViewCell.h │ │ │ └── IASKTextViewCell.m │ │ ├── JNKeychain-master/ │ │ │ ├── JNKeychain.h │ │ │ └── JNKeychain.m │ │ ├── KeychainItemWrapper/ │ │ │ ├── KeychainItemWrapper.h │ │ │ └── KeychainItemWrapper.m │ │ ├── LTHPasscodeViewController3.50/ │ │ │ ├── LTHKeychainUtils.h │ │ │ ├── LTHKeychainUtils.m │ │ │ ├── LTHPasscodeViewController.h │ │ │ └── LTHPasscodeViewController.m │ │ ├── Localizations/ │ │ │ ├── de.lproj/ │ │ │ │ └── LTHPasscodeViewController.strings │ │ │ ├── en.lproj/ │ │ │ │ └── LTHPasscodeViewController.strings │ │ │ ├── es.lproj/ │ │ │ │ └── LTHPasscodeViewController.strings │ │ │ ├── fr.lproj/ │ │ │ │ └── LTHPasscodeViewController.strings │ │ │ ├── ja.lproj/ │ │ │ │ └── LTHPasscodeViewController.strings │ │ │ ├── ro.lproj/ │ │ │ │ └── LTHPasscodeViewController.strings │ │ │ ├── ru.lproj/ │ │ │ │ └── LTHPasscodeViewController.strings │ │ │ ├── zh-Hans-CN.lproj/ │ │ │ │ └── LTHPasscodeViewController.strings │ │ │ └── zh-Hant.lproj/ │ │ │ └── LTHPasscodeViewController.strings │ │ ├── MySocketRocketExtras/ │ │ │ ├── SRWebSocket+Helpers.h │ │ │ └── SRWebSocket+Helpers.m │ │ ├── NSDate-Extensions/ │ │ │ ├── NSDate-Utilities.h │ │ │ └── NSDate-Utilities.m │ │ ├── QRCodeEncoderObjectiveCAtGithub/ │ │ │ ├── DataMatrix.h │ │ │ ├── DataMatrix.mm │ │ │ ├── QRCodeEncoderDemoViewController.h │ │ │ ├── QRCodeEncoderDemoViewController.mm │ │ │ ├── QRCodeEncoderObjectiveCAtGithub-Prefix.pch │ │ │ ├── QREncoder-Prefix.pch │ │ │ ├── QREncoder.h │ │ │ ├── QREncoder.mm │ │ │ ├── QR_Encode.cpp │ │ │ └── QR_Encode.h │ │ ├── SocketRocket/ │ │ │ ├── NSData+SRB64Additions.h │ │ │ ├── NSData+SRB64Additions.m │ │ │ ├── SRWebSocket.h │ │ │ ├── SRWebSocket.m │ │ │ ├── SocketRocket-Prefix.pch │ │ │ ├── base64.c │ │ │ └── base64.h │ │ ├── TLCloudDocumentSyncWrapper/ │ │ │ ├── TLCloudDocumentSyncWrapper.h │ │ │ └── TLCloudDocumentSyncWrapper.m │ │ ├── UIAlertController+Blocks/ │ │ │ ├── UIAlertConrtoller+Blocks.m │ │ │ └── UIAlertController+Blocks.h │ │ ├── UINavigationBar-FixedHeightWhenStatusBarHidden-master/ │ │ │ ├── UINavigationBar+FixedHeightWhenStatusBarHidden.h │ │ │ └── UINavigationBar+FixedHeightWhenStatusBarHidden.m │ │ ├── iToast/ │ │ │ ├── iToast.h │ │ │ └── iToast.m │ │ └── socket.io-client-swift-10.0.0/ │ │ └── Source/ │ │ ├── SSLSecurity.swift │ │ ├── SocketAckEmitter.swift │ │ ├── SocketAckManager.swift │ │ ├── SocketAnyEvent.swift │ │ ├── SocketClientManager.swift │ │ ├── SocketEngine.swift │ │ ├── SocketEngineClient.swift │ │ ├── SocketEnginePacketType.swift │ │ ├── SocketEnginePollable.swift │ │ ├── SocketEngineSpec.swift │ │ ├── SocketEngineWebsocket.swift │ │ ├── SocketEventHandler.swift │ │ ├── SocketExtensions.swift │ │ ├── SocketIOClient.swift │ │ ├── SocketIOClientConfiguration.swift │ │ ├── SocketIOClientOption.swift │ │ ├── SocketIOClientSpec.swift │ │ ├── SocketIOClientStatus.swift │ │ ├── SocketLogger.swift │ │ ├── SocketPacket.swift │ │ ├── SocketParsable.swift │ │ ├── SocketStringReader.swift │ │ ├── SocketTypes.swift │ │ └── WebSocket.swift │ ├── Images.xcassets/ │ │ ├── AppIcon.appiconset/ │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── home3.imageset/ │ │ │ └── Contents.json │ │ ├── lifebuoy.imageset/ │ │ │ └── Contents.json │ │ ├── link.imageset/ │ │ │ └── Contents.json │ │ ├── twitter.imageset/ │ │ │ └── Contents.json │ │ └── vault.imageset/ │ │ └── Contents.json │ ├── InAppSettings.bundle/ │ │ ├── Advanced.plist │ │ ├── Root.plist │ │ ├── de.lproj/ │ │ │ └── Root.strings │ │ ├── en.lproj/ │ │ │ └── Root.strings │ │ ├── es.lproj/ │ │ │ └── Root.strings │ │ ├── ru.lproj/ │ │ │ └── Root.strings │ │ ├── zh-Hans-CN.lproj/ │ │ │ └── Root.strings │ │ └── zh-Hant.lproj/ │ │ └── Root.strings │ ├── Info.plist │ ├── de.lproj/ │ │ └── Localizable.strings │ ├── es.lproj/ │ │ └── Localizable.strings │ ├── model/ │ │ ├── TLAccountObject.swift │ │ ├── TLAccounts.swift │ │ ├── TLAchievements.swift │ │ ├── TLAnalytics.swift │ │ ├── TLBlockchainStatus.swift │ │ ├── TLCoin.swift │ │ ├── TLColdWallet.swift │ │ ├── TLCoreBitcoinWrapper.swift │ │ ├── TLCrypto.swift │ │ ├── TLCurrencyFormat.swift │ │ ├── TLDisplayStrings.swift │ │ ├── TLHDWalletWrapper.swift │ │ ├── TLHelpDoc.swift │ │ ├── TLImportedAddress.swift │ │ ├── TLImportedAddresses.swift │ │ ├── TLOperationsManager.swift │ │ ├── TLPreferences.swift │ │ ├── TLSelectedObject.swift │ │ ├── TLSendFormData.swift │ │ ├── TLSpaghettiGodSend.swift │ │ ├── TLStealthAddress.swift │ │ ├── TLStealthWallet.swift │ │ ├── TLSuggestions.swift │ │ ├── TLTxObject.swift │ │ ├── TLWallet+Stealth.swift │ │ ├── TLWallet.swift │ │ ├── TLWalletConfig.swift │ │ ├── TLWalletJSONKeys.swift │ │ ├── TLWalletJson.swift │ │ ├── TLWalletPassphrase.swift │ │ └── TLWalletUtils.swift │ ├── ru.lproj/ │ │ ├── Localizable.strings │ │ └── Main.strings │ ├── utils/ │ │ ├── TLColors.swift │ │ ├── TLHUDWrapper.swift │ │ ├── TLMacros.swift │ │ ├── TLNotificationEvents.swift │ │ ├── TLPrompts.swift │ │ ├── TLQRImageModal.swift │ │ ├── TLStringLocalized.swift │ │ ├── TLUpdateAppData.swift │ │ ├── TLUtils.swift │ │ ├── TransitionDelegate.swift │ │ ├── UINavigationController+StatusBarStyle.swift │ │ ├── UIView+FormScroll.swift │ │ ├── UIViewController+Extras.swift │ │ └── UIViewController+Style.swift │ ├── viewControllers/ │ │ ├── TLAccountTableViewCell.swift │ │ ├── TLAccountsViewController.swift │ │ ├── TLAchievementsViewController.swift │ │ ├── TLAddressBookViewController.swift │ │ ├── TLAddressListViewController.swift │ │ ├── TLAddressTableViewCell.swift │ │ ├── TLAuthorizeColdWalletPaymentViewController.swift │ │ ├── TLBrainWalletViewController.swift │ │ ├── TLColdWalletViewController.swift │ │ ├── TLCreateColdWalletViewController.swift │ │ ├── TLHelpViewController.swift │ │ ├── TLHistoryViewController.swift │ │ ├── TLInstructionsViewController.swift │ │ ├── TLLinksViewController.swift │ │ ├── TLManageAccountsViewController.swift │ │ ├── TLMenuViewController.swift │ │ ├── TLPassPhraseViewController.swift │ │ ├── TLPreloadViewController.swift │ │ ├── TLQRCodeScannerViewController.swift │ │ ├── TLReceiveViewController.swift │ │ ├── TLRestoreWalletViewController.swift │ │ ├── TLReviewPaymentViewController.swift │ │ ├── TLSendViewController.swift │ │ ├── TLSettingsViewController.swift │ │ ├── TLTextViewViewController.swift │ │ ├── TLTransactionTableViewCell.swift │ │ ├── TransparentViewController.swift │ │ └── tableViewCells/ │ │ ├── createColdWalletTableViewCells/ │ │ │ ├── TLAdvancedNewWalletTableViewCell.swift │ │ │ ├── TLColdWalletSelectWayTableViewCell.swift │ │ │ └── TLNewWalletTableViewCell.swift │ │ ├── spendColdWalletTableViewCells/ │ │ │ ├── TLInputColdWalletKeyTableViewCell.swift │ │ │ ├── TLPassSignedTxTableViewCell.swift │ │ │ └── TLScanUnsignedTxTableViewCell.swift │ │ └── walletTableViewCells/ │ │ ├── TLAccountTableViewCell.swift │ │ ├── TLAddressTableViewCell.swift │ │ └── TLTransactionTableViewCell.swift │ ├── zh-Hans-CN.lproj/ │ │ └── Localizable.strings │ └── zh-Hant.lproj/ │ ├── LaunchScreen.strings │ ├── Localizable.strings │ └── Main.strings ├── ArcBit.xcodeproj/ │ ├── project.pbxproj │ └── project.xcworkspace/ │ └── contents.xcworkspacedata ├── ArcBit.xcworkspace/ │ └── contents.xcworkspacedata ├── ArcBitTests/ │ ├── ArcBitTests-Bridging-Header.h │ ├── ArcBitTests.swift │ ├── BreadWalletTests.m │ └── Info.plist ├── Podfile ├── Pods/ │ ├── AFNetworking/ │ │ ├── AFNetworking/ │ │ │ ├── AFHTTPRequestOperation.h │ │ │ ├── AFHTTPRequestOperation.m │ │ │ ├── AFHTTPRequestOperationManager.h │ │ │ ├── AFHTTPRequestOperationManager.m │ │ │ ├── AFHTTPSessionManager.h │ │ │ ├── AFHTTPSessionManager.m │ │ │ ├── AFNetworkReachabilityManager.h │ │ │ ├── AFNetworkReachabilityManager.m │ │ │ ├── AFNetworking.h │ │ │ ├── AFSecurityPolicy.h │ │ │ ├── AFSecurityPolicy.m │ │ │ ├── AFURLConnectionOperation.h │ │ │ ├── AFURLConnectionOperation.m │ │ │ ├── AFURLRequestSerialization.h │ │ │ ├── AFURLRequestSerialization.m │ │ │ ├── AFURLResponseSerialization.h │ │ │ ├── AFURLResponseSerialization.m │ │ │ ├── AFURLSessionManager.h │ │ │ └── AFURLSessionManager.m │ │ ├── LICENSE │ │ ├── README.md │ │ └── UIKit+AFNetworking/ │ │ ├── AFNetworkActivityIndicatorManager.h │ │ ├── AFNetworkActivityIndicatorManager.m │ │ ├── UIActivityIndicatorView+AFNetworking.h │ │ ├── UIActivityIndicatorView+AFNetworking.m │ │ ├── UIAlertView+AFNetworking.h │ │ ├── UIAlertView+AFNetworking.m │ │ ├── UIButton+AFNetworking.h │ │ ├── UIButton+AFNetworking.m │ │ ├── UIImageView+AFNetworking.h │ │ ├── UIImageView+AFNetworking.m │ │ ├── UIKit+AFNetworking.h │ │ ├── UIProgressView+AFNetworking.h │ │ ├── UIProgressView+AFNetworking.m │ │ ├── UIRefreshControl+AFNetworking.h │ │ ├── UIRefreshControl+AFNetworking.m │ │ ├── UIWebView+AFNetworking.h │ │ └── UIWebView+AFNetworking.m │ ├── CoreBitcoin/ │ │ ├── CoreBitcoin/ │ │ │ ├── BTC256.h │ │ │ ├── BTC256.m │ │ │ ├── BTCAddress.h │ │ │ ├── BTCAddress.m │ │ │ ├── BTCAddressSubclass.h │ │ │ ├── BTCAssetAddress.h │ │ │ ├── BTCAssetAddress.m │ │ │ ├── BTCAssetID.h │ │ │ ├── BTCAssetID.m │ │ │ ├── BTCAssetType.h │ │ │ ├── BTCAssetType.m │ │ │ ├── BTCBase58.h │ │ │ ├── BTCBase58.m │ │ │ ├── BTCBigNumber.h │ │ │ ├── BTCBigNumber.m │ │ │ ├── BTCBitcoinURL.h │ │ │ ├── BTCBitcoinURL.m │ │ │ ├── BTCBlindSignature.h │ │ │ ├── BTCBlindSignature.m │ │ │ ├── BTCBlock.h │ │ │ ├── BTCBlock.m │ │ │ ├── BTCBlockHeader.h │ │ │ ├── BTCBlockHeader.m │ │ │ ├── BTCBlockchainInfo.h │ │ │ ├── BTCBlockchainInfo.m │ │ │ ├── BTCChainCom.h │ │ │ ├── BTCChainCom.m │ │ │ ├── BTCCurrencyConverter.h │ │ │ ├── BTCCurrencyConverter.m │ │ │ ├── BTCCurvePoint.h │ │ │ ├── BTCCurvePoint.m │ │ │ ├── BTCData.h │ │ │ ├── BTCData.m │ │ │ ├── BTCEncryptedBackup.h │ │ │ ├── BTCEncryptedBackup.m │ │ │ ├── BTCEncryptedMessage.h │ │ │ ├── BTCEncryptedMessage.m │ │ │ ├── BTCErrors.h │ │ │ ├── BTCErrors.m │ │ │ ├── BTCFancyEncryptedMessage.h │ │ │ ├── BTCFancyEncryptedMessage.m │ │ │ ├── BTCHashID.h │ │ │ ├── BTCHashID.m │ │ │ ├── BTCKey.h │ │ │ ├── BTCKey.m │ │ │ ├── BTCKeychain.h │ │ │ ├── BTCKeychain.m │ │ │ ├── BTCMerkleTree.h │ │ │ ├── BTCMerkleTree.m │ │ │ ├── BTCMnemonic.h │ │ │ ├── BTCMnemonic.m │ │ │ ├── BTCNetwork.h │ │ │ ├── BTCNetwork.m │ │ │ ├── BTCNumberFormatter.h │ │ │ ├── BTCNumberFormatter.m │ │ │ ├── BTCOpcode.h │ │ │ ├── BTCOpcode.m │ │ │ ├── BTCOutpoint.h │ │ │ ├── BTCOutpoint.m │ │ │ ├── BTCPaymentMethod.h │ │ │ ├── BTCPaymentMethod.m │ │ │ ├── BTCPaymentMethodDetails.h │ │ │ ├── BTCPaymentMethodDetails.m │ │ │ ├── BTCPaymentMethodRequest.h │ │ │ ├── BTCPaymentMethodRequest.m │ │ │ ├── BTCPaymentProtocol.h │ │ │ ├── BTCPaymentProtocol.m │ │ │ ├── BTCPaymentRequest.h │ │ │ ├── BTCPaymentRequest.m │ │ │ ├── BTCPriceSource.h │ │ │ ├── BTCPriceSource.m │ │ │ ├── BTCProcessor.h │ │ │ ├── BTCProcessor.m │ │ │ ├── BTCProtocolBuffers.h │ │ │ ├── BTCProtocolBuffers.m │ │ │ ├── BTCProtocolSerialization.h │ │ │ ├── BTCProtocolSerialization.m │ │ │ ├── BTCQRCode.h │ │ │ ├── BTCQRCode.m │ │ │ ├── BTCScript.h │ │ │ ├── BTCScript.m │ │ │ ├── BTCScriptMachine.h │ │ │ ├── BTCScriptMachine.m │ │ │ ├── BTCSecretSharing.h │ │ │ ├── BTCSecretSharing.m │ │ │ ├── BTCSignatureHashType.h │ │ │ ├── BTCTransaction.h │ │ │ ├── BTCTransaction.m │ │ │ ├── BTCTransactionBuilder.h │ │ │ ├── BTCTransactionBuilder.m │ │ │ ├── BTCTransactionInput.h │ │ │ ├── BTCTransactionInput.m │ │ │ ├── BTCTransactionOutput.h │ │ │ ├── BTCTransactionOutput.m │ │ │ ├── BTCUnitsAndLimits.h │ │ │ ├── CoreBitcoin+Categories.h │ │ │ ├── CoreBitcoin.h │ │ │ ├── NS+BTCBase58.h │ │ │ ├── NS+BTCBase58.m │ │ │ ├── NSData+BTCData.h │ │ │ ├── NSData+BTCData.m │ │ │ └── SwiftBridgingHeader.h │ │ ├── LICENSE.txt │ │ └── README.md │ ├── Crashlytics/ │ │ ├── Crashlytics.framework/ │ │ │ ├── README │ │ │ └── submit │ │ ├── README.md │ │ ├── iOS/ │ │ │ └── Crashlytics.framework/ │ │ │ ├── Crashlytics │ │ │ ├── Headers/ │ │ │ │ ├── ANSCompatibility.h │ │ │ │ ├── Answers.h │ │ │ │ ├── CLSAttributes.h │ │ │ │ ├── CLSLogging.h │ │ │ │ ├── CLSReport.h │ │ │ │ ├── CLSStackFrame.h │ │ │ │ └── Crashlytics.h │ │ │ ├── Info.plist │ │ │ ├── Modules/ │ │ │ │ └── module.modulemap │ │ │ ├── run │ │ │ ├── submit │ │ │ └── uploadDSYM │ │ └── submit │ ├── ECSlidingViewController/ │ │ ├── ECSlidingViewController/ │ │ │ ├── ECPercentDrivenInteractiveTransition.h │ │ │ ├── ECPercentDrivenInteractiveTransition.m │ │ │ ├── ECSlidingAnimationController.h │ │ │ ├── ECSlidingAnimationController.m │ │ │ ├── ECSlidingConstants.h │ │ │ ├── ECSlidingInteractiveTransition.h │ │ │ ├── ECSlidingInteractiveTransition.m │ │ │ ├── ECSlidingSegue.h │ │ │ ├── ECSlidingSegue.m │ │ │ ├── ECSlidingViewController.h │ │ │ ├── ECSlidingViewController.m │ │ │ ├── UIViewController+ECSlidingViewController.h │ │ │ └── UIViewController+ECSlidingViewController.m │ │ ├── LICENSE │ │ └── README.md │ ├── Fabric/ │ │ ├── Fabric.framework/ │ │ │ ├── README │ │ │ └── run │ │ ├── README.md │ │ ├── iOS/ │ │ │ └── Fabric.framework/ │ │ │ ├── Fabric │ │ │ ├── Headers/ │ │ │ │ ├── FABAttributes.h │ │ │ │ └── Fabric.h │ │ │ ├── Info.plist │ │ │ ├── Modules/ │ │ │ │ └── module.modulemap │ │ │ ├── run │ │ │ └── uploadDSYM │ │ ├── run │ │ ├── upload-symbols │ │ └── uploadDSYM │ ├── ISO8601DateFormatter/ │ │ ├── ISO8601DateFormatter.h │ │ ├── ISO8601DateFormatter.m │ │ ├── LICENSE.txt │ │ └── README.md │ ├── Local Podspecs/ │ │ └── CoreBitcoin.podspec.json │ ├── MBProgressHUD/ │ │ ├── LICENSE │ │ ├── MBProgressHUD.h │ │ ├── MBProgressHUD.m │ │ └── README.mdown │ ├── OpenSSL-Universal/ │ │ ├── LICENSE.txt │ │ ├── README.md │ │ ├── include-ios/ │ │ │ └── openssl/ │ │ │ ├── aes.h │ │ │ ├── asn1.h │ │ │ ├── asn1_mac.h │ │ │ ├── asn1t.h │ │ │ ├── bio.h │ │ │ ├── blowfish.h │ │ │ ├── bn.h │ │ │ ├── buffer.h │ │ │ ├── camellia.h │ │ │ ├── cast.h │ │ │ ├── cmac.h │ │ │ ├── cms.h │ │ │ ├── comp.h │ │ │ ├── conf.h │ │ │ ├── conf_api.h │ │ │ ├── crypto.h │ │ │ ├── des.h │ │ │ ├── des_old.h │ │ │ ├── dh.h │ │ │ ├── dsa.h │ │ │ ├── dso.h │ │ │ ├── dtls1.h │ │ │ ├── e_os2.h │ │ │ ├── ebcdic.h │ │ │ ├── ec.h │ │ │ ├── ecdh.h │ │ │ ├── ecdsa.h │ │ │ ├── engine.h │ │ │ ├── err.h │ │ │ ├── evp.h │ │ │ ├── hmac.h │ │ │ ├── idea.h │ │ │ ├── krb5_asn.h │ │ │ ├── kssl.h │ │ │ ├── lhash.h │ │ │ ├── md4.h │ │ │ ├── md5.h │ │ │ ├── mdc2.h │ │ │ ├── modes.h │ │ │ ├── obj_mac.h │ │ │ ├── objects.h │ │ │ ├── ocsp.h │ │ │ ├── opensslconf.h │ │ │ ├── opensslv.h │ │ │ ├── ossl_typ.h │ │ │ ├── pem.h │ │ │ ├── pem2.h │ │ │ ├── pkcs12.h │ │ │ ├── pkcs7.h │ │ │ ├── pqueue.h │ │ │ ├── rand.h │ │ │ ├── rc2.h │ │ │ ├── rc4.h │ │ │ ├── ripemd.h │ │ │ ├── rsa.h │ │ │ ├── safestack.h │ │ │ ├── seed.h │ │ │ ├── sha.h │ │ │ ├── srp.h │ │ │ ├── srtp.h │ │ │ ├── ssl.h │ │ │ ├── ssl2.h │ │ │ ├── ssl23.h │ │ │ ├── ssl3.h │ │ │ ├── stack.h │ │ │ ├── symhacks.h │ │ │ ├── tls1.h │ │ │ ├── ts.h │ │ │ ├── txt_db.h │ │ │ ├── ui.h │ │ │ ├── ui_compat.h │ │ │ ├── whrlpool.h │ │ │ ├── x509.h │ │ │ ├── x509_vfy.h │ │ │ └── x509v3.h │ │ └── lib-ios/ │ │ ├── libcrypto.a │ │ └── libssl.a │ ├── Pods.xcodeproj/ │ │ └── project.pbxproj │ ├── RNCryptor/ │ │ ├── README.md │ │ └── RNCryptor/ │ │ ├── RNCryptor+Private.h │ │ ├── RNCryptor.h │ │ ├── RNCryptor.m │ │ ├── RNCryptorEngine.h │ │ ├── RNCryptorEngine.m │ │ ├── RNDecryptor.h │ │ ├── RNDecryptor.m │ │ ├── RNEncryptor.h │ │ ├── RNEncryptor.m │ │ ├── RNOpenSSLCryptor.h │ │ ├── RNOpenSSLCryptor.m │ │ ├── RNOpenSSLDecryptor.h │ │ ├── RNOpenSSLDecryptor.m │ │ ├── RNOpenSSLEncryptor.h │ │ └── RNOpenSSLEncryptor.m │ ├── SwiftTryCatch/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── SwiftTryCatch.h │ │ └── SwiftTryCatch.m │ ├── Target Support Files/ │ │ ├── AFNetworking/ │ │ │ ├── AFNetworking-dummy.m │ │ │ ├── AFNetworking-prefix.pch │ │ │ └── AFNetworking.xcconfig │ │ ├── CoreBitcoin/ │ │ │ ├── CoreBitcoin-dummy.m │ │ │ ├── CoreBitcoin-prefix.pch │ │ │ └── CoreBitcoin.xcconfig │ │ ├── ECSlidingViewController/ │ │ │ ├── ECSlidingViewController-dummy.m │ │ │ ├── ECSlidingViewController-prefix.pch │ │ │ └── ECSlidingViewController.xcconfig │ │ ├── ISO8601DateFormatter/ │ │ │ ├── ISO8601DateFormatter-dummy.m │ │ │ ├── ISO8601DateFormatter-prefix.pch │ │ │ └── ISO8601DateFormatter.xcconfig │ │ ├── MBProgressHUD/ │ │ │ ├── MBProgressHUD-dummy.m │ │ │ ├── MBProgressHUD-prefix.pch │ │ │ └── MBProgressHUD.xcconfig │ │ ├── Pods-ArcBit/ │ │ │ ├── Pods-ArcBit-acknowledgements.markdown │ │ │ ├── Pods-ArcBit-acknowledgements.plist │ │ │ ├── Pods-ArcBit-dummy.m │ │ │ ├── Pods-ArcBit-frameworks.sh │ │ │ ├── Pods-ArcBit-resources.sh │ │ │ ├── Pods-ArcBit.debug.xcconfig │ │ │ └── Pods-ArcBit.release.xcconfig │ │ ├── Pods-ArcBitTests/ │ │ │ ├── Pods-ArcBitTests-acknowledgements.markdown │ │ │ ├── Pods-ArcBitTests-acknowledgements.plist │ │ │ ├── Pods-ArcBitTests-dummy.m │ │ │ ├── Pods-ArcBitTests-frameworks.sh │ │ │ ├── Pods-ArcBitTests-resources.sh │ │ │ ├── Pods-ArcBitTests.debug.xcconfig │ │ │ └── Pods-ArcBitTests.release.xcconfig │ │ ├── RNCryptor/ │ │ │ ├── RNCryptor-dummy.m │ │ │ ├── RNCryptor-prefix.pch │ │ │ └── RNCryptor.xcconfig │ │ ├── SwiftTryCatch/ │ │ │ ├── SwiftTryCatch-dummy.m │ │ │ ├── SwiftTryCatch-prefix.pch │ │ │ └── SwiftTryCatch.xcconfig │ │ └── iCloudDocumentSync/ │ │ ├── iCloudDocumentSync-dummy.m │ │ ├── iCloudDocumentSync-prefix.pch │ │ └── iCloudDocumentSync.xcconfig │ └── iCloudDocumentSync/ │ ├── LICENSE.md │ ├── README.md │ └── iCloud/ │ ├── iCloud-Prefix.pch │ ├── iCloud.h │ ├── iCloud.m │ ├── iCloudDocument.h │ └── iCloudDocument.m └── README.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Xcode # build/ *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata *.xccheckout *.moved-aside DerivedData *.hmap *.ipa *.xcuserstate .DS_Store # CocoaPods # # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control # # Pods/ # Carthage # # Add this line if you want to avoid checking in source code from Carthage dependencies. # Carthage/Checkouts Carthage/Build ArcBit.xcworkspace/xcshareddata/ ================================================ FILE: ArcBit/APIs/TLBitcoinListener.swift ================================================ // // TLBitcoinListener.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation @objc class TLTransactionListener: NSObject, SRWebSocketDelegate { let MAX_CONSECUTIVE_FAILED_CONNECTIONS = 5 let SEND_EMPTY_PACKET_TIME_INTERVAL = 60.0 fileprivate var blockExplorerAPI: TLBlockExplorer? fileprivate var keepAliveTimer: Timer? fileprivate var socket: SocketIOClient? fileprivate var socketIsConnected: Bool = false fileprivate var webSocket: SRWebSocket? var consecutiveFailedConnections = 0 struct STATIC_MEMBERS { static var instance: TLTransactionListener? } class func instance() -> (TLTransactionListener) { if (STATIC_MEMBERS.instance == nil) { STATIC_MEMBERS.instance = TLTransactionListener() } return STATIC_MEMBERS.instance! } override init() { super.init() blockExplorerAPI = TLPreferences.getBlockExplorerAPI() } func reconnect() -> () { if (blockExplorerAPI == TLBlockExplorer.blockchain) { DLog("websocket reconnect blockchain.info") self.webSocket?.delegate = nil self.webSocket?.close() self.webSocket = SRWebSocket(urlRequest: URLRequest(url: URL(string: "wss://ws.blockchain.info/inv")!)) self.webSocket?.delegate = self self.webSocket?.open() } else { DLog("websocket reconnect insight") let url = String(format: "%@", TLPreferences.getBlockExplorerURL(TLBlockExplorer.insight)!) self.socket = SocketIOClient(socketURL: URL(string: url)!, config: [.log(false), .forcePolling(true)]) weak var weakSelf = self self.socket?.on("connect") {data, ack in DLog("socketio onConnect") self.consecutiveFailedConnections = 0 weakSelf!.socketIsConnected = true NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_TRANSACTION_LISTENER_OPEN()), object: nil, userInfo: nil) weakSelf!.socket!.emit("subscribe", "inv") } self.socket?.on("disconnect") {data, ack in DLog("socketio onDisconnect") NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_TRANSACTION_LISTENER_CLOSE()), object: nil, userInfo: nil) if self.consecutiveFailedConnections < self.MAX_CONSECUTIVE_FAILED_CONNECTIONS { self.reconnect() } self.consecutiveFailedConnections += 1 } self.socket?.on("error") {data, ack in DLog("socketio error: \(data as AnyObject)") } self.socket?.on("block") {data, ack in let dataArray = data as NSArray let firstObject: AnyObject? = dataArray.firstObject as AnyObject? // data!.debugDescription is lastest block hash // can't use this to update confirmations on transactions because insight tx does not contain blockheight field DLog("socketio received lastest block hash: \(firstObject!.debugDescription)") } // self.socket?.on("tx") {data, ack in // DLog("socketio__ tx \(data)") // } self.socket?.connect() } } func isWebSocketOpen() -> Bool { if (blockExplorerAPI == TLBlockExplorer.blockchain) { guard let webSocket = self.webSocket else { return false } return webSocket.readyState.rawValue == SR_OPEN.rawValue } else { return self.socketIsConnected } } fileprivate func sendWebSocketMessage(_ msg: String) -> Bool { DLog("sendWebSocketMessage msg: \(msg)") if self.isWebSocketOpen() { self.webSocket?.send(msg) return true } else { DLog("Websocket Error: not connect to websocket server") return false } } @discardableResult func listenToIncomingTransactionForAddress(_ address: String) -> Bool { //DLog("listen address: %@", address) if (blockExplorerAPI == TLBlockExplorer.blockchain) { if self.isWebSocketOpen() { let msg = String(format: "{\"op\":\"addr_sub\", \"addr\":\"%@\"}", address) self.sendWebSocketMessage(msg) return true } else { DLog("Websocket Error: not connect to websocket server") return false } } else { if (self.socketIsConnected) { guard let socket = self.socket else { return false } //DLog("socketio emit address: \(address)") socket.emit("unsubscribe", "bitcoind/addresstxid", [address]) socket.emit("subscribe", "bitcoind/addresstxid", [address]) socket.on("bitcoind/addresstxid") {data, ack in DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.high).async { DLog("socketio on data: \(data)") let dataArray = data as NSArray let dataDictionary = dataArray.firstObject as! NSDictionary let addr = dataDictionary["address"] as! String //bad api design, this on is not address specific, will call for every subscribe address if (addr == address) { let txHash = dataDictionary["txid"] as! String //DLog("socketio on address: \(addr)") //DLog("socketio transaction: \(txHash)") TLBlockExplorerAPI.instance().getTx(txHash, success: { (txDict: AnyObject?) in if let txDict = txDict { NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_NEW_UNCONFIRMED_TRANSACTION()), object: txDict, userInfo: nil) } }, failure: { (code, status) in }) } } } return true } else { return false } } } func close() -> () { if (blockExplorerAPI == TLBlockExplorer.blockchain) { DLog("closing blockchain.info websocket") self.webSocket?.close() } else { DLog("closing socketio") self.socket?.disconnect() } } fileprivate func keepAlive() -> () { keepAliveTimer?.invalidate() keepAliveTimer = nil keepAliveTimer = Timer.scheduledTimer(timeInterval: SEND_EMPTY_PACKET_TIME_INTERVAL, target: self, selector: #selector(TLTransactionListener.sendEmptyPacket), userInfo: nil, repeats: true) } func sendEmptyPacket() -> () { DLog("blockchain.info Websocket sendEmptyPacket") if self.isWebSocketOpen() { self.sendWebSocketMessage("") } } func webSocketDidOpen(_ webSocket: SRWebSocket) -> () { DLog("blockchain.info webSocketDidOpen") consecutiveFailedConnections = 0 self.sendWebSocketMessage("{\"op\":\"blocks_sub\"}") self.keepAlive() NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_TRANSACTION_LISTENER_OPEN()), object: nil, userInfo: nil) } func webSocket(_ webSocket:SRWebSocket, didFailWithError error:NSError) -> () { DLog("blockchain.info Websocket didFailWithError \(error.description)") self.webSocket?.delegate = nil self.webSocket?.close() self.webSocket = nil if consecutiveFailedConnections < MAX_CONSECUTIVE_FAILED_CONNECTIONS { self.reconnect() } consecutiveFailedConnections += 1 } public func webSocket(_ webSocket: SRWebSocket!, didReceiveMessage message: Any!) { DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.high).async { let data = (message as AnyObject).data(using: String.Encoding.utf8.rawValue) let jsonDict = (try! JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions(rawValue: 0))) as! NSDictionary DLog("blockchain.info didReceiveMessage \(jsonDict.description)") if (jsonDict.object(forKey: "op") as! String == "utx") { NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_NEW_UNCONFIRMED_TRANSACTION()), object: jsonDict.object(forKey: "x"), userInfo: nil) } else if (jsonDict.object(forKey: "op") as! String == "block") { NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_NEW_BLOCK()), object: jsonDict.object(forKey: "x"), userInfo: nil) } } } func webSocket(_ webSocket: SRWebSocket, didCloseWithCode code: Int, reason: String, wasClean: Bool) -> () { if wasClean { DLog("blockchain.info Websocket didCloseWithCode With No Error \(code) \(reason)") } else { DLog("blockchain.info Websocket didCloseWithCode With Error \(code) \(reason)") } self.webSocket?.delegate = nil self.webSocket?.close() self.webSocket = nil if consecutiveFailedConnections < MAX_CONSECUTIVE_FAILED_CONNECTIONS { self.reconnect() } consecutiveFailedConnections += 1 NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_TRANSACTION_LISTENER_CLOSE()), object: nil, userInfo: nil) } } ================================================ FILE: ArcBit/APIs/TLBlockExplorerAPI.swift ================================================ // // TLBlockExplorerAPI.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation enum TLBlockExplorer:Int { case blockchain = 0 case insight = 1 case toshi = 2 case blockr = 3 } class TLBlockExplorerAPI { struct STATIC_MEMBERS { static var blockExplorerAPI:TLBlockExplorer = .blockchain static var BLOCKEXPLORER_BASE_URL:String? = "https://blockchain.info/" static var _instance:TLBlockExplorerAPI? = nil } var blockchainAPI:TLBlockchainAPI? = nil var insightAPI:TLInsightAPI? = nil class func instance() -> (TLBlockExplorerAPI) { if(STATIC_MEMBERS._instance == nil) { var blockExplorerURL = TLPreferences.getBlockExplorerURL(TLPreferences.getBlockExplorerAPI()) if (blockExplorerURL == nil) { TLPreferences.resetBlockExplorerAPIURL() blockExplorerURL = TLPreferences.getBlockExplorerURL(TLPreferences.getBlockExplorerAPI()) } STATIC_MEMBERS.BLOCKEXPLORER_BASE_URL = blockExplorerURL STATIC_MEMBERS.blockExplorerAPI = TLPreferences.getBlockExplorerAPI() STATIC_MEMBERS._instance = TLBlockExplorerAPI() } return STATIC_MEMBERS._instance! } init() { if (STATIC_MEMBERS.blockExplorerAPI == .blockchain) { self.blockchainAPI = TLBlockchainAPI(baseURL: STATIC_MEMBERS.BLOCKEXPLORER_BASE_URL!) //needed for push tx api for stealth addresses self.insightAPI = TLInsightAPI(baseURL: "https://insight.bitpay.com/") } else if (STATIC_MEMBERS.blockExplorerAPI == .insight) { self.insightAPI = TLInsightAPI(baseURL: STATIC_MEMBERS.BLOCKEXPLORER_BASE_URL!) } } func getBlockHeight(_ success: @escaping TLNetworking.SuccessHandler, failure:@escaping TLNetworking.FailureHandler) -> (){ if (STATIC_MEMBERS.blockExplorerAPI == .blockchain) { self.blockchainAPI!.getBlockHeight({(height:AnyObject!) in let blockHeight = ["height": NSNumber(value: (height as! NSString).longLongValue as Int64)] success(blockHeight as AnyObject!) }, failure:failure) } else { //Insight does not have a good way to get block height /* self.insightAPI!.getBlockHeight({(jsonData:AnyObject!) in let blockHeight = ["height": ((jsonData as! NSDictionary).objectForKey("txoutsetinfo") as! NSDictionary).objectForKey("height")!] success(blockHeight) }, failure:{(code:NSInteger, status:String!) in }) */ } } func getAddressesInfoSynchronous(_ addressArray:Array) -> NSDictionary { if (STATIC_MEMBERS.blockExplorerAPI == .blockchain) { return self.blockchainAPI!.getAddressesInfoSynchronous(addressArray) } else { return self.insightAPI!.getAddressesInfoSynchronous(addressArray) } } func getAddressesInfo(_ addressArray:Array, success:@escaping TLNetworking.SuccessHandler, failure:@escaping TLNetworking.FailureHandler)-> () { if (STATIC_MEMBERS.blockExplorerAPI == .blockchain) { self.blockchainAPI!.getAddressesInfo(addressArray, success:success, failure:failure) } else { self.insightAPI!.getAddressesInfo(addressArray, success:success, failure:failure) } } func getUnspentOutputs(_ addressArray:Array, success:@escaping TLNetworking.SuccessHandler, failure:@escaping TLNetworking.FailureHandler) -> () { if (STATIC_MEMBERS.blockExplorerAPI == .blockchain) { self.blockchainAPI!.getUnspentOutputs(addressArray, success:success, failure:failure) } else { self.insightAPI!.getUnspentOutputs(addressArray, success:success, failure:failure) } } func getUnspentOutputsSynchronous(_ addressArray:NSArray) -> NSDictionary { if (STATIC_MEMBERS.blockExplorerAPI == .blockchain) { return self.blockchainAPI!.getUnspentOutputsSynchronous(addressArray) } else { return self.insightAPI!.getUnspentOutputsSynchronous(addressArray) } } func getAddressDataSynchronous(_ address:String) -> NSDictionary { if (STATIC_MEMBERS.blockExplorerAPI == .blockchain) { return self.blockchainAPI!.getAddressDataSynchronous(address) } else { return self.insightAPI!.getAddressDataSynchronous(address) } } func getAddressData(_ address:String, success:@escaping TLNetworking.SuccessHandler, failure:@escaping TLNetworking.FailureHandler) -> () { if (STATIC_MEMBERS.blockExplorerAPI == .blockchain) { self.blockchainAPI!.getAddressData(address, success:success, failure:failure) } else { self.insightAPI!.getAddressData(address, success: success, failure: failure) } } func getTx(_ txHash:String, success:@escaping TLNetworking.SuccessHandler, failure:@escaping TLNetworking.FailureHandler) -> () { if (STATIC_MEMBERS.blockExplorerAPI == .blockchain) { self.blockchainAPI!.getTx(txHash, success:success, failure:failure) } else { self.insightAPI!.getTx(txHash, success:{(jsonData:AnyObject!) in let transformedTx = TLInsightAPI.insightTxToBlockchainTx(jsonData as! NSDictionary) success(transformedTx) }, failure:{(code, status) in failure(code, status) }) } } func getTxBackground(_ txHash:String, success:@escaping TLNetworking.SuccessHandler, failure:@escaping TLNetworking.FailureHandler) -> () { if (STATIC_MEMBERS.blockExplorerAPI == .blockchain) { self.blockchainAPI!.getTxBackground(txHash, success:success, failure:failure) } else { self.insightAPI!.getTxBackground(txHash, success:{(jsonData:AnyObject!) in let transformedTx = TLInsightAPI.insightTxToBlockchainTx(jsonData as! NSDictionary) success(transformedTx) }, failure:{(code, status) in failure(code, status) }) } } func pushTx(_ txHex:String, txHash:String, success:@escaping TLNetworking.SuccessHandler, failure:@escaping TLNetworking.FailureHandler)-> () { if (STATIC_MEMBERS.blockExplorerAPI == .blockchain) { self.blockchainAPI!.pushTx(txHex, txHash:txHash, success:success, failure:failure) } else { self.insightAPI!.pushTx(txHex, success:success, failure:failure) } } func openWebViewForAddress(_ address:String) -> () { if (STATIC_MEMBERS.blockExplorerAPI == .blockchain) { let endPoint = "address/" let url = String(format: "%@%@%@", STATIC_MEMBERS.BLOCKEXPLORER_BASE_URL!, endPoint, address) UIApplication.shared.openURL(URL(string: url)!) } else { let endPoint = "address/" let url = String(format: "%@%@%@",STATIC_MEMBERS.BLOCKEXPLORER_BASE_URL!, endPoint, address) UIApplication.shared.openURL(URL(string:url)!) } } func openWebViewForTransaction(_ txid:String) -> () { if (STATIC_MEMBERS.blockExplorerAPI == .blockchain) { let endPoint = "tx/" let url = String(format: "%@%@%@",STATIC_MEMBERS.BLOCKEXPLORER_BASE_URL!, endPoint, txid) UIApplication.shared.openURL(URL(string: url)!) } else { let endPoint = "tx/" let url = String(format: "%@%@%@",STATIC_MEMBERS.BLOCKEXPLORER_BASE_URL!, endPoint, txid) UIApplication.shared.openURL(URL(string: url)!) } } } ================================================ FILE: ArcBit/APIs/TLBlockchainAPI.swift ================================================ // // TLBlockchainAPI.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation class TLBlockchainAPI { struct STATIC_MEMBERS { static let BLOCKCHAIN_ENDPOINT_ADDRESS = "address/" static let BLOCKCHAIN_ENDPOINT_TX = "tx/" static let BC_REQ_FORMAT = "format" static let BC_REQ_ACTIVE = "active" } var networking:TLNetworking var baseURL:String init(baseURL: String) { self.networking = TLNetworking() self.baseURL = baseURL } func getBlockHeight(_ success: @escaping TLNetworking.SuccessHandler, failure:@escaping TLNetworking.FailureHandler) -> (){ let endPoint = "q/getblockcount" let url = URL(string: endPoint, relativeTo:URL(string:self.baseURL)) self.networking.httpGET(url!, parameters:nil, success: {(jsonData:AnyObject!) in success(jsonData!) }, failure:{(code, status) in if (code == 200) { success(status as AnyObject!) } else { failure(code, status) } }) } func getAddressData(_ address:String, success:@escaping TLNetworking.SuccessHandler, failure:@escaping TLNetworking.FailureHandler) -> () { let endPoint = String(format:"%@%@", STATIC_MEMBERS.BLOCKCHAIN_ENDPOINT_ADDRESS, address) let parameters = [ STATIC_MEMBERS.BC_REQ_FORMAT: "json" ] let url = URL(string:endPoint, relativeTo:URL(string:self.baseURL)) self.networking.httpGET(url!, parameters:parameters as NSDictionary, success:success, failure:failure) } func getAddressDataSynchronous(_ address:String) -> NSDictionary { let endPoint = String(format:"%@%@", STATIC_MEMBERS.BLOCKCHAIN_ENDPOINT_ADDRESS, address) let parameters = [ STATIC_MEMBERS.BC_REQ_FORMAT: "json" ] let url = URL(string:endPoint, relativeTo:URL(string:self.baseURL)) return self.networking.httpGETSynchronous(url!, parameters:parameters as NSDictionary) as! NSDictionary } func getTx(_ txHash:String, success:@escaping TLNetworking.SuccessHandler, failure:@escaping TLNetworking.FailureHandler) -> () { let endPoint = String(format:"%@%@", STATIC_MEMBERS.BLOCKCHAIN_ENDPOINT_TX, txHash) let parameters = [ STATIC_MEMBERS.BC_REQ_FORMAT: "json" ] let url = URL(string:endPoint, relativeTo:URL(string:self.baseURL)) self.networking.httpGET(url!, parameters:parameters as NSDictionary, success:success, failure:failure) } func getTxBackground(_ txHash:String, success:@escaping TLNetworking.SuccessHandler, failure:@escaping TLNetworking.FailureHandler) -> () { let endPoint = String(format:"%@%@", STATIC_MEMBERS.BLOCKCHAIN_ENDPOINT_TX, txHash) let parameters = [ STATIC_MEMBERS.BC_REQ_FORMAT: "json" ] let url = URL(string:endPoint, relativeTo:URL(string:self.baseURL)) self.networking.httpGETBackground(url!, parameters:parameters as NSDictionary, success:success, failure:failure) } func pushTx(_ txHex:String, txHash:String, success:@escaping TLNetworking.SuccessHandler, failure:@escaping TLNetworking.FailureHandler) -> () { let endPoint = "pushtx" let parameters = [ STATIC_MEMBERS.BC_REQ_FORMAT: "plain", "tx":txHex ] let url = URL(string:endPoint, relativeTo:URL(string:self.baseURL)) self.networking.httpPOST(url!, parameters:parameters as NSDictionary, success:success, failure:failure) } func getUnspentOutputsSynchronous(_ addressArray:NSArray) -> NSDictionary { let endPoint = "unspent" let parameters = [ STATIC_MEMBERS.BC_REQ_ACTIVE:addressArray.componentsJoined(by: "|") ] let url = URL(string:endPoint, relativeTo:URL(string:self.baseURL)) return self.networking.httpGETSynchronous(url!, parameters:parameters as NSDictionary) as! NSDictionary } func getUnspentOutputs(_ addressArray:Array, success:@escaping TLNetworking.SuccessHandler, failure:@escaping TLNetworking.FailureHandler) -> () { let endPoint = "unspent" let parameters = [ STATIC_MEMBERS.BC_REQ_ACTIVE:addressArray.joined(separator: "|") ] let url = URL(string:endPoint, relativeTo:URL(string:self.baseURL)) self.networking.httpGET(url!, parameters:parameters as NSDictionary, success:success, failure:failure) } func getAddressesInfoSynchronous(_ addressArray:Array) -> NSDictionary{ let endPoint = "multiaddr" let parameters = [ STATIC_MEMBERS.BC_REQ_ACTIVE:addressArray.joined(separator: "|"), "no_buttons":"true"] let url = URL(string:endPoint, relativeTo:URL(string:self.baseURL)) return self.networking.httpGETSynchronous(url!, parameters:parameters as NSDictionary) as! NSDictionary } func getAddressesInfo(_ addressArray:Array, success:@escaping TLNetworking.SuccessHandler, failure:@escaping TLNetworking.FailureHandler) -> () { let endPoint = "multiaddr" let parameters = [ STATIC_MEMBERS.BC_REQ_ACTIVE:addressArray.joined(separator: "|"), "no_buttons":"true"] let url = URL(string:endPoint, relativeTo:URL(string:self.baseURL)) self.networking.httpGET(url!, parameters:parameters as NSDictionary, success:success, failure:failure) } } ================================================ FILE: ArcBit/APIs/TLBlockrAPI.swift ================================================ // // TLBlockrAPI.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation class TLBlockrAPI { var networking:TLNetworking let baseURL:String = "https://btc.blockr.io/" init() { self.networking = TLNetworking() } // https://btc.blockr.io/documentation/api /* //success response from pushTx { code = 200 data = txid message = "" status = success } */ func pushTx(_ txHex: String, success: @escaping TLNetworking.SuccessHandler, failure: @escaping TLNetworking.FailureHandler) { let endPoint = "api/v1/tx/push" let parameters = [ "hex": txHex ] let url = URL(string: endPoint, relativeTo: URL(string: self.baseURL))! self.networking.httpPOST(url, parameters: parameters as NSDictionary, success: success, failure: failure) } } ================================================ FILE: ArcBit/APIs/TLExchangeRate.swift ================================================ // // TLExchangeRate.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation class TLExchangeRate { struct STATIC_MEMBERS { static var instance:TLExchangeRate? } fileprivate var exchangeRateDict:NSMutableDictionary? = nil var networking:TLNetworking class func instance() -> (TLExchangeRate) { if(STATIC_MEMBERS.instance == nil) { STATIC_MEMBERS.instance = TLExchangeRate() } return STATIC_MEMBERS.instance! } init() { self.networking = TLNetworking() self.exchangeRateDict = NSMutableDictionary() updateExchangeRate() } func updateExchangeRate() { let queue = DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.background) queue.async { self.getExchangeRates({ (jsonData:AnyObject!) in let array = jsonData as! NSArray for i in stride(from: 0, to: array.count, by: 1) { let dict = array[i] as! NSDictionary (self.exchangeRateDict!)[dict["code"] as! String] = dict } NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_EXCHANGE_RATE_UPDATED()), object: nil) }, failure: {(code, status) in DLog("getExchangeRates failure: code:\(code) status:\(status)") }) } } fileprivate func getExchangeRate(_ currency:String) -> (Double) { if (self.exchangeRateDict == nil || self.exchangeRateDict![currency] == nil) { return 0 } else { return ((self.exchangeRateDict![currency] as! NSDictionary)["rate"] as! Double) } } fileprivate func getExchangeRates(_ success: @escaping TLNetworking.SuccessHandler, failure:@escaping TLNetworking.FailureHandler) -> () { self.networking.httpGET(URL(string: "https://bitpay.com/api/rates")!, parameters:[:], success:success, failure:failure) } fileprivate func fiatAmountFromBitcoin(_ currency:String, bitcoinAmount:TLCoin) -> (Double) { let exchangeRate = getExchangeRate(currency) return bitcoinAmount.bigIntegerToBitcoin() * exchangeRate } func bitcoinAmountFromFiat(_ currency:String, fiatAmount:Double) -> (TLCoin) { let exchangeRate = getExchangeRate(currency) let bitcoinAmount = TLCoin(doubleValue: fiatAmount/exchangeRate) return bitcoinAmount } func fiatAmountStringFromBitcoin(_ currency:String, bitcoinAmount:TLCoin) -> (String){ //TODO move bitcoinFormatter to property let bitcoinFormatter = NumberFormatter() bitcoinFormatter.numberStyle = .decimal bitcoinFormatter.maximumFractionDigits = 2 return bitcoinFormatter.string(from: NSNumber(value: fiatAmountFromBitcoin(currency, bitcoinAmount:bitcoinAmount) as Double))! } } ================================================ FILE: ArcBit/APIs/TLInsightAPI.swift ================================================ // // TLInsightAPI.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation class TLInsightAPI { var networking:TLNetworking var baseURL:String init(baseURL: String) { self.networking = TLNetworking() self.baseURL = baseURL } func getBlockHeight(_ success: @escaping (TLNetworking.SuccessHandler), failure: @escaping (TLNetworking.FailureHandler)) { let endPoint = "api/status/" let parameters = [ "q": "getTxOutSetInfo" ] let url = URL(string: endPoint, relativeTo: URL(string: self.baseURL))! self.networking.httpGET(url, parameters: parameters as NSDictionary, success: { (jsonData) in success(jsonData) }, failure: { (code, status) in failure(code, status) }) } func getUnspentOutputsSynchronous(_ addressArray: NSArray) -> NSDictionary { let endPoint = String(format: "%@%@%@", "api/addrs/", addressArray.componentsJoined(by: ","), "/utxo") let url = URL(string: endPoint, relativeTo: URL(string: self.baseURL))! let jsonData: AnyObject? = self.networking.httpGETSynchronous(url, parameters: nil) if jsonData is NSDictionary { // if don't get dict http error, will get array return jsonData as! NSDictionary } let transansformedJsonData = TLInsightAPI.insightUnspentOutputsToBlockchainUnspentOutputs(jsonData as! NSArray) return transansformedJsonData } func getUnspentOutputs(_ addressArray: Array, success: @escaping TLNetworking.SuccessHandler, failure: @escaping TLNetworking.FailureHandler) { let endPoint = String(format: "%@%@%@", "api/addrs/", addressArray.joined(separator: ","), "/utxo") let url = URL(string: endPoint, relativeTo: URL(string: self.baseURL))! self.networking.httpGET(url, parameters: nil, success: { (jsonData) in let transansformedJsonData = TLInsightAPI.insightUnspentOutputsToBlockchainUnspentOutputs(jsonData as! NSArray) as NSDictionary success(transansformedJsonData) }, failure: { (code, status) in failure(code, status) }) } func getAddressesInfoSynchronous(_ addressArray: Array, txCountFrom: Int=0, allTxs: NSMutableArray=[]) -> NSDictionary { let endPoint = String(format: "%@%@%@", "api/addrs/", addressArray.joined(separator: ","), "/txs") let parameters = ["from":txCountFrom, "to":txCountFrom+50] let url = URL(string: endPoint, relativeTo: URL(string: self.baseURL))! let jsonData: AnyObject? = self.networking.httpGETSynchronous(url, parameters: parameters as NSDictionary) if ((jsonData as! NSDictionary).object(forKey: TLNetworking.STATIC_MEMBERS.HTTP_ERROR_CODE) != nil) { return jsonData as! NSDictionary } let txs = (jsonData as! NSDictionary).object(forKey: "items") as! NSArray let to = ((jsonData as! NSDictionary).object(forKey: "to") as! NSNumber) let totalItems = ((jsonData as! NSDictionary).object(forKey: "totalItems") as! NSNumber).uint64Value if to.uint64Value >= totalItems { if allTxs.count == 0 { let transansformedJsonData = TLInsightAPI.insightAddressesTxsToBlockchainMultiaddr(addressArray as NSArray, txs: txs) return transansformedJsonData } else { allTxs.addObjects(from: txs as [AnyObject]) let transansformedJsonData = TLInsightAPI.insightAddressesTxsToBlockchainMultiaddr(addressArray as NSArray, txs: allTxs) return transansformedJsonData } } else { allTxs.addObjects(from: txs as [AnyObject]) return self.getAddressesInfoSynchronous(addressArray, txCountFrom: to.intValue, allTxs: allTxs) } } func getAddressesInfo(_ addressArray: Array, txCountFrom: Int=0, allTxs: NSMutableArray=[], success: @escaping TLNetworking.SuccessHandler, failure: @escaping TLNetworking.FailureHandler) { let endPoint = String(format: "%@%@%@", "api/addrs/", addressArray.joined(separator: ","), "/txs") let parameters = ["from":txCountFrom, "to":txCountFrom+50] let url = URL(string: endPoint, relativeTo: URL(string: self.baseURL))! self.networking.httpGET(url, parameters: parameters as NSDictionary, success: { (jsonData) in let txs = (jsonData as! NSDictionary).object(forKey: "items") as! NSArray let to = ((jsonData as! NSDictionary).object(forKey: "to") as! NSNumber) let totalItems = ((jsonData as! NSDictionary).object(forKey: "totalItems") as! NSNumber).uint64Value if to.uint64Value >= totalItems { if allTxs.count == 0 { let transformedJsonData = TLInsightAPI.insightAddressesTxsToBlockchainMultiaddr(addressArray as NSArray, txs: txs) success(transformedJsonData) } else { allTxs.addObjects(from: txs as [AnyObject]) let transformedJsonData = TLInsightAPI.insightAddressesTxsToBlockchainMultiaddr(addressArray as NSArray, txs: allTxs) success(transformedJsonData) } } else { allTxs.addObjects(from: txs as [AnyObject]) self.getAddressesInfo(addressArray, txCountFrom: to.intValue, allTxs: allTxs, success: success, failure: failure) } }, failure: failure) } func getAddressData(_ address: String, success: @escaping TLNetworking.SuccessHandler, failure: @escaping TLNetworking.FailureHandler) { let endPoint = String(format: "%@%@", "api/txs/?address=", address) let url = URL(string: endPoint, relativeTo: URL(string: self.baseURL))! self.networking.httpGET(url, parameters: nil, success: { (jsonData) in let txs = (jsonData as! NSDictionary!).object(forKey: "txs") as! NSArray let transformedTxs = NSMutableArray(capacity:txs.count) for tx in txs as! [NSDictionary] { if let transformedTx = TLInsightAPI.insightTxToBlockchainTx(tx) { transformedTxs.add(transformedTx) } } let transansformedJsonData = NSMutableDictionary() transansformedJsonData.setObject(transformedTxs, forKey:"txs" as NSCopying) success(transansformedJsonData) }, failure: failure) } func getAddressDataSynchronous(_ address: String) -> NSDictionary { let endPoint = String(format: "%@%@", "api/txs/?address=", address) let url = URL(string: endPoint, relativeTo: URL(string: self.baseURL))! let jsonData: AnyObject? = self.networking.httpGETSynchronous(url, parameters: nil) let txs = (jsonData as! NSDictionary!).object(forKey: "txs") as! NSArray let transformedTxs = NSMutableArray(capacity:txs.count) for tx in txs as! [NSDictionary] { if let transformedTx = TLInsightAPI.insightTxToBlockchainTx(tx) { transformedTxs.add(transformedTx) } } let transansformedJsonData = NSMutableDictionary() transansformedJsonData.setObject(transformedTxs, forKey:"txs" as NSCopying) return transansformedJsonData } func getTx(_ txHash: String, success: @escaping TLNetworking.SuccessHandler, failure: @escaping TLNetworking.FailureHandler) { let endPoint = String(format: "%@%@", "api/tx/", txHash) let url = URL(string: endPoint, relativeTo: URL(string: self.baseURL))! self.networking.httpGET(url, parameters: nil, success: success, failure: failure) } func getTxBackground(_ txHash: String, success: @escaping TLNetworking.SuccessHandler, failure: @escaping TLNetworking.FailureHandler) { let endPoint = String(format: "%@%@", "api/tx/", txHash) let url = URL(string: endPoint, relativeTo: URL(string: self.baseURL))! self.networking.httpGETBackground(url, parameters: nil, success: success, failure: failure) } class func insightToBlockchainUnspentOutput(_ unspentOutputDict: NSDictionary) -> NSDictionary? { // bug in insight, see https://insight.bitpay.com/api/addrs/1GjuCPLhmrdrAdifz1XLdpVVeQjsvZYGou/utxo if unspentOutputDict.object(forKey: "scriptPubKey") == nil { // got following so far // insight bug for txid 1cf031f8ac2896994e57c299e23b4ed35e2d218a7c6877302da0e3292337f530 when tried to do f5b0e820f23a6724f669463a6bf2e03806169b3d7fee7b6d27a642840109823d DLog("no scriptPubKey, insight bug? txid \(unspentOutputDict.object(forKey: "txid") as! String)") return nil } let blockchainUnspentOutputDict = NSMutableDictionary() let txid = unspentOutputDict.object(forKey: "txid") as! String let txHash = TLWalletUtils.reverseHexString(txid) blockchainUnspentOutputDict.setObject(txHash, forKey: "tx_hash" as NSCopying) blockchainUnspentOutputDict.setObject(txid, forKey: "tx_hash_big_endian" as NSCopying) blockchainUnspentOutputDict.setObject(unspentOutputDict.object(forKey: "vout")!, forKey: "tx_output_n" as NSCopying) blockchainUnspentOutputDict.setObject(unspentOutputDict.object(forKey: "scriptPubKey")!, forKey: "script" as NSCopying) let value = (unspentOutputDict.object(forKey: "satoshis") as! NSNumber).uint64Value blockchainUnspentOutputDict.setObject(value, forKey: "value" as NSCopying) let confirmations: AnyObject? = unspentOutputDict.object(forKey: "confirmations") as AnyObject? if (confirmations != nil) { blockchainUnspentOutputDict.setObject(confirmations!, forKey: "confirmations" as NSCopying) } else { blockchainUnspentOutputDict.setObject(0, forKey: "confirmations" as NSCopying) } return blockchainUnspentOutputDict } func pushTx(_ txHex: String, success: @escaping TLNetworking.SuccessHandler, failure: @escaping TLNetworking.FailureHandler) { let endPoint = "api/tx/send" let parameters = [ "rawtx": txHex ] let url = URL(string: endPoint, relativeTo: URL(string: self.baseURL))! self.networking.httpPOST(url, parameters: parameters as NSDictionary, success: success, failure: failure) } class func insightUnspentOutputsToBlockchainUnspentOutputs(_ unspentOutputs: NSArray) -> NSDictionary { let transansformedUnspentOutputs = NSMutableArray(capacity: unspentOutputs.count) for _unspentOutput in unspentOutputs { let unspentOutput = _unspentOutput as! NSDictionary if let dict = TLInsightAPI.insightToBlockchainUnspentOutput(unspentOutput) { transansformedUnspentOutputs.add(dict) } } let transansformedJsonData = NSMutableDictionary() transansformedJsonData.setObject(transansformedUnspentOutputs, forKey: "unspent_outputs" as NSCopying) return transansformedJsonData } class func insightAddressesTxsToBlockchainMultiaddr(_ addressArray: NSArray, txs: NSArray) -> NSDictionary { let addressExistDict = NSMutableDictionary(capacity: addressArray.count) let transansformedAddressesDict = NSMutableDictionary(capacity: addressArray.count) for _address in addressArray { let address = _address as! String addressExistDict.setObject("", forKey: address as NSCopying) let transformedAddress = NSMutableDictionary() transformedAddress.setObject(0, forKey: "n_tx" as NSCopying) transformedAddress.setObject(address, forKey: "address" as NSCopying) transformedAddress.setObject(TLCoin.zero(), forKey: "final_balance" as NSCopying) transansformedAddressesDict.setObject(transformedAddress, forKey: address as NSCopying) } let transformedTxs = NSMutableArray(capacity: txs.count) let transansformedAddresses = NSMutableArray(capacity: addressArray.count) var i = txs.count - 1 while i >= 0 { if txs.object(at: i) as! NSObject == NSNull() { i -= 1 continue } let tx = txs.object(at: i) as! NSDictionary let transformedTx = TLInsightAPI.insightTxToBlockchainTx(tx) if (transformedTx == nil) { i -= 1 continue; } transformedTxs.add(transformedTx!) let inputsArray = transformedTx!.object(forKey: "inputs") as? NSArray if (inputsArray != nil) { for _input in inputsArray! { let input = _input as! NSDictionary let prevOut = input.object(forKey: "prev_out") as? NSDictionary if (prevOut != nil) { let addr = prevOut!.object(forKey: "addr") as? String if (addr != nil && addressExistDict.object(forKey: addr!) != nil) { let transformedAddress = transansformedAddressesDict.object(forKey: addr!) as! NSMutableDictionary var addressBalance = transformedAddress.object(forKey: "final_balance") as! TLCoin let value = (prevOut!.object(forKey: "value") as! NSNumber).uint64Value addressBalance = addressBalance.subtract(TLCoin(uint64: value)) transformedAddress.setObject(addressBalance, forKey: "final_balance" as NSCopying) let nTxs = transformedAddress.object(forKey: "n_tx") as! Int transformedAddress.setObject(nTxs + 1, forKey: "n_tx" as NSCopying) } } } } let outsArray = transformedTx!.object(forKey: "out") as? NSArray if (outsArray != nil) { for _output in outsArray! { let output = _output as! NSDictionary let addr = output.object(forKey: "addr") as? String if (addr != nil && addressExistDict.object(forKey: addr!) != nil) { let transformedAddress = transansformedAddressesDict.object(forKey: addr!) as! NSMutableDictionary var addressBalance = transformedAddress.object(forKey: "final_balance") as! TLCoin let value = (output.object(forKey: "value") as! NSNumber).uint64Value addressBalance = addressBalance.add(TLCoin(uint64: value)) transformedAddress.setObject(addressBalance, forKey: "final_balance" as NSCopying) let nTxs = transformedAddress.object(forKey: "n_tx") as! Int transformedAddress.setObject(nTxs + 1, forKey: "n_tx" as NSCopying) } } } i -= 1 } //TODO: need to sort txs because insight does not sort it for you, ask devs to sort array let sortedtransformedTxs = transformedTxs.sortedArray(comparator: { (a, b) -> ComparisonResult in let first = (a as! NSDictionary).object(forKey: "time") as! Int if (first == 0) { return ComparisonResult.orderedAscending } let second = (b as! NSDictionary).object(forKey: "time") as! Int if (second == 0) { return ComparisonResult.orderedDescending } if(second > first) { return ComparisonResult.orderedDescending } else if (second == first) { return ComparisonResult.orderedSame } return ComparisonResult.orderedAscending }) for _key in transansformedAddressesDict { let key = _key.key as! String let transformedAddress = transansformedAddressesDict.object(forKey: key) as! NSMutableDictionary let addressBalance = transformedAddress.object(forKey: "final_balance") as! TLCoin transformedAddress.setObject(NSNumber(value: addressBalance.toUInt64() as UInt64), forKey: "final_balance" as NSCopying) transansformedAddresses.add(transformedAddress) } let transansformedJsonData = NSMutableDictionary() transansformedJsonData.setObject(sortedtransformedTxs, forKey: "txs" as NSCopying) transansformedJsonData.setObject(transansformedAddresses, forKey: "addresses" as NSCopying) return transansformedJsonData } class func insightTxToBlockchainTx(_ txDict: NSDictionary) -> NSDictionary? { let blockchainTxDict = NSMutableDictionary() let vins = txDict.object(forKey: "vin") as? NSArray let vouts = txDict.object(forKey: "vout") as? NSArray //if (vins == nil && vouts == nil && txDict.objectForKey("possibleDoubleSpend") != nil) { if (vins == nil && vouts == nil) { return nil; } if let txid = txDict.object(forKey: "txid") { blockchainTxDict.setObject(txid, forKey: "hash" as NSCopying) } if let version = txDict.object(forKey: "version") { blockchainTxDict.setObject(version, forKey: "ver" as NSCopying) } if let size = txDict.object(forKey: "size") { blockchainTxDict.setObject(size, forKey: "size" as NSCopying) } //WARNING: time dont match on different blockexplorers, and field does not exist if unconfirmed let time: AnyObject? = txDict.object(forKey: "time") as AnyObject? if (time != nil) { blockchainTxDict.setObject(time!, forKey: "time" as NSCopying) } else { blockchainTxDict.setObject(0, forKey: "time" as NSCopying) } //TODO: get current block and compute block_height let confirmations: AnyObject? = txDict.object(forKey: "confirmations") as AnyObject? if (confirmations != nil) { blockchainTxDict.setObject(confirmations!, forKey:"block_height" as NSCopying) blockchainTxDict.setObject(confirmations!, forKey:"confirmations" as NSCopying) } else { blockchainTxDict.setObject(0, forKey: "block_height" as NSCopying) blockchainTxDict.setObject(0, forKey: "confirmations" as NSCopying) } if vins != nil { let inputs = NSMutableArray() for _vin in vins! { let vin = _vin as! NSDictionary let input = NSMutableDictionary() if let sequence = vin.object(forKey: "sequence") { input.setObject(sequence, forKey: "sequence" as NSCopying) } let prev_out = NSMutableDictionary() let addr = vin.object(forKey: "addr") as? String if (addr != nil) { prev_out.setObject(addr!, forKey: "addr" as NSCopying) } else { //can be nil, for example, mined coins on tx 32ee55597c590bb104c524298b14fd1c0ac96a230810bd1e68d109df532a46a0 } if let valueSat = vin.object(forKey: "valueSat") { prev_out.setObject(valueSat, forKey: "value" as NSCopying) } if let n = vin.object(forKey: "n") { prev_out.setObject(n, forKey: "n" as NSCopying) } input.setObject(prev_out, forKey: "prev_out" as NSCopying) inputs.add(input) } blockchainTxDict.setObject(inputs, forKey: "inputs" as NSCopying) } if vouts != nil { let outs = NSMutableArray() for _vout in vouts! { let vout = _vout as! NSDictionary let aOut = NSMutableDictionary() if let n = vout.object(forKey: "n") { aOut.setObject(n, forKey: "n" as NSCopying) } if let scriptPubKey = (vout.object(forKey: "scriptPubKey") as? NSDictionary) { let addresses = scriptPubKey.object(forKey: "addresses") as? NSArray if (addresses != nil) { if (addresses!.count == 1) { aOut.setObject(addresses!.object(at: 0), forKey: "addr" as NSCopying) } } if let hex = scriptPubKey.object(forKey: "hex") { aOut.setObject(hex, forKey: "script" as NSCopying) } } if let value = vout.object(forKey: "value") as? String { let coinValue = TLCoin(bitcoinAmount: value, bitcoinDenomination: .bitcoin, locale: Locale(identifier: "en_US")) aOut.setObject(Int(coinValue.toUInt64()), forKey: "value" as NSCopying) } outs.add(aOut) } blockchainTxDict.setObject(outs, forKey: "out" as NSCopying) } return blockchainTxDict } } ================================================ FILE: ArcBit/APIs/TLNetworking.swift ================================================ // // TLNetworking.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation enum TLDOMAINREACHABLE:Int { case wwan = 0 case wifi = 1 case notreachable = 2 } class TLNetworking { typealias ReachableHandler = (TLDOMAINREACHABLE) -> () typealias SuccessHandler = (AnyObject!) -> () typealias FailureHandler = (Int, String?) -> () struct STATIC_MEMBERS { static var _instance:TLNetworking? = nil static let HTTP_ERROR_CODE = "HTTPErrorCode" static let HTTP_ERROR_MSG = "HTTPErrorMsg" } let getManager:AFHTTPRequestOperationManager let postManager:AFHTTPRequestOperationManager let getSynchronousManager:AFHTTPRequestOperationManager let postSynchronousManager:AFHTTPRequestOperationManager let getManagerBackground:AFHTTPRequestOperationManager init(certificateData: Data? = nil) { let ua = "Mozilla/5.0 (Macintosh Intel Mac OS X 10_9_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36" self.getManager = AFHTTPRequestOperationManager() var requestSerializer = AFHTTPRequestSerializer() requestSerializer.setValue(ua, forHTTPHeaderField:"User-Agent") requestSerializer.setValue("utf-8", forHTTPHeaderField:"charset") self.getManager.requestSerializer = requestSerializer self.getManagerBackground = AFHTTPRequestOperationManager() requestSerializer = AFHTTPRequestSerializer() requestSerializer.setValue(ua, forHTTPHeaderField:"User-Agent") requestSerializer.setValue("utf-8", forHTTPHeaderField:"charset") self.getManagerBackground.requestSerializer = requestSerializer self.getManagerBackground.completionQueue = DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.default) self.getSynchronousManager = AFHTTPRequestOperationManager() requestSerializer = AFHTTPRequestSerializer() requestSerializer.setValue(ua, forHTTPHeaderField:"User-Agent") requestSerializer.setValue("utf-8", forHTTPHeaderField:"charset") self.getSynchronousManager.requestSerializer = requestSerializer self.getSynchronousManager.completionQueue = DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.default) self.postManager = AFHTTPRequestOperationManager() var postRequestSerializer = AFHTTPRequestSerializer() postRequestSerializer.setValue(ua, forHTTPHeaderField:"User-Agent") postRequestSerializer.setValue("utf-8", forHTTPHeaderField:"charset") self.postManager.requestSerializer = postRequestSerializer self.postSynchronousManager = AFHTTPRequestOperationManager() postRequestSerializer = AFHTTPRequestSerializer() postRequestSerializer.setValue(ua, forHTTPHeaderField:"User-Agent") postRequestSerializer.setValue("utf-8", forHTTPHeaderField:"charset") self.postSynchronousManager.requestSerializer = postRequestSerializer self.postSynchronousManager.completionQueue = DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.default) if certificateData != nil { let securityPolicy = AFSecurityPolicy(pinningMode: AFSSLPinningMode.certificate) securityPolicy?.allowInvalidCertificates = true securityPolicy?.validatesCertificateChain = false securityPolicy?.validatesDomainName = false securityPolicy?.pinnedCertificates = [certificateData!] self.getManager.securityPolicy = securityPolicy self.getManagerBackground.securityPolicy = securityPolicy self.getSynchronousManager.securityPolicy = securityPolicy self.postManager.securityPolicy = securityPolicy self.postSynchronousManager.securityPolicy = securityPolicy } } class func isReachable(_ url: URL, reachable: @escaping ReachableHandler) -> () { let manager = AFHTTPRequestOperationManager(baseURL:url) let operationQueue = manager?.operationQueue manager?.reachabilityManager.setReachabilityStatusChange({(status: AFNetworkReachabilityStatus) in switch (status) { case AFNetworkReachabilityStatus.reachableViaWWAN: DLog("AFNetworkReachabilityStatusReachableViaWWAN") operationQueue?.isSuspended = false reachable(TLDOMAINREACHABLE.wwan) break case AFNetworkReachabilityStatus.reachableViaWiFi: DLog("AFNetworkReachabilityStatusReachableViaWiFi") operationQueue?.isSuspended = false reachable(TLDOMAINREACHABLE.wifi) break case AFNetworkReachabilityStatus.notReachable: reachable(TLDOMAINREACHABLE.notreachable) DLog("AFNetworkReachabilityStatusNotReachable") default: operationQueue?.isSuspended = true break } }) manager?.reachabilityManager.startMonitoring() } func httpGETSynchronous(_ url: URL, parameters: NSDictionary?) -> AnyObject? { var response:AnyObject? = nil let semaphore = DispatchSemaphore(value: 0) DLog("httpGETSynchronous: url \(url.absoluteString)") _ = self.getSynchronousManager.get(url.absoluteString, parameters: parameters, success:{(operation, responseObject) in response = responseObject as AnyObject? semaphore.signal() }, failure:{(operation, error) in DLog("httpGETSynchronous: requestFailed url \(url.absoluteString)") if operation?.response != nil { response = [STATIC_MEMBERS.HTTP_ERROR_CODE: operation?.response.statusCode, STATIC_MEMBERS.HTTP_ERROR_MSG:operation?.responseString] as AnyObject } else { response = [STATIC_MEMBERS.HTTP_ERROR_CODE: "499", STATIC_MEMBERS.HTTP_ERROR_MSG:"No Response"] as AnyObject } semaphore.signal() }) semaphore.wait(timeout: DispatchTime.distantFuture) return response } func httpGET(_ url: URL, parameters: NSDictionary?, success: SuccessHandler?, failure: FailureHandler?) -> () { DLog("httpGET: url \(url.absoluteString)") self.getManager.get(url.absoluteString, parameters:parameters, success:{(operation, responseObject) in if success != nil { success!(responseObject as AnyObject!) } } , failure:{(operation, error) in DLog("httpGET: requestFailed url \(url.absoluteString)") if (failure != nil) { failure!(operation?.response == nil ? 0 : (operation?.response.statusCode)!, operation?.response == nil ? "" : operation?.responseString) } }) } func httpGETBackground(_ url: URL, parameters: NSDictionary?, success: SuccessHandler?, failure: FailureHandler?) -> () { DLog("httpGETBackground: url \(url.absoluteString)") self.getManagerBackground.get(url.absoluteString, parameters:parameters, success:{(operation, responseObject) in if success != nil { success!(responseObject as AnyObject!) } } , failure:{(operation, error) in DLog("httpGETBackground: requestFailed url \(url.absoluteString)") if (failure != nil) { failure!(operation?.response == nil ? 0 : (operation?.response.statusCode)!, operation?.response == nil ? "" : operation?.responseString) } }) } func httpPOST(_ url: URL, parameters: NSDictionary?, success: SuccessHandler?, failure: FailureHandler?) -> () { DLog("httpPOST:function: url \(url.absoluteString)") self.postManager.post(url.absoluteString, parameters:parameters, success:{(operation, responseObject) in if success != nil { success!(responseObject as AnyObject!) } }, failure:{(operation, error) in DLog("httpPOST: requestFailed url \(url.absoluteString)") if (failure != nil) { failure!(operation?.response == nil ? 0 : (operation?.response.statusCode)!, operation?.response == nil ? "" : operation?.responseString) } }) } func httpPOSTSynchronous(_ url: URL, parameters: NSDictionary?) -> AnyObject? { var response:AnyObject? let semaphore = DispatchSemaphore(value: 0) DLog("httpPOSTSynchronous: url \(url.absoluteString)") _ = self.postSynchronousManager.post(url.absoluteString, parameters: parameters, success:{(operation, responseObject) in response = responseObject as AnyObject? semaphore.signal() }, failure:{(operation, error) in DLog("httpPOSTSynchronous: requestFailed url \(url.absoluteString)") if operation?.response != nil { response = [STATIC_MEMBERS.HTTP_ERROR_CODE: operation?.response.statusCode, STATIC_MEMBERS.HTTP_ERROR_MSG:operation?.responseString] as AnyObject } else { response = [STATIC_MEMBERS.HTTP_ERROR_CODE: "499", STATIC_MEMBERS.HTTP_ERROR_MSG:"No Response"] as AnyObject } semaphore.signal() }) semaphore.wait(timeout: DispatchTime.distantFuture) return response } } ================================================ FILE: ArcBit/APIs/TLPushTxAPI.swift ================================================ // // TLPushTxAPI.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation class TLPushTxAPI { struct STATIC_MEMBERS { static var _instance:TLPushTxAPI? = nil } class func instance() -> (TLPushTxAPI) { if(STATIC_MEMBERS._instance == nil) { STATIC_MEMBERS._instance = TLPushTxAPI() } return STATIC_MEMBERS._instance! } func sendTx(_ txHex:String, txHash:String, toAddress:String, success:@escaping TLNetworking.SuccessHandler, failure:@escaping TLNetworking.FailureHandler)-> () { DLog("TLPushTxAPI pushTx txHex \(txHex)") DLog("TLPushTxAPI pushTx txHash \(txHash)") DLog("TLPushTxAPI pushTx toAddress \(toAddress)") if TLStealthAddress.isStealthAddress(toAddress, isTestnet:false) == false { DLog("TLPushTxAPI TLBlockExplorerAPI") TLBlockExplorerAPI.instance().pushTx(txHex, txHash: txHash, success:success, failure:failure) } else { var pushTxMethod = TLBlockExplorerAPI.instance().insightAPI!.pushTx //pushTxMethod = TLBlockrAPI().pushTx pushTxMethod(txHex, { (jsonData) -> () in DLog("TLPushTxAPI pushTxMethod \(jsonData)") func getTxidFromInsightPushTx(_ jsonData:NSDictionary) -> String { return jsonData.object(forKey: "txid") as! String } func getTxidFromBlockrPushTx(_ jsonData:NSDictionary) -> String? { if jsonData.object(forKey: "status") as! String != "success" { let code = jsonData.object(forKey: "code") as! Int let message = jsonData.object(forKey: "message") as! String failure(code, message) return nil } return jsonData.object(forKey: "data") as? String } let txid = getTxidFromInsightPushTx(jsonData as! NSDictionary) //let txid = getTxidFromBlockrPushTx(jsonData as! NSDictionary) //if txid == nil { return } TLStealthExplorerAPI.instance().lookupTx(toAddress, txid: txid, success: { (jsonData) -> () in DLog("TLPushTxAPI TLStealthExplorerAPI success \(jsonData.description)") if let errorCode = (jsonData as! NSDictionary).object(forKey: TLStealthExplorerAPI.STATIC_MEMBERS.SERVER_ERROR_CODE) as? Int { let errorMsg = (jsonData as! NSDictionary).object(forKey: TLStealthExplorerAPI.STATIC_MEMBERS.SERVER_ERROR_MSG) as! String DLog(String(format: "TLPushTxAPI TLStealthExplorerAPI success failure \(errorCode) \(errorMsg)")) if errorCode == TLStealthExplorerAPI.STATIC_MEMBERS.SEND_TX_ERROR { DLog("TLPushTxAPI TLStealthExplorerAPI SEND_TX_ERROR \(errorMsg)") } failure(errorCode, errorMsg) } else { DLog("TLPushTxAPI TLStealthExplorerAPI success success") success(["txid":txid] as AnyObject) } }) { (code, status) -> () in DLog("TLPushTxAPI TLStealthExplorerAPI failure") DLog("TLPushTxAPI TLStealthExplorerAPI failure code: \(code)") DLog("TLPushTxAPI TLStealthExplorerAPI failure status: \(status)") if status != nil { failure(code, "No status") } else { failure(code, status) } } }) { (code, status) -> () in failure(code, status) } } } } ================================================ FILE: ArcBit/APIs/TLStealthServerAPI.swift ================================================ // // TLStealthServerAPI.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation class TLStealthExplorerAPI { struct STATIC_MEMBERS { static let STEALTH_PAYMENTS_FETCH_COUNT = 50 static let UNEXPECTED_ERROR = -1000 static let DATABASE_ERROR = -1001 static let INVALID_STEALTH_ADDRESS_ERROR = -1002 static let INVALID_SIGNATURE_ERROR = -1003 static let INVALID_SCAN_KEY_ERROR = -1004 static let TX_DECODE_FAILED_ERROR = -1005 static let INVALID_PARAMETER_ERROR = -1006 static let SEND_TX_ERROR = -1007 static let SERVER_ERROR_CODE = "error_code" static let SERVER_ERROR_MSG = "error_msg" static var instance:TLStealthExplorerAPI? = nil } var networking:TLNetworking var baseURL:String class func instance() -> (TLStealthExplorerAPI) { if(STATIC_MEMBERS.instance == nil) { TLPreferences.resetStealthExplorerAPIURL() TLPreferences.resetStealthServerPort() let baseURL = TLStealthServerConfig.instance().getWebServerProtocol()+"://"+TLPreferences.getStealthExplorerURL()!+":"+String(TLPreferences.getStealthServerPort()!) STATIC_MEMBERS.instance = TLStealthExplorerAPI(baseURL: baseURL) } return STATIC_MEMBERS.instance! } init(baseURL: String) { //let certificateData = TLStealthServerConfig.instance().getSSLCertificate() //self.networking = TLNetworking(certificateData: certificateData) self.networking = TLNetworking() self.baseURL = baseURL } func ping(_ success: @escaping TLNetworking.SuccessHandler, failure: @escaping TLNetworking.FailureHandler) -> () { let endPoint = "ping" let url = URL(string:endPoint, relativeTo:URL(string:self.baseURL)) self.networking.httpGET(url!, parameters:nil, success:success, failure:failure) } func getChallenge() -> NSDictionary { let endPoint = "challenge" let url = URL(string:endPoint, relativeTo:URL(string:self.baseURL)) let jsonDict = self.networking.httpGETSynchronous(url!, parameters: nil) as! NSDictionary return jsonDict } func getStealthPaymentsSynchronous(_ stealthAddress:String, signature:String, offset:Int) -> NSDictionary { let endPoint = "payments" let parameters = [ "addr": stealthAddress, "sig":signature, "offset":offset ] as [String : Any] let url = URL(string:endPoint, relativeTo:URL(string:self.baseURL)) let jsonDict = self.networking.httpGETSynchronous(url!, parameters: parameters as NSDictionary?) as! NSDictionary return jsonDict } func watchStealthAddressSynchronous(_ stealthAddress:String, scanPriv:String, signature:String) -> NSDictionary { let endPoint = "watch" let parameters = [ "addr": stealthAddress, "scan_key":scanPriv, "sig":signature ] let url = URL(string:endPoint, relativeTo:URL(string:self.baseURL)) let jsonDict = self.networking.httpGETSynchronous(url!, parameters: parameters as NSDictionary?) as! NSDictionary return jsonDict } func lookupTx(_ stealthAddress:String, txid:String, success: @escaping TLNetworking.SuccessHandler, failure: @escaping TLNetworking.FailureHandler) -> () { let endPoint = "lookuptx" let parameters = [ "addr": stealthAddress, "txid":txid, ] let url = URL(string:endPoint, relativeTo:URL(string:self.baseURL)) self.networking.httpGET(url!, parameters:parameters as NSDictionary!, success:success, failure:failure) } } ================================================ FILE: ArcBit/APIs/TLStealthServerConfig.swift ================================================ // // TLStealthServerConfig.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation class TLStealthServerConfig { struct STATIC_MEMBERS { static var instance:TLStealthServerConfig? } fileprivate var stealthServerUrl = "www.arcbit.net" fileprivate var stealthServerPort = 443 fileprivate var webSocketServerPort = 443 fileprivate var webServerProtocol = "https" fileprivate var webSocketProtocol = "wss" fileprivate var webSocketEndpoint = "/inv" class func instance() -> (TLStealthServerConfig) { if(STATIC_MEMBERS.instance == nil) { STATIC_MEMBERS.instance = TLStealthServerConfig() } return STATIC_MEMBERS.instance! } func getSSLCertificate() -> Data { let certificatePath = Bundle.main.path(forResource: "live", ofType: "cer")! let certificateData = try! Data(contentsOf: URL(fileURLWithPath: certificatePath)) return certificateData } func getWebServerProtocol() -> String { return self.webServerProtocol } func getWebSocketProtocol() -> String { return self.webSocketProtocol } func getWebSocketEndpoint() -> String { return self.webSocketEndpoint } func getStealthServerUrl() -> String { return self.stealthServerUrl } func getStealthServerPort() -> Int { return self.stealthServerPort } func getWebSocketServerPort() -> Int { return self.webSocketServerPort } } ================================================ FILE: ArcBit/APIs/TLStealthWebSocket.swift ================================================ // // TLStealthWebSocket.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation @objc class TLStealthWebSocket: NSObject, SRWebSocketDelegate { fileprivate var webSocket: SRWebSocket? fileprivate var consecutiveFailedConnections = 0 var challenge = "0" fileprivate let MAX_CONSECUTIVE_FAILED_CONNECTIONS = 5 struct STATIC_MEMBERS { static var instance: TLStealthWebSocket? } class func instance() -> (TLStealthWebSocket) { if (STATIC_MEMBERS.instance == nil) { STATIC_MEMBERS.instance = TLStealthWebSocket() TLPreferences.resetStealthExplorerAPIURL() TLPreferences.resetStealthWebSocketPort() } return STATIC_MEMBERS.instance! } func reconnect() -> () { if (self.webSocket != nil) { self.webSocket!.delegate = nil self.webSocket!.close() } let urlString = String(format: "%@://%@:%d%@", TLStealthServerConfig.instance().getWebSocketProtocol(), TLPreferences.getStealthExplorerURL()!, TLPreferences.getStealthWebSocketPort()!, TLStealthServerConfig.instance().getWebSocketEndpoint()) DLog("StealthWebSocket reconnect url: \(urlString)") //let certificateData = TLStealthServerConfig.instance().getSSLCertificate() //let urlRequest = SRWebSocket.createURLRequest(urlString, withPinnedCert: certificateData) //self.webSocket = SRWebSocket(URLRequest: urlRequest) self.webSocket = SRWebSocket(urlRequest: URLRequest(url: URL(string: urlString)!)) self.webSocket!.delegate = self self.webSocket!.open() } func isWebSocketOpen() -> Bool { return self.webSocket != nil && self.webSocket!.readyState.rawValue == SR_OPEN.rawValue } func close() -> () { self.webSocket!.close() } func sendMessagePing() -> Bool { let msg = TLUtils.dictionaryToJSONString(false, dict: ["op":"ping"]) return self.sendMessage(msg) } func sendMessageGetChallenge() -> Bool { let msg = TLUtils.dictionaryToJSONString(false, dict: ["op":"challenge"]) return self.sendMessage(msg) } @discardableResult func sendMessageSubscribeToStealthAddress(_ stealthAddress: String, signature: String) -> Bool { let msgDict = ["op":"addr_sub", "x":["addr":stealthAddress,"sig":signature]] as [String : Any] let msg = TLUtils.dictionaryToJSONString(false, dict: msgDict as NSDictionary) return self.sendMessage(msg) } func sendMessage(_ msg: String) -> Bool { DLog("StealthWebSocket sendMessage: \(msg)") if self.isWebSocketOpen() { self.webSocket!.send(msg) return true } else { DLog("StealthWebSocket Error: not connect to websocket server") return false } } func periodicPing() { let PERIODIC_PING_INTERVAL = 55.0 DispatchQueue.main.async { NSObject.cancelPreviousPerformRequests(withTarget: self, selector:#selector(TLStealthWebSocket.sendMessagePing), object:nil) Timer.scheduledTimer(timeInterval: PERIODIC_PING_INTERVAL, target: self, selector: #selector(TLStealthWebSocket.sendMessagePing), userInfo: nil, repeats: true) } } func webSocketDidOpen(_ webSocket: SRWebSocket) -> () { DLog("StealthWebSocket webSocketDidOpen") self.sendMessageGetChallenge() NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_STEALTH_PAYMENT_LISTENER_OPEN()), object: nil, userInfo: nil) self.periodicPing() } func webSocket(_ webSocket:SRWebSocket, didFailWithError error:NSError) -> () { DLog("StealthWebSocket didFailWithError \(error.description)") self.webSocket!.delegate = nil self.webSocket!.close() self.webSocket = nil NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_STEALTH_PAYMENT_LISTENER_CLOSE()), object: nil) if consecutiveFailedConnections < MAX_CONSECUTIVE_FAILED_CONNECTIONS { self.reconnect() } else { DispatchQueue.main.async { NSObject.cancelPreviousPerformRequests(withTarget: self, selector:#selector(TLStealthWebSocket.sendMessagePing), object:nil) } } consecutiveFailedConnections += 1 } public func webSocket(_ webSocket: SRWebSocket!, didReceiveMessage message: Any!) { DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.high).async { self.consecutiveFailedConnections = 0 let data = (message as AnyObject).data(using: String.Encoding.utf8.rawValue) let jsonDict = (try! JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions(rawValue: 0))) as! NSDictionary DLog("StealthWebSocket didReceiveMessage \(jsonDict.description)") if (jsonDict.object(forKey: "op") as! String == "challenge") { NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_RECEIVED_STEALTH_CHALLENGE()), object: jsonDict.object(forKey: "x"), userInfo: nil) } else if (jsonDict.object(forKey: "op") as! String == "addr_sub") { NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_RECEIVED_STEALTH_ADDRESS_SUBSCRIPTION()), object: jsonDict.object(forKey: "x"), userInfo: nil) } else if (jsonDict.object(forKey: "op") as! String == "tx") { NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_RECEIVED_STEALTH_PAYMENT()), object: jsonDict.object(forKey: "x"), userInfo: nil) } } } func webSocket(_ webSocket: SRWebSocket, didCloseWithCode code: Int, reason: String, wasClean: Bool) -> () { if wasClean { DLog("StealthWebSocket didCloseWithCode With No Error \(code) \(reason)") } else { DLog("StealthWebSocket didCloseWithCode With Error \(code) \(reason)") } self.webSocket!.delegate = nil self.webSocket!.close() self.webSocket = nil NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_STEALTH_PAYMENT_LISTENER_CLOSE()), object: nil) if consecutiveFailedConnections < MAX_CONSECUTIVE_FAILED_CONNECTIONS { self.reconnect() } else { DispatchQueue.main.async { NSObject.cancelPreviousPerformRequests(withTarget: self, selector:#selector(TLStealthWebSocket.sendMessagePing), object:nil) } } consecutiveFailedConnections += 1 } } ================================================ FILE: ArcBit/APIs/TLTxFeeAPI.swift ================================================ // // TLTxFeeAPI.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation enum TLDynamicFeeSetting:String { case FastestFee = "0" case HalfHourFee = "1" case HourFee = "2" static func getAPIValue(_ dynamicFeeSetting: TLDynamicFeeSetting) -> String { if dynamicFeeSetting == FastestFee { return "fastestFee" } else if dynamicFeeSetting == HalfHourFee { return "halfHourFee" } else if dynamicFeeSetting == HourFee { return "hourFee" } return "" } } class TLTxFeeAPI { var networking:TLNetworking fileprivate var cachedDynamicFees: NSDictionary? var cachedDynamicFeesTime: TimeInterval? init() { self.networking = TLNetworking() } func getCachedDynamicFee() -> NSNumber? { var dynamicFee:NSNumber? = nil if self.cachedDynamicFees != nil { let dynamicFeeSetting = TLPreferences.getInAppSettingsKitDynamicFeeSetting() if dynamicFeeSetting == TLDynamicFeeSetting.FastestFee { dynamicFee = self.cachedDynamicFees!.object(forKey: TLDynamicFeeSetting.getAPIValue(TLDynamicFeeSetting.FastestFee)) as? NSNumber } else if dynamicFeeSetting == TLDynamicFeeSetting.HalfHourFee { dynamicFee = self.cachedDynamicFees!.object(forKey: TLDynamicFeeSetting.getAPIValue(TLDynamicFeeSetting.HalfHourFee)) as? NSNumber } else if dynamicFeeSetting == TLDynamicFeeSetting.HourFee { dynamicFee = self.cachedDynamicFees!.object(forKey: TLDynamicFeeSetting.getAPIValue(TLDynamicFeeSetting.HourFee)) as? NSNumber } } return dynamicFee } func haveUpdatedCachedDynamicFees() -> Bool { let nowUnixTime = Date().timeIntervalSince1970 let tenMinutesInSeconds = 600.0 if self.cachedDynamicFeesTime == nil || nowUnixTime - self.cachedDynamicFeesTime! > tenMinutesInSeconds { return false } return true } func getDynamicTxFee(_ success:@escaping TLNetworking.SuccessHandler, failure:@escaping TLNetworking.FailureHandler)-> () { self.networking.httpGET(URL(string: "https://bitcoinfees.21.co/api/v1/fees/recommended")!, parameters:[:], success:{ (_jsonData) in if let jsonData = _jsonData as? NSDictionary { self.cachedDynamicFeesTime = Date().timeIntervalSince1970 self.cachedDynamicFees = jsonData DLog("TLTxFeeAPI getDynamicTxFee success \(jsonData.description)") } else { self.cachedDynamicFees = nil } success(_jsonData) }, failure: { (code, status) in self.cachedDynamicFees = nil DLog("TLTxFeeAPI getDynamicTxFee failure") failure(code, status) }) } } ================================================ FILE: ArcBit/AppDelegate.swift ================================================ // // AppDelegate.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit import AVFoundation import Fabric import Crashlytics @UIApplicationMain @objc(AppDelegate) class AppDelegate: UIResponder, UIApplicationDelegate, LTHPasscodeViewControllerDelegate { let MAX_CONSECUTIVE_FAILED_STEALTH_CHALLENGE_COUNT = 8 let SAVE_WALLET_PAYLOAD_DELAY = 2.0 let DEFAULT_BLOCKEXPLORER_API = TLBlockExplorer.blockchain let RESPOND_TO_STEALTH_PAYMENT_GET_TX_TRIES_MAX_TRIES = 3 var window:UIWindow? fileprivate var storyboard:UIStoryboard? fileprivate var modalDelegate:AnyObject? var appWallet = TLWallet(walletName: "App Wallet", walletConfig: TLWalletConfig(isTestnet: false)) var accounts:TLAccounts? var coldWalletAccounts:TLAccounts? var importedAccounts:TLAccounts? var importedWatchAccounts:TLAccounts? var importedAddresses:TLImportedAddresses? var importedWatchAddresses:TLImportedAddresses? var godSend:TLSpaghettiGodSend? var receiveSelectedObject:TLSelectedObject? var historySelectedObject:TLSelectedObject? var bitcoinURIOptionsDict:NSDictionary? var justSetupHDWallet = false var giveExitAppNoticeForBlockExplorerAPIToTakeEffect = false fileprivate var isAccountsAndImportsLoaded = false var saveWalletJSONEnabled = true var consecutiveFailedStealthChallengeCount = 0 fileprivate var savedPasscodeViewDefaultBackgroundColor: UIColor? fileprivate var savedPasscodeViewDefaultLabelTextColor: UIColor? fileprivate var savedPasscodeViewDefaultPasscodeTextColor: UIColor? fileprivate var hasFinishLaunching = false fileprivate var respondToStealthPaymentGetTxTries = 0 var scannedEncryptedPrivateKey:String? = nil var scannedAddressBookAddress:String? = nil let pendingOperations = PendingOperations() lazy var webSocketNotifiedTxHashSet:NSMutableSet = NSMutableSet() var pendingSelfStealthPaymentTxid: String? = nil lazy var txFeeAPI = TLTxFeeAPI(); class func instance() -> AppDelegate { return UIApplication.shared.delegate as! (AppDelegate) } func aAccountNeedsRecovering() -> Bool { guard let accounts = AppDelegate.instance().accounts else { return false } for i in stride(from: 0, to: accounts.getNumberOfAccounts(), by: 1) { let accountObject = accounts.getAccountObjectForIdx(i) if (accountObject.needsRecovering()) { return true } } guard let coldWalletAccounts = AppDelegate.instance().coldWalletAccounts else { return false } for i in stride(from: 0, to: coldWalletAccounts.getNumberOfAccounts(), by: 1) { let accountObject = coldWalletAccounts.getAccountObjectForIdx(i) if (accountObject.needsRecovering()) { return true } } guard let importedAccounts = AppDelegate.instance().importedAccounts else { return false } for i in stride(from: 0, to: importedAccounts.getNumberOfAccounts(), by: 1) { let accountObject = importedAccounts.getAccountObjectForIdx(i) if (accountObject.needsRecovering()) { return true } } guard let importedWatchAccounts = AppDelegate.instance().importedWatchAccounts else { return false } for i in stride(from: 0, to: importedWatchAccounts.getNumberOfAccounts(), by: 1) { let accountObject = importedWatchAccounts.getAccountObjectForIdx(i) if (accountObject.needsRecovering()) { return true } } return false } func checkToRecoverAccounts() { guard let accounts = AppDelegate.instance().accounts else { return } for i in stride(from: 0, to: accounts.getNumberOfAccounts(), by: 1) { let accountObject = accounts.getAccountObjectForIdx(i) if (accountObject.needsRecovering()) { accountObject.clearAllAddresses() accountObject.recoverAccount(false, recoverStealthPayments: true) } } guard let coldWalletsAccounts = AppDelegate.instance().coldWalletAccounts else { return } for i in stride(from: 0, to: coldWalletsAccounts.getNumberOfAccounts(), by: 1) { let accountObject = coldWalletAccounts?.getAccountObjectForIdx(i) if let accountObject = accountObject, accountObject.needsRecovering() { accountObject.clearAllAddresses() accountObject.recoverAccount(false, recoverStealthPayments: true) } } guard let importedAccounts = AppDelegate.instance().importedAccounts else { return } for i in stride(from: 0, to: importedAccounts.getNumberOfAccounts(), by: 1) { let accountObject = importedAccounts.getAccountObjectForIdx(i) if (accountObject.needsRecovering()) { accountObject.clearAllAddresses() accountObject.recoverAccount(false, recoverStealthPayments: true) } } guard let importedWatchAccounts = AppDelegate.instance().importedWatchAccounts else { return } for i in stride(from: 0, to: importedWatchAccounts.getNumberOfAccounts(), by: 1) { let accountObject = importedWatchAccounts.getAccountObjectForIdx(i) if (accountObject.needsRecovering()) { accountObject.clearAllAddresses() accountObject.recoverAccount(false, recoverStealthPayments: true) } } } func updateGodSend() { var sendFromType = TLPreferences.getSendFromType() var sendFromIndex = Int(TLPreferences.getSendFromIndex()) if (sendFromType == .hdWallet) { if let accounts = accounts, sendFromIndex > accounts.getNumberOfAccounts() - 1 { sendFromType = TLSendFromType.hdWallet sendFromIndex = 0 } } else if (sendFromType == .coldWalletAccount) { if let coldWalletAccounts = coldWalletAccounts, sendFromIndex > coldWalletAccounts.getNumberOfAccounts() - 1 { sendFromType = TLSendFromType.hdWallet sendFromIndex = 0 } } else if (sendFromType == .importedAccount) { if let importedAccounts = importedAccounts, sendFromIndex > importedAccounts.getNumberOfAccounts() - 1 { sendFromType = TLSendFromType.hdWallet sendFromIndex = 0 } } else if (sendFromType == .importedWatchAccount) { if let importedWatchAccounts = importedWatchAccounts, sendFromIndex > importedWatchAccounts.getNumberOfAccounts() - 1 { sendFromType = TLSendFromType.hdWallet sendFromIndex = 0 } } else if (sendFromType == .importedAddress) { if let importedAddresses = importedAddresses, sendFromIndex > importedAddresses.getCount() - 1 { sendFromType = TLSendFromType.hdWallet sendFromIndex = 0 } } else if (sendFromType == .importedWatchAddress) { if let importedWatchAddresses = importedWatchAddresses, sendFromIndex > importedWatchAddresses.getCount() - 1 { sendFromType = TLSendFromType.hdWallet sendFromIndex = 0 } } updateGodSend(sendFromType, sendFromIndex:sendFromIndex) } func updateGodSend(_ sendFromType: TLSendFromType, sendFromIndex: Int) { TLPreferences.setSendFromType(sendFromType) TLPreferences.setSendFromIndex(UInt(sendFromIndex)) if let accounts = accounts, sendFromType == .hdWallet { let accountObject = accounts.getAccountObjectForIdx(sendFromIndex) godSend?.setOnlyFromAccount(accountObject) } else if let coldWalletAccounts = coldWalletAccounts, sendFromType == .coldWalletAccount { let accountObject = coldWalletAccounts.getAccountObjectForIdx(sendFromIndex) godSend?.setOnlyFromAccount(accountObject) } else if let importedAccounts = importedAccounts, sendFromType == .importedAccount { let accountObject = importedAccounts.getAccountObjectForIdx(sendFromIndex) godSend?.setOnlyFromAccount(accountObject) } else if let importedWatchAccounts = importedWatchAccounts, sendFromType == .importedWatchAccount { let accountObject = importedWatchAccounts.getAccountObjectForIdx(sendFromIndex) godSend?.setOnlyFromAccount(accountObject) } else if let importedAddresses = importedAddresses, sendFromType == .importedAddress { let importedAddress = importedAddresses.getAddressObjectAtIdx(sendFromIndex) godSend?.setOnlyFromAddress(importedAddress) } else if let importedWatchAddresses = importedWatchAddresses, sendFromType == .importedWatchAddress { let importedAddress = importedWatchAddresses.getAddressObjectAtIdx(sendFromIndex) godSend?.setOnlyFromAddress(importedAddress) } } func updateReceiveSelectedObject(_ sendFromType: TLSendFromType, sendFromIndex: Int) { switch sendFromType { case .hdWallet: guard let accounts = accounts, let receiveSelectedObject = receiveSelectedObject else { return } let accountObject = accounts.getAccountObjectForIdx(sendFromIndex) receiveSelectedObject.setSelectedAccount(accountObject) case .coldWalletAccount: guard let coldWalletAccounts = coldWalletAccounts, let receiveSelectedObject = receiveSelectedObject else { return } let accountObject = coldWalletAccounts.getAccountObjectForIdx(sendFromIndex) receiveSelectedObject.setSelectedAccount(accountObject) case .importedAccount: guard let importedAccounts = importedAccounts, let receiveSelectedObject = receiveSelectedObject else { return } let accountObject = importedAccounts.getAccountObjectForIdx(sendFromIndex) receiveSelectedObject.setSelectedAccount(accountObject) case .importedWatchAccount: guard let importedWatchAccounts = importedWatchAccounts, let receiveSelectedObject = receiveSelectedObject else { return } let accountObject = importedWatchAccounts.getAccountObjectForIdx(sendFromIndex) receiveSelectedObject.setSelectedAccount(accountObject) case .importedAddress: guard let importedAddresses = importedAddresses, let receiveSelectedObject = receiveSelectedObject else { return } let importedAddress = importedAddresses.getAddressObjectAtIdx(sendFromIndex) receiveSelectedObject.setSelectedAddress(importedAddress) case .importedWatchAddress: guard let importedWatchAddresses = importedWatchAddresses, let receiveSelectedObject = receiveSelectedObject else { return } let importedAddress = importedWatchAddresses.getAddressObjectAtIdx(sendFromIndex) receiveSelectedObject.setSelectedAddress(importedAddress) } } func updateHistorySelectedObject(_ sendFromType: TLSendFromType, sendFromIndex: Int) { if let accounts = accounts, let historySelectedObject = historySelectedObject, sendFromType == .hdWallet { let accountObject = accounts.getAccountObjectForIdx(sendFromIndex) historySelectedObject.setSelectedAccount(accountObject) } else if let coldWalletAccounts = coldWalletAccounts, let historySelectedObject = historySelectedObject, sendFromType == .coldWalletAccount { let accountObject = coldWalletAccounts.getAccountObjectForIdx(sendFromIndex) historySelectedObject.setSelectedAccount(accountObject) } else if let importedAccounts = importedAccounts, let historySelectedObject = historySelectedObject, sendFromType == .importedAccount { let accountObject = importedAccounts.getAccountObjectForIdx(sendFromIndex) historySelectedObject.setSelectedAccount(accountObject) } else if let importedWatchAccounts = importedWatchAccounts, let historySelectedObject = historySelectedObject, sendFromType == .importedWatchAccount { let accountObject = importedWatchAccounts.getAccountObjectForIdx(sendFromIndex) historySelectedObject.setSelectedAccount(accountObject) } else if let importedAddresses = importedAddresses, let historySelectedObject = historySelectedObject, sendFromType == .importedAddress { let importedAddress = importedAddresses.getAddressObjectAtIdx(sendFromIndex) historySelectedObject.setSelectedAddress(importedAddress) } else if let importedWatchAddresses = importedWatchAddresses, let historySelectedObject = historySelectedObject, sendFromType == .importedWatchAddress { let importedAddress = importedWatchAddresses.getAddressObjectAtIdx(sendFromIndex) historySelectedObject.setSelectedAddress(importedAddress) } } func showLockViewForEnteringPasscode(_ notification: Notification) { if !hasFinishLaunching && LTHPasscodeViewController.doesPasscodeExist() { //LTHPasscodeViewController.sharedUser().maxNumberOfAllowedFailedAttempts = 0 UIApplication.shared.isStatusBarHidden = true LTHPasscodeViewController.sharedUser().delegate = self LTHPasscodeViewController.sharedUser().showLockScreen(withAnimation: false, withLogout:false , andLogoutTitle:nil) } hasFinishLaunching = true } func recoverHDWallet(_ mnemonic: String, shouldRefreshApp: Bool = true) { if shouldRefreshApp { refreshApp(mnemonic) } else { let masterHex = TLHDWalletWrapper.getMasterHex(mnemonic) appWallet.createInitialWalletPayload(mnemonic, masterHex:masterHex) accounts = TLAccounts(appWallet: appWallet, accountsArray:appWallet.getAccountObjectArray(), accountType:.hdWallet) coldWalletAccounts = TLAccounts(appWallet: appWallet, accountsArray:appWallet.getColdWalletAccountArray(), accountType:.coldWallet) importedAccounts = TLAccounts(appWallet: appWallet, accountsArray:appWallet.getImportedAccountArray(), accountType:.imported) importedWatchAccounts = TLAccounts(appWallet: appWallet, accountsArray: appWallet.getWatchOnlyAccountArray(), accountType:.importedWatch) importedAddresses = TLImportedAddresses(appWallet: appWallet, importedAddresses: appWallet.getImportedPrivateKeyArray(), accountAddressType:.imported) importedWatchAddresses = TLImportedAddresses(appWallet: appWallet, importedAddresses: appWallet.getWatchOnlyAddressArray(), accountAddressType:.importedWatch) } var accountIdx = 0 var consecutiveUnusedAccountCount = 0 let MAX_CONSECUTIVE_UNUSED_ACCOUNT_LOOK_AHEAD_COUNT = 4 guard let accounts = accounts else { return } while true { let accountName = String(format:TLDisplayStrings.ACCOUNT_X_STRING(), (accountIdx + 1)) let accountObject = accounts.createNewAccount(accountName, accountType:.normal, preloadStartingAddresses:false) guard let stealthWallet = accountObject.stealthWallet else { return } DLog("recoverHDWalletaccountName \(accountName)") let sumMainAndChangeAddressMaxIdx = accountObject.recoverAccount(false) DLog(String(format: "accountName \(accountName) sumMainAndChangeAddressMaxIdx: \(sumMainAndChangeAddressMaxIdx)")) if sumMainAndChangeAddressMaxIdx > -2 || TLWalletUtils.ENABLE_STEALTH_ADDRESS() && stealthWallet.checkIfHaveStealthPayments() { consecutiveUnusedAccountCount = 0 } else { consecutiveUnusedAccountCount += 1 if consecutiveUnusedAccountCount == MAX_CONSECUTIVE_UNUSED_ACCOUNT_LOOK_AHEAD_COUNT { break } } accountIdx += 1 } DLog("recoverHDWallet getNumberOfAccounts: \(accounts.getNumberOfAccounts())") if accounts.getNumberOfAccounts() == 0 { accounts.createNewAccount(TLDisplayStrings.ACCOUNT_1_STRING(), accountType:.normal) } else if accounts.getNumberOfAccounts() > 1 { while accounts.getNumberOfAccounts() > 1 && consecutiveUnusedAccountCount > 0 { accounts.popTopAccount() consecutiveUnusedAccountCount -= 1 } } } // work around to show SendView func checkToShowSendViewWithURL(_ notification: Notification) { if bitcoinURIOptionsDict != nil { assert(window?.rootViewController is ECSlidingViewController, "rootViewController != ECSlidingViewController") let vc = window?.rootViewController as! ECSlidingViewController vc.topViewController.showSendView() } } func setSettingsPasscodeViewColors() { LTHPasscodeViewController.sharedUser().view.backgroundColor = savedPasscodeViewDefaultBackgroundColor LTHPasscodeViewController.sharedUser().failedAttemptLabel.textColor = savedPasscodeViewDefaultLabelTextColor LTHPasscodeViewController.sharedUser().enterPasscodeLabel.textColor = savedPasscodeViewDefaultLabelTextColor LTHPasscodeViewController.sharedUser().okButton.setTitleColor(savedPasscodeViewDefaultLabelTextColor, for:UIControlState()) LTHPasscodeViewController.sharedUser().firstDigitTextField.textColor = savedPasscodeViewDefaultPasscodeTextColor LTHPasscodeViewController.sharedUser().secondDigitTextField.textColor = savedPasscodeViewDefaultPasscodeTextColor LTHPasscodeViewController.sharedUser().thirdDigitTextField.textColor = savedPasscodeViewDefaultPasscodeTextColor LTHPasscodeViewController.sharedUser().fourthDigitTextField.textColor = savedPasscodeViewDefaultPasscodeTextColor } fileprivate func setupPasscodeViewColors() { savedPasscodeViewDefaultBackgroundColor = LTHPasscodeViewController.sharedUser().backgroundColor savedPasscodeViewDefaultLabelTextColor = LTHPasscodeViewController.sharedUser().labelTextColor savedPasscodeViewDefaultPasscodeTextColor = LTHPasscodeViewController.sharedUser().passcodeTextColor LTHPasscodeViewController.sharedUser().backgroundColor = TLColors.mainAppColor() LTHPasscodeViewController.sharedUser().labelTextColor = TLColors.mainAppOppositeColor() LTHPasscodeViewController.sharedUser().passcodeTextColor = TLColors.mainAppOppositeColor() LTHPasscodeViewController.sharedUser().navigationBarTintColor = TLColors.mainAppColor() LTHPasscodeViewController.sharedUser().navigationTintColor = TLColors.mainAppOppositeColor() LTHPasscodeViewController.sharedUser().navigationTitleColor = TLColors.mainAppOppositeColor() } func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { Fabric.with([Crashlytics.self]) AFNetworkActivityIndicatorManager.shared().isEnabled = true window?.backgroundColor = TLColors.mainAppColor() application.statusBarStyle = UIStatusBarStyle.lightContent justSetupHDWallet = false let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as! String if TLPreferences.getInstallDate() == nil { // before version 1.4.0, install date was not getting set properly, this fixes things if TLPreferences.getAppVersion() == "0" { TLPreferences.setHasSetupHDWallet(false) TLPreferences.setInstallDate() DLog("set InstallDate \(TLPreferences.getInstallDate())") TLPreferences.setAppVersion(appVersion) } else { TLPreferences.setInstallDate() DLog("set fake InstallDate \(TLPreferences.getInstallDate())") if appVersion != TLPreferences.getAppVersion() { TLUpdateAppData.instance().beforeUpdatedAppVersion = TLPreferences.getAppVersion() DLog("set new appVersion \(appVersion)") TLPreferences.setAppVersion(appVersion) TLPreferences.setDisabledPromptRateApp(false) } } } else if appVersion != TLPreferences.getAppVersion() { TLUpdateAppData.instance().beforeUpdatedAppVersion = TLPreferences.getAppVersion() DLog("set new appVersion \(appVersion)") TLPreferences.setAppVersion(appVersion) TLPreferences.setDisabledPromptRateApp(false) } self.setupPasscodeViewColors() self.isAccountsAndImportsLoaded = false if (TLPreferences.hasSetupHDWallet() && UIApplication.instancesRespond(to: "registerUserNotificationSettings")) { application.registerUserNotificationSettings(UIUserNotificationSettings(types: [.alert, .badge, .sound], categories:nil)) } NotificationCenter.default.addObserver(self, selector:#selector(AppDelegate.checkToShowSendViewWithURL(_:)), name:NSNotification.Name.UIApplicationDidBecomeActive, object:nil) // condition is used so that I dont prompt user to setup notifactions when just installed app if (TLPreferences.hasSetupHDWallet()) { //setUpLocalNotification() } hasFinishLaunching = false NotificationCenter.default.addObserver(self, selector:#selector(AppDelegate.showLockViewForEnteringPasscode(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_SEND_SCREEN_LOADING()), object:nil) return true } func refreshApp(_ passphrase: String, clearWalletInMemory: Bool = true) { if (TLPreferences.getCloudBackupWalletFileName() == nil) { TLPreferences.setCloudBackupWalletFileName() } TLPreferences.deleteWalletPassphrase() TLPreferences.deleteEncryptedWalletJSONPassphrase() TLPreferences.setWalletPassphrase(passphrase, useKeychain: true) TLPreferences.setEncryptedWalletJSONPassphrase(passphrase, useKeychain: true) TLPreferences.clearEncryptedWalletPassphraseKey() TLPreferences.setCanRestoreDeletedApp(true) TLPreferences.setInAppSettingsCanRestoreDeletedApp(true) TLPreferences.setEnableBackupWithiCloud(false) TLPreferences.setInAppSettingsKitEnableBackupWithiCloud(false) TLPreferences.setInAppSettingsKitEnabledDynamicFee(true) TLPreferences.setInAppSettingsKitDynamicFeeSettingIdx(TLDynamicFeeSetting.FastestFee); TLPreferences.setInAppSettingsKitTransactionFee(TLWalletUtils.DEFAULT_FEE_AMOUNT_IN_BITCOINS()) TLPreferences.setEnablePINCode(false) TLSuggestions.instance().enabledAllSuggestions() TLPreferences.resetBlockExplorerAPIURL() TLPreferences.setBlockExplorerAPI(String(format:"%ld", DEFAULT_BLOCKEXPLORER_API.rawValue)) TLPreferences.setInAppSettingsKitBlockExplorerAPI(String(format:"%ld", DEFAULT_BLOCKEXPLORER_API.rawValue)) TLPreferences.resetStealthExplorerAPIURL() TLPreferences.resetStealthServerPort() TLPreferences.resetStealthWebSocketPort() LTHPasscodeViewController.deletePasscode() let DEFAULT_CURRENCY_IDX = "20" TLPreferences.setCurrency(DEFAULT_CURRENCY_IDX) TLPreferences.setInAppSettingsKitCurrency(DEFAULT_CURRENCY_IDX) TLPreferences.setSendFromType(.hdWallet) TLPreferences.setSendFromIndex(0) if clearWalletInMemory { let masterHex = TLHDWalletWrapper.getMasterHex(passphrase) self.appWallet.createInitialWalletPayload(passphrase, masterHex:masterHex) self.accounts = TLAccounts(appWallet: self.appWallet, accountsArray:self.appWallet.getAccountObjectArray(), accountType:.hdWallet) self.coldWalletAccounts = TLAccounts(appWallet: self.appWallet, accountsArray:self.appWallet.getColdWalletAccountArray(), accountType:.coldWallet) self.importedAccounts = TLAccounts(appWallet:self.appWallet, accountsArray:self.appWallet.getImportedAccountArray(), accountType:.imported) self.importedWatchAccounts = TLAccounts(appWallet: self.appWallet, accountsArray:self.appWallet.getWatchOnlyAccountArray(), accountType:.importedWatch) self.importedAddresses = TLImportedAddresses(appWallet: self.appWallet, importedAddresses:self.appWallet.getImportedPrivateKeyArray(), accountAddressType:.imported) self.importedWatchAddresses = TLImportedAddresses(appWallet: self.appWallet, importedAddresses:self.appWallet.getWatchOnlyAddressArray(), accountAddressType:.importedWatch) } self.receiveSelectedObject = TLSelectedObject() self.historySelectedObject = TLSelectedObject() //self.appWallet.addAddressBookEntry("vJmwhHhMNevDQh188gSeHd2xxxYGBQmnVuMY2yG2MmVTC31UWN5s3vaM3xsM2Q1bUremdK1W7eNVgPg1BnvbTyQuDtMKAYJanahvse", label: "ArcBit Donation") } func setAccountsListeningToStealthPaymentsToFalse() { guard let accounts = accounts else { return } for i in stride(from: 0, to: accounts.getNumberOfAccounts(), by: 1) { let accountObject = accounts.getAccountObjectForIdx(i) if accountObject.stealthWallet != nil { accountObject.stealthWallet?.isListeningToStealthPayment = false } } guard let importedAccounts = importedAccounts else { return } for i in stride(from: 0, to: importedAccounts.getNumberOfAccounts(), by: 1) { let accountObject = importedAccounts.getAccountObjectForIdx(i) if let stealthWallet = accountObject.stealthWallet { stealthWallet.isListeningToStealthPayment = false } } } func respondToStealthChallegeNotification(_ note: Notification) { let responseDict = note.object as! NSDictionary let challenge = responseDict.object(forKey: "challenge") as! String let lock = NSLock() lock.lock() TLStealthWebSocket.instance().challenge = challenge lock.unlock() respondToStealthChallege(challenge) } func respondToStealthChallege(_ challenge: String) { if (!isAccountsAndImportsLoaded || !TLStealthWebSocket.instance().isWebSocketOpen()) { return } guard let accounts = accounts else { return } for i in stride(from: 0, to: accounts.getNumberOfAccounts(), by: 1) { let accountObject = accounts.getAccountObjectForIdx(i) if accountObject.hasFetchedAccountData() && accountObject.stealthWallet != nil && accountObject.stealthWallet?.isListeningToStealthPayment == false { if let addrAndSignature = accountObject.stealthWallet?.getStealthAddressAndSignatureFromChallenge(challenge){ TLStealthWebSocket.instance().sendMessageSubscribeToStealthAddress(addrAndSignature.0, signature: addrAndSignature.1) } } } guard let importedAccounts = importedAccounts else { return } for i in stride(from: 0, to: importedAccounts.getNumberOfAccounts(), by: 1) { let accountObject = importedAccounts.getAccountObjectForIdx(i) if let stealthWallet = accountObject.stealthWallet, accountObject.hasFetchedAccountData() && stealthWallet.isListeningToStealthPayment == false { let addrAndSignature = stealthWallet.getStealthAddressAndSignatureFromChallenge(challenge) TLStealthWebSocket.instance().sendMessageSubscribeToStealthAddress(addrAndSignature.0, signature: addrAndSignature.1) } } } func respondToStealthAddressSubscription(_ note: Notification) { let responseDict = note.object as! NSDictionary let stealthAddress = responseDict.object(forKey: "addr") as! String let subscriptionSuccess = responseDict.object(forKey: "success") as! String if subscriptionSuccess == "False" && consecutiveFailedStealthChallengeCount < MAX_CONSECUTIVE_FAILED_STEALTH_CHALLENGE_COUNT { consecutiveFailedStealthChallengeCount += 1 TLStealthWebSocket.instance().sendMessageGetChallenge() return } consecutiveFailedStealthChallengeCount = 0 guard let accounts = accounts else { return } for i in stride(from: 0, to: accounts.getNumberOfAccounts(), by: 1) { let accountObject = accounts.getAccountObjectForIdx(i) if let stealthWallet = accountObject.stealthWallet, stealthWallet.getStealthAddress() == stealthAddress { stealthWallet.isListeningToStealthPayment = true } } guard let importedAccounts = importedAccounts else { return } for i in stride(from: 0, to: importedAccounts.getNumberOfAccounts(), by: 1) { let accountObject = importedAccounts.getAccountObjectForIdx(i) if let stealthWallet = accountObject.stealthWallet, stealthWallet.getStealthAddress() == stealthAddress { stealthWallet.isListeningToStealthPayment = true } } } func handleGetTxSuccessForRespondToStealthPayment(_ stealthAddress: String, paymentAddress: String, txid: String, txTime: UInt64, txObject: TLTxObject) { let inputAddresses = txObject.getInputAddressArray() let outputAddresses = txObject.getOutputAddressArray() if outputAddresses.index(of: paymentAddress) == nil { return } let possibleStealthDataScripts = txObject.getPossibleStealthDataScripts() func processStealthPayment(_ accountObject: TLAccountObject) { if let stealthWallet = accountObject.stealthWallet, stealthWallet.getStealthAddress() == stealthAddress { if accountObject.hasFetchedAccountData() { for stealthDataScript in possibleStealthDataScripts { let privateKey = stealthWallet.generateAndAddStealthAddressPaymentKey(stealthDataScript, expectedAddress: paymentAddress, txid: txid, txTime: txTime, stealthPaymentStatus: TLStealthPaymentStatus.unspent) if privateKey != nil { handleNewTxForAccount(accountObject, txObject: txObject) break } } } } else { // must refresh account balance if a input address belongs to account // this is needed because websocket api does not notify of addresses being used as inputs for address in inputAddresses { if accountObject.hasFetchedAccountData() && accountObject.isAddressPartOfAccount(address) { handleNewTxForAccount(accountObject, txObject: txObject) } } } } guard let accounts = accounts else { return } for i in stride(from: 0, to: accounts.getNumberOfAccounts(), by: 1) { let accountObject = accounts.getAccountObjectForIdx(i) processStealthPayment(accountObject) } guard let coldWalletAccounts = coldWalletAccounts else { return } for i in stride(from: 0, to: coldWalletAccounts.getNumberOfAccounts(), by: 1) { let accountObject = coldWalletAccounts.getAccountObjectForIdx(i) for address in inputAddresses { if accountObject.isAddressPartOfAccount(address) { handleNewTxForAccount(accountObject, txObject: txObject) } } } guard let importedAccounts = importedAccounts else { return } for i in stride(from: 0, to: importedAccounts.getNumberOfAccounts(), by: 1) { let accountObject = importedAccounts.getAccountObjectForIdx(i) processStealthPayment(accountObject) } guard let importedWatchAccounts = importedWatchAccounts else { return } for i in stride(from: 0, to: importedWatchAccounts.getNumberOfAccounts(), by: 1) { let accountObject = importedWatchAccounts.getAccountObjectForIdx(i) for address in inputAddresses { if accountObject.isAddressPartOfAccount(address) { handleNewTxForAccount(accountObject, txObject: txObject) } } } guard let importedAddresses = importedAddresses else { return } for i in stride(from: 0, to: importedAddresses.getCount(), by: 1) { let importedAddress = importedAddresses.getAddressObjectAtIdx(i) for addr in inputAddresses { if (addr == importedAddress.getAddress()) { handleNewTxForImportedAddress(importedAddress, txObject: txObject) } } } guard let importedWatchAddresses = importedWatchAddresses else { return } for i in stride(from: 0, to: importedWatchAddresses.getCount(), by: 1) { let importedAddress = importedWatchAddresses.getAddressObjectAtIdx(i) for addr in inputAddresses { if (addr == importedAddress.getAddress()) { handleNewTxForImportedAddress(importedAddress, txObject: txObject) } } } } func respondToStealthPayment(_ note: Notification) { let responseDict = note.object as! NSDictionary let stealthAddress = responseDict.object(forKey: "stealth_addr") as! String let txid = responseDict.object(forKey: "txid") as! String let paymentAddress = responseDict.object(forKey: "addr") as! String let txTime = UInt64((responseDict.object(forKey: "time") as! NSNumber).uint64Value) DLog("respondToStealthPayment stealthAddress: \(stealthAddress)") DLog("respondToStealthPayment respondToStealthPaymentGetTxTries: \(self.respondToStealthPaymentGetTxTries)") if self.respondToStealthPaymentGetTxTries < self.RESPOND_TO_STEALTH_PAYMENT_GET_TX_TRIES_MAX_TRIES { TLBlockExplorerAPI.instance().getTx(txid, success: { (jsonData:AnyObject?) -> () in if jsonData == nil { return; } let txObject = TLTxObject(dict:jsonData as! NSDictionary) self.handleGetTxSuccessForRespondToStealthPayment(stealthAddress, paymentAddress: paymentAddress, txid: txid, txTime: txTime, txObject: txObject) self.respondToStealthPaymentGetTxTries = 0 }, failure: { (code, status) -> () in DLog("respondToStealthPayment getTx fail \(txid)") self.respondToStealthPayment(note) self.respondToStealthPaymentGetTxTries += 1 }) } } func setWalletTransactionListenerClosed() { DLog("setWalletTransactionListenerClosed") guard let accounts = accounts else { return } for i in stride(from: 0, to: accounts.getNumberOfAccounts(), by: 1) { let accountObject = accounts.getAccountObjectForIdx(i) accountObject.listeningToIncomingTransactions = false } guard let coldWalletAccounts = coldWalletAccounts else { return } for i in stride(from: 0, to: coldWalletAccounts.getNumberOfAccounts(), by: 1) { let accountObject = coldWalletAccounts.getAccountObjectForIdx(i) accountObject.listeningToIncomingTransactions = false } guard let importedAccounts = importedAccounts else { return } for i in stride(from: 0, to: importedAccounts.getNumberOfAccounts(), by: 1) { let accountObject = importedAccounts.getAccountObjectForIdx(i) accountObject.listeningToIncomingTransactions = false } guard let importedWatchAccounts = importedWatchAccounts else { return } for i in stride(from: 0, to: importedWatchAccounts.getNumberOfAccounts(), by: 1) { let accountObject = importedWatchAccounts.getAccountObjectForIdx(i) accountObject.listeningToIncomingTransactions = false } guard let importedAddresses = importedAddresses else { return } for i in stride(from: 0, to: importedAddresses.getCount(), by: 1) { let importedAddress = importedAddresses.getAddressObjectAtIdx(i) importedAddress.listeningToIncomingTransactions = false } guard let importedWatchAddresses = importedWatchAddresses else { return } for i in stride(from: 0, to: importedWatchAddresses.getCount(), by: 1) { let importedAddress = importedWatchAddresses.getAddressObjectAtIdx(i) importedAddress.listeningToIncomingTransactions = false } } func listenToIncomingTransactionForGeneratedAddress(_ note: Notification) { let address: AnyObject? = note.object as AnyObject? TLTransactionListener.instance().listenToIncomingTransactionForAddress(address as! String) } func updateModelWithNewTransaction(_ note: Notification) { let txDict = note.object as! NSDictionary DLog("updateModelWithNewTransaction txDict: \(txDict.debugDescription)") DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.background).async { let txObject = TLTxObject(dict:txDict) if self.pendingSelfStealthPaymentTxid != nil { // Special case where receiving stealth payment from same sending account. // Let stealth websocket handle it // Need this cause, must generate private key and add address to account so that the bitcoins can be accounted for. if txObject.getHash() as? String == self.pendingSelfStealthPaymentTxid { //self.pendingSelfStealthPaymentTxid = nil return } } let addressesInTx = txObject.getAddresses() guard let accounts = self.accounts else { return } for i in stride(from: 0, to: accounts.getNumberOfAccounts(), by: 1) { let accountObject = accounts.getAccountObjectForIdx(i) if !accountObject.hasFetchedAccountData() { continue } for address in addressesInTx { if (accountObject.isAddressPartOfAccount(address )) { DLog("updateModelWithNewTransaction accounts \(accountObject.getAccountID())") self.handleNewTxForAccount(accountObject, txObject: txObject) } } } guard let coldWalletAccounts = self.coldWalletAccounts else { return } for i in stride(from: 0, to: coldWalletAccounts.getNumberOfAccounts(), by: 1) { let accountObject = coldWalletAccounts.getAccountObjectForIdx(i) if !accountObject.hasFetchedAccountData() { continue } for address in addressesInTx { if (accountObject.isAddressPartOfAccount(address)) { DLog("updateModelWithNewTransaction coldWalletAccounts \(accountObject.getAccountID())") self.handleNewTxForAccount(accountObject, txObject: txObject) } } } guard let importedAccounts = self.importedAccounts else { return } for i in stride(from: 0, to: importedAccounts.getNumberOfAccounts(), by: 1) { let accountObject = importedAccounts.getAccountObjectForIdx(i) if !accountObject.hasFetchedAccountData() { continue } for address in addressesInTx { if (accountObject.isAddressPartOfAccount(address)) { DLog("updateModelWithNewTransaction importedAccounts \(accountObject.getAccountID())") self.handleNewTxForAccount(accountObject, txObject: txObject) } } } guard let importedWatchAccounts = self.importedWatchAccounts else { return } for i in stride(from: 0, to: importedWatchAccounts.getNumberOfAccounts(), by: 1) { let accountObject = importedWatchAccounts.getAccountObjectForIdx(i) if !accountObject.hasFetchedAccountData() { continue } for address in addressesInTx { if (accountObject.isAddressPartOfAccount(address)) { DLog("updateModelWithNewTransaction importedWatchAccounts \(accountObject.getAccountID())") self.handleNewTxForAccount(accountObject, txObject: txObject) } } } guard let importedAddresses = self.importedAddresses else { return } for i in stride(from: 0, to: importedAddresses.getCount(), by: 1) { let importedAddress = importedAddresses.getAddressObjectAtIdx(i) if !importedAddress.hasFetchedAccountData() { continue } let address = importedAddress.getAddress() for addr in addressesInTx { if (addr == address) { DLog("updateModelWithNewTransaction importedAddresses \(address)") self.handleNewTxForImportedAddress(importedAddress, txObject: txObject) } } } guard let importedWatchAddresses = self.importedWatchAddresses else { return } for i in stride(from: 0, to: importedWatchAddresses.getCount(), by: 1) { let importedAddress = importedWatchAddresses.getAddressObjectAtIdx(i) if !importedAddress.hasFetchedAccountData() { continue } let address = importedAddress.getAddress() for addr in addressesInTx { if (addr == address) { DLog("updateModelWithNewTransaction importedWatchAddresses \(address)") self.handleNewTxForImportedAddress(importedAddress, txObject: txObject) } } } } } func handleNewTxForAccount(_ accountObject: TLAccountObject, txObject: TLTxObject) { let receivedAmount = accountObject.processNewTx(txObject) let receivedTo = accountObject.getAccountNameOrAccountPublicKey() //AppDelegate.instance().pendingOperations.addSetUpAccountOperation(accountObject, fetchDataAgain: true, success: { updateUIForNewTx(txObject.getHash() as! String, receivedAmount: receivedAmount, receivedTo: receivedTo) //}) } func handleNewTxForImportedAddress(_ importedAddress: TLImportedAddress, txObject: TLTxObject) { let receivedAmount = importedAddress.processNewTx(txObject) let receivedTo = importedAddress.getLabel() //AppDelegate.instance().pendingOperations.addSetUpImportedAddressOperation(importedAddress, fetchDataAgain: true, success: { updateUIForNewTx(txObject.getHash() as! String, receivedAmount: receivedAmount, receivedTo: receivedTo) //}) } func updateUIForNewTx(_ txHash: String, receivedAmount: TLCoin?, receivedTo: String) { DispatchQueue.main.async { DLog("updateUIForNewTx txHash \(txHash)") self.webSocketNotifiedTxHashSet.add(txHash) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_MODEL_UPDATED_NEW_UNCONFIRMED_TRANSACTION()), object: txHash, userInfo:nil) if let receivedAmount = receivedAmount { NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_RECEIVE_PAYMENT()), object:nil, userInfo:nil) self.promptReceivedPayment(receivedTo, receivedAmount: receivedAmount) } } } func promptReceivedPayment(_ receivedTo:String, receivedAmount:TLCoin) { let delayTime = DispatchTime.now() + Double(Int64(1 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC) DispatchQueue.main.asyncAfter(deadline: delayTime) { let msg = "\(receivedTo) received \(TLCurrencyFormat.getProperAmount(receivedAmount))" TLPrompts.promptSuccessMessage(msg, message: "") AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate)) } } func updateModelWithNewBlock(_ note: Notification) { let jsonData = note.object as! NSDictionary let blockHeight = jsonData.object(forKey: "height") as! NSNumber DLog("updateModelWithNewBlock: \(blockHeight)") TLBlockchainStatus.instance().blockHeight = blockHeight.uint64Value NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_MODEL_UPDATED_NEW_BLOCK()), object:nil, userInfo:nil) } func initializeWalletAppAndShowInitialScreen(_ recoverHDWalletIfNewlyInstalledApp:(Bool), walletPayload:(NSDictionary?)) { TLAnalytics.instance() NotificationCenter.default.addObserver(self ,selector:#selector(AppDelegate.saveWalletPayloadDelay(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(AppDelegate.updateModelWithNewTransaction(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_NEW_UNCONFIRMED_TRANSACTION()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(AppDelegate.updateModelWithNewBlock(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_NEW_BLOCK()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(AppDelegate.listenToIncomingTransactionForGeneratedAddress(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_NEW_ADDRESS_GENERATED()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(AppDelegate.setWalletTransactionListenerClosed), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_TRANSACTION_LISTENER_CLOSE()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(AppDelegate.listenToIncomingTransactionForWallet), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_TRANSACTION_LISTENER_OPEN()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(AppDelegate.setAccountsListeningToStealthPaymentsToFalse), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_STEALTH_PAYMENT_LISTENER_CLOSE()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(AppDelegate.respondToStealthChallegeNotification(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_RECEIVED_STEALTH_CHALLENGE()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(AppDelegate.respondToStealthAddressSubscription(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_RECEIVED_STEALTH_ADDRESS_SUBSCRIPTION()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(AppDelegate.respondToStealthPayment(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_RECEIVED_STEALTH_PAYMENT()), object:nil) var passphrase = TLWalletPassphrase.getDecryptedWalletPassphrase() if !TLPreferences.hasSetupHDWallet() { if (recoverHDWalletIfNewlyInstalledApp) { self.recoverHDWallet(passphrase!) } else { passphrase = TLHDWalletWrapper.generateMnemonicPassphrase() self.refreshApp(passphrase!) let accountObject = self.accounts!.createNewAccount(TLDisplayStrings.ACCOUNT_1_STRING(), accountType:.normal, preloadStartingAddresses:true) accountObject.updateAccountNeedsRecovering(false) AppDelegate.instance().updateGodSend(TLSendFromType.hdWallet, sendFromIndex:0) AppDelegate.instance().updateReceiveSelectedObject(TLSendFromType.hdWallet, sendFromIndex:0) AppDelegate.instance().updateHistorySelectedObject(TLSendFromType.hdWallet, sendFromIndex:0) } justSetupHDWallet = true guard let password = TLWalletJson.getDecryptedEncryptedWalletJSONPassphrase(), let walletsJson = appWallet.getWalletsJson() else { return } let encryptedWalletJson = TLWalletJson.getEncryptedWalletJsonContainer(walletsJson, password: password) let success = saveWalletJson(encryptedWalletJson as NSString, date:Date()) if success { TLPreferences.setHasSetupHDWallet(true) } else { NSException(name: NSExceptionName(rawValue: "Error"), reason: "Error saving wallet JSON file", userInfo: nil).raise() } } else { let masterHex = TLHDWalletWrapper.getMasterHex(passphrase ?? "") if let walletPayload = walletPayload { appWallet.loadWalletPayload(walletPayload, masterHex:masterHex) } else { TLPrompts.promptErrorMessage(TLDisplayStrings.ERROR_STRING(), message:TLDisplayStrings.ERROR_LOADING_WALLET_JSON_FILE_STRING()) NSException(name: NSExceptionName(rawValue: "Error"), reason: "Error loading wallet JSON file", userInfo: nil).raise() } } accounts = TLAccounts(appWallet: appWallet, accountsArray: appWallet.getAccountObjectArray(), accountType:.hdWallet) coldWalletAccounts = TLAccounts(appWallet: appWallet, accountsArray: appWallet.getColdWalletAccountArray(), accountType: .coldWallet) importedAccounts = TLAccounts(appWallet: appWallet, accountsArray: appWallet.getImportedAccountArray(), accountType: .imported) importedWatchAccounts = TLAccounts(appWallet: appWallet, accountsArray: appWallet.getWatchOnlyAccountArray(), accountType:.importedWatch) importedAddresses = TLImportedAddresses(appWallet: appWallet, importedAddresses: appWallet.getImportedPrivateKeyArray(), accountAddressType:TLAccountAddressType.imported) importedWatchAddresses = TLImportedAddresses(appWallet: appWallet, importedAddresses: appWallet.getWatchOnlyAddressArray(), accountAddressType:TLAccountAddressType.importedWatch) isAccountsAndImportsLoaded = true godSend = TLSpaghettiGodSend(appWallet: appWallet) receiveSelectedObject = TLSelectedObject() historySelectedObject = TLSelectedObject() updateGodSend() let selectObjected: AnyObject? = self.godSend?.getSelectedSendObject() if let receiveSelectedObject = receiveSelectedObject, let historySelectedObject = historySelectedObject { if selectObjected is TLAccountObject { receiveSelectedObject.setSelectedAccount(selectObjected as! TLAccountObject) historySelectedObject.setSelectedAccount(selectObjected as! TLAccountObject) } else if (selectObjected is TLImportedAddress) { receiveSelectedObject.setSelectedAddress(selectObjected as! TLImportedAddress) historySelectedObject.setSelectedAddress(selectObjected as! TLImportedAddress) } } guard let accounts = accounts else { return } assert(accounts.getNumberOfAccounts() > 0, "") TLBlockExplorerAPI.instance() TLExchangeRate.instance() TLAchievements.instance() guard let blockExplorerURL = TLPreferences.getBlockExplorerURL(TLPreferences.getBlockExplorerAPI()), let baseURL = URL(string: blockExplorerURL) else { return } TLNetworking.isReachable(baseURL, reachable:{(reachable: TLDOMAINREACHABLE) in if reachable == TLDOMAINREACHABLE.notreachable { TLPrompts.promptErrorMessage(TLDisplayStrings.NETWORK_ERROR_STRING(), message:String(format:TLDisplayStrings.X_SERVERS_NOT_REACHABLE_STRING(), blockExplorerURL)) } }) TLBlockExplorerAPI.instance().getBlockHeight({(jsonData: AnyObject!) in let blockHeight = (jsonData.object(forKey: "height") as! NSNumber).uint64Value DLog("setBlockHeight: \((jsonData.object(forKey: "height") as! NSNumber))") TLBlockchainStatus.instance().blockHeight = blockHeight }, failure:{(code, status) in DLog("Error getting block height.") // TLPrompts.promptErrorMessage(TLDisplayStrings.NETWORK_ERROR_STRING(), // message:String(format:TLDisplayStrings.ERROR_GETTING_BLOCK_HEIGHT_STRING())) }) } func refreshHDWalletAccounts(_ isRestoringWallet: Bool) { let group = DispatchGroup() guard let accounts = accounts else { return } for i in stride(from: 0, to: accounts.getNumberOfAccounts(), by: 1) { let accountObject = accounts.getAccountObjectForIdx(i) group.enter() // if account needs recovering dont fetch account data if (accountObject.needsRecovering()) { return } guard var activeAddresses = accountObject.getActiveMainAddresses() as? [String] else { return } activeAddresses += accountObject.getActiveChangeAddresses() as! [String] if TLWalletUtils.ENABLE_STEALTH_ADDRESS() { if let stealthWallet = accountObject.stealthWallet { activeAddresses += stealthWallet.getPaymentAddresses() group.enter() DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.default).async { accountObject.fetchNewStealthPayments(isRestoringWallet) group.leave() } } } accountObject.getAccountData(activeAddresses, shouldResetAccountBalance: true, success: { () in group.leave() }, failure: { () in group.leave() }) } group.wait(timeout: DispatchTime.distantFuture) } fileprivate func setUpLocalNotification() { if (TLUtils.getiOSVersion() >= 8) { let types: UIUserNotificationType = [UIUserNotificationType.badge, UIUserNotificationType.sound, UIUserNotificationType.alert] let mySettings = UIUserNotificationSettings(types: types, categories:nil) UIApplication.shared.registerUserNotificationSettings(mySettings) } } func application(_ applcation: UIApplication, didReceive notification: UILocalNotification) { if let alertBody = notification.alertBody { DLog("didReceiveLocalNotification: \(alertBody)") let av = UIAlertView(title: alertBody, message:"", delegate:nil, cancelButtonTitle:nil, otherButtonTitles:TLDisplayStrings.OK_STRING()) av.show() AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate)) } } fileprivate func showLocalNotification(_ message: String) { DLog("showLocalNotification: \(message)") let localNotification = UILocalNotification() localNotification.soundName = UILocalNotificationDefaultSoundName localNotification.fireDate = Date(timeIntervalSinceNow:1) localNotification.alertBody = message localNotification.timeZone = TimeZone.current UIApplication.shared.scheduleLocalNotification(localNotification) } fileprivate func isCameraAllowed() -> Bool { return AVCaptureDevice.authorizationStatus(forMediaType: AVMediaTypeVideo) != AVAuthorizationStatus.denied } fileprivate func promptAppNotAllowedCamera() { let displayName = TLUtils.defaultAppName() let av = UIAlertView(title: String(format:TLDisplayStrings.X_NOT_ALLOWED_TO_ACCESS_THE_CAMERA_STRING(), displayName), message: String(format:TLDisplayStrings.ALLOW_CAMERA_ACCESS_IN_STRING(), displayName), delegate:nil , cancelButtonTitle:TLDisplayStrings.OK_STRING()) av.show() } func showPrivateKeyReaderController(_ viewController: UIViewController, success: @escaping TLWalletUtils.SuccessWithDictionary, error: @escaping TLWalletUtils.ErrorWithString) { if !isCameraAllowed() { self.promptAppNotAllowedCamera() return } let reader = TLQRCodeScannerViewController(success:{(data: String?) in if let data = data, TLCoreBitcoinWrapper.isBIP38EncryptedKey(data, isTestnet: self.appWallet.walletConfig.isTestnet) { self.scannedEncryptedPrivateKey = data } else { guard let data = data else { error("No Data") return } success(["privateKey": data]) } }, error:{(e: String?) in error(e) }) viewController.present(reader, animated:true, completion:nil) } func showAddressReaderControllerFromViewController(_ viewController: (UIViewController), success: @escaping (TLWalletUtils.SuccessWithString), error: @escaping (TLWalletUtils.ErrorWithString)) { if (!isCameraAllowed()) { promptAppNotAllowedCamera() return } let reader = TLQRCodeScannerViewController(success:{(data: String?) in success(data) }, error:{(e: String?) in error(e) }) viewController.present(reader, animated:true, completion:nil) } func showExtendedPrivateKeyReaderController(_ viewController: (UIViewController), success: @escaping (TLWalletUtils.SuccessWithString), error: @escaping (TLWalletUtils.ErrorWithString)) { if (!isCameraAllowed()) { promptAppNotAllowedCamera() return } let reader = TLQRCodeScannerViewController(success:{(data: String?) in success(data) }, error:{(e: String?) in error(e) }) viewController.present(reader, animated:true, completion:nil) } func showExtendedPublicKeyReaderController(_ viewController: (UIViewController), success: @escaping (TLWalletUtils.SuccessWithString), error: @escaping (TLWalletUtils.ErrorWithString)) { if (!isCameraAllowed()) { promptAppNotAllowedCamera() return } let reader = TLQRCodeScannerViewController(success:{(data: String?) in success(data) }, error:{(e: String?) in error(e) }) viewController.present(reader, animated:true, completion:nil) } func showColdWalletSpendReaderControllerFromViewController(_ viewController: (UIViewController), success: @escaping (TLWalletUtils.SuccessWithString), error: @escaping (TLWalletUtils.ErrorWithString)) { if (!isCameraAllowed()) { promptAppNotAllowedCamera() return } let reader = TLQRCodeScannerViewController(success:{(data: String?) in success(data) }, error:{(e: String?) in error(e) }) viewController.present(reader, animated:true, completion:nil) } func listenToIncomingTransactionForWallet() { if (!isAccountsAndImportsLoaded || !TLTransactionListener.instance().isWebSocketOpen()) { return } guard let accounts = accounts else { return } for i in stride(from: 0, to: accounts.getNumberOfAccounts(), by: 1) { let accountObject = accounts.getAccountObjectForIdx(i) if accountObject.downloadState != .downloaded { continue } guard let activeMainAddresses = accountObject.getActiveMainAddresses() else { return } for address in activeMainAddresses { TLTransactionListener.instance().listenToIncomingTransactionForAddress(address as! String) } guard let activeChangeAddresses = accountObject.getActiveChangeAddresses() else { return } for address in activeChangeAddresses { TLTransactionListener.instance().listenToIncomingTransactionForAddress(address as! String) } if let stealthWallet = accountObject.stealthWallet { let stealthPaymentAddresses = stealthWallet.getUnspentPaymentAddresses() for address in stealthPaymentAddresses { TLTransactionListener.instance().listenToIncomingTransactionForAddress(address) } } accountObject.listeningToIncomingTransactions = true } guard let coldWalletAccounts = coldWalletAccounts else { return } for i in stride(from: 0, to: coldWalletAccounts.getNumberOfAccounts(), by: 1) { let accountObject = coldWalletAccounts.getAccountObjectForIdx(i) if accountObject.downloadState != .downloaded { continue } guard let activeMainAddresses = accountObject.getActiveMainAddresses() else { return } for address in activeMainAddresses { TLTransactionListener.instance().listenToIncomingTransactionForAddress(address as! String) } guard let activeChangeAddresses = accountObject.getActiveChangeAddresses() else { return } for address in activeChangeAddresses { TLTransactionListener.instance().listenToIncomingTransactionForAddress(address as! String) } accountObject.listeningToIncomingTransactions = true } guard let importedAccounts = importedAccounts else { return } for i in stride(from: 0, to: importedAccounts.getNumberOfAccounts(), by: 1) { let accountObject = importedAccounts.getAccountObjectForIdx(i) if accountObject.downloadState != .downloaded { continue } guard let activeMainAddresses = accountObject.getActiveMainAddresses() else { return } for address in activeMainAddresses { TLTransactionListener.instance().listenToIncomingTransactionForAddress(address as! String) } guard let activeChangeAddresses = accountObject.getActiveChangeAddresses() else { return } for address in activeChangeAddresses { TLTransactionListener.instance().listenToIncomingTransactionForAddress(address as! String) } if let stealthWallet = accountObject.stealthWallet { let stealthPaymentAddresses = stealthWallet.getUnspentPaymentAddresses() for address in stealthPaymentAddresses { TLTransactionListener.instance().listenToIncomingTransactionForAddress(address) } } accountObject.listeningToIncomingTransactions = true } guard let importedWatchAccounts = importedWatchAccounts else { return } for i in stride(from: 0, to: importedWatchAccounts.getNumberOfAccounts(), by: 1) { let accountObject = importedWatchAccounts.getAccountObjectForIdx(i) if accountObject.downloadState != .downloaded { continue } guard let activeMainAddresses = accountObject.getActiveMainAddresses() else { return } for address in activeMainAddresses { TLTransactionListener.instance().listenToIncomingTransactionForAddress(address as! String) } guard let activeChangeAddresses = accountObject.getActiveChangeAddresses() else { return } for address in activeChangeAddresses { TLTransactionListener.instance().listenToIncomingTransactionForAddress(address as! String) } accountObject.listeningToIncomingTransactions = true } guard let importedAddresses = importedAddresses else { return } for i in stride(from: 0, to: importedAddresses.getCount(), by: 1) { let importedAddress = importedAddresses.getAddressObjectAtIdx(i) if importedAddress.downloadState != .downloaded { continue } let address = importedAddress.getAddress() TLTransactionListener.instance().listenToIncomingTransactionForAddress(address) importedAddress.listeningToIncomingTransactions = true } guard let importedWatchAddresses = importedWatchAddresses else { return } for i in stride(from: 0, to: importedWatchAddresses.getCount(), by: 1) { let importedAddress = importedWatchAddresses.getAddressObjectAtIdx(i) if importedAddress.downloadState != .downloaded { continue } let address = importedAddress.getAddress() TLTransactionListener.instance().listenToIncomingTransactionForAddress(address) importedAddress.listeningToIncomingTransactions = true } } func application(_ application: (UIApplication), open url: URL, sourceApplication: (String)?, annotation:Any) -> Bool { self.bitcoinURIOptionsDict = TLWalletUtils.parseBitcoinURI(url.absoluteString) return true } func applicationWillResignActive(_ application: UIApplication) { } func applicationDidEnterBackground(_ application: UIApplication) { } func applicationWillEnterForeground(_ application: UIApplication) { TLExchangeRate.instance().updateExchangeRate() } func applicationDidBecomeActive(_ application: UIApplication) { } func applicationWillTerminate(_ application: UIApplication) { saveWalletJsonCloud() } func saveWalletPayloadDelay(_ notification: Notification) { DispatchQueue.main.async { if self.saveWalletJSONEnabled == false { return } NSObject.cancelPreviousPerformRequests(withTarget: self, selector:#selector(AppDelegate.saveWalletJsonCloudBackground), object:nil) Timer.scheduledTimer(timeInterval: self.SAVE_WALLET_PAYLOAD_DELAY, target: self, selector: #selector(AppDelegate.saveWalletJsonCloudBackground), userInfo: nil, repeats: false) } } func saveWalletJsonCloudBackground() { DLog("saveWalletJsonCloudBackground starting...") let queue = DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.background) queue.async { self.saveWalletJsonCloud() } } func printOutWalletJSON() { guard let walletJson = appWallet.getWalletsJson() else { return } DLog("printOutWalletJSON:\n\(walletJson)") } func saveWalletJsonCloud() -> Bool { if saveWalletJSONEnabled == false { DLog("saveWalletJSONEnabled disabled") return false } DLog("saveFileToCloud starting...") guard let walletJson = appWallet.getWalletsJson(), let password = TLWalletJson.getDecryptedEncryptedWalletJSONPassphrase() else { return false } let encryptedWalletJson = TLWalletJson.getEncryptedWalletJsonContainer(walletJson, password: password) saveWalletJson(encryptedWalletJson as (NSString), date:Date()) DLog("saveFileToCloud local done") return true } fileprivate func saveWalletJson(_ encryptedWalletJson: (NSString), date: (Date)) -> Bool { let success = TLWalletJson.saveWalletJson(encryptedWalletJson as String, date:date) if (!success) { DispatchQueue.main.async { TLPrompts.promptErrorMessage(TLDisplayStrings.LOCAL_BACK_UP_TO_WALLET_FAILED_STRING(), message:TLDisplayStrings.LOCAL_BACK_UP_TO_WALLET_FAILED_STRING()) } } return success } func getLocalWalletJsonDict() -> NSDictionary? { return TLWalletJson.getWalletJsonDict(TLWalletJson.getLocalWalletJSONFile(), password:TLWalletJson.getDecryptedEncryptedWalletJSONPassphrase()) } fileprivate func menuShownHideStatusBar() { UIApplication.shared.isStatusBarHidden = true } fileprivate func menuHiddenShowStatusBar() { UIApplication.shared.isStatusBarHidden = false } func passcodeViewControllerWillClose() { UIApplication.shared.isStatusBarHidden = false } func maxNumberOfFailedAttemptsReached() { } func passcodeWasEnteredSuccessfully() { UIApplication.shared.isStatusBarHidden = false } func logoutButtonWasPressed() { UIApplication.shared.isStatusBarHidden = false } } ================================================ FILE: ArcBit/ArcBit-Bridging-Header.h ================================================ // // Use this file to import your target's public headers that you would like to expose to Swift. // #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import "UIAlertController+Blocks.h" #import "NSMutableData+Bitcoin.h" #import "UIViewController+ECSlidingViewController.h" #import "UINavigationBar+FixedHeightWhenStatusBarHidden.h" #import #import "JNKeyChain.h" #import "MBProgressHUD.h" #import #import "RNCryptor/RNCryptor.h" #import "BRKey.h" #import "BRKey+BIP38.h" #import "NSString+Base58.h" #import #import "BRTransaction.h" #import "NSData+Hash.h" #import "QREncoder.h" #import "RNEncryptor.h" #import "RNDecryptor.h" #import "NSDate-Utilities.h" #import "RNCryptor.h" #import "iToast.h" #import "AFNetworking.h" #import "SRWebSocket.h" #import "SRWebSocket+Helpers.h" #import "AFNetworkActivityIndicatorManager.h" #import "CustomIOS7AlertView.h" #import "ECSlidingConstants.h" #import "IASKAppSettingsViewController.h" #import "IASKSettingsReader.h" #import "LTHPasscodeViewController.h" #import "TLCloudDocumentSyncWrapper.h" ================================================ FILE: ArcBit/ArcBit.entitlements ================================================ ================================================ FILE: ArcBit/Base.lproj/LaunchScreen.xib ================================================ ================================================ FILE: ArcBit/Base.lproj/Localizable.strings ================================================ "" = ""; "%@ is not allowed to access the camera" = "%@ is not allowed to access the camera"; "%@ servers not reachable." = "%@ servers not reachable."; "%d/%d parts scanned." = "%d/%d parts scanned."; "%llu confirmations" = "%llu confirmations"; "1 Confirmation" = "1 Confirmation"; "A bitcoin address typically begins with a '1' or '3'. You can see the transactions and track the balance of an address, but you cannot spend from an imported address.\nYou can temporarily import this watch addresses private key to spend its bitcoins. Simply go the Send screen and select a watch address to spend from and you will be prompted to temporarily import your addresses' private key when you click 'Review Payment' in the Send screen. The private key will stay in memory until the app exits or until you remove it manually in the Accounts screen." = "A bitcoin address typically begins with a '1' or '3'. You can see the transactions and track the balance of an address, but you cannot spend from an imported address.\nYou can however temporarily import this watch addresses' private key to spend its bitcoins. Simply go to the Send screen and select a watch address to spend from and you will be prompted to temporarily import your addresses' private key when you click 'Review Payment' on the Send screen. The private key will stay in memory until the app exits or until you remove it manually on the Accounts screen."; "A bitcoin wallet is a software application that allows people to send, receive and manage their bitcoins.\nBe aware of how other bitcoin applications store your bitcoins' private keys, which are needed to spend your bitcoins.\nThere are generally three different ways applications can your store your bitcoins.\n1.\nThe banking model, where your bitcoin private keys are held for you by someone else.\n2.\nThe security box model, where your bitcoin private keys are stored encrypted on someone else’s servers.\n3.\nThe wallet model, where your bitcoin private keys are stored only on your device." = "A bitcoin wallet is a software application that allows people to send, receive and manage their bitcoins.\nBe aware of how other bitcoin applications store your bitcoins' private keys, which are needed to spend your bitcoins.\nThere are generally three different ways applications can your store your bitcoins.\n1.\nThe banking model, where your bitcoin private keys are held for you by someone else.\n2.\nThe security box model, where your bitcoin private keys are stored encrypted on someone else’s servers.\n3.\nThe wallet model, where your bitcoin private keys are stored only on your device."; "A private key begins with an 'L', 'K', or '5'.\nBIP 38 encrypted private keys can also be imported. They can either be imported encrypted or unencrypted. If you choose to import it encrypted, you will need to input the password each time you spend from your encrypted private key." = "A private key begins with an 'L', 'K', or '5'.\nBIP 38 encrypted private keys can also be imported. They can either be imported encrypted or unencrypted. If you choose to import it encrypted, you will need to input the password each time you spend from your encrypted private key."; "Account %@ imported" = "Account %@ imported"; "Account %lu" = "Account %lu"; "Account 1" = "Account 1"; "Account ID" = "Account ID"; "Account ID: %u" = "Account ID: %u"; "Account Private Key" = "Account Private Key"; "Account Public Key" = "Account Public Key"; "Account private key does not match imported account public key" = "Account private key does not match imported account public key"; "Account private key missing" = "Account private key missing"; "Accounts" = "Accounts"; "Achievement List" = "Achievement List"; "Achievements" = "Achievements"; "Actions" = "Actions"; "Active Change Addresses" = "Active Change Addresses"; "Active Main Addresses" = "Active Main Addresses"; "Add Contacts Entry" = "Add Contacts Entry"; "Address" = "Address"; "Address ID " = "Address ID "; "Address ID: %lu" = "Address ID: %lu"; "Addresses" = "Addresses"; "Advanced Achievement List" = "Advanced Achievement List"; "Advanced FAQ" = "Advanced FAQ"; "Advanced how To:" = "Advanced how To:"; "After a transaction is broadcast to the Bitcoin network, it may be included in a block that is published to the network. When that happens, it is said that the transaction has been mined at a depth of 1 block. With each subsequent block that is found, the number of blocks deep is increased by one. To be secure against double spending, a transaction should not be considered as confirmed until it is a certain number of blocks deep.\nA good rule of thumb is that 1 confirmation is good for small value amounts of bitcoins and a user should wait for more confirmations for larger value amounts.\nArcBit will display the confirmation number up until the 6th confirmation." = "After a transaction is broadcast to the Bitcoin network, it may be included in a block that is published to the network. When that happens, it is said that the transaction has been mined at a depth of 1 block. With each subsequent block that is found, the number of blocks deep is increased by one. To be secure against double spending, a transaction should not be considered as confirmed until it is a certain number of blocks deep.\nA good rule of thumb is that 1 confirmation is good for small value amounts of bitcoins and a user should wait for more confirmations for larger value amounts.\nArcBit will display the confirmation number up until the 6th confirmation."; "Amount:" = "Amount:"; "An account is a collection of bitcoin addresses. With accounts, you will no longer have to manage bitcoin addresses directly anymore. Since address reuse results in a loss of privacy for people using Bitcoin, ArcBit’s HD wallet account system will automatically handle the cycling of bitcoin addresses for you. This ensures you don’t use the same bitcoin address more then once.\nEach account also has a reusable address. You can find it in your Receive screen. Swipe all the way to right on the QRCode in your Receive screen and you will find a reusable address.\nYou can create an unlimited amount of accounts with ArcBit. See the help section on how to create a new account in ArcBit." = "An account is a collection of bitcoin addresses. With accounts, you will no longer have to manage bitcoin addresses directly anymore. Since address reuse results in a loss of privacy for people using Bitcoin, ArcBit’s HD wallet account system will automatically handle the cycling of bitcoin addresses for you. This ensures you don’t use the same bitcoin address more then once.\nEach account also has a reusable address. You can find it in your Receive screen. Swipe all the way to right on the QRCode in your Receive screen and you will find a reusable address.\nYou can create an unlimited amount of accounts with ArcBit. See the help section on how to create a new account in ArcBit."; "An account private key begins with the letters 'xprv'. You can see, spend and recover the transactions and bitcoins of an entire account from an account private key." = "An account private key begins with the letters 'xprv'. You can see, spend and recover the transactions and bitcoins of an entire account from an account private key."; "An account public key begins with the letters 'xpub'. You can see the transactions and bitcoins of an entire account from an account private key, with the exception of reusable address payments. Future releases will address this issue.\nYou can temporarily import the corresponding account private key for this accounts' account public key to spend your watch accounts' bitcoins. Simply go to the Send screen and select a watch account to spend from and you will be prompted to temporarily import your account's private key when you click 'Review Payment' on the Send screen. The private key will stay in memory until the app exits or until you remove it manually on the Accounts screen." = "An account public key begins with the letters 'xpub'. You can see the transactions and bitcoins of an entire account from an account private key, with the exception of reusable address payments. Future releases will address this issue.\nYou can temporarily import the corresponding account private key for this accounts' account public key to spend your watch accounts' bitcoins. Simply go to the Send screen and select a watch account to spend from and you will be prompted to temporarily import your account's private key when you click 'Review Payment' on the Send screen. The private key will stay in memory until the app exits or until you remove it manually on the Accounts screen."; "ArcBit Brain Wallet" = "ArcBit Brain Wallet"; "ArcBit Web Wallet" = "ArcBit Web Wallet"; "ArcBit uses the the bitcoin wallet model (See the section ’What is a bitcoin wallet?’ to understand the 3 different security models of bitcoin software). However, if you use iCloud to backup your wallet, you will be using the security box model. It is recommended that you do not use iCloud and be responsible for your bitcoins yourself. For those who don’t want to remember a simple backup passphrase, iCloud backup is a good alternative." = "ArcBit uses the the bitcoin wallet model (See the section ’What is a bitcoin wallet?’ to understand the 3 different security models of bitcoin software). However, if you use iCloud to backup your wallet, you will be using the security box model. It is recommended that you do not use iCloud and be responsible for your bitcoins yourself. For those who don’t want to remember a simple backup passphrase, iCloud backup is a good alternative."; "Archive Account" = "Archive Account"; "Archive address" = "Archive address"; "Archived Accounts" = "Archived Accounts"; "Archived Change Addresses" = "Archived Change Addresses"; "Archived Cold Wallet Accounts" = "Archived Cold Wallet Accounts"; "Archived Imported Accounts" = "Archived Imported Accounts"; "Archived Imported Addresses" = "Archived Imported Addresses"; "Archived Imported Watch Accounts" = "Archived Imported Watch Accounts"; "Archived Imported Watch Addresses" = "Archived Imported Watch Addresses"; "Archived Main Addresses" = "Archived Main Addresses"; "Are you sure you want to archive account %@?" = "Are you sure you want to archive account %@?"; "Are you sure you want to archive address %@?" = "Are you sure you want to archive address %@?"; "Are you sure you want to delete this account?" = "Are you sure you want to delete this account?"; "Are you sure you want to unarchive account %@" = "Are you sure you want to unarchive account %@"; "Are you sure you want to unarchive address %@?" = "Are you sure you want to unarchive address %@?"; "Authorize Cold Wallet Account Payment" = "Authorize Cold Wallet Account Payment"; "Authorize Payment" = "Authorize Payment"; "Backup Passphrase" = "Backup Passphrase"; "Backup wallet" = "Backup wallet"; "Backup local wallet" = "Backup local wallet"; "Backup passphrase found in keychain" = "Backup passphrase found in keychain"; "Bitcoin cuts out the middleman and allows you to send money anywhere in the world with an internet connection with minimum to zero fees." = "Bitcoin cuts out the middleman and allows you to send money anywhere in the world with an internet connection with minimum to zero fees."; "Bitcoin, uppercase 'B', is an online payment system invented in 2008 and released as open-source software in 2009 by a programmer named Satoshi Nakamoto. The system is decentralized and peer-to-peer allowing users to transact directly without needing an intermediary.\nBitcoin is also a platform of which other decentralized applications can be built upon. Bitcoin, lowercase 'b' is the currency unit that Bitcoin uses." = "Bitcoin, uppercase 'B', is an online payment system invented in 2008 and released as open-source software in 2009 by a programmer named Satoshi Nakamoto. The system is decentralized and peer-to-peer allowing users to transact directly without needing an intermediary.\nBitcoin is also a platform of which other decentralized applications can be built upon. Bitcoin, lowercase 'b' is the currency unit that Bitcoin uses."; "Bitcoins can be purchased from various bitcoin exchanges. ArcBit is not a bitcoin exchange. ArcBit is a bitcoin wallet. After you purchase some bitcoins from an exchange, you can move it to a bitcoin wallet." = "Bitcoins can be purchased from various bitcoin exchanges. ArcBit is not a bitcoin exchange. ArcBit is a bitcoin wallet. After you purchase some bitcoins from an exchange, you can move it to a bitcoin wallet."; "Cancel" = "Cancel"; "Cannot archive your default account" = "Cannot archive your default account"; "Cannot archive your one and only account" = "Cannot archive your one and only account"; "Cannot create transactions with outputs less then %@" = "Cannot create transactions with outputs less then %@"; "Cannot decrypt iCloud backup wallet." = "Cannot decrypt iCloud backup wallet."; "Cannot import reusable address" = "Cannot import reusable address"; "Change Address ID " = "Change Address ID "; "Change Automatic Transaction Fee" = "Change Automatic Transaction Fee"; "Change Block Explorer URL" = "Change Block Explorer URL"; "Change Blockexplorer Type" = "Change Blockexplorer Type"; "Check out the ArcBit Brain Wallet" = "Check out the ArcBit Brain Wallet"; "Check out the ArcBit Web Wallet" = "Check out the ArcBit Web Wallet"; "Check out the ArcBit Web Wallet!" = "Check out the ArcBit Web Wallet!"; "Checking Transaction" = "Checking Transaction"; "Clear account private key from memory" = "Clear account private key from memory"; "Clear private key from memory" = "Clear private key from memory"; "Cleared from memory" = "Cleared from memory"; "Click an address" = "Click an address"; "Click the button with the arrow" = "Click the button with the arrow"; "Click the plus button at the top right" = "Click the plus button at the top right"; "Click the ‘Contacts’ button" = "Click the ‘Contacts’ button"; "Click ‘Accounts’" = "Click ‘Accounts’"; "Click ‘Advanced settings’" = "Click ‘Advanced settings’"; "Click ‘Archive Account’" = "Click ‘Archive Account’"; "Click ‘Create New Account’" = "Click ‘Create New Account’"; "Click ‘Delete’" = "Click ‘Delete’"; "Click ‘Done’" = "Click ‘Done’"; "Click ‘Edit Account Name’" = "Click ‘Edit Account Name’"; "Click ‘Edit’" = "Click ‘Edit’"; "Click ‘Enable PIN Code’" = "Click ‘Enable PIN Code’"; "Click ‘History’" = "Click ‘History’"; "Click ‘Import Account’" = "Click ‘Import Account’"; "Click ‘Import Private Key’" = "Click ‘Import Private Key’"; "Click ‘Import Watch Only Account’" = "Click ‘Import Watch Only Account’"; "Click ‘Import Watch Only Address’" = "Click ‘Import Watch Only Address’"; "Click ‘Label transaction’" = "Click ‘Label transaction’"; "Click ‘Restore Wallet’" = "Click ‘Restore Wallet’"; "Click ‘Restore’" = "Click ‘Restore’"; "Click ‘Review Payment’" = "Click ‘Review Payment’"; "Click ‘Send’" = "Click ‘Send’"; "Click ‘Set Transaction Fee’" = "Click ‘Set Transaction Fee’"; "Click ‘Settings’" = "Click ‘Settings’"; "Click ‘Show Backup Passphrase’" = "Click ‘Show Backup Passphrase’"; "Click ‘View Addresses’" = "Click ‘View Addresses’"; "Click ‘View account private key QR code’" = "Click ‘View account private key QR code’"; "Click ‘View account public key QR code’" = "Click ‘View account public key QR code’"; "Click ‘View address QR code’" = "Click ‘View address QR code’"; "Click ‘View in web’" = "Click ‘View in web’"; "Click ‘View private key QR code’" = "Click ‘View private key QR code’"; "Click ‘blockexplorer API type’" = "Click ‘blockexplorer API type’"; "Click ’Receive’" = "Click ’Receive’"; "Close" = "Close"; "Cold Wallet" = "Cold Wallet"; "Cold Wallet Accounts" = "Cold Wallet Accounts"; "Cold Wallet Accounts can't see reusable address payments, thus this accounts' reusable address is not available." = "Cold Wallet Accounts can't see reusable address payments, thus this accounts' reusable address is not available."; "Cold Wallet Overview" = "Cold Wallet Overview"; "Cold wallet private keys are not stored here and cannot be viewed" = "Cold wallet private keys are not stored here and cannot be viewed"; "Complete" = "Complete"; "Complete step 1" = "Complete step 1"; "Confirm Payment" = "Confirm Payment"; "Confirm Pin Code" = "Confirm Pin Code"; "Contacts" = "Contacts"; "Continue" = "Continue"; "Copied To clipboard" = "Copied To clipboard"; "Copy" = "Copy"; "Copy Transaction ID to Clipboard" = "Copy Transaction ID to Clipboard"; "Create Cold Wallet" = "Create Cold Wallet"; "Create New Account" = "Create New Account"; "Create new contact" = "Create new contact"; "Customize Fee" = "Customize Fee"; "Decrypting" = "Decrypting"; "Delete" = "Delete"; "Delete %@" = "Delete %@"; "Delete Account" = "Delete Account"; "Delete Contact" = "Delete Contact"; "Delete address" = "Delete address"; "Do not use the QR code from here to receive bitcoins. Go to the Receive screen to get a QR code to receive bitcoins." = "Do not use the QR code from here to receive bitcoins. Go to the Receive screen to get a QR code to receive bitcoins."; "Do you like using ArcBit?" = "Do you like using ArcBit?"; "Do you want to load and backup your current local wallet file?" = "Do you want to load and backup your current local wallet file?"; "Do you want to load local wallet file?" = "Do you want to load local wallet file?"; "Do you want to restore from your backup passphrase or start a new wallet?" = "Do you want to restore from your backup passphrase or start a new wallet?"; "Do you want to temporary import your account private key?" = "Do you want to temporary import your account private key?"; "Do you want to temporary import your private key?" = "Do you want to temporary import your private key?"; "Don't remind me" = "Don't remind me"; "Done" = "Done"; "Each account has a public and private account key. Account keys should be kept secret as they are used to view the account's transactions and spend the account's bitcoins." = "Each account has a public and private account key. Account keys should be kept secret as they are used to view the account's transactions and spend the account's bitcoins."; "Edit" = "Edit"; "Edit Account Name" = "Edit Account Name"; "Edit Contact Name" = "Edit Contact Name"; "Edit Label" = "Edit Label"; "Edit Transaction label" = "Edit Transaction label"; "Email Support" = "Email Support"; "Enable PIN code in settings to better secure your wallet." = "Enable PIN code in settings to better secure your wallet."; "Enable Pin Code" = "Enable Pin Code"; "Enable Transaction Fee" = "Enable Transaction Fee"; "Enable advanced mode" = "Enable advanced mode"; "Encountered error creating transaction. Please try again." = "Encountered error creating transaction. Please try again."; "Encrypted" = "Encrypted"; "Enter Label" = "Enter Label"; "Enter PIN" = "Enter PIN"; "Enter a wallet backup passphrase to wipe the current wallet and start/restore another." = "Enter a wallet backup passphrase to wipe the current wallet and start/restore another."; "Enter account private key" = "Enter account private key"; "Enter account public key" = "Enter account public key"; "Enter address" = "Enter address"; "Enter an account ID and click 'QR Code'. Then on your primary online device, enable Cold Wallet in settings. Then go to the Accounts screen and click 'Import Cold Wallet Account' and scan the Account Public Key QR Code. Afterwards use this cold wallet account as you would a normal account and deposit bitcoins into it. When you want to make a payment from a cold wallet account, go to the next section on the previous screen and follow the step by step instructions there." = "Enter an account ID and click 'QR Code'. Then on your primary online device, enable Cold Wallet in settings. Then go to the Accounts screen and click 'Import Cold Wallet Account' and scan the Account Public Key QR Code. Afterwards use this cold wallet account as you would a normal account and deposit bitcoins into it. When you want to make a payment from a cold wallet account, go to the next section on the previous screen and follow the step by step instructions there."; "Enter backup passphrase" = "Enter backup passphrase"; "Enter passphrase for your iCloud backup wallet." = "Enter passphrase for your iCloud backup wallet."; "Enter password for encrypted private key" = "Enter password for encrypted private key"; "Enter the 12 word passphrase that belongs to the cold wallet account that you want to make a payment from. This is the passphrase that was used to generate your account public key that was generated on the 'Create Cold Wallet' section found on the previous screen." = "Enter the 12 word passphrase that belongs to the cold wallet account that you want to make a payment from. This is the passphrase that was used to generate your account public key that was generated on the 'Create Cold Wallet' section found on the previous screen."; "Error" = "Error"; "Error fetching Transaction." = "Error fetching Transaction."; "Error fetching unspent outputs. Try again later." = "Error fetching unspent outputs. Try again later."; "Error fetching unspent outputs. Try again." = "Error fetching unspent outputs. Try again."; "Error getting block height." = "Error getting block height."; "Error importing account" = "Error importing account"; "Error loading wallet JSON file" = "Error loading wallet JSON file"; "Explanation" = "Explanation"; "FAQ" = "FAQ"; "Fee:" = "Fee:"; "Fill address field" = "Fill address field"; "Finished Passing Transaction Data" = "Finished Passing Transaction Data"; "First make sure you are using your secondary offline device for this screen (as mentioned in the overview on the previous screen). Click 'New Wallet' and write down or memorize the generated 12 word passphrase. This passphrase can recover and generate all your accounts and the bitcoins associated with it, so keep it safe and to yourself. Also instead of creating a new wallet, you can also input an existing 12 word passphrase that was generated here to create additional accounts." = "First make sure you are using your secondary offline device for this screen (as mentioned in the overview on the previous screen). Click 'New Wallet' and write down or memorize the generated 12 word passphrase. This passphrase can recover and generate all your accounts and the bitcoins associated with it, so keep it safe and to yourself. Also instead of creating a new wallet, you can also input an existing 12 word passphrase that was generated here to create additional accounts."; "Follow us on Twitter" = "Follow us on Twitter"; "From:" = "From:"; "Funds have been claimed already." = "Funds have been claimed already."; "Funds imported" = "Funds imported"; "Go" = "Go"; "Go to the side menu" = "Go to the side menu"; "Have sender scan QR code" = "Have sender scan QR code"; "Have sender send you payment" = "Have sender send you payment"; "Help" = "Help"; "Here are some features that no other mobile bitcoin wallet supports.\n- Reusable address support\n- Ability to import individual account (extended) keys\n- iCloud backup support\n- Over 150 local currencies supported" = "Here are some features that no other mobile bitcoin wallet supports.\n- Reusable address support\n- Ability to import individual account (extended) keys\n- iCloud backup support\n- Over 150 local currencies supported"; "Hierarchical Deterministic Wallet" = "Hierarchical Deterministic Wallet"; "History" = "History"; "How To:" = "How To:"; "How do I get bitcoins?" = "How do I get bitcoins?"; "How does ArcBit Wallet work?" = "How does ArcBit Wallet work?"; "Import Account" = "Import Account"; "Import Cold Wallet Account" = "Import Cold Wallet Account"; "Import Feature" = "Import Feature"; "Import Private Key" = "Import Private Key"; "Import Private/Encrypted Key" = "Import Private/Encrypted Key"; "Import Watch Account" = "Import Watch Account"; "Import Watch Address" = "Import Watch Address"; "Import private key encrypted or unencrypted?" = "Import private key encrypted or unencrypted?"; "Import with QR code" = "Import with QR code"; "Import with text input" = "Import with text input"; "Imported Account %@" = "Imported Account %@"; "Imported Accounts" = "Imported Accounts"; "Imported Address" = "Imported Address"; "Imported Addresses" = "Imported Addresses"; "Imported Cold Wallet Account %@" = "Imported Cold Wallet Account %@"; "Imported Watch Account %@" = "Imported Watch Account %@"; "Imported Watch Accounts" = "Imported Watch Accounts"; "Imported Watch Addresses" = "Imported Watch Addresses"; "Imported Watch Only Accounts can't see reusable address payments, thus this accounts' reusable address is not available. If you want see the reusable address for this account, import the account private key that corresponds to this accounts public key." = "Imported Watch Only Accounts can't see reusable address payments, thus this accounts' reusable address is not available. If you want see the reusable address for this account, import the account private key that corresponds to this accounts public key."; "Importing Account" = "Importing Account"; "Importing Cold Wallet Account" = "Importing Cold Wallet Account"; "Importing a Private Key" = "Importing a Private Key"; "Importing a Watch Only Account" = "Importing a Watch Only Account"; "Importing a Watch Only Address" = "Importing a Watch Only Address"; "Importing an Account" = "Importing an Account"; "Importing an encrypted key will require you to input the password every time you want to send bitcoins from it." = "Importing an encrypted key will require you to input the password every time you want to send bitcoins from it."; "In advanced mode, you can import bitcoin keys and addresses from other sources. You can import account private keys, account public keys, private keys, and addresses.\nPlease note that your 12 word passphrase cannot recover your bitcoins, so it is recommended that you backup imported keys and addresses separately." = "In advanced mode, you can import bitcoin keys and addresses from other sources. You can import account private keys, account public keys, private keys, and addresses.\nPlease note that your 12 word passphrase cannot recover your bitcoins, so it is recommended that you backup imported keys and addresses separately."; "Incomplete" = "Incomplete"; "Incorrect passphrase, could not decrypt iCloud wallet backup." = "Incorrect passphrase, could not decrypt iCloud wallet backup."; "Input a bitcoin address" = "Input a bitcoin address"; "Input a label" = "Input a label"; "Input a new label" = "Input a new label"; "Input a recommended amount. Somewhere between %@ and %@ BTC" = "Input a recommended amount. Somewhere between %@ and %@ BTC"; "Input amount" = "Input amount"; "Input label" = "Input label"; "Input new account name" = "Input new account name"; "Input transaction fee in bitcoins" = "Input transaction fee in bitcoins"; "Instructions" = "Instructions"; "Insufficient Funds" = "Insufficient Funds"; "Insufficient Funds. Account balance is %@ when %@ is required." = "Insufficient Funds. Account balance is %@ when %@ is required."; "Insufficient Funds. Account contains bitcoin dust. You can only send up to %@ for now." = "Insufficient Funds. Account contains bitcoin dust. You can only send up to %@ for now."; "Internal Wallet Data" = "Internal Wallet Data"; "Internal account transfer" = "Internal account transfer"; "Invalid Address" = "Invalid Address"; "Invalid Passphrase" = "Invalid Passphrase"; "Invalid URL" = "Invalid URL"; "Invalid account private key" = "Invalid account private key"; "Invalid account public Key" = "Invalid account public Key"; "Invalid amount" = "Invalid amount"; "Invalid backup passphrase" = "Invalid backup passphrase"; "Invalid private key" = "Invalid private key"; "Invalid scanned data" = "Invalid scanned data"; "Invalid transaction ID" = "Invalid transaction ID"; "It is not recommended that you manually manage private keys yourself. A leak of a private key can lead to the compromise of your accounts." = "It is not recommended that you manually manage private keys yourself. A leak of a private key can lead to the compromise of your accounts."; "It is not recommended that you use a regular bitcoin address for multiple payments, but instead you should import a reusable address. Add address anyways?" = "It is not recommended that you use a regular bitcoin address for multiple payments, but instead you should import a reusable address. Add address anyways?"; "Label" = "Label"; "Label Transaction" = "Label Transaction"; "Local backup to wallet failed!" = "Local backup to wallet failed!"; "Local wallet will be lost. Are you sure you want to restore wallet from iCloud?" = "Local wallet will be lost. Are you sure you want to restore wallet from iCloud?"; "Maximum accounts reached" = "Maximum accounts reached"; "More" = "More"; "Name" = "Name"; "Network Error" = "Network Error"; "New Wallet" = "New Wallet"; "New addresses will be automatically generated and cycled for you as you use your current available addresses." = "New addresses will be automatically generated and cycled for you as you use your current available addresses."; "Next" = "Next"; "No" = "No"; "None currently" = "None currently"; "Not now" = "Not now"; "Now authorize the transaction on your air gap device. When you have done so, click continue on this device to scan the authorized transaction data and make your payment." = "Now authorize the transaction on your air gap device. When you have done so, click continue on this device to scan the authorized transaction data and make your payment."; "OK" = "OK"; "On your primary online device, when you want to make a payment from a cold wallet account, simply do it as you normally would on a normal account. When you click 'Send' on the Review Payment screen, instead of the payment going out immediately, you will be prompted to pass the unauthorized transaction data. Then on your secondary offline device, within this screen click 'Scan' to import the transaction so it can be authorized." = "On your primary online device, when you want to make a payment from a cold wallet account, simply do it as you normally would on a normal account. When you click 'Send' on the Review Payment screen, instead of the payment going out immediately, you will be prompted to pass the unauthorized transaction data. Then on your secondary offline device, within this screen click 'Scan' to import the transaction so it can be authorized."; "Once the transaction has been authorized by completing the above two steps, pass the authorized transaction back to your primary online device to finalize your payment." = "Once the transaction has been authorized by completing the above two steps, pass the authorized transaction back to your primary online device to finalize your payment."; "Other Links" = "Other Links"; "Pass" = "Pass"; "Passphrase" = "Passphrase"; "Passphrase does not match the transaction" = "Passphrase does not match the transaction"; "Password" = "Password"; "Payment Index: %lu" = "Payment Index: %lu"; "Private key does not match address" = "Private key does not match address"; "Private key missing" = "Private key missing"; "QR code" = "QR code"; "Quit and re-enter app" = "Quit and re-enter app"; "Rate" = "Rate"; "Rate us in the App Store!" = "Rate us in the App Store!"; "Receive" = "Receive"; "Receive Payment" = "Receive Payment"; "Receive Payment From Reusable Address" = "Receive Payment From Reusable Address"; "Remind me Later" = "Remind me Later"; "Restore" = "Restore"; "Restore Wallet" = "Restore Wallet"; "Restore from iCloud" = "Restore from iCloud"; "Restoring Wallet" = "Restoring Wallet"; "Retry" = "Retry"; "Reusable Address Payment Addresses" = "Reusable Address Payment Addresses"; "Reusable Address:" = "Reusable Address:"; "Reusable Addresses" = "Reusable Addresses"; "Review Payment" = "Review Payment"; "Save" = "Save"; "Scan" = "Scan"; "Scan For Reusable Address Payment" = "Scan For Reusable Address Payment"; "Scan For Reusable Address Payment" = "Scan For Reusable Address Payment"; "Scan QR Code" = "Scan QR Code"; "Scan for reusable address transaction" = "Scan for reusable address transaction"; "Scan next part" = "Scan next part"; "Scroll down to the section ‘Account Actions’" = "Scroll down to the section ‘Account Actions’"; "Select Account" = "Select Account"; "Select and click a blockexplorer API" = "Select and click a blockexplorer API"; "Select and click a transaction" = "Select and click a transaction"; "Select and click an account" = "Select and click an account"; "Select and click an account to receive from" = "Select and click an account to receive from"; "Select and click an account to view it’s transaction history" = "Select and click an account to view it’s transaction history"; "Select and click an address" = "Select and click an address"; "Send" = "Send"; "Send Payment" = "Send Payment"; "Send To Address In Contacts" = "Send To Address In Contacts"; "Send authorized payment?" = "Send authorized payment?"; "Sending" = "Sending"; "Sending payment to a reusable address might take longer to show up then a normal transaction with the blockchain.info API. You might have to wait until at least 1 confirmation for the transaction to show up. This is due to the limitations of the blockchain.info API. For reusable address payments to show up faster, configure your app to use the Insight API in advance settings." = "Sending payment to a reusable address might take longer to show up then a normal transaction with the blockchain.info API. You might have to wait until at least 1 confirmation for the transaction to show up. This is due to the limitations of the blockchain.info API. For reusable address payments to show up faster, configure your app to use the Insight API in advance settings."; "Sent %@ to %@" = "Sent %@ to %@"; "Set Transaction Fee in %@" = "Set Transaction Fee in %@"; "Settings" = "Settings"; "Some funds may be pending confirmation and cannot be spent yet. (Check your account history) Account only has a spendable balance of %@" = "Some funds may be pending confirmation and cannot be spent yet. (Check your account history) Account only has a spendable balance of %@"; "Some people have compared bitcoin addresses to a bank routing number. It is a good analogy, however bitcoin addresses are public. So if you reuse the same bitcoin address for multiple payments like you would a routing number, people will be able to figure out how much bitcoin you have. Thus it is recommended that you only use one address per payment.\nThis causes usability issues making the user use a new address whenever receiving a payment is cumbersome.\nStealth/reusable addresses provides a better solution. When you give a sender a reusable address, the sender will derive a one time regular bitcoin address from the reusable address. Then the sender will send a payment to that regular bitcoin address. Now you can give many people just one reusable address and have them all send you payments without letting other people know how much bitcoin you have.\nA reusable address looks like this vJmxthatTBXibYe9aZavx18iAT9gyiJETGkhwPX2WbHQGuzX83YvQXynD2t8yHU4Xjfonu5x9m6B4yxquytFP1c2CRbVR9mecxesvE. A reusable address is a lot longer then a regular bitcoin address, it is 102 characters in length.\nReusable addresses are great, however there are no other mobile bitcoin wallets but ArcBit that supports reusable addresses for now. Which is why ArcBit supports receiving payments from both regular bitcoin addresses and reusable addresses.\nFor each account, you have one reusable address. You can find it on your Receive screen. Swipe all the way to right on the QRCode on your Receive screen and you will find a reusable address." = "Some people have compared bitcoin addresses to a bank routing number. It is a good analogy, however bitcoin addresses are public. So if you reuse the same bitcoin address for multiple payments like you would a routing number, people will be able to figure out how much bitcoin you have. Thus it is recommended that you only use one address per payment.\nThis causes usability issues making the user use a new address whenever receiving a payment is cumbersome.\nStealth/reusable addresses provides a better solution. When you give a sender a reusable address, the sender will derive a one time regular bitcoin address from the reusable address. Then the sender will send a payment to that regular bitcoin address. Now you can give many people just one reusable address and have them all send you payments without letting other people know how much bitcoin you have.\nA reusable address looks like this vJmxthatTBXibYe9aZavx18iAT9gyiJETGkhwPX2WbHQGuzX83YvQXynD2t8yHU4Xjfonu5x9m6B4yxquytFP1c2CRbVR9mecxesvE. A reusable address is a lot longer then a regular bitcoin address, it is 102 characters in length.\nReusable addresses are great, however there are no other mobile bitcoin wallets but ArcBit that supports reusable addresses for now. Which is why ArcBit supports receiving payments from both regular bitcoin addresses and reusable addresses.\nFor each account, you have one reusable address. You can find it on your Receive screen. Swipe all the way to right on the QRCode on your Receive screen and you will find a reusable address."; "Spending from a cold wallet account" = "Spending from a cold wallet account"; "Start fresh" = "Start fresh"; "Start/Restore Another Wallet" = "Start/Restore Another Wallet"; "Starting Change address ID:" = "Starting Change address ID:"; "Starting Receiving Address ID:" = "Starting Receiving Address ID:"; "Step 1: Scan transaction to authorize" = "Step 1: Scan transaction to authorize"; "Step 2: Input 12 word backup passphrase" = "Step 2: Input 12 word backup passphrase"; "Step 3: Pass authorized transaction data" = "Step 3: Pass authorized transaction data"; "Steps" = "Steps"; "Success" = "Success"; "Swipe right on an address" = "Swipe right on an address"; "Swipe to the right on the QR Code Image until you see the reusable address" = "Swipe to the right on the QR Code Image until you see the reusable address"; "Temporarily import account private key" = "Temporarily import account private key"; "Temporarily import private key" = "Temporarily import private key"; "The cold wallet feature will allow you to create accounts which offer better security compared to normal online wallets. You will need 2 devices to use this feature. Your normal day to day device that is connected to the internet and a secondary device that is not connected to the internet (Your secondary device would need to be online once to download the ArcBit app. Afterwards keep the secondary device offline for maximum security). This feature allows you to authorize bitcoin payments from an offline device so that the keys to your bitcoins will never need to be stored on your online device. Follow the step by step instructions by clicking the info buttons within the below sections." = "The cold wallet feature will allow you to create accounts which offer better security compared to normal online wallets. You will need 2 devices to use this feature. Your normal day to day device that is connected to the internet and a secondary device that is not connected to the internet (Your secondary device would need to be online once to download the ArcBit app. Afterwards keep the secondary device offline for maximal security). This feature allows you to authorize bitcoin payments from an offline device so that the keys to your bitcoins will never need to be stored on your online device. Follow the step by step instructions by clicking the info buttons within the below sections."; "The cold wallet feature will allow you to create accounts which offer better security compared to normal online wallets. You will need 2 devices to use this feature. Your normal day to day device that is connected to the internet and a secondary device that is not connected to the internet (Your secondary device would need to be online once to download the ArcBit app. Afterwards keep the secondary device offline for maximum security). This feature allows you to authorize bitcoin payments from an offline device so that the keys to your bitcoins will never need to be stored on your online device. You can enable the cold wallet feature by going into advanced settings." = "The cold wallet feature will allow you to create accounts which offer better security compared to normal online wallets. You will need 2 devices to use this feature. Your normal day to day device that is connected to the internet and a secondary device that is not connected to the internet (Your secondary device would need to be online once to download the ArcBit app. Afterwards keep the secondary device offline for maximum security). This feature allows you to authorize bitcoin payments from an offline device so that the keys to your bitcoins will never need to be stored on your online device. You can enable the cold wallet feature by going into advanced settings."; "This account type can't see reusable address payments" = "This account type can't see reusable address payments"; "This feature allows you to manually input a transaction ID and see if the corresponding transaction contains a reusable address payment to your reusable address. If so, then the funds will be added to your wallet. Normally the app will discover reusable address payments automatically for you, but if you believe a payment is missing you can use this feature." = "This feature allows you to manually input a transaction ID and see if the corresponding transaction contains a reusable address payment to your reusable address. If so, then the funds will be added to your wallet. Normally the app will discover reusable address payments automatically for you, but if you believe a payment is missing you can use this feature."; "To:" = "To:"; "Today" = "Today"; "Toggle Automatic Transaction Fee" = "Toggle Automatic Transaction Fee"; "Toggle ‘Enable Transaction Fee’" = "Toggle ‘Enable Transaction Fee’"; "Toggle ’Enable advanced mode’" = "Toggle ’Enable advanced mode’"; "Total:" = "Total:"; "Transaction %@ already accounted for." = "Transaction %@ already accounted for."; "Transaction %@ does not belong to this account." = "Transaction %@ does not belong to this account."; "Transaction Fee" = "Transaction Fee"; "Transaction ID" = "Transaction ID"; "Transaction ID: %@" = "Transaction ID: %@"; "Transaction authorized" = "Transaction authorized"; "Transaction confirmations" = "Transaction confirmations"; "Transaction fees impact how quickly the Bitcoin network will confirm your transactions. Higher fees means faster confirmation times. Default fee behavior can be configured in settings." = "Transaction fees impact how quickly the Bitcoin network will confirm your transactions. Higher fees means faster confirmation times. Default fee behavior can be configured in settings."; "Transaction is not a reusable address transaction." = "Transaction is not a reusable address transaction."; "Transaction needs to be authorized by an offline and air gap device. Send transaction to an offline device for authorization?" = "Transaction needs to be authorized by an offline and air gap device. Send transaction to an offline device for authorization?"; "Transaction needs to be passed back to your online device in order for the payment to be sent" = "Transaction needs to be passed back to your online device in order for the payment to be sent"; "Try Again" = "Try Again"; "Try our new cold wallet feature!" = "Try our new cold wallet feature!"; "URL does not contain an address." = "URL does not contain an address."; "Unable to get dynamic fees. Falling back on fixed transaction fee. (fee can be configured on review payment)" = "Unable to get dynamic fees. Falling back on fixed transaction fee. (fee can be configured on review payment)"; "Unarchive Account" = "Unarchive Account"; "Unarchive address" = "Unarchive address"; "Unarchived address" = "Unarchived address"; "Unconfirmed" = "Unconfirmed"; "Unencrypted" = "Unencrypted"; "Use ArcBit on your browser to complement the mobile app. The web wallet has all the features that the mobile wallet has plus more!" = "Use ArcBit on your browser to complement the mobile app. The web wallet has all the features that the mobile wallet has plus more!"; "Use all funds" = "Use all funds"; "View Account Address" = "View Account Address"; "View Account Address In Web" = "View Account Address In Web"; "View Account Addresses" = "View Account Addresses"; "View Account Private Key" = "View Account Private Key"; "View Account Public Key" = "View Account Public Key"; "View Achievements" = "View Achievements"; "View Addresses" = "View Addresses"; "View ArcBit Brain Wallet Details" = "View ArcBit Brain Wallet Details"; "View ArcBit Web Wallet Details" = "View ArcBit Web Wallet Details"; "View History" = "View History"; "View Private Key" = "View Private Key"; "View Transaction In Web" = "View Transaction In Web"; "View account private key QR code" = "View account private key QR code"; "View account public key QR code" = "View account public key QR code"; "View address QR code" = "View address QR code"; "View address in web" = "View address in web"; "View in web" = "View in web"; "View private key QR code" = "View private key QR code"; "Visit our home page" = "Visit our home page"; "Wallet backup passphrase" = "Wallet backup passphrase"; "Warning" = "Warning"; "Welcome to ArcBit, a user only controlled Bitcoin wallet. Start using the app now by depositing your Bitcoins here." = "Welcome to ArcBit, a user only controlled Bitcoin wallet. Start using the app now by depositing your Bitcoins here."; "Welcome!" = "Welcome!"; "What are Account/Extended Keys?" = "What are Account/Extended Keys?"; "What are accounts?" = "What are accounts?"; "What are reusable addresses?" = "What are reusable addresses?"; "What are the benefits and advantages of Bitcoin?" = "What are the benefits and advantages of Bitcoin?"; "What are transaction confirmations?" = "What are transaction confirmations?"; "What is ArcBit's cold wallet feature?" = "What is ArcBit's cold wallet feature?"; "What is Bitcoin?" = "What is Bitcoin?"; "What is a bitcoin wallet?" = "What is a bitcoin wallet?"; "What makes ArcBit different from other bitcoin wallets?" = "What makes ArcBit different from other bitcoin wallets?"; "With an ArcBit cold wallet feature, you can create wallets and make payments offline without exposing your private keys to an internet connected device. This feature is great for storing large amounts of bitcoin or for the security conscious minded. Check out this feature in the cold wallet section in the side menu." = "With an ArcBit cold wallet feature, you can create wallets and make payments offline without exposing your private keys to an internet connected device. This feature is great for storing large amounts of bitcoin or for the security conscious minded. Check out this feature in the cold wallet section in the side menu."; "Write down backup passphrase" = "Write down backup passphrase"; "Write down or memorize your 12 word wallet backup passphrase. You can view it by clicking \"Show backup passphrase\" in Settings. Your wallet backup passphrase is needed to recover your bitcoins." = "Write down or memorize your 12 word wallet backup passphrase. You can view it by clicking \"Show backup passphrase\" in Settings. Your wallet backup passphrase is needed to recover your bitcoins."; "Write down the 12 word passphrase below and keep it safe. This passphrase alone can restore your entire wallets\' bitcoins (excluding imports)." = "Write down the 12 word passphrase below and keep it safe. This passphrase alone can restore your entire wallets\' bitcoins (excluding imports)."; "Write down the 12 word passphrase below and keep it safe. This passphrase alone can restore your entire wallets\' bitcoins." = "Write down the 12 word passphrase below and keep it safe. This passphrase alone can restore your entire wallets\' bitcoins."; "Yes" = "Yes"; "You are making a payment to a reusable address. Make sure that the receiver can see the payment made to them. (All ArcBit reusable addresses are compatible with other ArcBit wallets)" = "You are making a payment to a reusable address. Make sure that the receiver can see the payment made to them. (All ArcBit reusable addresses are compatible with other ArcBit wallets)"; "You have %@, but %@ is needed. (This includes the transactions fee)" = "You have %@, but %@ is needed. (This includes the transactions fee)"; "You must exit and kill this app in order for this to take effect." = "You must exit and kill this app in order for this to take effect."; "Your current wallet will be deleted. Your can restore your current wallet later with the wallet passphrase, but any imported accounts or addresses created in advanced mode cannot be recovered. Do you wish to continue?" = "Your current wallet will be deleted. Your can restore your current wallet later with the wallet passphrase, but any imported accounts or addresses created in advanced mode cannot be recovered. Do you wish to continue?"; "Your iCloud backup was last saved on %@. Do you want to restore your wallet from iCloud or backup your local wallet to iCloud?" = "Your iCloud backup was last saved on %@. Do you want to restore your wallet from iCloud or backup your local wallet to iCloud?"; "Your new transaction fee is too high" = "Your new transaction fee is too high"; "Your wallet is now restored" = "Your wallet is now restored"; "\nAllow camera access in\n Settings->Privacy->Camera->%@" = "\nAllow camera access in\n Settings->Privacy->Camera->%@"; "\tArcBit Web Wallet is a Chrome extension. It has all the features of the mobile wallet plus more. Highlights include the ability to create multiple wallets instead of just one, and a new non-cumbersome way to generate wallets, store and spend bitcoins all from cold storage! ArcBit's new way to manage your cold storage bitcoins also offers a more compelling reason to use ArcBit's watch account feature. Now you can safely watch the balance of your cold storage bitcoins by enabling advance mode in ArcBit and importing your cold storage account public keys.\n\tUse ArcBit Web Wallet in whatever way you wish. You can create a new wallet, or you can input your current 12 word backup passphrase to manage the same bitcoins across different devices. Check out the ArcBit Web Wallet in the Chrome Web Store for more details!\n" = "\tArcBit Web Wallet is a Chrome extension. It has all the features of the mobile wallet plus more. Highlights include the ability to create multiple wallets instead of just one, and a new non-cumbersome way to generate wallets, store and spend bitcoins all from cold storage! ArcBit's new way to manage your cold storage bitcoins also offers a more compelling reason to use ArcBit's watch account feature. Now you can safely watch the balance of your cold storage bitcoins by enabling advance mode in ArcBit and importing your cold storage account public keys.\n\tUse ArcBit Web Wallet in whatever way you wish. You can create a new wallet, or you can input your current 12 word backup passphrase to manage the same bitcoins across different devices. Check out the ArcBit Web Wallet in the Chrome Web Store for more details!\n"; "\tWith the Arcbit Brain Wallet you can safely spend your bitcoins without ever having your private keys be exposed to the internet. It can be use in conjunction with your Arcbit Wallet or as a stand alone wallet.\n" = "\tWith the Arcbit Brain Wallet you can safely spend your bitcoins without ever having your private keys be exposed to the internet. It can be use in conjunction with your Arcbit Wallet or as a stand alone wallet.\n"; "iCloud Error: %@" = "iCloud Error: %@"; "iCloud backup found" = "iCloud backup found"; "iCloud backup not found" = "iCloud backup not found"; "iCloud backup will be lost. Are you sure you want to backup your local wallet to iCloud?" = "iCloud backup will be lost. Are you sure you want to backup your local wallet to iCloud?"; "Wallet backup passphrase will be shown" = "Wallet backup passphrase will be shown"; "Write down or memorize your wallet backup passphrase. If you lose your backup passphrase, your wallet cannot be recovered." = "Write down or memorize your wallet backup passphrase. If you lose your backup passphrase, your wallet cannot be recovered."; "I understand" = "I understand"; "iCloud support for ArcBit discontinued" = "iCloud support for ArcBit discontinued"; "iCloud support for ArcBit is being discontinued. If your backup passphrase has not been backed up already, please do so." = "iCloud support for ArcBit is being discontinued. If your backup passphrase has not been backed up already, please do so."; "Reusable address payments are disabled until further notice." = "Reusable address payments are disabled until further notice."; ================================================ FILE: ArcBit/Base.lproj/Main.storyboard ================================================ f23324e18c237876f96d2064abbc26399cc3cd34d503cc6f2b00a9403b003dcad2cf7642e4e3311cf2e5de32307d88b116ac743f0b02fc4c2afe0e30dcd843f6 ================================================ FILE: ArcBit/External/BreadWalletClassesV0.5/BRKey+BIP38.h ================================================ // // BRKey+BIP38.h // BreadWallet // // Created by Aaron Voisine on 4/9/14. // Copyright (c) 2014 Aaron Voisine // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import "BRKey.h" // BIP38 is a method for encrypting private keys with a passphrase // https://github.com/bitcoin/bips/blob/master/bip-0038.mediawiki @interface BRKey (BIP38) // decrypts a BIP38 key using the given passphrase or retuns nil if passphrase is incorrect + (instancetype)keyWithBIP38Key:(NSString *)key andPassphrase:(NSString *)passphrase isTestnet:(BOOL)isTestnet; // generates an "intermediate code" for an EC multiply mode key, salt should be 64bits of random data + (NSString *)BIP38IntermediateCodeWithSalt:(uint64_t)salt andPassphrase:(NSString *)passphrase; // generates an "intermediate code" for an EC multiply mode key with a lot and sequence number, lot must be less than // 1048576, sequence must be less than 4096, and salt should be 32bits of random data + (NSString *)BIP38IntermediateCodeWithLot:(uint32_t)lot sequence:(uint16_t)sequence salt:(uint32_t)salt passphrase:(NSString *)passphrase; // generates a BIP38 key from an "intermediate code" and 24 bytes of cryptographically random data (seedb), // compressed indicates if compressed pubKey format should be used for the bitcoin address, confcode (optional) will // be set to the "confirmation code" + (NSString *)BIP38KeyWithIntermediateCode:(NSString *)code seedb:(NSData *)seedb compressed:(BOOL)compressed confirmationCode:(NSString **)confcode isTestnet:(BOOL)isTestnet; // returns true if the "confirmation code" confirms that the given bitcoin address depends on the specified passphrase + (BOOL)confirmWithBIP38ConfirmationCode:(NSString *)code address:(NSString *)address passphrase:(NSString *)passphrase isTestnet:(BOOL)isTestnet; - (instancetype)initWithBIP38Key:(NSString *)key andPassphrase:(NSString *)passphrase isTestnet:(BOOL)isTestnet; // encrypts receiver with passphrase and returns BIP38 key - (NSString *)BIP38KeyWithPassphrase:(NSString *)passphrase isTestnet:(BOOL)isTestnet; @end ================================================ FILE: ArcBit/External/BreadWalletClassesV0.5/BRKey+BIP38.m ================================================ // // BRKey+BIP38.m // BreadWallet // // Created by Aaron Voisine on 4/9/14. // Copyright (c) 2014 Aaron Voisine // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import "BRKey+BIP38.h" #import "NSString+Base58.h" #import "NSData+Hash.h" #import "NSMutableData+Bitcoin.h" #import "ccMemory.h" #import #import #import // BIP38 is a method for encrypting private keys with a passphrase // https://github.com/bitcoin/bips/blob/master/bip-0038.mediawiki #define BIP38_SCRYPT_N 16384 #define BIP38_SCRYPT_R 8 #define BIP38_SCRYPT_P 8 #define BIP38_SCRYPT_EC_N 1024 #define BIP38_SCRYPT_EC_R 1 #define BIP38_SCRYPT_EC_P 1 // bitwise left rotation, this will typically be compiled into a single instruction #define rotl(a, b) (((a) << (b)) | ((a) >> (32 - (b)))) // salsa20/8 stream cypher: http://cr.yp.to/snuffle.html static void salsa20_8(uint32_t b[16]) { uint32_t x00 = b[0], x01 = b[1], x02 = b[2], x03 = b[3], x04 = b[4], x05 = b[5], x06 = b[6], x07 = b[7], x08 = b[8], x09 = b[9], x10 = b[10], x11 = b[11], x12 = b[12], x13 = b[13], x14 = b[14], x15 = b[15]; for (int i = 0; i < 8; i += 2) { // operate on columns x04 ^= rotl(x00 + x12, 7), x08 ^= rotl(x04 + x00, 9), x12 ^= rotl(x08 + x04, 13), x00 ^= rotl(x12 + x08, 18); x09 ^= rotl(x05 + x01, 7), x13 ^= rotl(x09 + x05, 9), x01 ^= rotl(x13 + x09, 13), x05 ^= rotl(x01 + x13, 18); x14 ^= rotl(x10 + x06, 7), x02 ^= rotl(x14 + x10, 9), x06 ^= rotl(x02 + x14, 13), x10 ^= rotl(x06 + x02, 18); x03 ^= rotl(x15 + x11, 7), x07 ^= rotl(x03 + x15, 9), x11 ^= rotl(x07 + x03, 13), x15 ^= rotl(x11 + x07, 18); // operate on rows x01 ^= rotl(x00 + x03, 7), x02 ^= rotl(x01 + x00, 9), x03 ^= rotl(x02 + x01, 13), x00 ^= rotl(x03 + x02, 18); x06 ^= rotl(x05 + x04, 7), x07 ^= rotl(x06 + x05, 9), x04 ^= rotl(x07 + x06, 13), x05 ^= rotl(x04 + x07, 18); x11 ^= rotl(x10 + x09, 7), x08 ^= rotl(x11 + x10, 9), x09 ^= rotl(x08 + x11, 13), x10 ^= rotl(x09 + x08, 18); x12 ^= rotl(x15 + x14, 7), x13 ^= rotl(x12 + x15, 9), x14 ^= rotl(x13 + x12, 13), x15 ^= rotl(x14 + x13, 18); } b[0] += x00, b[1] += x01, b[2] += x02, b[3] += x03, b[4] += x04, b[5] += x05, b[6] += x06, b[7] += x07; b[8] += x08, b[9] += x09, b[10] += x10, b[11] += x11, b[12] += x12, b[13] += x13, b[14] += x14, b[15] += x15; } static void blockmix_salsa8(uint64_t *dest, const uint64_t *src, uint64_t *b, uint32_t r) { CC_XMEMCPY(b, &src[(2*r - 1)*8], 64); for (uint32_t i = 0; i < 2*r; i += 2) { for (uint32_t j = 0; j < 8; j++) b[j] ^= src[i*8 + j]; salsa20_8((uint32_t *)b); CC_XMEMCPY(&dest[i*4], b, 64); for (uint32_t j = 0; j < 8; j++) b[j] ^= src[i*8 + 8 + j]; salsa20_8((uint32_t *)b); CC_XMEMCPY(&dest[i*4 + r*8], b, 64); } } // scrypt key derivation: http://www.tarsnap.com/scrypt.html static NSData *scrypt(NSData *password, NSData *salt, int64_t n, uint32_t r, uint32_t p, NSUInteger length) { NSMutableData *d = [NSMutableData secureDataWithLength:length]; uint8_t b[128*r*p]; uint64_t x[16*r], y[16*r], z[8], *v = CC_XMALLOC(128*r*(int)n), m; CCKeyDerivationPBKDF(kCCPBKDF2, password.bytes, password.length, salt.bytes, salt.length, kCCPRFHmacAlgSHA256, 1, b, sizeof(b)); for (uint32_t i = 0; i < p; i++) { for (uint32_t j = 0; j < 32*r; j++) { ((uint32_t *)x)[j] = CFSwapInt32LittleToHost(*(uint32_t *)&b[i*128*r + j*4]); } for (uint64_t j = 0; j < n; j += 2) { CC_XMEMCPY(&v[j*(16*r)], x, 128*r); blockmix_salsa8(y, x, z, r); CC_XMEMCPY(&v[(j + 1)*(16*r)], y, 128*r); blockmix_salsa8(x, y, z, r); } for (uint64_t j = 0; j < n; j += 2) { m = CFSwapInt64LittleToHost(x[(2*r - 1)*8]) & (n - 1); for (uint32_t k = 0; k < 16*r; k++) x[k] ^= v[m*(16*r) + k]; blockmix_salsa8(y, x, z, r); m = CFSwapInt64LittleToHost(y[(2*r - 1)*8]) & (n - 1); for (uint32_t k = 0; k < 16*r; k++) y[k] ^= v[m*(16*r) + k]; blockmix_salsa8(x, y, z, r); } for (uint32_t j = 0; j < 32*r; j++) { *(uint32_t *)&b[i*128*r + j*4] = CFSwapInt32HostToLittle(((uint32_t *)x)[j]); } } CCKeyDerivationPBKDF(kCCPBKDF2, password.bytes, password.length, b, sizeof(b), kCCPRFHmacAlgSHA256, 1, d.mutableBytes, d.length); CC_XZEROMEM(b, sizeof(b)); CC_XZEROMEM(x, sizeof(x)); CC_XZEROMEM(y, sizeof(y)); CC_XZEROMEM(z, sizeof(z)); CC_XZEROMEM(v, 128*r*(int)n); CC_XFREE(v, 128*r*(int)n); CC_XZEROMEM(&m, sizeof(m)); return d; } static NSData *normalize_passphrase(NSString *passphrase) { NSData *password; CFMutableStringRef pw = CFStringCreateMutableCopy(SecureAllocator(), passphrase.length, (CFStringRef)passphrase); CFStringNormalize(pw, kCFStringNormalizationFormC); password = CFBridgingRelease(CFStringCreateExternalRepresentation(SecureAllocator(), pw, kCFStringEncodingUTF8, 0)); CFRelease(pw); return password; } static void derive_passfactor(BIGNUM *passfactor, uint8_t flag, uint64_t entropy, NSString *passphrase) { NSData *password = normalize_passphrase(passphrase); NSData *salt = [NSData dataWithBytesNoCopy:&entropy length:(flag & BIP38_LOTSEQUENCE_FLAG) ? 4 : 8 freeWhenDone:NO]; NSData *prefactor = scrypt(password, salt, BIP38_SCRYPT_N, BIP38_SCRYPT_R, BIP38_SCRYPT_P, 32); NSMutableData *d; if (flag & BIP38_LOTSEQUENCE_FLAG) { // passfactor = SHA256(SHA256(prefactor + entropy)) d = [NSMutableData secureDataWithData:prefactor]; [d appendBytes:&entropy length:sizeof(entropy)]; BN_bin2bn(d.SHA256_2.bytes, CC_SHA256_DIGEST_LENGTH, passfactor); } else BN_bin2bn(prefactor.bytes, (int)prefactor.length, passfactor); // passfactor = prefactor } static NSData *derive_key(NSData *passpoint, uint32_t addresshash, uint64_t entropy) { NSMutableData *salt = [NSMutableData secureData]; [salt appendBytes:&addresshash length:sizeof(addresshash)]; [salt appendBytes:&entropy length:sizeof(entropy)]; // salt = addresshash + entropy return scrypt(passpoint, salt, BIP38_SCRYPT_EC_N, BIP38_SCRYPT_EC_R, BIP38_SCRYPT_EC_P, 64); } static NSData *point_multiply(NSData *point, const BIGNUM *factor, BOOL compressed, BN_CTX *ctx) { NSMutableData *d = [NSMutableData secureData]; EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp256k1); EC_POINT *r = EC_POINT_new(group), *p; point_conversion_form_t form = (compressed) ? POINT_CONVERSION_COMPRESSED : POINT_CONVERSION_UNCOMPRESSED; if (point) { p = EC_POINT_new(group); EC_POINT_oct2point(group, p, point.bytes, point.length, ctx); EC_POINT_mul(group, r, NULL, p, factor, ctx); // r = point*factor EC_POINT_clear_free(p); } else EC_POINT_mul(group, r, factor, NULL, NULL, ctx); // r = G*factor d.length = EC_POINT_point2oct(group, r, form, NULL, 0, ctx); EC_POINT_point2oct(group, r, form, d.mutableBytes, d.length, ctx); EC_POINT_clear_free(r); EC_GROUP_free(group); return d; } @implementation BRKey (BIP38) // decrypts a BIP38 key using the given passphrase or retuns nil if passphrase is incorrect + (instancetype)keyWithBIP38Key:(NSString *)key andPassphrase:(NSString *)passphrase isTestnet:(BOOL)isTestnet { return [[self alloc] initWithBIP38Key:key andPassphrase:passphrase isTestnet:isTestnet]; } // generates an "intermediate code" for an EC multiply mode key, salt should be 64bits of random data + (NSString *)BIP38IntermediateCodeWithSalt:(uint64_t)salt andPassphrase:(NSString *)passphrase; { if (! passphrase) return nil; salt = CFSwapInt64HostToBig(salt); BN_CTX *ctx = BN_CTX_new(); BN_CTX_start(ctx); NSMutableData *code = [NSMutableData secureData]; BIGNUM *passfactor = BN_CTX_get(ctx); derive_passfactor(passfactor, 0, salt, passphrase); [code appendBytes:"\x2C\xE9\xB3\xE1\xFF\x39\xE2\x53" length:8]; [code appendBytes:&salt length:sizeof(salt)]; [code appendData:point_multiply(nil, passfactor, YES, ctx)]; // passpoint = G*passfactor BN_CTX_end(ctx); BN_CTX_free(ctx); return [NSString base58checkWithData:code]; } // generates an "intermediate code" for an EC multiply mode key with a lot and sequence number, lot must be less than // 1048576, sequence must be less than 4096, and salt should be 32bits of random data + (NSString *)BIP38IntermediateCodeWithLot:(uint32_t)lot sequence:(uint16_t)sequence salt:(uint32_t)salt passphrase:(NSString *)passphrase { if (lot >= 0x100000 || sequence >= 0x1000 || ! passphrase) return nil; salt = CFSwapInt32HostToBig(salt); BN_CTX *ctx = BN_CTX_new(); BN_CTX_start(ctx); uint32_t lotsequence = CFSwapInt32HostToBig(lot*0x1000 + sequence); NSMutableData *entropy = [NSMutableData secureData], *code = [NSMutableData secureData]; BIGNUM *passfactor = BN_CTX_get(ctx); [entropy appendBytes:&salt length:sizeof(salt)]; [entropy appendBytes:&lotsequence length:sizeof(lotsequence)]; derive_passfactor(passfactor, BIP38_LOTSEQUENCE_FLAG, *(const uint64_t *)entropy.bytes, passphrase); [code appendBytes:"\x2C\xE9\xB3\xE1\xFF\x39\xE2\x51" length:8]; [code appendData:entropy]; [code appendData:point_multiply(nil, passfactor, YES, ctx)]; // passpoint = G*passfactor BN_CTX_end(ctx); BN_CTX_free(ctx); return [NSString base58checkWithData:code]; } // generates a BIP38 key from an "intermediate code" and 24 bytes of cryptographically random data (seedb), // compressed indicates if compressed pubKey format should be used for the bitcoin address, confcode (optional) will // be set to the "confirmation code" + (NSString *)BIP38KeyWithIntermediateCode:(NSString *)code seedb:(NSData *)seedb compressed:(BOOL)compressed confirmationCode:(NSString **)confcode isTestnet:(BOOL)isTestnet; { NSData *d = code.base58checkToData; // d = 0x2C 0xE9 0xB3 0xE1 0xFF 0x39 0xE2 0x51|0x53 + entropy + passpoint if (d.length != 49 || seedb.length != 24) return nil; BN_CTX *ctx = BN_CTX_new(); BN_CTX_start(ctx); NSData *passpoint = [NSData dataWithBytesNoCopy:(uint8_t *)d.bytes + 16 length:33 freeWhenDone:NO], *pubKey; BIGNUM *factorb = BN_CTX_get(ctx); BN_bin2bn(seedb.SHA256_2.bytes, CC_SHA256_DIGEST_LENGTH, factorb); // factorb = SHA256(SHA256(seedb)) pubKey = point_multiply(passpoint, factorb, compressed, ctx); // pubKey = passpoint*factorb uint16_t prefix = CFSwapInt16HostToBig(BIP38_EC_PREFIX); uint8_t flag = (compressed) ? BIP38_COMPRESSED_FLAG : 0; NSData *address = [[BRKey keyWithPublicKey:pubKey isTestnet:isTestnet].address dataUsingEncoding:NSUTF8StringEncoding]; uint32_t addresshash = (address) ? *(uint32_t *)address.SHA256_2.bytes : 0; uint64_t entropy = *(const uint64_t *)((const uint8_t *)d.bytes + 8); NSData *derived = derive_key(passpoint, addresshash, entropy); const uint64_t *derived1 = (const uint64_t *)derived.bytes, *derived2 = &derived1[4]; NSMutableData *key = [NSMutableData secureData], *encrypted1, *encrypted2; NSMutableData *x = [NSMutableData secureDataWithLength:16]; size_t l; if (((const uint8_t *)d.bytes)[7] == 0x51) flag |= BIP38_LOTSEQUENCE_FLAG; // enctryped1 = AES256Encrypt(seedb[0...15] xor derived1[0...15], derived2) ((uint64_t *)x.mutableBytes)[0] = ((const uint64_t *)seedb.bytes)[0] ^ derived1[0]; ((uint64_t *)x.mutableBytes)[1] = ((const uint64_t *)seedb.bytes)[1] ^ derived1[1]; encrypted1 = [NSMutableData secureDataWithLength:16]; CCCrypt(kCCEncrypt, kCCAlgorithmAES, kCCOptionECBMode, derived2, 32, NULL, x.bytes, x.length, encrypted1.mutableBytes, encrypted1.length, &l); // encrypted2 = AES256Encrypt((encrypted1[8...15] + seedb[16...23]) xor derived1[16...31], derived2) ((uint64_t *)x.mutableBytes)[0] = ((const uint64_t *)encrypted1.bytes)[1] ^ derived1[2]; ((uint64_t *)x.mutableBytes)[1] = ((const uint64_t *)seedb.bytes)[2] ^ derived1[3]; encrypted2 = [NSMutableData secureDataWithLength:16]; CCCrypt(kCCEncrypt, kCCAlgorithmAES, kCCOptionECBMode, derived2, 32, NULL, x.bytes, x.length, encrypted2.mutableBytes, encrypted2.length, &l); [key appendBytes:&prefix length:sizeof(prefix)]; [key appendBytes:&flag length:sizeof(flag)]; [key appendBytes:&addresshash length:sizeof(addresshash)]; [key appendBytes:&entropy length:sizeof(entropy)]; [key appendBytes:(const uint8_t *)encrypted1.bytes length:8]; [key appendData:encrypted2]; if (confcode) { NSData *pointb = point_multiply(nil, factorb, YES, ctx); // pointb = G*factorb NSMutableData *c = [NSMutableData secureData], *pointbx1, *pointbx2; uint8_t pointbprefix = ((const uint8_t *)pointb.bytes)[0] ^ (((const uint8_t *)derived2)[31] & 0x01); // pointbx1 = AES256Encrypt(pointb[1...16] xor derived1[0...15], derived2) ((uint64_t *)x.mutableBytes)[0] = ((const uint64_t *)((const uint8_t *)pointb.bytes + 1))[0] ^ derived1[0]; ((uint64_t *)x.mutableBytes)[1] = ((const uint64_t *)((const uint8_t *)pointb.bytes + 1))[1] ^ derived1[1]; pointbx1 = [NSMutableData secureDataWithLength:16]; CCCrypt(kCCEncrypt, kCCAlgorithmAES, kCCOptionECBMode, derived2, 32, NULL, x.bytes, x.length, pointbx1.mutableBytes, pointbx1.length, &l); // pointbx2 = AES256Encrypt(pointb[17...32] xor derived1[16...31], derived2) ((uint64_t *)x.mutableBytes)[0] = ((const uint64_t *)((const uint8_t *)pointb.bytes + 1))[2] ^ derived1[2]; ((uint64_t *)x.mutableBytes)[1] = ((const uint64_t *)((const uint8_t *)pointb.bytes + 1))[3] ^ derived1[3]; pointbx2 = [NSMutableData secureDataWithLength:16]; CCCrypt(kCCEncrypt, kCCAlgorithmAES, kCCOptionECBMode, derived2, 32, NULL, x.bytes, x.length, pointbx2.mutableBytes, pointbx2.length, &l); [c appendBytes:"\x64\x3B\xF6\xA8\x9A" length:5]; [c appendBytes:&flag length:sizeof(flag)]; [c appendBytes:&addresshash length:sizeof(addresshash)]; [c appendBytes:&entropy length:sizeof(entropy)]; [c appendBytes:&pointbprefix length:sizeof(pointbprefix)]; [c appendData:pointbx1]; [c appendData:pointbx2]; *confcode = [NSString base58checkWithData:c]; } BN_CTX_end(ctx); BN_CTX_free(ctx); return [NSString base58checkWithData:key]; } // returns true if the "confirmation code" confirms that the given bitcoin address depends on the specified passphrase + (BOOL)confirmWithBIP38ConfirmationCode:(NSString *)code address:(NSString *)address passphrase:(NSString *)passphrase isTestnet:(BOOL)isTestnet { NSData *d = code.base58checkToData; if (d.length != 51 || ! address || ! passphrase) return NO; uint8_t flag = ((const uint8_t *)d.bytes)[5]; uint32_t addresshash = *(const uint32_t *)((const uint8_t *)d.bytes + 6); if (*(const uint32_t *)[address dataUsingEncoding:NSUTF8StringEncoding].SHA256_2.bytes != addresshash) return NO; BN_CTX *ctx = BN_CTX_new(); BN_CTX_start(ctx); uint64_t entropy = *(const uint64_t *)((const uint8_t *)d.bytes + 10); uint8_t pointprefix = ((const uint8_t *)d.bytes)[18]; const uint8_t *pointbx1 = (const uint8_t *)d.bytes + 19, *pointbx2 = (const uint8_t *)d.bytes + 35; BIGNUM *passfactor = BN_CTX_get(ctx); derive_passfactor(passfactor, flag, entropy, passphrase); NSData *passpoint = point_multiply(nil, passfactor, YES, ctx); // passpoint = G*passfactor NSData *derived = derive_key(passpoint, addresshash, entropy), *pubKey; const uint64_t *derived1 = (const uint64_t *)derived.bytes, *derived2 = &derived1[4]; NSMutableData *pointb = [NSMutableData secureDataWithLength:33]; size_t l; ((uint8_t *)pointb.mutableBytes)[0] = pointprefix ^ (((const uint8_t *)derived2)[31] & 0x01); CCCrypt(kCCDecrypt, kCCAlgorithmAES, kCCOptionECBMode, derived2, 32, NULL, pointbx1, 16, (uint8_t *)pointb.mutableBytes + 1, 16, &l); // pointb[1...16] xor derived1[0...15] ((uint64_t *)((uint8_t *)pointb.mutableBytes + 1))[0] ^= derived1[0]; ((uint64_t *)((uint8_t *)pointb.mutableBytes + 1))[1] ^= derived1[1]; CCCrypt(kCCDecrypt, kCCAlgorithmAES, kCCOptionECBMode, derived2, 32, NULL, pointbx2, 16, (uint8_t *)pointb.mutableBytes + 17, 16, &l); // pointb[17...32] xor derived1[16...31] ((uint64_t *)((uint8_t *)pointb.mutableBytes + 1))[2] ^= derived1[2]; ((uint64_t *)((uint8_t *)pointb.mutableBytes + 1))[3] ^= derived1[3]; pubKey = point_multiply(pointb, passfactor, flag & BIP38_COMPRESSED_FLAG, ctx); // pubKey = pointb*passfactor BN_CTX_end(ctx); BN_CTX_free(ctx); return ([[BRKey keyWithPublicKey:pubKey isTestnet:isTestnet].address isEqual:address]) ? YES : NO; } - (instancetype)initWithBIP38Key:(NSString *)key andPassphrase:(NSString *)passphrase isTestnet:(BOOL)isTestnet { NSData *d = key.base58checkToData; if (d.length != 39 || ! passphrase) return nil; uint16_t prefix = CFSwapInt16BigToHost(*(const uint16_t *)d.bytes); uint8_t flag = ((const uint8_t *)d.bytes)[2]; uint32_t addresshash = *(const uint32_t *)((const uint8_t *)d.bytes + 3); NSMutableData *secret = [NSMutableData secureDataWithLength:32]; size_t l; if (prefix == BIP38_NOEC_PREFIX) { // non EC multiplied key // d = prefix + flag + addresshash + encrypted1 + encrypted2 NSData *password = normalize_passphrase(passphrase); NSData *salt = [NSData dataWithBytesNoCopy:&addresshash length:sizeof(addresshash) freeWhenDone:NO]; const uint8_t *encrypted1 = (const uint8_t *)d.bytes + 7, *encrypted2 = (const uint8_t *)d.bytes + 23; NSData *derived = scrypt(password, salt, BIP38_SCRYPT_N, BIP38_SCRYPT_R, BIP38_SCRYPT_P, 64); const uint64_t *derived1 = (const uint64_t *)derived.bytes, *derived2 = &((const uint64_t *)derived.bytes)[4]; CCCrypt(kCCDecrypt, kCCAlgorithmAES, kCCOptionECBMode, derived2, 32, NULL, encrypted1, 16, secret.mutableBytes, 16, &l); CCCrypt(kCCDecrypt, kCCAlgorithmAES, kCCOptionECBMode, derived2, 32, NULL, encrypted2, 16, (uint8_t *)secret.mutableBytes + 16, 16, &l); for (size_t i = 0; i < secret.length/sizeof(uint64_t); i++) { ((uint64_t *)secret.mutableBytes)[i] ^= derived1[i]; } } else if (prefix == BIP38_EC_PREFIX) { // EC multipled key BN_CTX *ctx = BN_CTX_new(); BN_CTX_start(ctx); // d = prefix + flag + addresshash + entropy + encrypted1[0...7] + encrypted2 uint64_t entropy = *(const uint64_t *)((const uint8_t *)d.bytes + 7); NSMutableData *encrypted1 = [NSMutableData secureData]; const uint8_t *encrypted2 = (const uint8_t *)d.bytes + 23; BIGNUM *passfactor = BN_CTX_get(ctx), *factorb = BN_CTX_get(ctx), *priv = BN_CTX_get(ctx), *order = BN_CTX_get(ctx); derive_passfactor(passfactor, flag, entropy, passphrase); NSData *passpoint = point_multiply(nil, passfactor, YES, ctx); // passpoint = G*passfactor NSData *derived = derive_key(passpoint, addresshash, entropy); const uint64_t *derived1 = (const uint64_t *)derived.bytes, *derived2 = &derived1[4]; NSMutableData *seedb = [NSMutableData secureDataWithLength:24], *o = [NSMutableData secureDataWithLength:16]; EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp256k1); [encrypted1 appendBytes:(const uint8_t *)d.bytes + 15 length:8]; encrypted1.length = 16; CCCrypt(kCCDecrypt, kCCAlgorithmAES, kCCOptionECBMode, derived2, 32, NULL, encrypted2, 16, o.mutableBytes, o.length, &l); // o = (encrypted1[8...15] + seedb[16...23]) xor derived1[16...31] ((uint64_t *)encrypted1.mutableBytes)[1] = ((const uint64_t *)o.bytes)[0] ^ derived1[2]; ((uint64_t *)seedb.mutableBytes)[2] = ((const uint64_t *)o.bytes)[1] ^ derived1[3]; CCCrypt(kCCDecrypt, kCCAlgorithmAES, kCCOptionECBMode, derived2, 32, NULL, encrypted1.bytes, encrypted1.length, o.mutableBytes, o.length, &l); // o = seedb[0...15] xor derived1[0...15] ((uint64_t *)seedb.mutableBytes)[0] = ((const uint64_t *)o.bytes)[0] ^ derived1[0]; ((uint64_t *)seedb.mutableBytes)[1] = ((const uint64_t *)o.bytes)[1] ^ derived1[1]; EC_GROUP_get_order(group, order, ctx); BN_bin2bn(seedb.SHA256_2.bytes, CC_SHA256_DIGEST_LENGTH, factorb); // factorb = SHA256(SHA256(seedb)) BN_mod_mul(priv, passfactor, factorb, order, ctx); // secret = passfactor*factorb mod N BN_bn2bin(priv, (unsigned char *)secret.mutableBytes + secret.length - BN_num_bytes(priv)); EC_GROUP_free(group); BN_CTX_end(ctx); BN_CTX_free(ctx); } if (! (self = [self initWithSecret:secret compressed:flag & BIP38_COMPRESSED_FLAG isTestnet:isTestnet])) return nil; NSData *address = [self.address dataUsingEncoding:NSUTF8StringEncoding]; if (! address || *(const uint32_t *)address.SHA256_2.bytes != addresshash) { NSLog(@"BIP38 bad passphrase"); return nil; } return self; } // encrypts receiver with passphrase and returns BIP38 key - (NSString *)BIP38KeyWithPassphrase:(NSString *)passphrase isTestnet:(BOOL)isTestnet { NSData *priv = self.privateKey.base58checkToData; if (priv.length < 33 || ! passphrase) return nil; uint16_t prefix = CFSwapInt16HostToBig(BIP38_NOEC_PREFIX); uint8_t flag = BIP38_NOEC_FLAG; NSData *password = normalize_passphrase(passphrase); NSData *address = [self.address dataUsingEncoding:NSUTF8StringEncoding]; NSData *salt = [address.SHA256_2 subdataWithRange:NSMakeRange(0, 4)]; NSData *derived = scrypt(password, salt, BIP38_SCRYPT_N, BIP38_SCRYPT_R, BIP38_SCRYPT_P, 64); const uint64_t *derived1 = (const uint64_t *)derived.bytes, *derived2 = &derived1[4]; NSMutableData *secret = [NSMutableData secureDataWithLength:32], *encrypted1, *encrypted2, *key; size_t l; if (priv.length > 33) flag |= BIP38_COMPRESSED_FLAG; for (size_t i = 0; i < secret.length/sizeof(uint64_t); i++) { ((uint64_t *)secret.mutableBytes)[i] = ((const uint64_t *)((const uint8_t *)priv.bytes + 1))[i] ^ derived1[i]; } // enctryped1 = AES256Encrypt(privkey[0...15] xor derived1[0...15], derived2) encrypted1 = [NSMutableData secureDataWithLength:16]; CCCrypt(kCCEncrypt, kCCAlgorithmAES, kCCOptionECBMode, derived2, 32, NULL, secret.bytes, 16, encrypted1.mutableBytes, encrypted1.length, &l); // encrypted2 = AES256Encrypt(privkey[16...31] xor derived1[16...31], derived2) encrypted2 = [NSMutableData secureDataWithLength:16]; CCCrypt(kCCEncrypt, kCCAlgorithmAES, kCCOptionECBMode, derived2, 32, NULL, (const uint8_t *)secret.bytes + 16, 16, encrypted2.mutableBytes, encrypted2.length, &l); key = [NSMutableData secureData]; [key appendBytes:&prefix length:sizeof(prefix)]; [key appendBytes:&flag length:sizeof(flag)]; [key appendData:salt]; [key appendData:encrypted1]; [key appendData:encrypted2]; return [NSString base58checkWithData:key]; } @end ================================================ FILE: ArcBit/External/BreadWalletClassesV0.5/BRKey.h ================================================ // // BRKey.h // BreadWallet // // Created by Aaron Voisine on 5/22/13. // Copyright (c) 2013 Aaron Voisine // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import @interface BRKey : NSObject @property (nonatomic, strong) NSString *privateKey; @property (nonatomic, strong) NSData *publicKey; @property (nonatomic, readonly) NSString *address; @property (nonatomic, readonly) NSData *hash160; + (instancetype)keyWithPrivateKey:(NSString *)privateKey isTestnet:(BOOL)isTestnet; + (instancetype)keyWithSecret:(NSData *)secret compressed:(BOOL)compressed isTestnet:(BOOL)isTestnet; + (instancetype)keyWithPublicKey:(NSData *)publicKey isTestnet:(BOOL)isTestnet; - (instancetype)initWithPrivateKey:(NSString *)privateKey isTestnet:(BOOL)isTestnet; - (instancetype)initWithSecret:(NSData *)secret compressed:(BOOL)compressed isTestnet:(BOOL)isTestnet; - (instancetype)initWithPublicKey:(NSData *)publicKey isTestnet:(BOOL)isTestnet; - (NSData *)sign:(NSData *)d; - (BOOL)verify:(NSData *)d signature:(NSData *)sig; @end ================================================ FILE: ArcBit/External/BreadWalletClassesV0.5/BRKey.m ================================================ // // BRKey.m // BreadWallet // // Created by Aaron Voisine on 5/22/13. // Copyright (c) 2013 Aaron Voisine // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import "BRKey.h" #import "NSString+Base58.h" #import "NSData+Hash.h" #import "NSMutableData+Bitcoin.h" #import #import #import // HMAC-SHA256 DRBG, using no prediction resistance or personalization string and outputing 256bits static NSData *hmac_drbg(NSData *entropy, NSData *nonce) { NSMutableData *V = [NSMutableData secureDataWithCapacity:CC_SHA256_DIGEST_LENGTH + 1 + entropy.length + nonce.length], *K = [NSMutableData secureDataWithCapacity:CC_SHA256_DIGEST_LENGTH], *T = [NSMutableData secureDataWithLength:CC_SHA256_DIGEST_LENGTH]; V.length = CC_SHA256_DIGEST_LENGTH; memset(V.mutableBytes, 0x01, V.length); // V = 0x01 0x01 0x01 ... 0x01 K.length = CC_SHA256_DIGEST_LENGTH; // K = 0x00 0x00 0x00 ... 0x00 [V appendBytes:"\0" length:1]; [V appendBytes:entropy.bytes length:entropy.length]; [V appendBytes:nonce.bytes length:nonce.length]; CCHmac(kCCHmacAlgSHA256, K.bytes, K.length, V.bytes, V.length, K.mutableBytes); // K = HMAC_K(V || 0x00 || seed) V.length = CC_SHA256_DIGEST_LENGTH; CCHmac(kCCHmacAlgSHA256, K.bytes, K.length, V.bytes, V.length, V.mutableBytes); // V = HMAC_K(V) [V appendBytes:"\x01" length:1]; [V appendBytes:entropy.bytes length:entropy.length]; [V appendBytes:nonce.bytes length:nonce.length]; CCHmac(kCCHmacAlgSHA256, K.bytes, K.length, V.bytes, V.length, K.mutableBytes); // K = HMAC_K(V || 0x01 || seed) V.length = CC_SHA256_DIGEST_LENGTH; CCHmac(kCCHmacAlgSHA256, K.bytes, K.length, V.bytes, V.length, V.mutableBytes); // V = HMAC_K(V) CCHmac(kCCHmacAlgSHA256, K.bytes, K.length, V.bytes, V.length, T.mutableBytes); // T = HMAC_K(V) return T; } @interface BRKey () @property (nonatomic, assign) EC_KEY *key; @property (nonatomic, assign) BOOL isTestnet; @end @implementation BRKey + (instancetype)keyWithPrivateKey:(NSString *)privateKey isTestnet:(BOOL)isTestnet { return [[self alloc] initWithPrivateKey:privateKey isTestnet:isTestnet]; } + (instancetype)keyWithSecret:(NSData *)secret compressed:(BOOL)compressed isTestnet:(BOOL)isTestnet { return [[self alloc] initWithSecret:secret compressed:compressed isTestnet:isTestnet]; } + (instancetype)keyWithPublicKey:(NSData *)publicKey isTestnet:(BOOL)isTestnet { return [[self alloc] initWithPublicKey:publicKey isTestnet:isTestnet]; } - (instancetype)init { if (! (self = [super init])) return nil; _key = EC_KEY_new_by_curve_name(NID_secp256k1); return _key ? self : nil; } - (void)dealloc { if (_key) EC_KEY_free(_key); } - (instancetype)initWithSecret:(NSData *)secret compressed:(BOOL)compressed isTestnet:(BOOL)isTestnet { if (secret.length != 32) return nil; if (! (self = [self init])) return nil; self.isTestnet = isTestnet; [self setSecret:secret compressed:compressed]; return (EC_KEY_check_key(_key)) ? self : nil; } - (instancetype)initWithPrivateKey:(NSString *)privateKey isTestnet:(BOOL)isTestnet { if (! (self = [self init])) return nil; self.isTestnet = isTestnet; self.privateKey = privateKey; return (EC_KEY_check_key(_key)) ? self : nil; } - (instancetype)initWithPublicKey:(NSData *)publicKey isTestnet:(BOOL)isTestnet { if (! (self = [self init])) return nil; self.isTestnet = isTestnet; self.publicKey = publicKey; return (EC_KEY_check_key(_key)) ? self : nil; } - (void)setSecret:(NSData *)secret compressed:(BOOL)compressed { if (secret.length != 32 || ! _key) return; BN_CTX *ctx = BN_CTX_new(); if (! ctx) return; BN_CTX_start(ctx); BIGNUM *priv = BN_CTX_get(ctx); const EC_GROUP *group = EC_KEY_get0_group(_key); EC_POINT *pub = EC_POINT_new(group); if (pub) { BN_bin2bn(secret.bytes, 32, priv); if (EC_POINT_mul(group, pub, priv, NULL, NULL, ctx)) { EC_KEY_set_private_key(_key, priv); EC_KEY_set_public_key(_key, pub); EC_KEY_set_conv_form(_key, compressed ? POINT_CONVERSION_COMPRESSED : POINT_CONVERSION_UNCOMPRESSED); } EC_POINT_free(pub); } BN_CTX_end(ctx); BN_CTX_free(ctx); } - (void)setPrivateKey:(NSString *)privateKey { // mini private key format if ((privateKey.length == 30 || privateKey.length == 22) && [privateKey characterAtIndex:0] == 'S') { if (! [privateKey isValidBitcoinPrivateKey:self.isTestnet]) return; [self setSecret:[CFBridgingRelease(CFStringCreateExternalRepresentation(SecureAllocator(), (CFStringRef)privateKey, kCFStringEncodingUTF8, 0)) SHA256] compressed:NO]; return; } NSData *d = privateKey.base58checkToData; uint8_t version = BITCOIN_PRIVKEY; if (self.isTestnet) { version = BITCOIN_PRIVKEY_TEST; } if (! d || d.length == 28) d = privateKey.base58ToData; if (d.length < 32 || d.length > 34) d = privateKey.hexToData; if ((d.length == 33 || d.length == 34) && *(const unsigned char *)d.bytes == version) { [self setSecret:[NSData dataWithBytesNoCopy:(unsigned char *)d.bytes + 1 length:32 freeWhenDone:NO] compressed:(d.length == 34) ? YES : NO]; } else if (d.length == 32) [self setSecret:d compressed:NO]; } - (NSString *)privateKey { if (! EC_KEY_check_key(_key)) return nil; const BIGNUM *priv = EC_KEY_get0_private_key(_key); NSMutableData *d = [NSMutableData secureDataWithCapacity:34]; uint8_t version = BITCOIN_PRIVKEY; if (self.isTestnet) { version = BITCOIN_PRIVKEY_TEST; } [d appendBytes:&version length:1]; d.length = 33; BN_bn2bin(priv, (unsigned char *)d.mutableBytes + d.length - BN_num_bytes(priv)); if (EC_KEY_get_conv_form(_key) == POINT_CONVERSION_COMPRESSED) [d appendBytes:"\x01" length:1]; return [NSString base58checkWithData:d]; } - (void)setPublicKey:(NSData *)publicKey { const unsigned char *bytes = publicKey.bytes; o2i_ECPublicKey(&_key, &bytes, publicKey.length); } - (NSData *)publicKey { if (! EC_KEY_check_key(_key)) return nil; size_t l = i2o_ECPublicKey(_key, NULL); NSMutableData *pubKey = [NSMutableData secureDataWithLength:l]; unsigned char *bytes = pubKey.mutableBytes; if (i2o_ECPublicKey(_key, &bytes) != l) return nil; return pubKey; } - (NSData *)hash160 { return [[self publicKey] hash160]; } - (NSString *)address { NSData *hash = [self hash160]; if (! hash.length) return nil; NSMutableData *d = [NSMutableData secureDataWithCapacity:hash.length + 1]; uint8_t version = BITCOIN_PUBKEY_ADDRESS; if (self.isTestnet) { version = BITCOIN_PUBKEY_ADDRESS_TEST; } [d appendBytes:&version length:1]; [d appendData:hash]; return [NSString base58checkWithData:d]; } - (NSData *)sign:(NSData *)d { if (d.length != CC_SHA256_DIGEST_LENGTH) { NSLog(@"%s:%d: %s: Only 256 bit hashes can be signed", __FILE__, __LINE__, __func__); return nil; } BN_CTX *ctx = BN_CTX_new(); BN_CTX_start(ctx); BIGNUM *order = BN_CTX_get(ctx), *halforder = BN_CTX_get(ctx), *k = BN_CTX_get(ctx), *r = BN_CTX_get(ctx); const BIGNUM *priv = EC_KEY_get0_private_key(_key); const EC_GROUP *group = EC_KEY_get0_group(_key); EC_POINT *p = EC_POINT_new(group); NSMutableData *sig = nil, *entropy = [NSMutableData secureDataWithLength:32]; unsigned char *b; EC_GROUP_get_order(group, order, ctx); BN_rshift1(halforder, order); // generate k deterministicly per RFC6979: https://tools.ietf.org/html/rfc6979 BN_bn2bin(priv, (unsigned char *)entropy.mutableBytes + entropy.length - BN_num_bytes(priv)); BN_bin2bn(hmac_drbg(entropy, d).bytes, CC_SHA256_DIGEST_LENGTH, k); EC_POINT_mul(group, p, k, NULL, NULL, ctx); // compute r, the x-coordinate of generator*k EC_POINT_get_affine_coordinates_GFp(group, p, r, NULL, ctx); EC_POINT_clear_free(p); BN_mod_inverse(k, k, order, ctx); // compute the inverse of k ECDSA_SIG *s = ECDSA_do_sign_ex(d.bytes, (int)d.length, k, r, _key); if (s) { // enforce low s values, negate the value (modulo the order) if above order/2. if (BN_cmp(s->s, halforder) > 0) BN_sub(s->s, order, s->s); sig = [NSMutableData dataWithLength:ECDSA_size(_key)]; b = sig.mutableBytes; sig.length = i2d_ECDSA_SIG(s, &b); ECDSA_SIG_free(s); } BN_CTX_end(ctx); BN_CTX_free(ctx); return sig; } - (BOOL)verify:(NSData *)d signature:(NSData *)sig { // -1 = error, 0 = bad sig, 1 = good return (ECDSA_verify(0, d.bytes, (int)d.length, sig.bytes, (int)sig.length, _key) == 1) ? YES : NO; } @end ================================================ FILE: ArcBit/External/BreadWalletClassesV0.5/BRTransaction.h ================================================ // // BRTransaction.h // BreadWallet // // Created by Aaron Voisine on 5/16/13. // Copyright (c) 2013 Aaron Voisine // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import #if TX_FEE_0_8_RULES #define TX_FEE_PER_KB 10000ULL // standard tx fee per kb of tx size, rounded up to nearest kb (0.8 rules) #else #define TX_FEE_PER_KB 1000ULL // standard tx fee per kb of tx size, rounded up to nearest kb #endif #define TX_MIN_OUTPUT_AMOUNT (TX_FEE_PER_KB*3*(34 + 148)/1000) // no txout can be below this amount (or it won't relay) #define TX_MAX_SIZE 100000 // no tx can be larger than this size in bytes #define TX_FREE_MAX_SIZE 1000 // tx must not be larger than this size in bytes without a fee #define TX_FREE_MIN_PRIORITY 57600000ULL // tx must not have a priority below this value without a fee #define TX_UNCONFIRMED INT32_MAX // block height indicating transaction is unconfirmed #define TX_MAX_LOCK_HEIGHT 500000000u // a lockTime below this value is a block height, otherwise a timestamp #define TX_AVERAGE_SIZE 550 // average transaction size in bytes as of november 2014 (relatively stable) @interface BRTransaction : NSObject //@property (nonatomic, readonly) NSArray *inputAddresses; - (NSArray*)getInputAddresses:(BOOL)isTestnet; @property (nonatomic, readonly) NSArray *inputHashes; @property (nonatomic, readonly) NSArray *inputIndexes; @property (nonatomic, readonly) NSArray *inputScripts; @property (nonatomic, readonly) NSArray *inputSignatures; @property (nonatomic, readonly) NSArray *inputSequences; @property (nonatomic, readonly) NSArray *outputAmounts; @property (nonatomic, readonly) NSArray *outputAddresses; @property (nonatomic, readonly) NSArray *outputScripts; @property (nonatomic, assign) uint32_t version; @property (nonatomic, strong) NSData *txHash; @property (nonatomic, assign) uint32_t lockTime; @property (nonatomic, assign) uint32_t blockHeight; @property (nonatomic, readonly) size_t size; @property (nonatomic, readonly) uint64_t standardFee; @property (nonatomic, readonly) BOOL isSigned; // checks if all signatures exist, but does not verify them @property (nonatomic, readonly, getter = toData) NSData *data; + (instancetype)transactionWithMessage:(NSData *)message isTestnet:(BOOL)isTestnet; - (instancetype)initWithMessage:(NSData *)message isTestnet:(BOOL)isTestnet; - (instancetype)initWithInputHashes:(NSArray *)hashes inputIndexes:(NSArray *)indexes inputScripts:(NSArray *)scripts outputAddresses:(NSArray *)addresses outputAmounts:(NSArray *)amounts isTestnet:(BOOL)isTestnet; - (void)addInputHash:(NSData *)hash index:(NSUInteger)index script:(NSData *)script; - (void)addInputHash:(NSData *)hash index:(NSUInteger)index script:(NSData *)script signature:(NSData *)signature sequence:(uint32_t)sequence; - (void)addOutputAddress:(NSString *)address amount:(uint64_t)amount isTestnet:(BOOL)isTestnet; - (void)addOutputScript:(NSData *)script amount:(uint64_t)amount isTestnet:(BOOL)isTestnet; - (void)insertOutputScript:(NSData *)script amount:(uint64_t)amount isTestnet:(BOOL)isTestnet; - (void)setInputAddress:(NSString *)address atIndex:(NSUInteger)index isTestnet:(BOOL)isTestnet; - (BOOL)signWithPrivateKeys:(NSArray *)privateKeys isTestnet:(BOOL)isTestnet; // priority = sum(input_amount_in_satoshis*input_age_in_blocks)/tx_size_in_bytes - (uint64_t)priorityForAmounts:(NSArray *)amounts withAges:(NSArray *)ages; // the block height after which the transaction can be confirmed without a fee, or TX_UNCONFIRMED for never - (uint32_t)blockHeightUntilFreeForAmounts:(NSArray *)amounts withBlockHeights:(NSArray *)heights; @end ================================================ FILE: ArcBit/External/BreadWalletClassesV0.5/BRTransaction.m ================================================ // // BRTransaction.m // BreadWallet // // Created by Aaron Voisine on 5/16/13. // Copyright (c) 2013 Aaron Voisine // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import "BRTransaction.h" #import "BRKey.h" #import "NSString+Base58.h" #import "NSMutableData+Bitcoin.h" #import "NSData+Bitcoin.h" #import "NSData+Hash.h" #define TX_VERSION 0x00000001u #define TX_LOCKTIME 0x00000000u #define TXIN_SEQUENCE UINT32_MAX #define SIGHASH_ALL 0x00000001u @interface BRTransaction () @property (nonatomic, strong) NSMutableArray *hashes, *indexes, *inScripts, *signatures, *sequences; @property (nonatomic, strong) NSMutableArray *amounts, *addresses, *outScripts; @end @implementation BRTransaction + (instancetype)transactionWithMessage:(NSData *)message isTestnet:(BOOL)isTestnet { return [[self alloc] initWithMessage:message isTestnet:isTestnet]; } - (instancetype)init { if (! (self = [super init])) return nil; _version = TX_VERSION; self.hashes = [NSMutableArray array]; self.indexes = [NSMutableArray array]; self.inScripts = [NSMutableArray array]; self.amounts = [NSMutableArray array]; self.addresses = [NSMutableArray array]; self.outScripts = [NSMutableArray array]; self.signatures = [NSMutableArray array]; self.sequences = [NSMutableArray array]; _lockTime = TX_LOCKTIME; _blockHeight = TX_UNCONFIRMED; return self; } - (instancetype)initWithMessage:(NSData *)message isTestnet:(BOOL)isTestnet { if (! (self = [self init])) return nil; NSString *address = nil; NSUInteger l = 0, off = 0, count = 0; NSData *d = nil; _version = [message UInt32AtOffset:off]; // tx version off += sizeof(uint32_t); count = (NSUInteger)[message varIntAtOffset:off length:&l]; // input count if (count == 0) return nil; // at least one input is required off += l; for (NSUInteger i = 0; i < count; i++) { // inputs d = [message hashAtOffset:off]; // input tx hash if (! d) return nil; // required [self.hashes addObject:d]; off += CC_SHA256_DIGEST_LENGTH; [self.indexes addObject:@([message UInt32AtOffset:off])]; // input index off += sizeof(uint32_t); [self.inScripts addObject:[NSNull null]]; // placeholder for input script (comes from previous transaction) d = [message dataAtOffset:off length:&l]; [self.signatures addObject:(d.length > 0) ? d : [NSNull null]]; // input signature off += l; [self.sequences addObject:@([message UInt32AtOffset:off])]; // input sequence number (for replacement tx) off += sizeof(uint32_t); } count = (NSUInteger)[message varIntAtOffset:off length:&l]; // output count off += l; for (NSUInteger i = 0; i < count; i++) { // outputs [self.amounts addObject:@([message UInt64AtOffset:off])]; // output amount off += sizeof(uint64_t); d = [message dataAtOffset:off length:&l]; [self.outScripts addObject:(d) ? d : [NSNull null]]; // output script off += l; address = [NSString addressWithScriptPubKey:d isTestnet:isTestnet]; // address from output script if applicable [self.addresses addObject:(address) ? address : [NSNull null]]; } _lockTime = [message UInt32AtOffset:off]; // tx locktime _txHash = self.data.SHA256_2; return self; } - (instancetype)initWithInputHashes:(NSArray *)hashes inputIndexes:(NSArray *)indexes inputScripts:(NSArray *)scripts outputAddresses:(NSArray *)addresses outputAmounts:(NSArray *)amounts isTestnet:(BOOL)isTestnet { if (hashes.count == 0 || hashes.count != indexes.count) return nil; if (scripts.count > 0 && hashes.count != scripts.count) return nil; if (addresses.count != amounts.count) return nil; if (! (self = [super init])) return nil; _version = TX_VERSION; self.hashes = [NSMutableArray arrayWithArray:hashes]; self.indexes = [NSMutableArray arrayWithArray:indexes]; if (scripts.count > 0) { self.inScripts = [NSMutableArray arrayWithArray:scripts]; } else self.inScripts = [NSMutableArray arrayWithCapacity:hashes.count]; while (self.inScripts.count < hashes.count) { [self.inScripts addObject:[NSNull null]]; } self.amounts = [NSMutableArray arrayWithArray:amounts]; self.addresses = [NSMutableArray arrayWithArray:addresses]; self.outScripts = [NSMutableArray arrayWithCapacity:addresses.count]; for (int i = 0; i < addresses.count; i++) { [self.outScripts addObject:[NSMutableData data]]; [self.outScripts.lastObject appendScriptPubKeyForAddress:self.addresses[i] isTestnet:isTestnet]; } self.signatures = [NSMutableArray arrayWithCapacity:hashes.count]; self.sequences = [NSMutableArray arrayWithCapacity:hashes.count]; for (int i = 0; i < hashes.count; i++) { [self.signatures addObject:[NSNull null]]; [self.sequences addObject:@(TXIN_SEQUENCE)]; } _lockTime = TX_LOCKTIME; _blockHeight = TX_UNCONFIRMED; return self; } - (NSArray *)inputHashes { return self.hashes; } - (NSArray *)inputIndexes { return self.indexes; } - (NSArray *)inputScripts { return self.inScripts; } - (NSArray *)inputSignatures { return self.signatures; } - (NSArray *)inputSequences { return self.sequences; } - (NSArray *)outputAmounts { return self.amounts; } - (NSArray *)outputAddresses { return self.addresses; } - (NSArray *)outputScripts { return self.outScripts; } - (size_t)size { //TODO: not all keys come from this wallet (private keys can be swept), might cause a lower than standard tx fee size_t sigSize = 149; // electrum seeds generate uncompressed keys, bip32 uses compressed // size_t sigSize = 181; return 8 + [NSMutableData sizeOfVarInt:self.hashes.count] + [NSMutableData sizeOfVarInt:self.addresses.count] + sigSize*self.hashes.count + 34*self.addresses.count; } - (uint64_t)standardFee { return ((self.size + 999)/1000)*TX_FEE_PER_KB; } // checks if all signatures exist, but does not verify them - (BOOL)isSigned { return (self.signatures.count > 0 && self.signatures.count == self.hashes.count && ! [self.signatures containsObject:[NSNull null]]); } - (NSData *)toData { return [self toDataWithSubscriptIndex:NSNotFound]; } - (void)addInputHash:(NSData *)hash index:(NSUInteger)index script:(NSData *)script { [self addInputHash:hash index:index script:script signature:nil sequence:TXIN_SEQUENCE]; } - (void)addInputHash:(NSData *)hash index:(NSUInteger)index script:(NSData *)script signature:(NSData *)signature sequence:(uint32_t)sequence { [self.hashes addObject:hash]; [self.indexes addObject:@(index)]; [self.inScripts addObject:(script) ? script : [NSNull null]]; [self.signatures addObject:(signature) ? signature : [NSNull null]]; [self.sequences addObject:@(sequence)]; } - (void)addOutputAddress:(NSString *)address amount:(uint64_t)amount isTestnet:(BOOL)isTestnet { [self.amounts addObject:@(amount)]; [self.addresses addObject:address]; [self.outScripts addObject:[NSMutableData data]]; [self.outScripts.lastObject appendScriptPubKeyForAddress:address isTestnet:isTestnet]; } - (void)addOutputScript:(NSData *)script amount:(uint64_t)amount isTestnet:(BOOL)isTestnet; { NSString *address = [NSString addressWithScriptPubKey:script isTestnet:isTestnet]; [self.amounts addObject:@(amount)]; [self.outScripts addObject:script]; [self.addresses addObject:(address) ? address : [NSNull null]]; } - (void)insertOutputScript:(NSData *)script amount:(uint64_t)amount isTestnet:(BOOL)isTestnet { NSString *address = [NSString addressWithScriptPubKey:script isTestnet:isTestnet]; [self.amounts insertObject:@(amount) atIndex:0]; [self.outScripts insertObject:script atIndex: 0]; [self.addresses insertObject:(address) ? address : [NSNull null] atIndex: 0]; } - (void)setInputAddress:(NSString *)address atIndex:(NSUInteger)index isTestnet:(BOOL)isTestnet; { NSMutableData *d = [NSMutableData data]; [d appendScriptPubKeyForAddress:address isTestnet:isTestnet]; [self.inScripts replaceObjectAtIndex:index withObject:d]; } - (NSArray *)getInputAddresses:(BOOL)isTestnet { NSMutableArray *addresses = [NSMutableArray arrayWithCapacity:self.inScripts.count]; NSInteger i = 0; for (NSData *script in self.inScripts) { NSString *addr = [NSString addressWithScriptPubKey:script isTestnet:isTestnet]; if (! addr) addr = [NSString addressWithScriptSig:self.signatures[i] isTestnet:isTestnet]; [addresses addObject:(addr) ? addr : [NSNull null]]; i++; } return addresses; } // Returns the binary transaction data that needs to be hashed and signed with the private key for the tx input at // subscriptIndex. A subscriptIndex of NSNotFound will return the entire signed transaction - (NSData *)toDataWithSubscriptIndex:(NSUInteger)subscriptIndex { NSMutableData *d = [NSMutableData dataWithCapacity:self.size]; [d appendUInt32:self.version]; [d appendVarInt:self.hashes.count]; for (NSUInteger i = 0; i < self.hashes.count; i++) { [d appendData:self.hashes[i]]; [d appendUInt32:[self.indexes[i] unsignedIntValue]]; if (subscriptIndex == NSNotFound && self.signatures[i] != [NSNull null]) { [d appendVarInt:[self.signatures[i] length]]; [d appendData:self.signatures[i]]; } else if (subscriptIndex == i && self.inScripts[i] != [NSNull null]) { //TODO: to fully match the reference implementation, OP_CODESEPARATOR related checksig logic should go here [d appendVarInt:[self.inScripts[i] length]]; [d appendData:self.inScripts[i]]; } else [d appendVarInt:0]; [d appendUInt32:[self.sequences[i] unsignedIntValue]]; } [d appendVarInt:self.amounts.count]; for (NSUInteger i = 0; i < self.amounts.count; i++) { [d appendUInt64:[self.amounts[i] unsignedLongLongValue]]; [d appendVarInt:[self.outScripts[i] length]]; [d appendData:self.outScripts[i]]; } [d appendUInt32:self.lockTime]; if (subscriptIndex != NSNotFound) { [d appendUInt32:SIGHASH_ALL]; } return d; } - (BOOL)signWithPrivateKeys:(NSArray *)privateKeys isTestnet:(BOOL)isTestnet { NSMutableArray *addresses = [NSMutableArray arrayWithCapacity:privateKeys.count], *keys = [NSMutableArray arrayWithCapacity:privateKeys.count]; for (NSString *pk in privateKeys) { BRKey *key = [BRKey keyWithPrivateKey:pk isTestnet:isTestnet]; if (! key) continue; [keys addObject:key]; [addresses addObject:key.address]; } for (NSUInteger i = 0; i < self.hashes.count; i++) { NSString *addr = [NSString addressWithScriptPubKey:self.inScripts[i] isTestnet:isTestnet]; NSUInteger keyIdx = (addr) ? [addresses indexOfObject:addr] : NSNotFound; if (keyIdx == NSNotFound) continue; NSMutableData *sig = [NSMutableData data]; NSData *hash = [self toDataWithSubscriptIndex:i].SHA256_2; NSMutableData *s = [NSMutableData dataWithData:[keys[keyIdx] sign:hash]]; NSArray *elem = [self.inScripts[i] scriptElements]; [s appendUInt8:SIGHASH_ALL]; [sig appendScriptPushData:s]; if (elem.count >= 2 && [elem[elem.count - 2] intValue] == OP_EQUALVERIFY) { // pay-to-pubkey-hash scriptSig [sig appendScriptPushData:[keys[keyIdx] publicKey]]; } [self.signatures replaceObjectAtIndex:i withObject:sig]; } if (! [self isSigned]) return NO; _txHash = self.data.SHA256_2; return YES; } // priority = sum(input_amount_in_satoshis*input_age_in_blocks)/size_in_bytes - (uint64_t)priorityForAmounts:(NSArray *)amounts withAges:(NSArray *)ages { uint64_t p = 0; if (amounts.count != self.hashes.count || ages.count != self.hashes.count || [ages containsObject:@(0)]) return 0; for (NSUInteger i = 0; i < amounts.count; i++) { p += [amounts[i] unsignedLongLongValue]*[ages[i] unsignedLongLongValue]; } return p/self.size; } // the block height after which the transaction can be confirmed without a fee, or TX_UNCONFIRMRED for never - (uint32_t)blockHeightUntilFreeForAmounts:(NSArray *)amounts withBlockHeights:(NSArray *)heights { if (amounts.count != self.hashes.count || heights.count != self.hashes.count || self.size > TX_FREE_MAX_SIZE || [heights containsObject:@(TX_UNCONFIRMED)]) { return TX_UNCONFIRMED; } for (NSNumber *amount in self.amounts) { if (amount.unsignedLongLongValue < TX_MIN_OUTPUT_AMOUNT) return TX_UNCONFIRMED; } uint64_t amountTotal = 0, amountsByHeights = 0; for (NSUInteger i = 0; i < amounts.count; i++) { amountTotal += [amounts[i] unsignedLongLongValue]; amountsByHeights += [amounts[i] unsignedLongLongValue]*[heights[i] unsignedLongLongValue]; } if (amountTotal == 0) return TX_UNCONFIRMED; // this could possibly overflow a uint64 for very large input amounts and far in the future block heights, // however we should be okay up to the largest current bitcoin balance in existence for the next 40 years or so, // and the worst case is paying a transaction fee when it's not needed return (uint32_t)((TX_FREE_MIN_PRIORITY*(uint64_t)self.size + amountsByHeights + amountTotal - 1ULL)/amountTotal); } - (NSUInteger)hash { if (self.txHash.length < sizeof(NSUInteger)) return [super hash]; return *(const NSUInteger *)self.txHash.bytes; } - (BOOL)isEqual:(id)object { return self == object || ([object isKindOfClass:[BRTransaction class]] && [[object txHash] isEqual:self.txHash]); } @end ================================================ FILE: ArcBit/External/BreadWalletClassesV0.5/NSData+Bitcoin.h ================================================ // // NSData+Bitcoin.h // BreadWallet // // Created by Aaron Voisine on 10/9/13. // Copyright (c) 2013 Aaron Voisine // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import #define VAR_INT16_HEADER 0xfd #define VAR_INT32_HEADER 0xfe #define VAR_INT64_HEADER 0xff // bitcoin script opcodes: https://en.bitcoin.it/wiki/Script#Constants #define OP_PUSHDATA1 0x4c #define OP_PUSHDATA2 0x4d #define OP_PUSHDATA4 0x4e #define OP_DUP 0x76 #define OP_EQUAL 0x87 #define OP_EQUALVERIFY 0x88 #define OP_HASH160 0xa9 #define OP_CHECKSIG 0xac @interface NSData (Bitcoin) - (uint8_t)UInt8AtOffset:(NSUInteger)offset; - (uint16_t)UInt16AtOffset:(NSUInteger)offset; - (uint32_t)UInt32AtOffset:(NSUInteger)offset; - (uint64_t)UInt64AtOffset:(NSUInteger)offset; - (uint64_t)varIntAtOffset:(NSUInteger)offset length:(NSUInteger *)length; - (NSData *)hashAtOffset:(NSUInteger)offset; - (NSString *)stringAtOffset:(NSUInteger)offset length:(NSUInteger *)length; - (NSData *)dataAtOffset:(NSUInteger)offset length:(NSUInteger *)length; - (NSArray *)scriptElements; // an array of NSNumber and NSData objects representing each script element - (int)intValue; // returns the opcode used to store the receiver in a script (i.e. OP_PUSHDATA1) @end ================================================ FILE: ArcBit/External/BreadWalletClassesV0.5/NSData+Bitcoin.m ================================================ // // NSData+Bitcoin.m // BreadWallet // // Created by Aaron Voisine on 10/9/13. // Copyright (c) 2013 Aaron Voisine // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import "NSData+Bitcoin.h" #import @implementation NSData (Bitcoin) - (uint8_t)UInt8AtOffset:(NSUInteger)offset { if (self.length < offset + sizeof(uint8_t)) return 0; return *((const uint8_t *)self.bytes + offset); } - (uint16_t)UInt16AtOffset:(NSUInteger)offset { if (self.length < offset + sizeof(uint16_t)) return 0; return CFSwapInt16LittleToHost(*(const uint16_t *)((const uint8_t *)self.bytes + offset)); } - (uint32_t)UInt32AtOffset:(NSUInteger)offset { if (self.length < offset + sizeof(uint32_t)) return 0; return CFSwapInt32LittleToHost(*(const uint32_t *)((const uint8_t *)self.bytes + offset)); } - (uint64_t)UInt64AtOffset:(NSUInteger)offset { if (self.length < offset + sizeof(uint64_t)) return 0; return CFSwapInt64LittleToHost(*(const uint64_t *)((const uint8_t *)self.bytes + offset)); } - (uint64_t)varIntAtOffset:(NSUInteger)offset length:(NSUInteger *)length { uint8_t h = [self UInt8AtOffset:offset]; switch (h) { case VAR_INT16_HEADER: if (length) *length = sizeof(h) + sizeof(uint16_t); return [self UInt16AtOffset:offset + 1]; case VAR_INT32_HEADER: if (length) *length = sizeof(h) + sizeof(uint32_t); return [self UInt32AtOffset:offset + 1]; case VAR_INT64_HEADER: if (length) *length = sizeof(h) + sizeof(uint64_t); return [self UInt64AtOffset:offset + 1]; default: if (length) *length = sizeof(h); return h; } } - (NSData *)hashAtOffset:(NSUInteger)offset { if (self.length < offset + CC_SHA256_DIGEST_LENGTH) return nil; return [self subdataWithRange:NSMakeRange(offset, CC_SHA256_DIGEST_LENGTH)]; } - (NSString *)stringAtOffset:(NSUInteger)offset length:(NSUInteger *)length { NSUInteger ll, l = (NSUInteger)[self varIntAtOffset:offset length:&ll]; if (length) *length = ll + l; if (ll == 0 || self.length < offset + ll + l) return nil; return [[NSString alloc] initWithBytes:(const char *)self.bytes + offset + ll length:l encoding:NSUTF8StringEncoding]; } - (NSData *)dataAtOffset:(NSUInteger)offset length:(NSUInteger *)length { NSUInteger ll, l = (NSUInteger)[self varIntAtOffset:offset length:&ll]; if (length) *length = ll + l; if (ll == 0 || self.length < offset + ll + l) return nil; return [self subdataWithRange:NSMakeRange(offset + ll, l)]; } // an array of NSNumber and NSData objects representing each script element - (NSArray *)scriptElements { NSMutableArray *a = [NSMutableArray array]; const uint8_t *b = (const uint8_t *)self.bytes; NSUInteger l, length = self.length; for (NSUInteger i = 0; i < length; i++) { if (b[i] > OP_PUSHDATA4) { [a addObject:@(b[i])]; continue; } switch (b[i]) { case 0: [a addObject:@(0)]; continue; case OP_PUSHDATA1: i++; if (i + sizeof(uint8_t) > length) return a; l = b[i]; i += sizeof(uint8_t); break; case OP_PUSHDATA2: i++; if (i + sizeof(uint16_t) > length) return a; l = CFSwapInt16LittleToHost(*(uint16_t *)&b[i]); i += sizeof(uint16_t); break; case OP_PUSHDATA4: i++; if (i + sizeof(uint32_t) > length) return a; l = CFSwapInt32LittleToHost(*(uint32_t *)&b[i]); i += sizeof(uint32_t); break; default: l = b[i]; i++; break; } if (i + l > length) return a; [a addObject:[NSData dataWithBytes:&b[i] length:l]]; i += l - 1; } return a; } // returns the opcode used to store the receiver in a script (i.e. OP_PUSHDATA1) - (int)intValue { if (self.length < OP_PUSHDATA1) return (int)self.length; else if (self.length <= UINT8_MAX) return OP_PUSHDATA1; else if (self.length <= UINT16_MAX) return OP_PUSHDATA2; else return OP_PUSHDATA4; } @end ================================================ FILE: ArcBit/External/BreadWalletClassesV0.5/NSData+Hash.h ================================================ // // NSData+Hash.h // BreadWallet // // Created by Aaron Voisine on 5/13/13. // Copyright (c) 2013 Aaron Voisine // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import #import @interface NSData (Hash) - (NSData *)SHA1; - (NSData *)SHA256; - (NSData *)SHA256_2; - (NSData *)RMD160; - (NSData *)hash160; - (NSData *)reverse; @end ================================================ FILE: ArcBit/External/BreadWalletClassesV0.5/NSData+Hash.m ================================================ // // NSData+Hash.m // BreadWallet // // Created by Aaron Voisine on 5/13/13. // Copyright (c) 2013 Aaron Voisine // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import "NSData+Hash.h" #import #import @implementation NSData (Hash) - (NSData *)SHA1 { NSMutableData *d = [NSMutableData dataWithLength:CC_SHA1_DIGEST_LENGTH]; CC_SHA1(self.bytes, (CC_LONG)self.length, d.mutableBytes); return d; } - (NSData *)SHA256 { NSMutableData *d = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH]; CC_SHA256(self.bytes, (CC_LONG)self.length, d.mutableBytes); return d; } - (NSData *)SHA256_2 { NSMutableData *d = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH]; CC_SHA256(self.bytes, (CC_LONG)self.length, d.mutableBytes); CC_SHA256(d.bytes, (CC_LONG)d.length, d.mutableBytes); return d; } - (NSData *)RMD160 { NSMutableData *d = [NSMutableData dataWithLength:RIPEMD160_DIGEST_LENGTH]; RIPEMD160(self.bytes, self.length, d.mutableBytes); return d; } - (NSData *)hash160 { return self.SHA256.RMD160; } - (NSData *)reverse { NSUInteger l = self.length; NSMutableData *d = [NSMutableData dataWithLength:l]; uint8_t *b1 = d.mutableBytes; const uint8_t *b2 = self.bytes; for (NSUInteger i = 0; i < l; i++) { b1[i] = b2[l - i - 1]; } return d; } @end ================================================ FILE: ArcBit/External/BreadWalletClassesV0.5/NSMutableData+Bitcoin.h ================================================ // // NSMutableData+Bitcoin.h // BreadWallet // // Created by Aaron Voisine on 5/20/13. // Copyright (c) 2013 Aaron Voisine // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import #if BITCOIN_TESTNET #define BITCOIN_MAGIC_NUMBER 0x0709110b #else #define BITCOIN_MAGIC_NUMBER 0xd9b4bef9 #endif @interface NSMutableData (Bitcoin) + (NSMutableData *)secureData; + (NSMutableData *)secureDataWithLength:(NSUInteger)length; + (NSMutableData *)secureDataWithCapacity:(NSUInteger)capacity; + (NSMutableData *)secureDataWithData:(NSData *)data; + (size_t)sizeOfVarInt:(uint64_t)i; - (void)appendUInt8:(uint8_t)i; - (void)appendUInt16:(uint16_t)i; - (void)appendUInt32:(uint32_t)i; - (void)appendUInt64:(uint64_t)i; - (void)appendVarInt:(uint64_t)i; - (void)appendString:(NSString *)s; - (void)appendScriptPubKeyForAddress:(NSString *)address isTestnet:(BOOL)isTestnet; - (void)appendScriptPushData:(NSData *)d; - (void)appendMessage:(NSData *)message type:(NSString *)type isTestnet:(BOOL)isTestnet; - (void)appendNullPaddedString:(NSString *)s length:(NSUInteger)length; - (void)appendNetAddress:(uint32_t)address port:(uint16_t)port services:(uint64_t)services; @end ================================================ FILE: ArcBit/External/BreadWalletClassesV0.5/NSMutableData+Bitcoin.m ================================================ // // NSMutableData+Bitcoin.m // BreadWallet // // Created by Aaron Voisine on 5/20/13. // Copyright (c) 2013 Aaron Voisine // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import "NSMutableData+Bitcoin.h" #import "NSData+Bitcoin.h" #import "NSString+Base58.h" #import "NSData+Hash.h" @implementation NSMutableData (Bitcoin) + (NSMutableData *)secureData { return [self secureDataWithCapacity:0]; } + (NSMutableData *)secureDataWithCapacity:(NSUInteger)aNumItems { return CFBridgingRelease(CFDataCreateMutable(SecureAllocator(), aNumItems)); } + (NSMutableData *)secureDataWithLength:(NSUInteger)length { NSMutableData *d = [self secureDataWithCapacity:length]; d.length = length; return d; } + (NSMutableData *)secureDataWithData:(NSData *)data { return CFBridgingRelease(CFDataCreateMutableCopy(SecureAllocator(), 0, (__bridge CFDataRef)data)); } + (size_t)sizeOfVarInt:(uint64_t)i { if (i < VAR_INT16_HEADER) return sizeof(uint8_t); else if (i <= UINT16_MAX) return sizeof(uint8_t) + sizeof(uint16_t); else if (i <= UINT32_MAX) return sizeof(uint8_t) + sizeof(uint32_t); else return sizeof(uint8_t) + sizeof(uint64_t); } - (void)appendUInt8:(uint8_t)i { [self appendBytes:&i length:sizeof(i)]; } - (void)appendUInt16:(uint16_t)i { i = CFSwapInt16HostToLittle(i); [self appendBytes:&i length:sizeof(i)]; } - (void)appendUInt32:(uint32_t)i { i = CFSwapInt32HostToLittle(i); [self appendBytes:&i length:sizeof(i)]; } - (void)appendUInt64:(uint64_t)i { i = CFSwapInt64HostToLittle(i); [self appendBytes:&i length:sizeof(i)]; } - (void)appendVarInt:(uint64_t)i { if (i < VAR_INT16_HEADER) { uint8_t payload = (uint8_t)i; [self appendBytes:&payload length:sizeof(payload)]; } else if (i <= UINT16_MAX) { uint8_t header = VAR_INT16_HEADER; uint16_t payload = CFSwapInt16HostToLittle((uint16_t)i); [self appendBytes:&header length:sizeof(header)]; [self appendBytes:&payload length:sizeof(payload)]; } else if (i <= UINT32_MAX) { uint8_t header = VAR_INT32_HEADER; uint32_t payload = CFSwapInt32HostToLittle((uint32_t)i); [self appendBytes:&header length:sizeof(header)]; [self appendBytes:&payload length:sizeof(payload)]; } else { uint8_t header = VAR_INT64_HEADER; uint64_t payload = CFSwapInt64HostToLittle(i); [self appendBytes:&header length:sizeof(header)]; [self appendBytes:&payload length:sizeof(payload)]; } } - (void)appendString:(NSString *)s { NSUInteger l = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; [self appendVarInt:l]; [self appendBytes:s.UTF8String length:l]; } #pragma mark - bitcoin script - (void)appendScriptPushData:(NSData *)d { if (d.length == 0) { return; } else if (d.length < OP_PUSHDATA1) { [self appendUInt8:d.length]; } else if (d.length < UINT8_MAX) { [self appendUInt8:OP_PUSHDATA1]; [self appendUInt8:d.length]; } else if (d.length < UINT16_MAX) { [self appendUInt8:OP_PUSHDATA2]; [self appendUInt16:d.length]; } else { [self appendUInt8:OP_PUSHDATA4]; [self appendUInt32:(uint32_t)d.length]; } [self appendData:d]; } - (void)appendScriptPubKeyForAddress:(NSString *)address isTestnet:(BOOL)isTestnet { static uint8_t pubkeyAddress = BITCOIN_PUBKEY_ADDRESS, scriptAddress = BITCOIN_SCRIPT_ADDRESS; NSData *d = address.base58checkToData; if (d.length != 21) return; uint8_t version = *(const uint8_t *)d.bytes; NSData *hash = [d subdataWithRange:NSMakeRange(1, d.length - 1)]; if (isTestnet) { pubkeyAddress = BITCOIN_PUBKEY_ADDRESS_TEST; scriptAddress = BITCOIN_SCRIPT_ADDRESS_TEST; } if (version == pubkeyAddress) { [self appendUInt8:OP_DUP]; [self appendUInt8:OP_HASH160]; [self appendScriptPushData:hash]; [self appendUInt8:OP_EQUALVERIFY]; [self appendUInt8:OP_CHECKSIG]; } else if (version == scriptAddress) { [self appendUInt8:OP_HASH160]; [self appendScriptPushData:hash]; [self appendUInt8:OP_EQUAL]; } } #pragma mark - bitcoin protocol - (void)appendMessage:(NSData *)message type:(NSString *)type isTestnet:(BOOL)isTestnet; { if (isTestnet) { [self appendUInt32:0x0709110b]; //BITCOIN_MAGIC_NUMBER } else { [self appendUInt32:0xd9b4bef9]; //BITCOIN_MAGIC_NUMBER } [self appendNullPaddedString:type length:12]; [self appendUInt32:(uint32_t)message.length]; [self appendBytes:message.SHA256_2.bytes length:4]; [self appendBytes:message.bytes length:message.length]; } - (void)appendNullPaddedString:(NSString *)s length:(NSUInteger)length { NSUInteger l = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; [self appendBytes:s.UTF8String length:l]; while (l++ < length) { [self appendBytes:"\0" length:1]; } } - (void)appendNetAddress:(uint32_t)address port:(uint16_t)port services:(uint64_t)services { address = CFSwapInt32HostToBig(address); port = CFSwapInt16HostToBig(port); [self appendUInt64:services]; [self appendBytes:"\0\0\0\0\0\0\0\0\0\0\xFF\xFF" length:12]; // IPv4 mapped IPv6 header [self appendBytes:&address length:sizeof(address)]; [self appendBytes:&port length:sizeof(port)]; } @end ================================================ FILE: ArcBit/External/BreadWalletClassesV0.5/NSString+Base58.h ================================================ // // NSString+Base58.h // BreadWallet // // Created by Aaron Voisine on 5/13/13. // Copyright (c) 2013 Aaron Voisine // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import #define BITCOIN_PUBKEY_ADDRESS 0 #define BITCOIN_SCRIPT_ADDRESS 5 #define BITCOIN_PUBKEY_ADDRESS_TEST 111 #define BITCOIN_SCRIPT_ADDRESS_TEST 196 #define BITCOIN_PRIVKEY 128 #define BITCOIN_PRIVKEY_TEST 239 #define BIP38_NOEC_PREFIX 0x0142 #define BIP38_EC_PREFIX 0x0143 #define BIP38_NOEC_FLAG (0x80 | 0x40) #define BIP38_COMPRESSED_FLAG 0x20 #define BIP38_LOTSEQUENCE_FLAG 0x04 #define BIP38_INVALID_FLAG (0x10 | 0x08 | 0x02 | 0x01) CFAllocatorRef SecureAllocator(); @interface NSString (Base58) + (NSString *)base58WithData:(NSData *)d; + (NSString *)base58checkWithData:(NSData *)d; + (NSString *)hexWithData:(NSData *)d; + (NSString *)addressWithScriptPubKey:(NSData *)script isTestnet:(BOOL)isTestnet; + (NSString *)addressWithScriptSig:(NSData *)script isTestnet:(BOOL)isTestnet; - (NSData *)base58ToData; - (NSString *)hexToBase58; - (NSString *)base58ToHex; - (NSData *)base58checkToData; - (NSString *)hexToBase58check; - (NSString *)base58checkToHex; - (NSData *)hexToData; - (NSData *)addressToHash160; - (BOOL)isValidBitcoinAddress:(BOOL)isTestnet; - (BOOL)isValidBitcoinPrivateKey:(BOOL)isTestnet; - (BOOL)isValidBitcoinBIP38Key; // BIP38 encrypted keys: https://github.com/bitcoin/bips/blob/master/bip-0038.mediawiki @end ================================================ FILE: ArcBit/External/BreadWalletClassesV0.5/NSString+Base58.m ================================================ // // NSString+Base58.mm // BreadWallet // // Created by Aaron Voisine on 5/13/13. // Copyright (c) 2013 Aaron Voisine // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import "NSString+Base58.h" #import "NSData+Hash.h" #import "NSData+Bitcoin.h" #import "NSMutableData+Bitcoin.h" #import "ccMemory.h" #import static const char base58chars[] = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; static void *secureAllocate(CFIndex allocSize, CFOptionFlags hint, void *info) { void *ptr = CC_XMALLOC(sizeof(CFIndex) + allocSize); if (ptr) { // we need to keep track of the size of the allocation so it can be cleansed before deallocation *(CFIndex *)ptr = allocSize; return (CFIndex *)ptr + 1; } else return NULL; } static void secureDeallocate(void *ptr, void *info) { CFIndex size = *((CFIndex *)ptr - 1); if (size) { CC_XZEROMEM(ptr, size); CC_XFREE((CFIndex *)ptr - 1, sizeof(CFIndex) + size); } } static void *secureReallocate(void *ptr, CFIndex newsize, CFOptionFlags hint, void *info) { // There's no way to tell ahead of time if the original memory will be deallocted even if the new size is smaller // than the old size, so just cleanse and deallocate every time. void *newptr = secureAllocate(newsize, hint, info); CFIndex size = *((CFIndex *)ptr - 1); if (newptr && size) { CC_XMEMCPY(newptr, ptr, (size < newsize) ? size : newsize); secureDeallocate(ptr, info); } return newptr; } // Since iOS does not page memory to storage, all we need to do is cleanse allocated memory prior to deallocation. CFAllocatorRef SecureAllocator() { static CFAllocatorRef alloc = NULL; static dispatch_once_t onceToken = 0; dispatch_once(&onceToken, ^{ CFAllocatorContext context; context.version = 0; CFAllocatorGetContext(kCFAllocatorDefault, &context); context.allocate = secureAllocate; context.reallocate = secureReallocate; context.deallocate = secureDeallocate; alloc = CFAllocatorCreate(kCFAllocatorDefault, &context); }); return alloc; } @implementation NSString (Base58) + (NSString *)base58WithData:(NSData *)d { BN_CTX *ctx = BN_CTX_new(); BN_CTX_start(ctx); NSUInteger i = d.length*138/100 + 2; char s[i]; BIGNUM *base = BN_CTX_get(ctx), *x = BN_CTX_get(ctx), *r = BN_CTX_get(ctx); BN_set_word(base, 58); BN_bin2bn(d.bytes, (int)d.length, x); s[--i] = '\0'; while (! BN_is_zero(x)) { BN_div(x, r, x, base, ctx); s[--i] = base58chars[BN_get_word(r)]; } for (NSUInteger j = 0; j < d.length && *((const uint8_t *)d.bytes + j) == 0; j++) { s[--i] = base58chars[0]; } BN_CTX_end(ctx); BN_CTX_free(ctx); NSString *ret = CFBridgingRelease(CFStringCreateWithCString(SecureAllocator(), &s[i], kCFStringEncodingUTF8)); CC_XZEROMEM(&s[0], d.length*138/100 + 2); return ret; } + (NSString *)base58checkWithData:(NSData *)d { NSMutableData *data = [NSMutableData secureDataWithData:d]; [data appendBytes:d.SHA256_2.bytes length:4]; return [self base58WithData:data]; } - (NSData *)base58ToData { BN_CTX *ctx = BN_CTX_new(); BN_CTX_start(ctx); NSMutableData *d = [NSMutableData secureDataWithCapacity:self.length + 1]; unsigned int b; BIGNUM *base = BN_CTX_get(ctx), *x = BN_CTX_get(ctx), *y = BN_CTX_get(ctx); BN_set_word(base, 58); BN_zero(x); for (NSUInteger i = 0; i < self.length && [self characterAtIndex:i] == base58chars[0]; i++) { [d appendBytes:"\0" length:1]; } for (NSUInteger i = 0; i < self.length; i++) { b = [self characterAtIndex:i]; switch (b) { case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': b -= '1'; break; case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': b += 9 - 'A'; break; case 'J': case 'K': case 'L': case 'M': case 'N': b += 17 - 'J'; break; case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': b += 22 - 'P'; break; case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': b += 33 - 'a'; break; case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': b += 44 - 'm'; break; case ' ': continue; default: goto breakout; } BN_mul(x, x, base, ctx); BN_set_word(y, b); BN_add(x, x, y); } breakout: d.length += BN_num_bytes(x); BN_bn2bin(x, (unsigned char *)d.mutableBytes + d.length - BN_num_bytes(x)); CC_XZEROMEM(&b, sizeof(b)); BN_CTX_end(ctx); BN_CTX_free(ctx); return d; } + (NSString *)hexWithData:(NSData *)d { const uint8_t *bytes = d.bytes; NSMutableString *hex = CFBridgingRelease(CFStringCreateMutable(SecureAllocator(), d.length*2)); for (NSUInteger i = 0; i < d.length; i++) { [hex appendFormat:@"%02x", bytes[i]]; } return hex; } // NOTE: It's important here to be permissive with scriptSig (spends) and strict with scriptPubKey (receives). If we // miss a receive transaction, only that transaction's funds are missed, however if we accept a receive transaction that // we are unable to correctly sign later, then the entire wallet balance after that point would become stuck with the // current coin selection code + (NSString *)addressWithScriptPubKey:(NSData *)script isTestnet:(BOOL)isTestnet { if (script == (id)[NSNull null]) return nil; NSArray *elem = [script scriptElements]; NSUInteger l = elem.count; NSMutableData *d = [NSMutableData data]; uint8_t v = BITCOIN_PUBKEY_ADDRESS; if (isTestnet) { v = BITCOIN_PUBKEY_ADDRESS_TEST; } if (l == 5 && [elem[0] intValue] == OP_DUP && [elem[1] intValue] == OP_HASH160 && [elem[2] intValue] == 20 && [elem[3] intValue] == OP_EQUALVERIFY && [elem[4] intValue] == OP_CHECKSIG) { // pay-to-pubkey-hash scriptPubKey [d appendBytes:&v length:1]; [d appendData:elem[2]]; } else if (l == 3 && [elem[0] intValue] == OP_HASH160 && [elem[1] intValue] == 20 && [elem[2] intValue] == OP_EQUAL) { // pay-to-script-hash scriptPubKey v = BITCOIN_SCRIPT_ADDRESS; if (isTestnet) { v = BITCOIN_SCRIPT_ADDRESS_TEST; } [d appendBytes:&v length:1]; [d appendData:elem[1]]; } else if (l == 2 && ([elem[0] intValue] == 65 || [elem[0] intValue] == 33) && [elem[1] intValue] == OP_CHECKSIG) { // pay-to-pubkey scriptPubKey [d appendBytes:&v length:1]; [d appendData:[elem[0] hash160]]; } else return nil; // unknown script type return [self base58checkWithData:d]; } + (NSString *)addressWithScriptSig:(NSData *)script isTestnet:(BOOL)isTestnet { if (script == (id)[NSNull null]) return nil; NSArray *elem = [script scriptElements]; NSUInteger l = elem.count; NSMutableData *d = [NSMutableData data]; uint8_t v = BITCOIN_PUBKEY_ADDRESS; if (isTestnet) { v = BITCOIN_PUBKEY_ADDRESS_TEST; } if (l >= 2 && [elem[l - 2] intValue] <= OP_PUSHDATA4 && [elem[l - 2] intValue] > 0 && ([elem[l - 1] intValue] == 65 || [elem[l - 1] intValue] == 33)) { // pay-to-pubkey-hash scriptSig [d appendBytes:&v length:1]; [d appendData:[elem[l - 1] hash160]]; } else if (l >= 2 && [elem[l - 2] intValue] <= OP_PUSHDATA4 && [elem[l - 2] intValue] > 0 && [elem[l - 1] intValue] <= OP_PUSHDATA4 && [elem[l - 1] intValue] > 0) { // pay-to-script-hash scriptSig v = BITCOIN_SCRIPT_ADDRESS; if (isTestnet) { v = BITCOIN_SCRIPT_ADDRESS_TEST; } [d appendBytes:&v length:1]; [d appendData:[elem[l - 1] hash160]]; } else if (l >= 1 && [elem[l - 1] intValue] <= OP_PUSHDATA4 && [elem[l - 1] intValue] > 0) {// pay-to-pubkey scriptSig //TODO: implement Peter Wullie's pubKey recovery from signature return nil; } else return nil; // unknown script type return [self base58checkWithData:d]; } - (NSString *)hexToBase58 { return [[self class] base58WithData:self.hexToData]; } - (NSString *)base58ToHex { return [NSString hexWithData:self.base58ToData]; } - (NSData *)base58checkToData { NSData *d = self.base58ToData; if (d.length < 4) return nil; NSData *data = CFBridgingRelease(CFDataCreate(SecureAllocator(), d.bytes, d.length - 4)); // verify checksum if (*(uint32_t *)((const uint8_t *)d.bytes + d.length - 4) != *(uint32_t *)data.SHA256_2.bytes) return nil; return data; } - (NSString *)hexToBase58check { return [NSString base58checkWithData:self.hexToData]; } - (NSString *)base58checkToHex { return [NSString hexWithData:self.base58checkToData]; } - (NSData *)hexToData { if (self.length % 2) return nil; NSMutableData *d = [NSMutableData secureDataWithCapacity:self.length/2]; uint8_t b = 0; for (NSUInteger i = 0; i < self.length; i++) { unichar c = [self characterAtIndex:i]; switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': b += c - '0'; break; case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': b += c + 10 - 'A'; break; case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': b += c + 10 - 'a'; break; default: return d; } if (i % 2) { [d appendBytes:&b length:1]; b = 0; } else b *= 16; } return d; } - (NSData *)addressToHash160 { NSData *d = self.base58checkToData; return (d.length == 160/8 + 1) ? [d subdataWithRange:NSMakeRange(1, d.length - 1)] : nil; } - (BOOL)isValidBitcoinAddress:(BOOL)isTestnet { NSData *d = self.base58checkToData; if (d.length != 21) return NO; uint8_t version = *(const uint8_t *)d.bytes; if (isTestnet) { return (version == BITCOIN_PUBKEY_ADDRESS_TEST || version == BITCOIN_SCRIPT_ADDRESS_TEST) ? YES : NO; } return (version == BITCOIN_PUBKEY_ADDRESS || version == BITCOIN_SCRIPT_ADDRESS) ? YES : NO; } - (BOOL)isValidBitcoinPrivateKey:(BOOL)isTestnet { NSData *d = self.base58checkToData; if (d.length == 33 || d.length == 34) { // wallet import format: https://en.bitcoin.it/wiki/Wallet_import_format if (isTestnet) { return (*(const uint8_t *)d.bytes == BITCOIN_PRIVKEY_TEST) ? YES : NO; } else { return (*(const uint8_t *)d.bytes == BITCOIN_PRIVKEY) ? YES : NO; } } else if ((self.length == 30 || self.length == 22) && [self characterAtIndex:0] == 'S') { // mini private key format NSMutableData *d = [NSMutableData secureDataWithCapacity:self.length + 1]; d.length = self.length; [self getBytes:d.mutableBytes maxLength:d.length usedLength:NULL encoding:NSUTF8StringEncoding options:0 range:NSMakeRange(0, self.length) remainingRange:NULL]; [d appendBytes:"?" length:1]; return (*(const uint8_t *)d.SHA256.bytes == 0) ? YES : NO; } else return (self.hexToData.length == 32) ? YES : NO; // hex encoded key } // BIP38 encrypted keys: https://github.com/bitcoin/bips/blob/master/bip-0038.mediawiki - (BOOL)isValidBitcoinBIP38Key { NSData *d = self.base58checkToData; if (d.length != 39) return NO; // invalid length uint16_t prefix = CFSwapInt16BigToHost(*(const uint16_t *)d.bytes); uint8_t flag = ((const uint8_t *)d.bytes)[2]; if (prefix == BIP38_NOEC_PREFIX) { // non EC multiplied key return ((flag & BIP38_NOEC_FLAG) == BIP38_NOEC_FLAG && (flag & BIP38_LOTSEQUENCE_FLAG) == 0 && (flag & BIP38_INVALID_FLAG) == 0) ? YES : NO; } else if (prefix == BIP38_EC_PREFIX) { // EC multiplied key return ((flag & BIP38_NOEC_FLAG) == 0 && (flag & BIP38_INVALID_FLAG) == 0) ? YES : NO; } else return NO; // invalid prefix } @end ================================================ FILE: ArcBit/External/BreadWalletClassesV0.5/ccMemory.h ================================================ /* * Copyright (c) 2010 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * ccMemory.h * CommonCrypto */ #ifndef CCMEMORY_H #define CCMEMORY_H #ifdef KERNEL #define CC_XMALLOC(s) OSMalloc((s), CC_OSMallocTag) #define CC_XFREE(p, s) OSFree((p), (s), CC_OSMallocTag) #else /* KERNEL */ #include #include #define CC_XMALLOC(s) malloc(s) #define CC_XCALLOC(c, s) calloc((c), (s)) #define CC_XREALLOC(p, s) realloc((p), (s)) #define CC_XFREE(p, s) free(p) #define CC_XMEMCPY(s1, s2, n) memcpy((s1), (s2), (n)) #define CC_XMEMCMP(s1, s2, n) memcmp((s1), (s2), (n)) #define CC_XMEMSET(s1, s2, n) memset((s1), (s2), (n)) #define CC_XZEROMEM(p, n) memset((p), 0, (n)) #define CC_XSTRCMP(s1, s2) strcmp((s1), (s2)) #define CC_XSTORE32H(x, y) do { \ (y)[0] = (unsigned char)(((x)>>24)&255); \ (y)[1] = (unsigned char)(((x)>>16)&255); \ (y)[2] = (unsigned char)(((x)>>8)&255); \ (y)[3] = (unsigned char)((x)&255); \ } while(0) #define CC_XSTORE64H(x, y) \ { (y)[0] = (unsigned char)(((x)>>56)&255); (y)[1] = (unsigned char)(((x)>>48)&255); \ (y)[2] = (unsigned char)(((x)>>40)&255); (y)[3] = (unsigned char)(((x)>>32)&255); \ (y)[4] = (unsigned char)(((x)>>24)&255); (y)[5] = (unsigned char)(((x)>>16)&255); \ (y)[6] = (unsigned char)(((x)>>8)&255); (y)[7] = (unsigned char)((x)&255); } #define CC_XQSORT(base, nelement, width, comparfunc) qsort((base), (nelement), (width), (comparfunc)) #define CC_XALIGNED(PTR,NBYTE) (!(((size_t)(PTR))%(NBYTE))) #define CC_XMIN(X,Y) (((X) < (Y)) ? (X): (Y)) #endif #endif /* CCMEMORY_H */ ================================================ FILE: ArcBit/External/CustomIOS7AlertView/CustomIOS7AlertView.h ================================================ // // CustomIOS7AlertView.h // CustomIOS7AlertView // // Created by Richard on 20/09/2013. // Copyright (c) 2013 Wimagguc. // // Lincesed under The MIT License (MIT) // http://opensource.org/licenses/MIT // #import @class CustomIOS7AlertView; @protocol CustomIOS7AlertViewDelegate - (void)customIOS7dialogButtonTouchUpInside:(CustomIOS7AlertView*)alertView clickedButtonAtIndex:(NSInteger)buttonIndex; @end @interface CustomIOS7AlertView : UIView @property (nonatomic, retain) UIView *parentView; // The parent view this 'dialog' is attached to @property (nonatomic, retain) UIView *dialogView; // Dialog's container view @property (nonatomic, retain) UIView *containerView; // Container within the dialog (place your ui elements here) @property (nonatomic, retain) UIView *buttonView; // Buttons on the bottom of the dialog @property (nonatomic, assign) id delegate; @property (nonatomic, retain) NSArray *buttonTitles; @property (nonatomic, assign) BOOL useMotionEffects; @property (copy) void (^onButtonTouchUpInside)(CustomIOS7AlertView *alertView, int buttonIndex) ; - (id)init; /*! DEPRECATED: Use the [CustomIOS7AlertView init] method without passing a parent view. */ - (id)initWithParentView: (UIView *)_parentView __attribute__ ((deprecated)); - (void)show; - (void)close; - (IBAction)customIOS7dialogButtonTouchUpInside:(id)sender; - (void)setOnButtonTouchUpInside:(void (^)(CustomIOS7AlertView *alertView, int buttonIndex))onButtonTouchUpInside; - (void)deviceOrientationDidChange: (NSNotification *)notification; - (void)dealloc; @end ================================================ FILE: ArcBit/External/CustomIOS7AlertView/CustomIOS7AlertView.m ================================================ // // CustomIOS7AlertView.m // CustomIOS7AlertView // // Created by Richard on 20/09/2013. // Copyright (c) 2013 Wimagguc. // // Lincesed under The MIT License (MIT) // http://opensource.org/licenses/MIT // #import "CustomIOS7AlertView.h" #import const static CGFloat kCustomIOS7AlertViewDefaultButtonHeight = 50; const static CGFloat kCustomIOS7AlertViewDefaultButtonSpacerHeight = 1; const static CGFloat kCustomIOS7AlertViewCornerRadius = 7; const static CGFloat kCustomIOS7MotionEffectExtent = 10.0; @implementation CustomIOS7AlertView CGFloat buttonHeight = 0; CGFloat buttonSpacerHeight = 0; @synthesize parentView, containerView, dialogView, buttonView, onButtonTouchUpInside; @synthesize delegate; @synthesize buttonTitles; @synthesize useMotionEffects; - (id)initWithParentView: (UIView *)_parentView { self = [self init]; if (_parentView) { self.frame = _parentView.frame; self.parentView = _parentView; } return self; } - (id)init { self = [super init]; if (self) { self.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height); delegate = self; useMotionEffects = false; buttonTitles = @[@"Close"]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deviceOrientationDidChange:) name:UIDeviceOrientationDidChangeNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; } return self; } // Create the dialog view, and animate opening the dialog - (void)show { dialogView = [self createContainerView]; dialogView.layer.shouldRasterize = YES; dialogView.layer.rasterizationScale = [[UIScreen mainScreen] scale]; self.layer.shouldRasterize = YES; self.layer.rasterizationScale = [[UIScreen mainScreen] scale]; #if (defined(__IPHONE_7_0)) if (useMotionEffects) { [self applyMotionEffects]; } #endif dialogView.layer.opacity = 0.5f; dialogView.layer.transform = CATransform3DMakeScale(1.3f, 1.3f, 1.0); self.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0]; [self addSubview:dialogView]; // Can be attached to a view or to the top most window // Attached to a view: if (parentView != NULL) { [parentView addSubview:self]; // Attached to the top most window (make sure we are using the right orientation): } else { UIInterfaceOrientation interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation]; switch (interfaceOrientation) { case UIInterfaceOrientationLandscapeLeft: self.transform = CGAffineTransformMakeRotation(M_PI * 270.0 / 180.0); break; case UIInterfaceOrientationLandscapeRight: self.transform = CGAffineTransformMakeRotation(M_PI * 90.0 / 180.0); break; case UIInterfaceOrientationPortraitUpsideDown: self.transform = CGAffineTransformMakeRotation(M_PI * 180.0 / 180.0); break; default: break; } [self setFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)]; [[[[UIApplication sharedApplication] windows] firstObject] addSubview:self]; } [UIView animateWithDuration:0.2f delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ self.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.4f]; dialogView.layer.opacity = 1.0f; dialogView.layer.transform = CATransform3DMakeScale(1, 1, 1); } completion:NULL ]; } // Button has been touched - (IBAction)customIOS7dialogButtonTouchUpInside:(id)sender { if (delegate != NULL) { [delegate customIOS7dialogButtonTouchUpInside:self clickedButtonAtIndex:[sender tag]]; } if (onButtonTouchUpInside != NULL) { onButtonTouchUpInside(self, [sender tag]); } } // Default button behaviour - (void)customIOS7dialogButtonTouchUpInside: (CustomIOS7AlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { [self close]; } // Dialog close animation then cleaning and removing the view from the parent - (void)close { CATransform3D currentTransform = dialogView.layer.transform; CGFloat startRotation = [[dialogView valueForKeyPath:@"layer.transform.rotation.z"] floatValue]; CATransform3D rotation = CATransform3DMakeRotation(-startRotation + M_PI * 270.0 / 180.0, 0.0f, 0.0f, 0.0f); dialogView.layer.transform = CATransform3DConcat(rotation, CATransform3DMakeScale(1, 1, 1)); dialogView.layer.opacity = 1.0f; [UIView animateWithDuration:0.2f delay:0.0 options:UIViewAnimationOptionTransitionNone animations:^{ self.backgroundColor = [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.0f]; dialogView.layer.transform = CATransform3DConcat(currentTransform, CATransform3DMakeScale(0.6f, 0.6f, 1.0)); dialogView.layer.opacity = 0.0f; } completion:^(BOOL finished) { for (UIView *v in [self subviews]) { [v removeFromSuperview]; } [self removeFromSuperview]; } ]; } - (void)setSubView: (UIView *)subView { containerView = subView; } // Creates the container view here: create the dialog, then add the custom content and buttons - (UIView *)createContainerView { if (containerView == NULL) { containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 300, 150)]; } CGSize screenSize = [self countScreenSize]; CGSize dialogSize = [self countDialogSize]; // For the black background [self setFrame:CGRectMake(0, 0, screenSize.width, screenSize.height)]; // This is the dialog's container; we attach the custom content and the buttons to this one UIView *dialogContainer = [[UIView alloc] initWithFrame:CGRectMake((screenSize.width - dialogSize.width) / 2, (screenSize.height - dialogSize.height) / 2, dialogSize.width, dialogSize.height)]; // First, we style the dialog to match the iOS7 UIAlertView >>> CAGradientLayer *gradient = [CAGradientLayer layer]; gradient.frame = dialogContainer.bounds; gradient.colors = [NSArray arrayWithObjects: (id)[[UIColor colorWithRed:218.0/255.0 green:218.0/255.0 blue:218.0/255.0 alpha:1.0f] CGColor], (id)[[UIColor colorWithRed:233.0/255.0 green:233.0/255.0 blue:233.0/255.0 alpha:1.0f] CGColor], (id)[[UIColor colorWithRed:218.0/255.0 green:218.0/255.0 blue:218.0/255.0 alpha:1.0f] CGColor], nil]; CGFloat cornerRadius = kCustomIOS7AlertViewCornerRadius; gradient.cornerRadius = cornerRadius; [dialogContainer.layer insertSublayer:gradient atIndex:0]; dialogContainer.layer.cornerRadius = cornerRadius; dialogContainer.layer.borderColor = [[UIColor colorWithRed:198.0/255.0 green:198.0/255.0 blue:198.0/255.0 alpha:1.0f] CGColor]; dialogContainer.layer.borderWidth = 1; dialogContainer.layer.shadowRadius = cornerRadius + 5; dialogContainer.layer.shadowOpacity = 0.1f; dialogContainer.layer.shadowOffset = CGSizeMake(0 - (cornerRadius+5)/2, 0 - (cornerRadius+5)/2); dialogContainer.layer.shadowColor = [UIColor blackColor].CGColor; dialogContainer.layer.shadowPath = [UIBezierPath bezierPathWithRoundedRect:dialogContainer.bounds cornerRadius:dialogContainer.layer.cornerRadius].CGPath; // There is a line above the button UIView *lineView = [[UIView alloc] initWithFrame:CGRectMake(0, dialogContainer.bounds.size.height - buttonHeight - buttonSpacerHeight, dialogContainer.bounds.size.width, buttonSpacerHeight)]; lineView.backgroundColor = [UIColor colorWithRed:198.0/255.0 green:198.0/255.0 blue:198.0/255.0 alpha:1.0f]; [dialogContainer addSubview:lineView]; // ^^^ // Add the custom container if there is any [dialogContainer addSubview:containerView]; // Add the buttons too [self addButtonsToView:dialogContainer]; return dialogContainer; } // Helper function: add buttons to container - (void)addButtonsToView: (UIView *)container { if (buttonTitles==NULL) { return; } CGFloat buttonWidth = container.bounds.size.width / [buttonTitles count]; for (int i=0; i<[buttonTitles count]; i++) { UIButton *closeButton = [UIButton buttonWithType:UIButtonTypeCustom]; [closeButton setFrame:CGRectMake(i * buttonWidth, container.bounds.size.height - buttonHeight, buttonWidth, buttonHeight)]; [closeButton addTarget:self action:@selector(customIOS7dialogButtonTouchUpInside:) forControlEvents:UIControlEventTouchUpInside]; [closeButton setTag:i]; [closeButton setTitle:[buttonTitles objectAtIndex:i] forState:UIControlStateNormal]; [closeButton setTitleColor:[UIColor colorWithRed:0.0f green:0.5f blue:1.0f alpha:1.0f] forState:UIControlStateNormal]; [closeButton setTitleColor:[UIColor colorWithRed:0.2f green:0.2f blue:0.2f alpha:0.5f] forState:UIControlStateHighlighted]; [closeButton.titleLabel setFont:[UIFont boldSystemFontOfSize:14.0f]]; [closeButton.layer setCornerRadius:kCustomIOS7AlertViewCornerRadius]; [container addSubview:closeButton]; } } // Helper function: count and return the dialog's size - (CGSize)countDialogSize { CGFloat dialogWidth = containerView.frame.size.width; CGFloat dialogHeight = containerView.frame.size.height + buttonHeight + buttonSpacerHeight; return CGSizeMake(dialogWidth, dialogHeight); } // Helper function: count and return the screen's size - (CGSize)countScreenSize { if (buttonTitles!=NULL && [buttonTitles count] > 0) { buttonHeight = kCustomIOS7AlertViewDefaultButtonHeight; buttonSpacerHeight = kCustomIOS7AlertViewDefaultButtonSpacerHeight; } else { buttonHeight = 0; buttonSpacerHeight = 0; } CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width; CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height; UIInterfaceOrientation interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation]; if (UIInterfaceOrientationIsLandscape(interfaceOrientation)) { CGFloat tmp = screenWidth; screenWidth = screenHeight; screenHeight = tmp; } return CGSizeMake(screenWidth, screenHeight); } #if (defined(__IPHONE_7_0)) // Add motion effects - (void)applyMotionEffects { if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_6_1) { return; } UIInterpolatingMotionEffect *horizontalEffect = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.x" type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis]; horizontalEffect.minimumRelativeValue = @(-kCustomIOS7MotionEffectExtent); horizontalEffect.maximumRelativeValue = @( kCustomIOS7MotionEffectExtent); UIInterpolatingMotionEffect *verticalEffect = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.y" type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis]; verticalEffect.minimumRelativeValue = @(-kCustomIOS7MotionEffectExtent); verticalEffect.maximumRelativeValue = @( kCustomIOS7MotionEffectExtent); UIMotionEffectGroup *motionEffectGroup = [[UIMotionEffectGroup alloc] init]; motionEffectGroup.motionEffects = @[horizontalEffect, verticalEffect]; [dialogView addMotionEffect:motionEffectGroup]; } #endif - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil]; } // Handle device orientation changes - (void)deviceOrientationDidChange: (NSNotification *)notification { // If dialog is attached to the parent view, it probably wants to handle the orientation change itself if (parentView != NULL) { return; } UIInterfaceOrientation interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation]; CGFloat startRotation = [[self valueForKeyPath:@"layer.transform.rotation.z"] floatValue]; CGAffineTransform rotation; switch (interfaceOrientation) { case UIInterfaceOrientationLandscapeLeft: rotation = CGAffineTransformMakeRotation(-startRotation + M_PI * 270.0 / 180.0); break; case UIInterfaceOrientationLandscapeRight: rotation = CGAffineTransformMakeRotation(-startRotation + M_PI * 90.0 / 180.0); break; case UIInterfaceOrientationPortraitUpsideDown: rotation = CGAffineTransformMakeRotation(-startRotation + M_PI * 180.0 / 180.0); break; default: rotation = CGAffineTransformMakeRotation(-startRotation + 0.0); break; } [UIView animateWithDuration:0.2f delay:0.0 options:UIViewAnimationOptionTransitionNone animations:^{ dialogView.transform = rotation; } completion:^(BOOL finished){ // fix errors caused by being rotated one too many times dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ UIInterfaceOrientation endInterfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation]; if (interfaceOrientation != endInterfaceOrientation) { // TODO user moved phone again before than animation ended: rotation animation can introduce errors here } }); } ]; } // Handle keyboard show/hide changes - (void)keyboardWillShow: (NSNotification *)notification { CGSize screenSize = [self countScreenSize]; CGSize dialogSize = [self countDialogSize]; CGSize keyboardSize = [[[notification userInfo] objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size; UIInterfaceOrientation interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation]; if (UIInterfaceOrientationIsLandscape(interfaceOrientation)) { CGFloat tmp = keyboardSize.height; keyboardSize.height = keyboardSize.width; keyboardSize.width = tmp; } [UIView animateWithDuration:0.2f delay:0.0 options:UIViewAnimationOptionTransitionNone animations:^{ dialogView.frame = CGRectMake((screenSize.width - dialogSize.width) / 2, (screenSize.height - keyboardSize.height - dialogSize.height) / 2, dialogSize.width, dialogSize.height); } completion:nil ]; } - (void)keyboardWillHide: (NSNotification *)notification { CGSize screenSize = [self countScreenSize]; CGSize dialogSize = [self countDialogSize]; [UIView animateWithDuration:0.2f delay:0.0 options:UIViewAnimationOptionTransitionNone animations:^{ dialogView.frame = CGRectMake((screenSize.width - dialogSize.width) / 2, (screenSize.height - dialogSize.height) / 2, dialogSize.width, dialogSize.height); } completion:nil ]; } @end ================================================ FILE: ArcBit/External/InAppSettingsKit/Controllers/IASKAppSettingsViewController.h ================================================ // // IASKAppSettingsViewController.h // http://www.inappsettingskit.com // // Copyright (c) 2009: // Luc Vandal, Edovia Inc., http://www.edovia.com // Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com // All rights reserved. // // It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz, // as the original authors of this code. You can give credit in a blog post, a tweet or on // a info page of your app. Also, the original authors appreciate letting them know if you use this code. // // This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php // #import #import #import "IASKSettingsStore.h" #import "IASKViewController.h" #import "IASKSpecifier.h" @class IASKSettingsReader; @class IASKAppSettingsViewController; @protocol IASKSettingsDelegate - (void)settingsViewControllerDidEnd:(IASKAppSettingsViewController*)sender; @optional #pragma mark - UITableView header customization - (CGFloat) settingsViewController:(id)settingsViewController tableView:(UITableView *)tableView heightForHeaderForSection:(NSInteger)section; - (UIView *) settingsViewController:(id)settingsViewController tableView:(UITableView *)tableView viewForHeaderForSection:(NSInteger)section; #pragma mark - UITableView cell customization - (CGFloat)tableView:(UITableView*)tableView heightForSpecifier:(IASKSpecifier*)specifier; - (UITableViewCell*)tableView:(UITableView*)tableView cellForSpecifier:(IASKSpecifier*)specifier; #pragma mark - mail composing customization - (NSString*) settingsViewController:(id)settingsViewController mailComposeBodyForSpecifier:(IASKSpecifier*) specifier; - (UIViewController*) settingsViewController:(id)settingsViewController viewControllerForMailComposeViewForSpecifier:(IASKSpecifier*) specifier; - (void) settingsViewController:(id) settingsViewController mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error; #pragma mark - respond to button taps - (void)settingsViewController:(IASKAppSettingsViewController*)sender buttonTappedForKey:(NSString*)key __attribute__((deprecated)); // use the method below with specifier instead - (void)settingsViewController:(IASKAppSettingsViewController*)sender buttonTappedForSpecifier:(IASKSpecifier*)specifier; - (void)settingsViewController:(IASKAppSettingsViewController*)sender tableView:(UITableView *)tableView didSelectCustomViewSpecifier:(IASKSpecifier*)specifier; @end @interface IASKAppSettingsViewController : UITableViewController @property (nonatomic, assign) IBOutlet id delegate; @property (nonatomic, copy) NSString *file; @property (nonatomic, assign) BOOL showCreditsFooter; @property (nonatomic, assign) IBInspectable BOOL showDoneButton; @property (nonatomic, retain) NSSet *hiddenKeys; @property (nonatomic) IBInspectable BOOL neverShowPrivacySettings; - (void)synchronizeSettings; - (void)dismiss:(id)sender; - (void)setHiddenKeys:(NSSet*)hiddenKeys animated:(BOOL)animated; @end ================================================ FILE: ArcBit/External/InAppSettingsKit/Controllers/IASKAppSettingsViewController.m ================================================ // // IASKAppSettingsViewController.m // http://www.inappsettingskit.com // // Copyright (c) 2009-2010: // Luc Vandal, Edovia Inc., http://www.edovia.com // Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com // All rights reserved. // // It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz, // as the original authors of this code. You can give credit in a blog post, a tweet or on // a info page of your app. Also, the original authors appreciate letting them know if you use this code. // // This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php // #import "IASKAppSettingsViewController.h" #import "IASKSettingsReader.h" #import "IASKSettingsStoreUserDefaults.h" #import "IASKPSSliderSpecifierViewCell.h" #import "IASKPSTextFieldSpecifierViewCell.h" #import "IASKSwitch.h" #import "IASKSlider.h" #import "IASKSpecifier.h" #import "IASKSpecifierValuesViewController.h" #import "IASKTextField.h" #import "IASKTextViewCell.h" #import "IASKMultipleValueSelection.h" #if !__has_feature(objc_arc) #error "IASK needs ARC" #endif static NSString *kIASKCredits = @"Powered by InAppSettingsKit"; // Leave this as-is!!! #define kIASKSpecifierValuesViewControllerIndex 0 #define kIASKSpecifierChildViewControllerIndex 1 #define kIASKCreditsViewWidth 285 CGRect IASKCGRectSwap(CGRect rect); @interface IASKAppSettingsViewController () { IASKSettingsReader *_settingsReader; id _settingsStore; id _currentFirstResponder; __weak UIViewController *_currentChildViewController; BOOL _reloadDisabled; /// The selected index for every group (in case it's a radio group). NSArray *_selections; } @property (nonatomic, strong) id currentFirstResponder; @property (nonatomic, strong) NSMutableDictionary *rowHeights; - (void)_textChanged:(id)sender; - (void)synchronizeSettings; - (void)userDefaultsDidChange; - (void)reload; @end @implementation IASKAppSettingsViewController //synthesize properties from protocol @synthesize settingsReader = _settingsReader; @synthesize settingsStore = _settingsStore; @synthesize file = _file; #pragma mark accessors - (IASKSettingsReader*)settingsReader { if (!_settingsReader) { _settingsReader = [[IASKSettingsReader alloc] initWithFile:self.file]; if (self.neverShowPrivacySettings) { _settingsReader.showPrivacySettings = NO; } } return _settingsReader; } - (id)settingsStore { if (!_settingsStore) { _settingsStore = [[IASKSettingsStoreUserDefaults alloc] init]; } return _settingsStore; } - (NSString*)file { if (!_file) { self.file = @"Root"; } return _file; } - (void)setFile:(NSString *)file { _file = [file copy]; self.tableView.contentOffset = CGPointMake(0, -self.tableView.contentInset.top); self.settingsReader = nil; // automatically initializes itself if (!_reloadDisabled) { [self.tableView reloadData]; [self createSelections]; } } - (void)createSelections { NSMutableArray *sectionSelection = [NSMutableArray new]; for (int i = 0; i < _settingsReader.numberOfSections; i++) { IASKSpecifier *specifier = [self.settingsReader headerSpecifierForSection:i]; if ([specifier.type isEqualToString:kIASKPSRadioGroupSpecifier]) { IASKMultipleValueSelection *selection = [IASKMultipleValueSelection new]; selection.tableView = self.tableView; selection.specifier = specifier; selection.section = i; selection.settingsStore = self.settingsStore; [sectionSelection addObject:selection]; } else { [sectionSelection addObject:[NSNull null]]; } } _selections = sectionSelection; } - (BOOL)isPad { BOOL isPad = NO; #if (__IPHONE_OS_VERSION_MAX_ALLOWED >= 30200) isPad = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad; #endif return isPad; } #pragma mark standard view controller methods - (id)init { return [self initWithStyle:UITableViewStyleGrouped]; } - (id)initWithStyle:(UITableViewStyle)style { if (style != UITableViewStyleGrouped) { NSLog(@"WARNING: only UITableViewStyleGrouped style is supported by InAppSettingsKit."); } if ((self = [super initWithStyle:style])) { [self configure]; } return self; } - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { if (nibNameOrNil) { NSLog (@"%@ is now deprecated, we are moving away from nibs.", NSStringFromSelector(_cmd)); self = [super initWithStyle:UITableViewStyleGrouped]; } else { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; } if (self) { [self configure]; } return self; } - (id)initWithCoder:(NSCoder *)aDecoder { if ((self = [super initWithCoder:aDecoder])) { [self configure]; _showDoneButton = NO; } return self; } - (void)configure { _reloadDisabled = NO; _showDoneButton = YES; _showCreditsFooter = YES; // display credits for InAppSettingsKit creators self.rowHeights = [NSMutableDictionary dictionary]; } - (void)viewDidLoad { [super viewDidLoad]; if ([self isPad]) { #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_6_1) // don't use etched style on iOS 7 #endif self.tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLineEtched; } UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(singleTapToEndEdit:)]; tapGesture.cancelsTouchesInView = NO; [self.tableView addGestureRecognizer:tapGesture]; } - (void)viewWillAppear:(BOOL)animated { NSIndexPath *selectedIndexPath = [self.tableView indexPathForSelectedRow]; [super viewWillAppear:animated]; // if there's something selected, the value might have changed // so reload that row if(selectedIndexPath) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(animated * UINavigationControllerHideShowBarDuration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:selectedIndexPath] withRowAnimation:UITableViewRowAnimationNone]; // and reselect it, so we get the nice default deselect animation from UITableViewController [self.tableView selectRowAtIndexPath:selectedIndexPath animated:NO scrollPosition:UITableViewScrollPositionNone]; [self.tableView deselectRowAtIndexPath:selectedIndexPath animated:YES]; }); } if (_showDoneButton) { UIBarButtonItem *buttonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(dismiss:)]; self.navigationItem.rightBarButtonItem = buttonItem; } if (!self.title) { self.title = NSLocalizedString(@"Settings", @""); } if ([self.settingsStore isKindOfClass:[IASKSettingsStoreUserDefaults class]]) { NSNotificationCenter *dc = NSNotificationCenter.defaultCenter; IASKSettingsStoreUserDefaults *udSettingsStore = (id)self.settingsStore; [dc addObserver:self selector:@selector(userDefaultsDidChange) name:NSUserDefaultsDidChangeNotification object:udSettingsStore.defaults]; [dc addObserver:self selector:@selector(didChangeSettingViaIASK:) name:kIASKAppSettingChanged object:nil]; [self userDefaultsDidChange]; // force update in case of changes while we were hidden } } - (CGSize)preferredContentSize { return [[self view] sizeThatFits:CGSizeMake(320, 2000)]; } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; NSNotificationCenter *dc = [NSNotificationCenter defaultCenter]; [dc addObserver:self selector:@selector(synchronizeSettings) name:UIApplicationDidEnterBackgroundNotification object:[UIApplication sharedApplication]]; [dc addObserver:self selector:@selector(reload) name:UIApplicationWillEnterForegroundNotification object:[UIApplication sharedApplication]]; [dc addObserver:self selector:@selector(synchronizeSettings) name:UIApplicationWillTerminateNotification object:[UIApplication sharedApplication]]; [self.tableView beginUpdates]; [self.tableView endUpdates]; } - (void)viewWillDisappear:(BOOL)animated { [NSObject cancelPreviousPerformRequestsWithTarget:self]; // hide the keyboard [self.currentFirstResponder resignFirstResponder]; [super viewWillDisappear:animated]; } - (void)viewDidDisappear:(BOOL)animated { NSNotificationCenter *dc = [NSNotificationCenter defaultCenter]; if ([self.settingsStore isKindOfClass:[IASKSettingsStoreUserDefaults class]]) { IASKSettingsStoreUserDefaults *udSettingsStore = (id)self.settingsStore; [dc removeObserver:self name:NSUserDefaultsDidChangeNotification object:udSettingsStore.defaults]; [dc removeObserver:self name:kIASKAppSettingChanged object:self]; } [dc removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:[UIApplication sharedApplication]]; [dc removeObserver:self name:UIApplicationWillEnterForegroundNotification object:[UIApplication sharedApplication]]; [dc removeObserver:self name:UIApplicationWillTerminateNotification object:[UIApplication sharedApplication]]; [super viewDidDisappear:animated]; } - (void)setHiddenKeys:(NSSet *)theHiddenKeys { [self setHiddenKeys:theHiddenKeys animated:NO]; } - (void)setHiddenKeys:(NSSet*)theHiddenKeys animated:(BOOL)animated { if (_hiddenKeys != theHiddenKeys) { NSSet *oldHiddenKeys = _hiddenKeys; _hiddenKeys = theHiddenKeys; if (animated) { NSMutableSet *showKeys = [NSMutableSet setWithSet:oldHiddenKeys]; [showKeys minusSet:theHiddenKeys]; NSMutableSet *hideKeys = [NSMutableSet setWithSet:theHiddenKeys]; [hideKeys minusSet:oldHiddenKeys]; // calculate rows to be deleted NSMutableArray *hideIndexPaths = [NSMutableArray array]; for (NSString *key in hideKeys) { NSIndexPath *indexPath = [self.settingsReader indexPathForKey:key]; if (indexPath) { [hideIndexPaths addObject:indexPath]; } } // calculate sections to be deleted NSMutableIndexSet *hideSections = [NSMutableIndexSet indexSet]; for (NSInteger section = 0; section < [self numberOfSectionsInTableView:self.tableView ]; section++) { NSInteger rowsInSection = 0; for (NSIndexPath *indexPath in hideIndexPaths) { if (indexPath.section == section) { rowsInSection++; } } if (rowsInSection && rowsInSection >= [self.settingsReader numberOfRowsForSection:section]) { [hideSections addIndex:section]; } } // set the datasource self.settingsReader.hiddenKeys = theHiddenKeys; // calculate rows to be inserted NSMutableArray *showIndexPaths = [NSMutableArray array]; for (NSString *key in showKeys) { NSIndexPath *indexPath = [self.settingsReader indexPathForKey:key]; if (indexPath) { [showIndexPaths addObject:indexPath]; } } // calculate sections to be inserted NSMutableIndexSet *showSections = [NSMutableIndexSet indexSet]; for (NSInteger section = 0; section < [self.settingsReader numberOfSections]; section++) { NSInteger rowsInSection = 0; for (NSIndexPath *indexPath in showIndexPaths) { if (indexPath.section == section) { rowsInSection++; } } if (rowsInSection && rowsInSection >= [self.settingsReader numberOfRowsForSection:section]) { [showSections addIndex:section]; } } if (hideSections.count || hideIndexPaths.count || showSections.count || showIndexPaths.count) { [self.tableView beginUpdates]; UITableViewRowAnimation animation = animated ? UITableViewRowAnimationAutomatic : UITableViewRowAnimationNone; if (hideSections.count) { [self.tableView deleteSections:hideSections withRowAnimation:animation]; } if (hideIndexPaths) { [self.tableView deleteRowsAtIndexPaths:hideIndexPaths withRowAnimation:animation]; } if (showSections.count) { [self.tableView insertSections:showSections withRowAnimation:animation]; } if (showIndexPaths) { [self.tableView insertRowsAtIndexPaths:showIndexPaths withRowAnimation:animation]; } [self.tableView endUpdates]; } } else { self.settingsReader.hiddenKeys = theHiddenKeys; if (!_reloadDisabled) [self.tableView reloadData]; } } UIViewController *childViewController = _currentChildViewController; if([childViewController respondsToSelector:@selector(setHiddenKeys:animated:)]) { [(id)childViewController setHiddenKeys:theHiddenKeys animated:animated]; } } - (void)setNeverShowPrivacySettings:(BOOL)neverShowPrivacySettings { _neverShowPrivacySettings = neverShowPrivacySettings; self.settingsReader = nil; [self reload]; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } #pragma mark - #pragma mark Actions - (void)dismiss:(id)sender { [self.settingsStore synchronize]; if (self.delegate && [self.delegate conformsToProtocol:@protocol(IASKSettingsDelegate)]) { [self.delegate settingsViewControllerDidEnd:self]; } } - (void)toggledValue:(id)sender { IASKSwitch *toggle = (IASKSwitch*)sender; IASKSpecifier *spec = [_settingsReader specifierForKey:[toggle key]]; if ([toggle isOn]) { if ([spec trueValue] != nil) { [self.settingsStore setObject:[spec trueValue] forKey:[toggle key]]; } else { [self.settingsStore setBool:YES forKey:[toggle key]]; } } else { if ([spec falseValue] != nil) { [self.settingsStore setObject:[spec falseValue] forKey:[toggle key]]; } else { [self.settingsStore setBool:NO forKey:[toggle key]]; } } [[NSNotificationCenter defaultCenter] postNotificationName:kIASKAppSettingChanged object:self userInfo:[NSDictionary dictionaryWithObject:[self.settingsStore objectForKey:[toggle key]] forKey:[toggle key]]]; } - (void)sliderChangedValue:(id)sender { IASKSlider *slider = (IASKSlider*)sender; [self.settingsStore setFloat:[slider value] forKey:[slider key]]; [[NSNotificationCenter defaultCenter] postNotificationName:kIASKAppSettingChanged object:self userInfo:[NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:[slider value]] forKey:[slider key]]]; } #pragma mark - #pragma mark UITableView Functions - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return [self.settingsReader numberOfSections]; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.settingsReader numberOfRowsForSection:section]; } - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { IASKSpecifier *specifier = [self.settingsReader specifierForIndexPath:indexPath]; if ([specifier.type isEqualToString:kIASKTextViewSpecifier]) { CGFloat height = (CGFloat)[self.rowHeights[specifier.key] doubleValue]; return height > 0 ? height : UITableViewAutomaticDimension; } else if ([[specifier type] isEqualToString:kIASKCustomViewSpecifier]) { if ([self.delegate respondsToSelector:@selector(tableView:heightForSpecifier:)]) { return [self.delegate tableView:tableView heightForSpecifier:specifier]; } else { return 0; } } IASK_IF_IOS7_OR_GREATER ( NSDictionary *rowHeights = @{UIContentSizeCategoryExtraSmall: @(44), UIContentSizeCategorySmall: @(44), UIContentSizeCategoryMedium: @(44), UIContentSizeCategoryLarge: @(44), UIContentSizeCategoryExtraLarge: @(47)}; CGFloat rowHeight = (CGFloat)[rowHeights[UIApplication.sharedApplication.preferredContentSizeCategory] doubleValue]; return rowHeight != 0 ? rowHeight : 51; ); return 44; } - (NSString *)tableView:(UITableView*)tableView titleForHeaderInSection:(NSInteger)section { NSString *header = [self.settingsReader titleForSection:section]; if (0 == header.length) { return nil; } return header; } - (UIView *)tableView:(UITableView*)tableView viewForHeaderInSection:(NSInteger)section { if ([self.delegate respondsToSelector:@selector(settingsViewController:tableView:viewForHeaderForSection:)]) { return [self.delegate settingsViewController:self tableView:tableView viewForHeaderForSection:section]; } else { return nil; } } - (CGFloat)tableView:(UITableView*)tableView heightForHeaderInSection:(NSInteger)section { if ([self tableView:tableView viewForHeaderInSection:section] && [self.delegate respondsToSelector:@selector(settingsViewController:tableView:heightForHeaderForSection:)]) { CGFloat result = [self.delegate settingsViewController:self tableView:tableView heightForHeaderForSection:section]; if (result > 0) { return result; } } return UITableViewAutomaticDimension; } - (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section { NSString *footerText = [self.settingsReader footerTextForSection:section]; if (_showCreditsFooter && (section == [self.settingsReader numberOfSections]-1)) { // show credits since this is the last section if ((footerText == nil) || ([footerText length] == 0)) { // show the credits on their own return @""; // return kIASKCredits; } else { // show the credits below the app's FooterText return @""; // return [NSString stringWithFormat:@"%@\n\n%@", footerText, kIASKCredits]; } } else { return footerText; } } - (UITableViewCell*)tableView:(UITableView *)tableView newCellForSpecifier:(IASKSpecifier*)specifier { NSString *identifier = [NSString stringWithFormat:@"%@-%ld-%d", specifier.type, (long)specifier.textAlignment, !!specifier.subtitle.length]; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier]; if (cell) { return cell; } UITableViewCellStyle style = (specifier.textAlignment == NSTextAlignmentLeft || specifier.subtitle.length) ? UITableViewCellStyleSubtitle : UITableViewCellStyleDefault; if ([identifier hasPrefix:kIASKPSToggleSwitchSpecifier]) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:kIASKPSToggleSwitchSpecifier]; cell.accessoryView = [[IASKSwitch alloc] initWithFrame:CGRectMake(0, 0, 79, 27)]; [((IASKSwitch*)cell.accessoryView) addTarget:self action:@selector(toggledValue:) forControlEvents:UIControlEventValueChanged]; cell.selectionStyle = UITableViewCellSelectionStyleNone; } else if ([identifier hasPrefix:kIASKPSMultiValueSpecifier] || [identifier hasPrefix:kIASKPSTitleValueSpecifier]) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:identifier]; cell.accessoryType = [identifier hasPrefix:kIASKPSMultiValueSpecifier] ? UITableViewCellAccessoryDisclosureIndicator : UITableViewCellAccessoryNone; } else if ([identifier hasPrefix:kIASKPSTextFieldSpecifier]) { cell = [[IASKPSTextFieldSpecifierViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kIASKPSTextFieldSpecifier]; [((IASKPSTextFieldSpecifierViewCell*)cell).textField addTarget:self action:@selector(_textChanged:) forControlEvents:UIControlEventEditingChanged]; } else if ([identifier hasPrefix:kIASKTextViewSpecifier]) { cell = [[IASKTextViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kIASKTextViewSpecifier]; } else if ([identifier hasPrefix:kIASKPSSliderSpecifier]) { cell = [[IASKPSSliderSpecifierViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kIASKPSSliderSpecifier]; } else if ([identifier hasPrefix:kIASKPSChildPaneSpecifier]) { cell = [[UITableViewCell alloc] initWithStyle:style reuseIdentifier:identifier]; cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; } else if ([identifier isEqualToString:kIASKMailComposeSpecifier]) { cell = [[UITableViewCell alloc] initWithStyle:style reuseIdentifier:identifier]; [cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator]; } else { cell = [[UITableViewCell alloc] initWithStyle:style reuseIdentifier:identifier]; if ([identifier isEqualToString:kIASKOpenURLSpecifier]) { cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; } } IASK_IF_PRE_IOS6(cell.textLabel.minimumFontSize = kIASKMinimumFontSize; cell.detailTextLabel.minimumFontSize = kIASKMinimumFontSize;); IASK_IF_IOS6_OR_GREATER(cell.textLabel.minimumScaleFactor = kIASKMinimumFontSize / cell.textLabel.font.pointSize; cell.detailTextLabel.minimumScaleFactor = kIASKMinimumFontSize / cell.detailTextLabel.font.pointSize;); return cell; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { IASKSpecifier *specifier = [self.settingsReader specifierForIndexPath:indexPath]; if ([specifier.type isEqualToString:kIASKCustomViewSpecifier] && [self.delegate respondsToSelector:@selector(tableView:cellForSpecifier:)]) { UITableViewCell* cell = [self.delegate tableView:tableView cellForSpecifier:specifier]; assert(nil != cell && "delegate must return a UITableViewCell for custom cell types"); return cell; } UITableViewCell* cell = [self tableView:tableView newCellForSpecifier:specifier]; if ([specifier.type isEqualToString:kIASKPSToggleSwitchSpecifier]) { cell.textLabel.text = specifier.title; cell.detailTextLabel.text = specifier.subtitle; id currentValue = [self.settingsStore objectForKey:specifier.key]; BOOL toggleState; if (currentValue) { if ([currentValue isEqual:specifier.trueValue]) { toggleState = YES; } else if ([currentValue isEqual:specifier.falseValue]) { toggleState = NO; } else { toggleState = [currentValue boolValue]; } } else { toggleState = specifier.defaultBoolValue; } IASKSwitch *toggle = (IASKSwitch*)cell.accessoryView; toggle.on = toggleState; toggle.key = specifier.key; } else if ([specifier.type isEqualToString:kIASKPSMultiValueSpecifier]) { cell.textLabel.text = specifier.title; cell.detailTextLabel.text = [[specifier titleForCurrentValue:[self.settingsStore objectForKey:specifier.key] != nil ? [self.settingsStore objectForKey:specifier.key] : specifier.defaultValue] description]; } else if ([specifier.type isEqualToString:kIASKPSTitleValueSpecifier]) { cell.textLabel.text = specifier.title; id value = [self.settingsStore objectForKey:specifier.key] ? : specifier.defaultValue; NSString *stringValue; if (specifier.multipleValues || specifier.multipleTitles) { stringValue = [specifier titleForCurrentValue:value]; } else { stringValue = [value description]; } cell.detailTextLabel.text = stringValue; cell.userInteractionEnabled = NO; } else if ([specifier.type isEqualToString:kIASKPSTextFieldSpecifier]) { cell.textLabel.text = specifier.title; NSString *textValue = [self.settingsStore objectForKey:specifier.key] != nil ? [self.settingsStore objectForKey:specifier.key] : specifier.defaultStringValue; if (textValue && ![textValue isMemberOfClass:[NSString class]]) { textValue = [NSString stringWithFormat:@"%@", textValue]; } IASKTextField *textField = ((IASKPSTextFieldSpecifierViewCell*)cell).textField; textField.text = textValue; textField.placeholder = specifier.placeholder; textField.key = specifier.key; textField.delegate = self; textField.secureTextEntry = [specifier isSecure]; textField.keyboardType = specifier.keyboardType; textField.autocapitalizationType = specifier.autocapitalizationType; if([specifier isSecure]){ textField.autocorrectionType = UITextAutocorrectionTypeNo; } else { textField.autocorrectionType = specifier.autoCorrectionType; } textField.textAlignment = specifier.textAlignment; textField.adjustsFontSizeToFitWidth = specifier.adjustsFontSizeToFitWidth; } else if ([specifier.type isEqualToString:kIASKTextViewSpecifier]) { IASKTextViewCell *textCell = (id)cell; NSString *value = [self.settingsStore objectForKey:specifier.key] != nil ? [self.settingsStore objectForKey:specifier.key] : specifier.defaultStringValue; textCell.textView.text = value; textCell.textView.delegate = self; textCell.textView.key = specifier.key; textCell.textView.keyboardType = specifier.keyboardType; textCell.textView.autocapitalizationType = specifier.autocapitalizationType; textCell.textView.autocorrectionType = specifier.autoCorrectionType; dispatch_async(dispatch_get_main_queue(), ^{ [self cacheRowHeightForTextView:textCell.textView]; }); } else if ([specifier.type isEqualToString:kIASKPSSliderSpecifier]) { if (specifier.minimumValueImage.length > 0) { ((IASKPSSliderSpecifierViewCell*)cell).minImage.image = [UIImage imageWithContentsOfFile:[_settingsReader pathForImageNamed:specifier.minimumValueImage]]; } if (specifier.maximumValueImage.length > 0) { ((IASKPSSliderSpecifierViewCell*)cell).maxImage.image = [UIImage imageWithContentsOfFile:[_settingsReader pathForImageNamed:specifier.maximumValueImage]]; } IASKSlider *slider = ((IASKPSSliderSpecifierViewCell*)cell).slider; slider.minimumValue = specifier.minimumValue; slider.maximumValue = specifier.maximumValue; slider.value = [self.settingsStore objectForKey:specifier.key] != nil ? [[self.settingsStore objectForKey:specifier.key] floatValue] : [specifier.defaultValue floatValue]; [slider addTarget:self action:@selector(sliderChangedValue:) forControlEvents:UIControlEventValueChanged]; slider.key = specifier.key; [cell setNeedsLayout]; } else if ([specifier.type isEqualToString:kIASKPSChildPaneSpecifier]) { cell.textLabel.text = specifier.title; cell.detailTextLabel.text = specifier.subtitle; } else if ([specifier.type isEqualToString:kIASKOpenURLSpecifier] || [specifier.type isEqualToString:kIASKMailComposeSpecifier]) { cell.textLabel.text = specifier.title; cell.detailTextLabel.text = specifier.subtitle ? : [specifier.defaultValue description]; cell.accessoryType = (specifier.textAlignment == NSTextAlignmentLeft) ? UITableViewCellAccessoryDisclosureIndicator : UITableViewCellAccessoryNone; } else if ([specifier.type isEqualToString:kIASKButtonSpecifier]) { NSString *value = [self.settingsStore objectForKey:specifier.key]; cell.textLabel.text = ([value isKindOfClass:NSString.class] && [self.settingsReader titleForId:value].length) ? [self.settingsReader titleForId:value] : specifier.title; cell.detailTextLabel.text = specifier.subtitle; IASK_IF_IOS7_OR_GREATER (if (specifier.textAlignment != NSTextAlignmentLeft) { cell.textLabel.textColor = tableView.tintColor; }); cell.textLabel.textAlignment = specifier.textAlignment; cell.accessoryType = (specifier.textAlignment == NSTextAlignmentLeft) ? UITableViewCellAccessoryDisclosureIndicator : UITableViewCellAccessoryNone; } else if ([specifier.type isEqualToString:kIASKPSRadioGroupSpecifier]) { NSInteger index = [specifier.multipleValues indexOfObject:specifier.radioGroupValue]; cell.textLabel.text = [self.settingsReader titleForId:specifier.multipleTitles[index]]; [_selections[indexPath.section] updateSelectionInCell:cell indexPath:indexPath]; } else { cell.textLabel.text = specifier.title; } cell.imageView.image = specifier.cellImage; cell.imageView.highlightedImage = specifier.highlightedCellImage; if (![specifier.type isEqualToString:kIASKPSMultiValueSpecifier] && ![specifier.type isEqualToString:kIASKPSTitleValueSpecifier] && ![specifier.type isEqualToString:kIASKPSTextFieldSpecifier] && ![specifier.type isEqualToString:kIASKTextViewSpecifier]) { cell.textLabel.textAlignment = specifier.textAlignment; } cell.detailTextLabel.textAlignment = specifier.textAlignment; cell.textLabel.adjustsFontSizeToFitWidth = specifier.adjustsFontSizeToFitWidth; cell.detailTextLabel.adjustsFontSizeToFitWidth = specifier.adjustsFontSizeToFitWidth; return cell; } - (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath { //create a set of specifier types that can't be selected static NSSet* noSelectionTypes = nil; if(nil == noSelectionTypes) { noSelectionTypes = [NSSet setWithObjects:kIASKPSToggleSwitchSpecifier, kIASKPSSliderSpecifier, nil]; } IASKSpecifier *specifier = [self.settingsReader specifierForIndexPath:indexPath]; if([noSelectionTypes containsObject:specifier.type]) { return nil; } else { return indexPath; } } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { IASKSpecifier *specifier = [self.settingsReader specifierForIndexPath:indexPath]; //switches and sliders can't be selected (should be captured by tableView:willSelectRowAtIndexPath: delegate method) assert(![[specifier type] isEqualToString:kIASKPSToggleSwitchSpecifier]); assert(![[specifier type] isEqualToString:kIASKPSSliderSpecifier]); if ([[specifier type] isEqualToString:kIASKPSMultiValueSpecifier]) { IASKSpecifierValuesViewController *targetViewController = [[IASKSpecifierValuesViewController alloc] init]; [targetViewController setCurrentSpecifier:specifier]; targetViewController.settingsReader = self.settingsReader; targetViewController.settingsStore = self.settingsStore; IASK_IF_IOS7_OR_GREATER(targetViewController.view.tintColor = self.view.tintColor;) _currentChildViewController = targetViewController; [[self navigationController] pushViewController:targetViewController animated:YES]; } else if ([[specifier type] isEqualToString:kIASKPSTextFieldSpecifier]) { IASKPSTextFieldSpecifierViewCell *textFieldCell = (id)[tableView cellForRowAtIndexPath:indexPath]; [textFieldCell.textField becomeFirstResponder]; } else if ([[specifier type] isEqualToString:kIASKPSChildPaneSpecifier]) { if ([specifier viewControllerStoryBoardID]){ NSString *storyBoardFileFromSpecifier = [specifier viewControllerStoryBoardFile]; storyBoardFileFromSpecifier = storyBoardFileFromSpecifier && storyBoardFileFromSpecifier.length > 0 ? storyBoardFileFromSpecifier : [[NSBundle mainBundle].infoDictionary objectForKey:@"UIMainStoryboardFile"]; UIStoryboard *storyBoard = [UIStoryboard storyboardWithName:storyBoardFileFromSpecifier bundle:nil]; UIViewController * vc = [storyBoard instantiateViewControllerWithIdentifier:[specifier viewControllerStoryBoardID]]; IASK_IF_IOS7_OR_GREATER(vc.view.tintColor = self.view.tintColor;) [self.navigationController pushViewController:vc animated:YES]; return; } Class vcClass = [specifier viewControllerClass]; if (vcClass) { if (vcClass == [NSNull class]) { NSLog(@"class '%@' not found", [specifier localizedObjectForKey:kIASKViewControllerClass]); [tableView deselectRowAtIndexPath:indexPath animated:YES]; return; } SEL initSelector = [specifier viewControllerSelector]; if (!initSelector) { initSelector = @selector(init); } UIViewController * vc = [vcClass alloc]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" vc = [vc performSelector:initSelector withObject:[specifier file] withObject:specifier]; #pragma clang diagnostic pop if ([vc respondsToSelector:@selector(setDelegate:)]) { [vc performSelector:@selector(setDelegate:) withObject:self.delegate]; } if ([vc respondsToSelector:@selector(setSettingsStore:)]) { [vc performSelector:@selector(setSettingsStore:) withObject:self.settingsStore]; } IASK_IF_IOS7_OR_GREATER(vc.view.tintColor = self.view.tintColor;) [self.navigationController pushViewController:vc animated:YES]; return; } NSString *segueIdentifier = [specifier segueIdentifier]; if (segueIdentifier) { @try { [self performSegueWithIdentifier:segueIdentifier sender:self]; } @catch (NSException *exception) { NSLog(@"segue with identifier '%@' not defined", segueIdentifier); [tableView deselectRowAtIndexPath:indexPath animated:YES]; } return; } if (nil == [specifier file]) { [tableView deselectRowAtIndexPath:indexPath animated:YES]; return; } _reloadDisabled = YES; // Disable internal unnecessary reloads IASKAppSettingsViewController *targetViewController = [[[self class] alloc] init]; targetViewController.showDoneButton = NO; targetViewController.showCreditsFooter = NO; // Does not reload the tableview (but next setters do it) targetViewController.delegate = self.delegate; targetViewController.settingsStore = self.settingsStore; targetViewController.file = specifier.file; targetViewController.hiddenKeys = self.hiddenKeys; targetViewController.title = specifier.title; IASK_IF_IOS7_OR_GREATER(targetViewController.view.tintColor = self.view.tintColor;) _currentChildViewController = targetViewController; _reloadDisabled = NO; [[self navigationController] pushViewController:targetViewController animated:YES]; } else if ([[specifier type] isEqualToString:kIASKOpenURLSpecifier]) { [tableView deselectRowAtIndexPath:indexPath animated:YES]; [[UIApplication sharedApplication] openURL:[NSURL URLWithString:[specifier localizedObjectForKey:kIASKFile]]]; } else if ([[specifier type] isEqualToString:kIASKButtonSpecifier]) { [tableView deselectRowAtIndexPath:indexPath animated:YES]; if ([self.delegate respondsToSelector:@selector(settingsViewController:buttonTappedForSpecifier:)]) { [self.delegate settingsViewController:self buttonTappedForSpecifier:specifier]; } else if ([self.delegate respondsToSelector:@selector(settingsViewController:buttonTappedForKey:)]) { // deprecated, provided for backward compatibility NSLog(@"InAppSettingsKit Warning: -settingsViewController:buttonTappedForKey: is deprecated. Please use -settingsViewController:buttonTappedForSpecifier:"); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" [self.delegate settingsViewController:self buttonTappedForKey:[specifier key]]; #pragma clang diagnostic pop } else { // legacy code, provided for backward compatibility // the delegate mechanism above is much cleaner and doesn't leak Class buttonClass = [specifier buttonClass]; SEL buttonAction = [specifier buttonAction]; if ([buttonClass respondsToSelector:buttonAction]) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" [buttonClass performSelector:buttonAction withObject:self withObject:[specifier key]]; #pragma clang diagnostic pop NSLog(@"InAppSettingsKit Warning: Using IASKButtonSpecifier without implementing the delegate method is deprecated"); } } } else if ([[specifier type] isEqualToString:kIASKMailComposeSpecifier]) { [tableView deselectRowAtIndexPath:indexPath animated:YES]; if ([MFMailComposeViewController canSendMail]) { MFMailComposeViewController *mailViewController = [[MFMailComposeViewController alloc] init]; mailViewController.navigationBar.barStyle = self.navigationController.navigationBar.barStyle; IASK_IF_IOS7_OR_GREATER(mailViewController.navigationBar.tintColor = self.navigationController.navigationBar.tintColor;); mailViewController.navigationBar.titleTextAttributes = self.navigationController.navigationBar.titleTextAttributes; if ([specifier localizedObjectForKey:kIASKMailComposeSubject]) { [mailViewController setSubject:[specifier localizedObjectForKey:kIASKMailComposeSubject]]; } if ([[specifier specifierDict] objectForKey:kIASKMailComposeToRecipents]) { [mailViewController setToRecipients:[[specifier specifierDict] objectForKey:kIASKMailComposeToRecipents]]; } if ([[specifier specifierDict] objectForKey:kIASKMailComposeCcRecipents]) { [mailViewController setCcRecipients:[[specifier specifierDict] objectForKey:kIASKMailComposeCcRecipents]]; } if ([[specifier specifierDict] objectForKey:kIASKMailComposeBccRecipents]) { [mailViewController setBccRecipients:[[specifier specifierDict] objectForKey:kIASKMailComposeBccRecipents]]; } if ([specifier localizedObjectForKey:kIASKMailComposeBody]) { BOOL isHTML = NO; if ([[specifier specifierDict] objectForKey:kIASKMailComposeBodyIsHTML]) { isHTML = [[[specifier specifierDict] objectForKey:kIASKMailComposeBodyIsHTML] boolValue]; } if ([self.delegate respondsToSelector:@selector(settingsViewController:mailComposeBodyForSpecifier:)]) { [mailViewController setMessageBody:[self.delegate settingsViewController:self mailComposeBodyForSpecifier:specifier] isHTML:isHTML]; } else { [mailViewController setMessageBody:[specifier localizedObjectForKey:kIASKMailComposeBody] isHTML:isHTML]; } } UIViewController *vc = nil; if ([self.delegate respondsToSelector:@selector(settingsViewController:viewControllerForMailComposeViewForSpecifier:)]) { vc = [self.delegate settingsViewController:self viewControllerForMailComposeViewForSpecifier:specifier]; } if (vc == nil) { vc = self; } mailViewController.mailComposeDelegate = vc; _currentChildViewController = mailViewController; UIStatusBarStyle savedStatusBarStyle = [UIApplication sharedApplication].statusBarStyle; [vc presentViewController:mailViewController animated:YES completion:^{ [UIApplication sharedApplication].statusBarStyle = savedStatusBarStyle; }]; } else { IASK_IF_PRE_IOS8 ( UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Mail not configured", @"InAppSettingsKit") message:NSLocalizedString(@"This device is not configured for sending Email. Please configure the Mail settings in the Settings app.", @"InAppSettingsKit") delegate: nil cancelButtonTitle:NSLocalizedString(@"OK", @"InAppSettingsKit") otherButtonTitles:nil]; [alert show]; ) IASK_IF_IOS8_OR_GREATER ( UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Mail not configured", @"InAppSettingsKit") message:NSLocalizedString(@"This device is not configured for sending Email. Please configure the Mail settings in the Settings app.", @"InAppSettingsKit") preferredStyle:UIAlertControllerStyleAlert]; [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"InAppSettingsKit") style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) {}]]; [self presentViewController:alert animated:YES completion:nil]; ) } } else if ([[specifier type] isEqualToString:kIASKCustomViewSpecifier] && [self.delegate respondsToSelector:@selector(settingsViewController:tableView:didSelectCustomViewSpecifier:)]) { [self.delegate settingsViewController:self tableView:tableView didSelectCustomViewSpecifier:specifier]; } else if ([[specifier type] isEqualToString:kIASKPSRadioGroupSpecifier]) { [_selections[indexPath.section] selectRowAtIndexPath:indexPath]; } else { [tableView deselectRowAtIndexPath:indexPath animated:NO]; } } #pragma mark - #pragma mark MFMailComposeViewControllerDelegate Function -(void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error { // Forward the mail compose delegate if ([self.delegate respondsToSelector:@selector(settingsViewController:mailComposeController:didFinishWithResult:error:)]) { [self.delegate settingsViewController:self mailComposeController:controller didFinishWithResult:result error:error]; } [self dismissViewControllerAnimated:YES completion:nil]; } #pragma mark - #pragma mark UITextFieldDelegate Functions - (void)textFieldDidBeginEditing:(UITextField *)textField { self.currentFirstResponder = textField; } - (void)_textChanged:(id)sender { IASKTextField *text = sender; [_settingsStore setObject:[text text] forKey:[text key]]; [[NSNotificationCenter defaultCenter] postNotificationName:kIASKAppSettingChanged object:self userInfo:[NSDictionary dictionaryWithObject:[text text] forKey:[text key]]]; } - (BOOL)textFieldShouldReturn:(UITextField *)textField{ [textField resignFirstResponder]; self.currentFirstResponder = nil; return YES; } - (void)singleTapToEndEdit:(UIGestureRecognizer *)sender { [self.tableView endEditing:NO]; } #pragma mark - UITextViewDelegate - (void)textViewDidEndEditing:(UITextView *)textView { self.currentFirstResponder = textView; } - (void)textViewDidChange:(IASKTextView *)textView { [self cacheRowHeightForTextView:textView]; CGRect visibleTableRect = UIEdgeInsetsInsetRect(self.tableView.bounds, self.tableView.contentInset); NSIndexPath *indexPath = [self.settingsReader indexPathForKey:textView.key]; CGRect cellFrame = [self.tableView rectForRowAtIndexPath:indexPath]; if (!CGRectContainsRect(visibleTableRect, cellFrame)) { [self.tableView scrollRectToVisible:CGRectInset(cellFrame, 0, - 30) animated:YES]; } [_settingsStore setObject:textView.text forKey:textView.key]; [[NSNotificationCenter defaultCenter] postNotificationName:kIASKAppSettingChanged object:textView.key userInfo:@{textView.key: textView.text}]; } - (void)cacheRowHeightForTextView:(IASKTextView *)textView { CGFloat maxHeight = self.tableView.bounds.size.height - self.tableView.contentInset.top - self.tableView.contentInset.bottom - 60; CGFloat contentHeight = [textView sizeThatFits:CGSizeMake(textView.frame.size.width, 10000)].height + 16; self.rowHeights[textView.key] = @(MAX(44, MIN(maxHeight, contentHeight))); textView.scrollEnabled = contentHeight > maxHeight; [self.tableView beginUpdates]; [self.tableView endUpdates]; } #pragma mark Notifications - (void)synchronizeSettings { [_settingsStore synchronize]; } static NSDictionary *oldUserDefaults = nil; - (void)userDefaultsDidChange { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ IASKSettingsStoreUserDefaults *udSettingsStore = (id)self.settingsStore; NSDictionary *currentDict = udSettingsStore.defaults.dictionaryRepresentation; NSMutableArray *indexPathsToUpdate = [NSMutableArray array]; for (NSString *key in currentDict.allKeys) { if (oldUserDefaults && ![[oldUserDefaults valueForKey:key] isEqual:[currentDict valueForKey:key]]) { NSIndexPath *path = [self.settingsReader indexPathForKey:key]; if (path && ![[self.settingsReader specifierForKey:key].type isEqualToString:kIASKCustomViewSpecifier] && [self.tableView.indexPathsForVisibleRows containsObject:path]) { [indexPathsToUpdate addObject:path]; } } } oldUserDefaults = currentDict; for (UITableViewCell *cell in self.tableView.visibleCells) { if ([cell isKindOfClass:[IASKPSTextFieldSpecifierViewCell class]] && [((IASKPSTextFieldSpecifierViewCell*)cell).textField isFirstResponder]) { [indexPathsToUpdate removeObject:[self.tableView indexPathForCell:cell]]; } } if (indexPathsToUpdate.count) { [self.tableView reloadRowsAtIndexPaths:indexPathsToUpdate withRowAnimation:UITableViewRowAnimationAutomatic]; } }); } - (void)didChangeSettingViaIASK:(NSNotification*)notification { NSString *key = notification.userInfo.allKeys.firstObject; [oldUserDefaults setValue:notification.userInfo[key] forKey:key]; } - (void)reload { // wait 0.5 sec until UI is available after applicationWillEnterForeground [self.tableView performSelector:@selector(reloadData) withObject:nil afterDelay:0.5]; } #pragma mark CGRect Utility function CGRect IASKCGRectSwap(CGRect rect) { CGRect newRect; newRect.origin.x = rect.origin.y; newRect.origin.y = rect.origin.x; newRect.size.width = rect.size.height; newRect.size.height = rect.size.width; return newRect; } @end ================================================ FILE: ArcBit/External/InAppSettingsKit/Controllers/IASKAppSettingsWebViewController.h ================================================ // // IASKAppSettingsWebViewController.h // http://www.inappsettingskit.com // // Copyright (c) 2009: // Luc Vandal, Edovia Inc., http://www.edovia.com // Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com // All rights reserved. // // It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz, // as the original authors of this code. You can give credit in a blog post, a tweet or on // a info page of your app. Also, the original authors appreciate letting them know if you use this code. // // This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php // #import #import #import "IASKSpecifier.h" @interface IASKAppSettingsWebViewController : UIViewController - (id)initWithFile:(NSString*)htmlFileName specifier:(IASKSpecifier*)specifier; @property (nonatomic, strong) UIWebView *webView; @property (nonatomic, strong) NSURL *url; @property (nonatomic, strong) NSString *customTitle; @end ================================================ FILE: ArcBit/External/InAppSettingsKit/Controllers/IASKAppSettingsWebViewController.m ================================================ // // IASKAppSettingsWebViewController.h // http://www.inappsettingskit.com // // Copyright (c) 2010: // Luc Vandal, Edovia Inc., http://www.edovia.com // Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com // All rights reserved. // // It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz, // as the original authors of this code. You can give credit in a blog post, a tweet or on // a info page of your app. Also, the original authors appreciate letting them know if you use this code. // // This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php // #import "IASKAppSettingsWebViewController.h" #import "IASKSettingsReader.h" @implementation IASKAppSettingsWebViewController - (id)initWithFile:(NSString*)urlString specifier:(IASKSpecifier*)specifier { self = [super init]; if (self) { self.url = [NSURL URLWithString:urlString]; if (!self.url || ![self.url scheme]) { NSString *path = [[NSBundle mainBundle] pathForResource:[urlString stringByDeletingPathExtension] ofType:[urlString pathExtension]]; if(path) self.url = [NSURL fileURLWithPath:path]; else self.url = nil; } self.customTitle = [specifier localizedObjectForKey:kIASKChildTitle]; self.title = self.customTitle ? : specifier.title; } return self; } - (void)loadView { self.webView = [[UIWebView alloc] init]; self.webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; self.webView.delegate = self; self.view = self.webView; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; UIActivityIndicatorView *activityIndicatorView = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(0, 0, 40, 20)]; activityIndicatorView.activityIndicatorViewStyle = UIActivityIndicatorViewStyleWhite; [activityIndicatorView startAnimating]; self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:activityIndicatorView]; [self.webView loadRequest:[NSURLRequest requestWithURL:self.url]]; } - (void)webViewDidFinishLoad:(UIWebView *)webView { self.navigationItem.rightBarButtonItem = nil; self.title = self.customTitle.length ? self.customTitle : [self.webView stringByEvaluatingJavaScriptFromString:@"document.title"]; } - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { NSURL *newURL = [request URL]; // intercept mailto URL and send it to an in-app Mail compose view instead if ([[newURL scheme] isEqualToString:@"mailto"]) { NSArray *rawURLparts = [[newURL resourceSpecifier] componentsSeparatedByString:@"?"]; if (rawURLparts.count > 2) { return NO; // invalid URL } MFMailComposeViewController *mailViewController = [[MFMailComposeViewController alloc] init]; mailViewController.mailComposeDelegate = self; NSMutableArray *toRecipients = [NSMutableArray array]; NSString *defaultRecipient = [rawURLparts objectAtIndex:0]; if (defaultRecipient.length) { [toRecipients addObject:defaultRecipient]; } if (rawURLparts.count == 2) { NSString *queryString = [rawURLparts objectAtIndex:1]; NSArray *params = [queryString componentsSeparatedByString:@"&"]; for (NSString *param in params) { NSArray *keyValue = [param componentsSeparatedByString:@"="]; if (keyValue.count != 2) { continue; } NSString *key = [[keyValue objectAtIndex:0] lowercaseString]; NSString *value = [keyValue objectAtIndex:1]; value = CFBridgingRelease(CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault, (CFStringRef)value, CFSTR(""), kCFStringEncodingUTF8)); if ([key isEqualToString:@"subject"]) { [mailViewController setSubject:value]; } if ([key isEqualToString:@"body"]) { [mailViewController setMessageBody:value isHTML:NO]; } if ([key isEqualToString:@"to"]) { [toRecipients addObjectsFromArray:[value componentsSeparatedByString:@","]]; } if ([key isEqualToString:@"cc"]) { NSArray *recipients = [value componentsSeparatedByString:@","]; [mailViewController setCcRecipients:recipients]; } if ([key isEqualToString:@"bcc"]) { NSArray *recipients = [value componentsSeparatedByString:@","]; [mailViewController setBccRecipients:recipients]; } } } [mailViewController setToRecipients:toRecipients]; mailViewController.navigationBar.barStyle = self.navigationController.navigationBar.barStyle; IASK_IF_IOS7_OR_GREATER(mailViewController.navigationBar.tintColor = self.navigationController.navigationBar.tintColor;); mailViewController.navigationBar.titleTextAttributes = self.navigationController.navigationBar.titleTextAttributes; UIStatusBarStyle savedStatusBarStyle = [UIApplication sharedApplication].statusBarStyle; [self presentViewController:mailViewController animated:YES completion:^{ [UIApplication sharedApplication].statusBarStyle = savedStatusBarStyle; }]; return NO; } // open inline if host is the same, otherwise, pass to the system if (![newURL host] || [[newURL host] isEqualToString:[self.url host]]) { return YES; } [[UIApplication sharedApplication] openURL:newURL]; return NO; } - (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error { [self dismissViewControllerAnimated:YES completion:nil]; } @end ================================================ FILE: ArcBit/External/InAppSettingsKit/Controllers/IASKMultipleValueSelection.h ================================================ #import @class IASKSpecifier; @protocol IASKSettingsStore; /// Encapsulates the selection among multiple values. /// This is used for PSMultiValueSpecifier and PSRadioGroupSpecifier @interface IASKMultipleValueSelection : NSObject @property (nonatomic, assign) UITableView *tableView; @property (nonatomic, retain) IASKSpecifier *specifier; @property (nonatomic, assign) NSInteger section; @property (nonatomic, copy, readonly) NSIndexPath *checkedItem; @property (nonatomic, strong) id settingsStore; - (void)selectRowAtIndexPath:(NSIndexPath *)indexPath; - (void)updateSelectionInCell:(UITableViewCell *)cell indexPath:(NSIndexPath *)indexPath; @end ================================================ FILE: ArcBit/External/InAppSettingsKit/Controllers/IASKMultipleValueSelection.m ================================================ #import "IASKMultipleValueSelection.h" #import "IASKSettingsStore.h" #import "IASKSettingsStoreUserDefaults.h" #import "IASKSpecifier.h" #import "IASKSettingsReader.h" @implementation IASKMultipleValueSelection { NSInteger _checkedIndex; } @synthesize settingsStore = _settingsStore; - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self name:NSUserDefaultsDidChangeNotification object:nil]; } - (void)setSpecifier:(IASKSpecifier *)specifier { _specifier = specifier; [self updateCheckedItem]; } - (NSIndexPath *)checkedItem { return [NSIndexPath indexPathForRow:_checkedIndex inSection:_section];; } - (void)updateCheckedItem { // Find the currently checked item id value = [self.settingsStore objectForKey:[_specifier key]]; if (!value) { value = [_specifier defaultValue]; } _checkedIndex = [[_specifier multipleValues] indexOfObject:value]; } - (id)settingsStore { if (_settingsStore == nil) { self.settingsStore = [[IASKSettingsStoreUserDefaults alloc] init]; } return _settingsStore; } - (void)setSettingsStore:(id)settingsStore { if ([_settingsStore isKindOfClass:IASKSettingsStoreUserDefaults.class]) { IASKSettingsStoreUserDefaults *udSettingsStore = (id)_settingsStore; [[NSNotificationCenter defaultCenter] removeObserver:self name:NSUserDefaultsDidChangeNotification object:udSettingsStore.defaults]; } _settingsStore = settingsStore; if ([settingsStore isKindOfClass:IASKSettingsStoreUserDefaults.class]) { IASKSettingsStoreUserDefaults *udSettingsStore = (id)settingsStore; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(userDefaultsDidChange) name:NSUserDefaultsDidChangeNotification object:udSettingsStore.defaults]; } } #pragma mark - selection - (void)selectRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath == self.checkedItem) { [self.tableView deselectRowAtIndexPath:indexPath animated:YES]; return; } NSArray *values = [_specifier multipleValues]; [self.tableView deselectRowAtIndexPath:indexPath animated:YES]; [self deselectCell:[self.tableView cellForRowAtIndexPath:self.checkedItem]]; [self selectCell:[self.tableView cellForRowAtIndexPath:indexPath]]; _checkedIndex = indexPath.row; [self.settingsStore setObject:[values objectAtIndex:indexPath.row] forKey:[_specifier key]]; [self.settingsStore synchronize]; [[NSNotificationCenter defaultCenter] postNotificationName:kIASKAppSettingChanged object:self userInfo:@{ _specifier.key: values[indexPath.row] }]; }; - (void)updateSelectionInCell:(UITableViewCell *)cell indexPath:(NSIndexPath *)indexPath { if ([indexPath isEqual:self.checkedItem]) { [self selectCell:cell]; } else { [self deselectCell:cell]; } } - (void)selectCell:(UITableViewCell *)cell { [cell setAccessoryType:UITableViewCellAccessoryCheckmark]; IASK_IF_PRE_IOS7([[cell textLabel] setTextColor:kIASKgrayBlueColor];); } - (void)deselectCell:(UITableViewCell *)cell { [cell setAccessoryType:UITableViewCellAccessoryNone]; IASK_IF_PRE_IOS7([[cell textLabel] setTextColor:[UIColor darkTextColor]];); } #pragma mark Notifications - (void)userDefaultsDidChange { NSIndexPath *oldCheckedItem = self.checkedItem; if (_specifier) { [self updateCheckedItem]; } // only reload the table if it had changed; prevents animation cancellation if (![self.checkedItem isEqual:oldCheckedItem]) { [self.tableView reloadData]; } } @end ================================================ FILE: ArcBit/External/InAppSettingsKit/Controllers/IASKSpecifierValuesViewController.h ================================================ // // IASKSpecifierValuesViewController.h // http://www.inappsettingskit.com // // Copyright (c) 2009: // Luc Vandal, Edovia Inc., http://www.edovia.com // Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com // All rights reserved. // // It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz, // as the original authors of this code. You can give credit in a blog post, a tweet or on // a info page of your app. Also, the original authors appreciate letting them know if you use this code. // // This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php // #import #import "IASKSettingsStore.h" #import "IASKViewController.h" @class IASKSpecifier; @class IASKSettingsReader; @interface IASKSpecifierValuesViewController : UIViewController { UITableView *_tableView; IASKSpecifier *_currentSpecifier; IASKSettingsReader *_settingsReader; } @property (nonatomic, retain) UITableView *tableView; @property (nonatomic, retain) IASKSpecifier *currentSpecifier; @end ================================================ FILE: ArcBit/External/InAppSettingsKit/Controllers/IASKSpecifierValuesViewController.m ================================================ // // IASKSpecifierValuesViewController.m // http://www.inappsettingskit.com // // Copyright (c) 2009: // Luc Vandal, Edovia Inc., http://www.edovia.com // Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com // All rights reserved. // // It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz, // as the original authors of this code. You can give credit in a blog post, a tweet or on // a info page of your app. Also, the original authors appreciate letting them know if you use this code. // // This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php // #import "IASKSpecifierValuesViewController.h" #import "IASKSpecifier.h" #import "IASKSettingsReader.h" #import "IASKMultipleValueSelection.h" #define kCellValue @"kCellValue" @interface IASKSpecifierValuesViewController() @property (nonatomic, strong, readonly) IASKMultipleValueSelection *selection; @property (nonatomic) BOOL didFirstLayout; @end @implementation IASKSpecifierValuesViewController @synthesize tableView=_tableView; @synthesize currentSpecifier=_currentSpecifier; @synthesize settingsReader = _settingsReader; @synthesize settingsStore = _settingsStore; - (void)setSettingsStore:(id )settingsStore { _settingsStore = settingsStore; _selection.settingsStore = settingsStore; } - (void)loadView { _tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped]; _tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; _tableView.delegate = self; _tableView.dataSource = self; self.view = _tableView; _selection = [IASKMultipleValueSelection new]; _selection.tableView = _tableView; _selection.settingsStore = _settingsStore; } - (void)viewWillAppear:(BOOL)animated { if (_currentSpecifier) { [self setTitle:[_currentSpecifier title]]; _selection.specifier = _currentSpecifier; } if (_tableView) { [_tableView reloadData]; _selection.tableView = _tableView; } self.didFirstLayout = NO; [super viewWillAppear:animated]; } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; [_tableView flashScrollIndicators]; } - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; if (!self.didFirstLayout) { // Make sure the currently checked item is visible // this needs to be done as early as possible when pushing the view but after the first layout // otherwise scrolling to the first entry doesn't respect tableView.contentInset [_tableView scrollToRowAtIndexPath:_selection.checkedItem atScrollPosition:UITableViewScrollPositionMiddle animated:NO]; self.didFirstLayout = YES; } } - (void)viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; _selection.tableView = nil; } - (void)didReceiveMemoryWarning { // Releases the view if it doesn't have a superview. [super didReceiveMemoryWarning]; // Release any cached data, images, etc that aren't in use. } #pragma mark - #pragma mark UITableView delegates - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [_currentSpecifier multipleValuesCount]; } - (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section { return [_currentSpecifier footerText]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellValue]; NSArray *titles = [_currentSpecifier multipleTitles]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kCellValue]; } [_selection updateSelectionInCell:cell indexPath:indexPath]; @try { [[cell textLabel] setText:[self.settingsReader titleForId:[titles objectAtIndex:indexPath.row]]]; } @catch (NSException * e) {} return cell; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { [_selection selectRowAtIndexPath:indexPath]; } - (CGSize)preferredContentSize { return [[self view] sizeThatFits:CGSizeMake(320, 2000)]; } @end ================================================ FILE: ArcBit/External/InAppSettingsKit/Controllers/IASKViewController.h ================================================ // // IASKAppSettingsViewController.h // http://www.inappsettingskit.com // // Copyright (c) 2009: // Luc Vandal, Edovia Inc., http://www.edovia.com // Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com // All rights reserved. // // It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz, // as the original authors of this code. You can give credit in a blog post, a tweet or on // a info page of your app. Also, the original authors appreciate letting them know if you use this code. // // This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php // @class IASKSettingsReader; @protocol IASKSettingsStore; // protocol all IASK view controllers implement @protocol IASKViewController @property (nonatomic, retain) IASKSettingsReader* settingsReader; @property (nonatomic, retain) id settingsStore; @end ================================================ FILE: ArcBit/External/InAppSettingsKit/Models/IASKSettingsReader.h ================================================ // // IASKSettingsReader.h // http://www.inappsettingskit.com // // Copyright (c) 2009: // Luc Vandal, Edovia Inc., http://www.edovia.com // Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com // All rights reserved. // // It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz, // as the original authors of this code. You can give credit in a blog post, a tweet or on // a info page of your app. Also, the original authors appreciate letting them know if you use this code. // // This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php // #import #import #define kIASKPreferenceSpecifiers @"PreferenceSpecifiers" #define kIASKCellImage @"IASKCellImage" #define kIASKType @"Type" #define kIASKTitle @"Title" #define kIASKFooterText @"FooterText" #define kIASKKey @"Key" #define kIASKFile @"File" #define kIASKDefaultValue @"DefaultValue" #define kIASKDisplaySortedByTitle @"DisplaySortedByTitle" #define kIASKMinimumValue @"MinimumValue" #define kIASKMaximumValue @"MaximumValue" #define kIASKTrueValue @"TrueValue" #define kIASKFalseValue @"FalseValue" #define kIASKIsSecure @"IsSecure" #define KIASKKeyboardType @"KeyboardType" #define kIASKAutocapitalizationType @"AutocapitalizationType" #define kIASKAutoCorrectionType @"AutocorrectionType" #define kIASKValues @"Values" #define kIASKTitles @"Titles" #define kIASKShortTitles @"ShortTitles" #define kIASKSupportedUserInterfaceIdioms @"SupportedUserInterfaceIdioms" #define kIASKSubtitle @"IASKSubtitle" #define kIASKPlaceholder @"IASKPlaceholder" #define kIASKViewControllerClass @"IASKViewControllerClass" #define kIASKViewControllerSelector @"IASKViewControllerSelector" #define kIASKViewControllerStoryBoardFile @"IASKViewControllerStoryBoardFile" #define kIASKViewControllerStoryBoardId @"IASKViewControllerStoryBoardId" #define kIASKSegueIdentifier @"IASKSegueIdentifier" #define kIASKButtonClass @"IASKButtonClass" #define kIASKButtonAction @"IASKButtonAction" #define kIASKMailComposeToRecipents @"IASKMailComposeToRecipents" #define kIASKMailComposeCcRecipents @"IASKMailComposeCcRecipents" #define kIASKMailComposeBccRecipents @"IASKMailComposeBccRecipents" #define kIASKMailComposeSubject @"IASKMailComposeSubject" #define kIASKMailComposeBody @"IASKMailComposeBody" #define kIASKMailComposeBodyIsHTML @"IASKMailComposeBodyIsHTML" #define kIASKKeyboardAlphabet @"Alphabet" #define kIASKKeyboardNumbersAndPunctuation @"NumbersAndPunctuation" #define kIASKKeyboardNumberPad @"NumberPad" #define kIASKKeyboardDecimalPad @"DecimalPad" #define kIASKKeyboardPhonePad @"PhonePad" #define kIASKKeyboardNamePhonePad @"NamePhonePad" #define kIASKKeyboardASCIICapable @"AsciiCapable" #define KIASKKeyboardURL @"URL" #define kIASKKeyboardEmailAddress @"EmailAddress" #define kIASKAutoCapNone @"None" #define kIASKAutoCapSentences @"Sentences" #define kIASKAutoCapWords @"Words" #define kIASKAutoCapAllCharacters @"AllCharacters" #define kIASKAutoCorrDefault @"Default" #define kIASKAutoCorrNo @"No" #define kIASKAutoCorrYes @"Yes" #define kIASKMinimumValueImage @"MinimumValueImage" #define kIASKMaximumValueImage @"MaximumValueImage" #define kIASKAdjustsFontSizeToFitWidth @"IASKAdjustsFontSizeToFitWidth" #define kIASKTextLabelAlignment @"IASKTextAlignment" #define kIASKTextLabelAlignmentLeft @"IASKUITextAlignmentLeft" #define kIASKTextLabelAlignmentCenter @"IASKUITextAlignmentCenter" #define kIASKTextLabelAlignmentRight @"IASKUITextAlignmentRight" #define kIASKPSGroupSpecifier @"PSGroupSpecifier" #define kIASKPSToggleSwitchSpecifier @"PSToggleSwitchSpecifier" #define kIASKPSMultiValueSpecifier @"PSMultiValueSpecifier" #define kIASKPSRadioGroupSpecifier @"PSRadioGroupSpecifier" #define kIASKPSSliderSpecifier @"PSSliderSpecifier" #define kIASKPSTitleValueSpecifier @"PSTitleValueSpecifier" #define kIASKPSTextFieldSpecifier @"PSTextFieldSpecifier" #define kIASKPSChildPaneSpecifier @"PSChildPaneSpecifier" #define kIASKTextViewSpecifier @"IASKTextViewSpecifier" #define kIASKOpenURLSpecifier @"IASKOpenURLSpecifier" #define kIASKButtonSpecifier @"IASKButtonSpecifier" #define kIASKMailComposeSpecifier @"IASKMailComposeSpecifier" #define kIASKCustomViewSpecifier @"IASKCustomViewSpecifier" // IASKChildTitle can be set if IASKViewControllerClass is set to IASKAppSettingsWebViewController. // If IASKChildTitle is set, the navigation title is fixed to it; otherwise, the title value is used and is overridden by the HTML title tag // as soon as the web page is loaded; if IASKChildTitle is set to the empty string, the title is not shown on push but _will_ be replaced by // the HTML title as soon as the page is loaded. The value of IASKChildTitle is localizable. #define kIASKChildTitle @"IASKChildTitle" #define kIASKAppSettingChanged @"kAppSettingChanged" #define kIASKSectionHeaderIndex 0 #define kIASKSliderImageGap 10 #define kIASKSpacing 8 #define kIASKMinLabelWidth 97 #define kIASKMaxLabelWidth 240 #define kIASKMinValueWidth 35 #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 #define kIASKPaddingLeft (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_6_1 ? 15 : 9) #else #define kIASKPaddingLeft 9 #endif #define kIASKPaddingRight 10 #define kIASKHorizontalPaddingGroupTitles 19 #define kIASKVerticalPaddingGroupTitles 15 #define kIASKLabelFontSize 17 #define kIASKgrayBlueColor [UIColor colorWithRed:0.318f green:0.4f blue:0.569f alpha:1.f] #define kIASKMinimumFontSize 12.0f #ifndef kCFCoreFoundationVersionNumber_iOS_7_0 #define kCFCoreFoundationVersionNumber_iOS_7_0 843.00 #endif #ifndef kCFCoreFoundationVersionNumber_iOS_8_0 #define kCFCoreFoundationVersionNumber_iOS_8_0 1129.150000 #endif #ifdef __IPHONE_6_0 #define IASK_IF_IOS6_OR_GREATER(...) \ if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_6_0) \ { \ __VA_ARGS__ \ } #else #define IASK_IF_IOS6_OR_GREATER(...) #endif #ifdef __IPHONE_6_0 #define IASK_IF_PRE_IOS6(...) \ _Pragma("clang diagnostic push") \ _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") \ if (kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_6_0) \ { \ __VA_ARGS__ \ } \ _Pragma("clang diagnostic pop") #else #define IASK_IF_PRE_IOS6(...) __VA_ARGS__ #endif #ifdef __IPHONE_7_0 #define IASK_IF_IOS7_OR_GREATER(...) \ if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_7_0) \ { \ __VA_ARGS__ \ } #else #define IASK_IF_IOS7_OR_GREATER(...) #endif #ifdef __IPHONE_7_0 #define IASK_IF_PRE_IOS7(...) \ _Pragma("clang diagnostic push") \ _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") \ if (kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_7_0) \ { \ __VA_ARGS__ \ } \ _Pragma("clang diagnostic pop") #else #define IASK_IF_PRE_IOS7(...) __VA_ARGS__ #endif #ifdef __IPHONE_8_0 #define IASK_IF_PRE_IOS8(...) \ _Pragma("clang diagnostic push") \ _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") \ if (kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_8_0) \ { \ __VA_ARGS__ \ } \ _Pragma("clang diagnostic pop") #else #define IASK_IF_PRE_IOS8(...) __VA_ARGS__ #endif #ifdef __IPHONE_8_0 #define IASK_IF_IOS8_OR_GREATER(...) \ if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0) \ { \ __VA_ARGS__ \ } #else #define IASK_IF_IOS8_OR_GREATER(...) #endif @class IASKSpecifier; /** settings reader transform iOS's settings plist files to the IASKSpecifier model objects. Besides that, it also hides the complexity of finding the 'proper' Settings.bundle */ @interface IASKSettingsReader : NSObject /** designated initializer searches for a settings bundle that contains a plist with the specified fileName that must be contained in the given bundle calls initWithFile where applicationBundle is set to [NSBundle mainBundle] */ - (id) initWithSettingsFileNamed:(NSString*) fileName applicationBundle:(NSBundle*) bundle; - (id) initWithFile:(NSString*)file; - (NSInteger)numberOfSections; - (NSInteger)numberOfRowsForSection:(NSInteger)section; - (IASKSpecifier*)specifierForIndexPath:(NSIndexPath*)indexPath; - (IASKSpecifier *)headerSpecifierForSection:(NSInteger)section; - (NSIndexPath*)indexPathForKey:(NSString*)key; - (IASKSpecifier*)specifierForKey:(NSString*)key; - (NSString*)titleForSection:(NSInteger)section; - (NSString*)keyForSection:(NSInteger)section; - (NSString*)footerTextForSection:(NSInteger)section; - (NSString*)titleForId:(NSObject*)titleId; - (NSString*)pathForImageNamed:(NSString*)image; ///the main application bundle. most often [NSBundle mainBundle] @property (nonatomic, readonly) NSBundle *applicationBundle; ///the actual settings bundle @property (nonatomic, readonly) NSBundle *settingsBundle; ///the actual settings plist, parsed into a dictionary @property (nonatomic, readonly) NSDictionary *settingsDictionary; @property (nonatomic, retain) NSString *localizationTable; @property (nonatomic, retain) NSArray *dataSource; @property (nonatomic, retain) NSSet *hiddenKeys; @property (nonatomic) BOOL showPrivacySettings; #pragma mark - internal use. public only for testing - (NSString *)file:(NSString *)file withBundle:(NSString *)bundle suffix:(NSString *)suffix extension:(NSString *)extension; - (NSString *)locateSettingsFile:(NSString *)file; - (NSString *)platformSuffixForInterfaceIdiom:(UIUserInterfaceIdiom) interfaceIdiom; @end ================================================ FILE: ArcBit/External/InAppSettingsKit/Models/IASKSettingsReader.m ================================================ // // IASKSettingsReader.m // http://www.inappsettingskit.com // // Copyright (c) 2009: // Luc Vandal, Edovia Inc., http://www.edovia.com // Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com // All rights reserved. // // It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz, // as the original authors of this code. You can give credit in a blog post, a tweet or on // a info page of your app. Also, the original authors appreciate letting them know if you use this code. // // This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php // #import "IASKSettingsReader.h" #import "IASKSpecifier.h" #pragma mark - @interface IASKSettingsReader () { } @end @implementation IASKSettingsReader - (id) initWithSettingsFileNamed:(NSString*) fileName applicationBundle:(NSBundle*) bundle { self = [super init]; if (self) { _applicationBundle = bundle; NSString* plistFilePath = [self locateSettingsFile: fileName]; _settingsDictionary = [NSDictionary dictionaryWithContentsOfFile:plistFilePath]; //store the bundle which we'll need later for getting localizations NSString* settingsBundlePath = [plistFilePath stringByDeletingLastPathComponent]; _settingsBundle = [NSBundle bundleWithPath:settingsBundlePath]; // Look for localization file self.localizationTable = [_settingsDictionary objectForKey:@"StringsTable"]; if (!self.localizationTable) { // Look for localization file using filename self.localizationTable = [[[[plistFilePath stringByDeletingPathExtension] // removes '.plist' stringByDeletingPathExtension] // removes potential '.inApp' lastPathComponent] // strip absolute path stringByReplacingOccurrencesOfString:[self platformSuffixForInterfaceIdiom:UI_USER_INTERFACE_IDIOM()] withString:@""]; // removes potential '~device' (~ipad, ~iphone) if([self.settingsBundle pathForResource:self.localizationTable ofType:@"strings"] == nil){ // Could not find the specified localization: use default self.localizationTable = @"Root"; } } self.showPrivacySettings = NO; IASK_IF_IOS8_OR_GREATER ( NSArray *privacyRelatedInfoPlistKeys = @[@"NSBluetoothPeripheralUsageDescription", @"NSCalendarsUsageDescription", @"NSCameraUsageDescription", @"NSContactsUsageDescription", @"NSLocationAlwaysUsageDescription", @"NSLocationUsageDescription", @"NSLocationWhenInUseUsageDescription", @"NSMicrophoneUsageDescription", @"NSMotionUsageDescription", @"NSPhotoLibraryUsageDescription", @"NSRemindersUsageDescription", @"NSHealthShareUsageDescription", @"NSHealthUpdateUsageDescription"]; NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary]; if ([fileName isEqualToString:@"Root"]) { for (NSString* key in privacyRelatedInfoPlistKeys) { if (infoDictionary[key]) { self.showPrivacySettings = YES; break; } } } ); if (self.settingsDictionary) { [self _reinterpretBundle:self.settingsDictionary]; } } return self; } - (id)initWithFile:(NSString*)file { return [self initWithSettingsFileNamed:file applicationBundle:[NSBundle mainBundle]]; } - (id)init { return [self initWithFile:@"Root"]; } - (void)setHiddenKeys:(NSSet *)anHiddenKeys { if (_hiddenKeys != anHiddenKeys) { _hiddenKeys = anHiddenKeys; if (self.settingsDictionary) { [self _reinterpretBundle:self.settingsDictionary]; } } } - (void)setShowPrivacySettings:(BOOL)showPrivacySettings { if (_showPrivacySettings != showPrivacySettings) { _showPrivacySettings = showPrivacySettings; [self _reinterpretBundle:self.settingsDictionary]; } } - (NSArray*)privacySettingsSpecifiers { NSMutableDictionary *dict = [@{kIASKTitle: [[self getBundle] localizedStringForKey:@"Privacy" value:@"" table:@"IASKLocalizable"], kIASKKey: @"IASKPrivacySettingsCellKey", kIASKType: kIASKOpenURLSpecifier, kIASKFile: UIApplicationOpenSettingsURLString, } mutableCopy]; NSString *subtitle = [[self getBundle] localizedStringForKey:@"Open in Settings app" value:@"" table:@"IASKLocalizable"]; if (subtitle.length) { dict [kIASKSubtitle] = subtitle; } return @[@[[[IASKSpecifier alloc] initWithSpecifier:@{kIASKKey: @"IASKPrivacySettingsHeaderKey", kIASKType: kIASKPSGroupSpecifier}], [[IASKSpecifier alloc] initWithSpecifier:dict]]]; } - (NSBundle*)getBundle { NSURL *inAppSettingsBundlePath = [[NSBundle bundleForClass:[self class]] URLForResource:@"InAppSettingsKit" withExtension:@"bundle"]; NSBundle *bundle; if (inAppSettingsBundlePath) { bundle = [NSBundle bundleWithURL:inAppSettingsBundlePath]; } else { bundle = [NSBundle mainBundle]; } return bundle; } - (void)_reinterpretBundle:(NSDictionary*)settingsBundle { NSArray *preferenceSpecifiers = [settingsBundle objectForKey:kIASKPreferenceSpecifiers]; NSMutableArray *dataSource = [NSMutableArray array]; if (self.showPrivacySettings) { IASK_IF_IOS8_OR_GREATER ( [dataSource addObjectsFromArray:self.privacySettingsSpecifiers]; ); } for (NSDictionary *specifierDictionary in preferenceSpecifiers) { IASKSpecifier *newSpecifier = [[IASKSpecifier alloc] initWithSpecifier:specifierDictionary]; newSpecifier.settingsReader = self; [newSpecifier sortIfNeeded]; if ([self.hiddenKeys containsObject:newSpecifier.key]) { continue; } NSString *type = newSpecifier.type; if ([type isEqualToString:kIASKPSGroupSpecifier] || [type isEqualToString:kIASKPSRadioGroupSpecifier]) { NSMutableArray *newArray = [NSMutableArray array]; [newArray addObject:newSpecifier]; [dataSource addObject:newArray]; if ([type isEqualToString:kIASKPSRadioGroupSpecifier]) { for (NSString *value in newSpecifier.multipleValues) { IASKSpecifier *valueSpecifier = [[IASKSpecifier alloc] initWithSpecifier:specifierDictionary radioGroupValue:value]; valueSpecifier.settingsReader = self; [valueSpecifier sortIfNeeded]; [newArray addObject:valueSpecifier]; } } } else { if (dataSource.count == 0 || (dataSource.count == 1 && self.showPrivacySettings)) { [dataSource addObject:[NSMutableArray array]]; } if ([newSpecifier.userInterfaceIdioms containsObject:@(UI_USER_INTERFACE_IDIOM())]) { [(NSMutableArray*)dataSource.lastObject addObject:newSpecifier]; } } } [self setDataSource:dataSource]; } - (BOOL)_sectionHasHeading:(NSInteger)section { return [self headerSpecifierForSection:section] != nil; } /// Returns the specifier describing the section's header, or nil if there is no header. - (IASKSpecifier *)headerSpecifierForSection:(NSInteger)section { IASKSpecifier *specifier = self.dataSource[section][kIASKSectionHeaderIndex]; if ([specifier.type isEqualToString:kIASKPSGroupSpecifier] || [specifier.type isEqualToString:kIASKPSRadioGroupSpecifier]) { return specifier; } return nil; } - (NSInteger)numberOfSections { return self.dataSource.count; } - (NSInteger)numberOfRowsForSection:(NSInteger)section { int headingCorrection = [self _sectionHasHeading:section] ? 1 : 0; return [(NSArray*)[[self dataSource] objectAtIndex:section] count] - headingCorrection; } - (IASKSpecifier*)specifierForIndexPath:(NSIndexPath*)indexPath { int headingCorrection = [self _sectionHasHeading:indexPath.section] ? 1 : 0; IASKSpecifier *specifier = [[[self dataSource] objectAtIndex:indexPath.section] objectAtIndex:(indexPath.row+headingCorrection)]; specifier.settingsReader = self; return specifier; } - (NSIndexPath*)indexPathForKey:(NSString *)key { for (NSUInteger sectionIndex = 0; sectionIndex < self.dataSource.count; sectionIndex++) { NSArray *section = [self.dataSource objectAtIndex:sectionIndex]; for (NSUInteger rowIndex = 0; rowIndex < section.count; rowIndex++) { IASKSpecifier *specifier = (IASKSpecifier*)[section objectAtIndex:rowIndex]; if ([specifier isKindOfClass:[IASKSpecifier class]] && [specifier.key isEqualToString:key]) { NSUInteger correctedRowIndex = rowIndex - [self _sectionHasHeading:sectionIndex]; return [NSIndexPath indexPathForRow:correctedRowIndex inSection:sectionIndex]; } } } return nil; } - (IASKSpecifier*)specifierForKey:(NSString*)key { for (NSArray *specifiers in _dataSource) { for (id sp in specifiers) { if ([sp isKindOfClass:[IASKSpecifier class]]) { if ([[sp key] isEqualToString:key]) { return sp; } } } } return nil; } - (NSString*)titleForSection:(NSInteger)section { return [self titleForId:[self headerSpecifierForSection:section].title]; } - (NSString*)keyForSection:(NSInteger)section { return [self headerSpecifierForSection:section].key; } - (NSString*)footerTextForSection:(NSInteger)section { return [self titleForId:[self headerSpecifierForSection:section].footerText]; } - (NSString*)titleForId:(NSObject*)titleId { if([titleId isKindOfClass:[NSNumber class]]) { NSNumber* numberTitleId = (NSNumber*)titleId; NSNumberFormatter* formatter = [NSNumberFormatter new]; [formatter setNumberStyle:NSNumberFormatterNoStyle]; return [formatter stringFromNumber:numberTitleId]; } else { NSString* stringTitleId = (NSString*)titleId; return [self.settingsBundle localizedStringForKey:stringTitleId value:stringTitleId table:self.localizationTable]; } } - (NSString*)pathForImageNamed:(NSString*)image { return [[self.settingsBundle bundlePath] stringByAppendingPathComponent:image]; } - (NSString *)platformSuffixForInterfaceIdiom:(UIUserInterfaceIdiom) interfaceIdiom { switch (interfaceIdiom) { case UIUserInterfaceIdiomPad: return @"~ipad"; case UIUserInterfaceIdiomPhone: return @"~iphone"; default: return @"~iphone"; } } - (NSString *)file:(NSString *)file withBundle:(NSString *)bundle suffix:(NSString *)suffix extension:(NSString *)extension { bundle = [self.applicationBundle pathForResource:bundle ofType:nil]; file = [file stringByAppendingFormat:@"%@%@", suffix, extension]; return [bundle stringByAppendingPathComponent:file]; } - (NSString *)locateSettingsFile: (NSString *)file { static NSString* const kIASKBundleFolder = @"Settings.bundle"; static NSString* const kIASKBundleFolderAlt = @"InAppSettings.bundle"; static NSString* const kIASKBundleLocaleFolderExtension = @".lproj"; // The file is searched in the following order: // // InAppSettings.bundle/FILE~DEVICE.inApp.plist // InAppSettings.bundle/FILE.inApp.plist // InAppSettings.bundle/FILE~DEVICE.plist // InAppSettings.bundle/FILE.plist // Settings.bundle/FILE~DEVICE.inApp.plist // Settings.bundle/FILE.inApp.plist // Settings.bundle/FILE~DEVICE.plist // Settings.bundle/FILE.plist // // where DEVICE is either "iphone" or "ipad" depending on the current // interface idiom. // // Settings.app uses the ~DEVICE suffixes since iOS 4.0. There are some // differences from this implementation: // - For an iPhone-only app running on iPad, Settings.app will not use the // ~iphone suffix. There is no point in using these suffixes outside // of universal apps anyway. // - This implementation uses the device suffixes on iOS 3.x as well. // - also check current locale (short only) NSArray *settingsBundleNames = @[kIASKBundleFolderAlt, kIASKBundleFolder]; NSArray *extensions = @[@".inApp.plist", @".plist"]; NSArray *plattformSuffixes = @[[self platformSuffixForInterfaceIdiom:UI_USER_INTERFACE_IDIOM()], @""]; NSArray *preferredLanguages = [NSLocale preferredLanguages]; NSArray *languageFolders = @[[ (preferredLanguages.count ? [preferredLanguages objectAtIndex:0] : @"en") stringByAppendingString:kIASKBundleLocaleFolderExtension], @""]; NSString *path = nil; NSFileManager *fileManager = [NSFileManager defaultManager]; for (NSString *settingsBundleName in settingsBundleNames) { for (NSString *extension in extensions) { for (NSString *platformSuffix in plattformSuffixes) { for (NSString *languageFolder in languageFolders) { path = [self file:file withBundle:[settingsBundleName stringByAppendingPathComponent:languageFolder] suffix:platformSuffix extension:extension]; if ([fileManager fileExistsAtPath:path]) { goto exitFromNestedLoop; } } } } } exitFromNestedLoop: return path; } @end ================================================ FILE: ArcBit/External/InAppSettingsKit/Models/IASKSettingsStore.h ================================================ // // IASKSettingsStore.h // http://www.inappsettingskit.com // // Copyright (c) 2010: // Luc Vandal, Edovia Inc., http://www.edovia.com // Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com // Marc-Etienne M.Léveillé, Edovia Inc., http://www.edovia.com // All rights reserved. // // It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz, // as the original authors of this code. You can give credit in a blog post, a tweet or on // a info page of your app. Also, the original authors appreciate letting them know if you use this code. // // This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php // #import /** protocol that needs to be implemented from a settings store */ @protocol IASKSettingsStore @required - (void)setBool:(BOOL)value forKey:(NSString*)key; - (void)setFloat:(float)value forKey:(NSString*)key; - (void)setDouble:(double)value forKey:(NSString*)key; - (void)setInteger:(NSInteger)value forKey:(NSString*)key; - (void)setObject:(id)value forKey:(NSString*)key; - (BOOL)boolForKey:(NSString*)key; - (float)floatForKey:(NSString*)key; - (double)doubleForKey:(NSString*)key; - (NSInteger)integerForKey:(NSString*)key; - (id)objectForKey:(NSString*)key; - (BOOL)synchronize; // Write settings to a permanant storage. Returns YES on success, NO otherwise @end /** abstract default implementation of IASKSettingsStore protocol helper to implement a store which maps all methods to setObject:forKey: and objectForKey:. Those 2 methods need to be overwritten. */ @interface IASKAbstractSettingsStore : NSObject /** default implementation raises an exception must be overridden by subclasses */ - (void)setObject:(id)value forKey:(NSString*)key; /** default implementation raises an exception must be overridden by subclasses */ - (id)objectForKey:(NSString*)key; /** default implementation does nothing and returns NO */ - (BOOL)synchronize; @end ================================================ FILE: ArcBit/External/InAppSettingsKit/Models/IASKSettingsStore.m ================================================ // // IASKSettingsStore.m // http://www.inappsettingskit.com // // Copyright (c) 2010: // Luc Vandal, Edovia Inc., http://www.edovia.com // Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com // Marc-Etienne M.Léveillé, Edovia Inc., http://www.edovia.com // All rights reserved. // // It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz, // as the original authors of this code. You can give credit in a blog post, a tweet or on // a info page of your app. Also, the original authors appreciate letting them know if you use this code. // // This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php // #import "IASKSettingsStore.h" @implementation IASKAbstractSettingsStore - (void)setObject:(id)value forKey:(NSString*)key { [NSException raise:@"Unimplemented" format:@"setObject:forKey: must be implemented in subclasses of IASKAbstractSettingsStore"]; } - (id)objectForKey:(NSString*)key { [NSException raise:@"Unimplemented" format:@"objectForKey: must be implemented in subclasses of IASKAbstractSettingsStore"]; return nil; } - (void)setBool:(BOOL)value forKey:(NSString*)key { [self setObject:[NSNumber numberWithBool:value] forKey:key]; } - (void)setFloat:(float)value forKey:(NSString*)key { [self setObject:[NSNumber numberWithFloat:value] forKey:key]; } - (void)setInteger:(NSInteger)value forKey:(NSString*)key { [self setObject:[NSNumber numberWithInteger:value] forKey:key]; } - (void)setDouble:(double)value forKey:(NSString*)key { [self setObject:[NSNumber numberWithDouble:value] forKey:key]; } - (BOOL)boolForKey:(NSString*)key { return [[self objectForKey:key] boolValue]; } - (float)floatForKey:(NSString*)key { return [[self objectForKey:key] floatValue]; } - (NSInteger)integerForKey:(NSString*)key { return [[self objectForKey:key] integerValue]; } - (double)doubleForKey:(NSString*)key { return [[self objectForKey:key] doubleValue]; } - (BOOL)synchronize { return NO; } @end ================================================ FILE: ArcBit/External/InAppSettingsKit/Models/IASKSettingsStoreFile.h ================================================ // // IASKSettingsStoreFile.h // http://www.inappsettingskit.com // // Copyright (c) 2010: // Luc Vandal, Edovia Inc., http://www.edovia.com // Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com // Marc-Etienne M.Léveillé, Edovia Inc., http://www.edovia.com // All rights reserved. // // It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz, // as the original authors of this code. You can give credit in a blog post, a tweet or on // a info page of your app. Also, the original authors appreciate letting them know if you use this code. // // This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php // #import #import "IASKSettingsStore.h" /** file-based implementation of IASKAbstractSettingsStore uses an NSDictionary + its read/write to file support to store settings in a file at the specified path */ @interface IASKSettingsStoreFile : IASKAbstractSettingsStore /** designated initializer @param path absolute file-path to store settings dictionary */ - (id)initWithPath:(NSString*)path; @property (nonatomic, copy, readonly) NSString* filePath; @end ================================================ FILE: ArcBit/External/InAppSettingsKit/Models/IASKSettingsStoreFile.m ================================================ // // IASKSettingsStoreFile.m // http://www.inappsettingskit.com // // Copyright (c) 2010: // Luc Vandal, Edovia Inc., http://www.edovia.com // Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com // Marc-Etienne M.Léveillé, Edovia Inc., http://www.edovia.com // All rights reserved. // // It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz, // as the original authors of this code. You can give credit in a blog post, a tweet or on // a info page of your app. Also, the original authors appreciate letting them know if you use this code. // // This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php // #import "IASKSettingsStoreFile.h" @interface IASKSettingsStoreFile() { NSMutableDictionary * _dict; } @end @implementation IASKSettingsStoreFile - (id)initWithPath:(NSString*)path { if((self = [super init])) { _filePath = [path copy]; _dict = [[NSMutableDictionary alloc] initWithContentsOfFile:path]; if(_dict == nil) { _dict = [[NSMutableDictionary alloc] init]; } } return self; } - (void)setObject:(id)value forKey:(NSString *)key { [_dict setObject:value forKey:key]; } - (id)objectForKey:(NSString *)key { return [_dict objectForKey:key]; } - (BOOL)synchronize { return [_dict writeToFile:_filePath atomically:YES]; } @end ================================================ FILE: ArcBit/External/InAppSettingsKit/Models/IASKSettingsStoreUserDefaults.h ================================================ // // IASKSettingsStoreUserDefaults.h // http://www.inappsettingskit.com // // Copyright (c) 2010: // Luc Vandal, Edovia Inc., http://www.edovia.com // Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com // Marc-Etienne M.Léveillé, Edovia Inc., http://www.edovia.com // All rights reserved. // // It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz, // as the original authors of this code. You can give credit in a blog post, a tweet or on // a info page of your app. Also, the original authors appreciate letting them know if you use this code. // // This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php // #import #import "IASKSettingsStore.h" /** implementation of IASKSettingsStore that uses NSUserDefaults */ @interface IASKSettingsStoreUserDefaults : NSObject ///designated initializer - (id) initWithUserDefaults:(NSUserDefaults*) defaults; ///calls initWithUserDefaults: with [NSUserDefaults standardUserDefaults] - (id) init; @property (nonatomic, retain, readonly) NSUserDefaults* defaults; @end ================================================ FILE: ArcBit/External/InAppSettingsKit/Models/IASKSettingsStoreUserDefaults.m ================================================ // // IASKSettingsStoreUserDefaults.m // http://www.inappsettingskit.com // // Copyright (c) 2010: // Luc Vandal, Edovia Inc., http://www.edovia.com // Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com // Marc-Etienne M.Léveillé, Edovia Inc., http://www.edovia.com // All rights reserved. // // It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz, // as the original authors of this code. You can give credit in a blog post, a tweet or on // a info page of your app. Also, the original authors appreciate letting them know if you use this code. // // This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php // #import "IASKSettingsStoreUserDefaults.h" @interface IASKSettingsStoreUserDefaults () @property (nonatomic, retain, readwrite) NSUserDefaults* defaults; @end @implementation IASKSettingsStoreUserDefaults - (id)initWithUserDefaults:(NSUserDefaults *)defaults { self = [super init]; if( self ) { _defaults = defaults; } return self; } - (id)init { return [self initWithUserDefaults:[NSUserDefaults standardUserDefaults]]; } - (void)setBool:(BOOL)value forKey:(NSString*)key { [self.defaults setBool:value forKey:key]; } - (void)setFloat:(float)value forKey:(NSString*)key { [self.defaults setFloat:value forKey:key]; } - (void)setDouble:(double)value forKey:(NSString*)key { [self.defaults setDouble:value forKey:key]; } - (void)setInteger:(NSInteger)value forKey:(NSString*)key { [self.defaults setInteger:value forKey:key]; } - (void)setObject:(id)value forKey:(NSString*)key { [self.defaults setObject:value forKey:key]; } - (BOOL)boolForKey:(NSString*)key { return [self.defaults boolForKey:key]; } - (float)floatForKey:(NSString*)key { return [self.defaults floatForKey:key]; } - (double)doubleForKey:(NSString*)key { return [self.defaults doubleForKey:key]; } - (NSInteger)integerForKey:(NSString*)key { return [self.defaults integerForKey:key]; } - (id)objectForKey:(NSString*)key { return [self.defaults objectForKey:key]; } - (BOOL)synchronize { return [self.defaults synchronize]; } @end ================================================ FILE: ArcBit/External/InAppSettingsKit/Models/IASKSpecifier.h ================================================ // // IASKSpecifier.h // http://www.inappsettingskit.com // // Copyright (c) 2009: // Luc Vandal, Edovia Inc., http://www.edovia.com // Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com // All rights reserved. // // It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz, // as the original authors of this code. You can give credit in a blog post, a tweet or on // a info page of your app. Also, the original authors appreciate letting them know if you use this code. // // This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php // #import #import @class IASKSettingsReader; @interface IASKSpecifier : NSObject @property (nonatomic, retain) NSDictionary *specifierDict; @property (nonatomic, weak) IASKSettingsReader *settingsReader; - (id)initWithSpecifier:(NSDictionary*)specifier; /// A specifier for one entry in a radio group preceeded by a radio group specifier. - (id)initWithSpecifier:(NSDictionary *)specifier radioGroupValue:(NSString *)radioGroupValue; - (void)sortIfNeeded; - (NSString*)localizedObjectForKey:(NSString*)key; - (NSString*)title; - (NSString*)subtitle; - (NSString*)placeholder; - (NSString*)key; - (NSString*)type; - (NSString*)titleForCurrentValue:(id)currentValue; - (NSInteger)multipleValuesCount; - (NSArray*)multipleValues; - (NSArray*)multipleTitles; - (NSString*)file; - (id)defaultValue; - (id)defaultStringValue; - (BOOL)defaultBoolValue; - (id)trueValue; - (id)falseValue; - (float)minimumValue; - (float)maximumValue; - (NSString*)minimumValueImage; - (NSString*)maximumValueImage; - (BOOL)isSecure; - (BOOL)displaySortedByTitle; - (UIKeyboardType)keyboardType; - (UITextAutocapitalizationType)autocapitalizationType; - (UITextAutocorrectionType)autoCorrectionType; - (NSString*)footerText; - (Class)viewControllerClass; - (SEL)viewControllerSelector; - (NSString*)viewControllerStoryBoardFile; - (NSString*)viewControllerStoryBoardID; - (NSString*)segueIdentifier; - (Class)buttonClass; - (SEL)buttonAction; - (UIImage *)cellImage; - (UIImage *)highlightedCellImage; - (BOOL)adjustsFontSizeToFitWidth; - (NSTextAlignment)textAlignment; - (NSArray *)userInterfaceIdioms; - (NSString *)radioGroupValue; @end ================================================ FILE: ArcBit/External/InAppSettingsKit/Models/IASKSpecifier.m ================================================ // // IASKSpecifier.m // http://www.inappsettingskit.com // // Copyright (c) 2009: // Luc Vandal, Edovia Inc., http://www.edovia.com // Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com // All rights reserved. // // It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz, // as the original authors of this code. You can give credit in a blog post, a tweet or on // a info page of your app. Also, the original authors appreciate letting them know if you use this code. // // This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php // #import "IASKSpecifier.h" #import "IASKSettingsReader.h" #import "IASKAppSettingsWebViewController.h" @interface IASKSpecifier () @property (nonatomic, retain) NSDictionary *multipleValuesDict; @property (nonatomic, copy) NSString *radioGroupValue; @end @implementation IASKSpecifier - (id)initWithSpecifier:(NSDictionary*)specifier { if ((self = [super init])) { [self setSpecifierDict:specifier]; if ([self isMultiValueSpecifierType]) { [self updateMultiValuesDict]; } } return self; } - (BOOL)isMultiValueSpecifierType { static NSArray *types = nil; if (!types) { types = @[kIASKPSMultiValueSpecifier, kIASKPSTitleValueSpecifier, kIASKPSRadioGroupSpecifier]; } return [types containsObject:[self type]]; } - (id)initWithSpecifier:(NSDictionary *)specifier radioGroupValue:(NSString *)radioGroupValue { self = [self initWithSpecifier:specifier]; if (self) { self.radioGroupValue = radioGroupValue; } return self; } - (void)updateMultiValuesDict { NSArray *values = [_specifierDict objectForKey:kIASKValues]; NSArray *titles = [_specifierDict objectForKey:kIASKTitles]; NSArray *shortTitles = [_specifierDict objectForKey:kIASKShortTitles]; NSMutableDictionary *multipleValuesDict = [NSMutableDictionary new]; if (values) { [multipleValuesDict setObject:values forKey:kIASKValues]; } if (titles) { [multipleValuesDict setObject:titles forKey:kIASKTitles]; } if (shortTitles.count) { [multipleValuesDict setObject:shortTitles forKey:kIASKShortTitles]; } [self setMultipleValuesDict:multipleValuesDict]; } - (void)sortIfNeeded { if (self.displaySortedByTitle) { NSArray *values = [_specifierDict objectForKey:kIASKValues]; NSArray *titles = [_specifierDict objectForKey:kIASKTitles]; NSArray *shortTitles = [_specifierDict objectForKey:kIASKShortTitles]; NSAssert(values.count == titles.count, @"Malformed multi-value specifier found in settings bundle. Number of values and titles differ."); NSAssert(shortTitles == nil || shortTitles.count == values.count, @"Malformed multi-value specifier found in settings bundle. Number of short titles and values differ."); NSMutableDictionary *multipleValuesDict = [NSMutableDictionary new]; NSMutableArray *temporaryMappingsForSort = [NSMutableArray arrayWithCapacity:titles.count]; static NSString *const titleKey = @"title"; static NSString *const shortTitleKey = @"shortTitle"; static NSString *const localizedTitleKey = @"localizedTitle"; static NSString *const valueKey = @"value"; IASKSettingsReader *strongSettingsReader = self.settingsReader; [titles enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { NSString *localizedTitle = [strongSettingsReader titleForId:obj]; [temporaryMappingsForSort addObject:@{titleKey : obj, valueKey : values[idx], localizedTitleKey : localizedTitle, shortTitleKey : (shortTitles[idx] ?: [NSNull null]), }]; }]; NSArray *sortedTemporaryMappings = [temporaryMappingsForSort sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) { NSString *localizedTitle1 = obj1[localizedTitleKey]; NSString *localizedTitle2 = obj2[localizedTitleKey]; if ([localizedTitle1 isKindOfClass:[NSString class]] && [localizedTitle2 isKindOfClass:[NSString class]]) { return [localizedTitle1 localizedCompare:localizedTitle2]; } else { return NSOrderedSame; } }]; NSMutableArray *sortedTitles = [NSMutableArray arrayWithCapacity:sortedTemporaryMappings.count]; NSMutableArray *sortedShortTitles = [NSMutableArray arrayWithCapacity:sortedTemporaryMappings.count]; NSMutableArray *sortedValues = [NSMutableArray arrayWithCapacity:sortedTemporaryMappings.count]; [sortedTemporaryMappings enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { NSDictionary *mapping = obj; sortedTitles[idx] = mapping[titleKey]; sortedValues[idx] = mapping[valueKey]; if (mapping[shortTitleKey] != [NSNull null]) { sortedShortTitles[idx] = mapping[shortTitleKey]; } }]; titles = [sortedTitles copy]; values = [sortedValues copy]; shortTitles = [sortedShortTitles copy]; if (values) { [multipleValuesDict setObject:values forKey:kIASKValues]; } if (titles) { [multipleValuesDict setObject:titles forKey:kIASKTitles]; } if (shortTitles.count) { [multipleValuesDict setObject:shortTitles forKey:kIASKShortTitles]; } [self setMultipleValuesDict:multipleValuesDict]; } } - (BOOL)displaySortedByTitle { return [[_specifierDict objectForKey:kIASKDisplaySortedByTitle] boolValue]; } - (NSString*)localizedObjectForKey:(NSString*)key { IASKSettingsReader *settingsReader = self.settingsReader; return [settingsReader titleForId:[_specifierDict objectForKey:key]]; } - (NSString*)title { return [self localizedObjectForKey:kIASKTitle]; } - (NSString*)subtitle { return [self localizedObjectForKey:kIASKSubtitle]; } - (NSString *)placeholder { return [self localizedObjectForKey:kIASKPlaceholder]; } - (NSString*)footerText { return [self localizedObjectForKey:kIASKFooterText]; } - (Class)viewControllerClass { [IASKAppSettingsWebViewController class]; // make sure this is linked into the binary/library NSString *classString = [_specifierDict objectForKey:kIASKViewControllerClass]; return classString ? ([self classFromString:classString] ?: [NSNull class]) : nil; } - (Class)classFromString:(NSString *)className { Class class = NSClassFromString(className); if (!class) { // if the class doesn't exist as a pure Obj-C class then try to retrieve it as a Swift class. NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"]; NSString *classStringName = [NSString stringWithFormat:@"_TtC%lu%@%lu%@", (unsigned long)appName.length, appName, (unsigned long)className.length, className]; class = NSClassFromString(classStringName); } return class; } - (SEL)viewControllerSelector { return NSSelectorFromString([_specifierDict objectForKey:kIASKViewControllerSelector]); } - (NSString*)viewControllerStoryBoardFile { return [_specifierDict objectForKey:kIASKViewControllerStoryBoardFile]; } - (NSString*)viewControllerStoryBoardID { return [_specifierDict objectForKey:kIASKViewControllerStoryBoardId]; } - (NSString*)segueIdentifier { return [_specifierDict objectForKey:kIASKSegueIdentifier]; } - (Class)buttonClass { return NSClassFromString([_specifierDict objectForKey:kIASKButtonClass]); } - (SEL)buttonAction { return NSSelectorFromString([_specifierDict objectForKey:kIASKButtonAction]); } - (NSString*)key { return [_specifierDict objectForKey:kIASKKey]; } - (NSString*)type { return [_specifierDict objectForKey:kIASKType]; } - (NSString*)titleForCurrentValue:(id)currentValue { NSArray *values = [self multipleValues]; NSArray *titles = [self multipleShortTitles]; if (!titles) { titles = [self multipleTitles]; } if (values.count != titles.count) { return nil; } NSInteger keyIndex = [values indexOfObject:currentValue]; if (keyIndex == NSNotFound) { return nil; } @try { IASKSettingsReader *strongSettingsReader = self.settingsReader; return [strongSettingsReader titleForId:[titles objectAtIndex:keyIndex]]; } @catch (NSException * e) {} return nil; } - (NSInteger)multipleValuesCount { return [[_multipleValuesDict objectForKey:kIASKValues] count]; } - (NSArray*)multipleValues { return [_multipleValuesDict objectForKey:kIASKValues]; } - (NSArray*)multipleTitles { return [_multipleValuesDict objectForKey:kIASKTitles]; } - (NSArray*)multipleShortTitles { return [_multipleValuesDict objectForKey:kIASKShortTitles]; } - (NSString*)file { return [_specifierDict objectForKey:kIASKFile]; } - (id)defaultValue { return [_specifierDict objectForKey:kIASKDefaultValue]; } - (id)defaultStringValue { return [[_specifierDict objectForKey:kIASKDefaultValue] description]; } - (BOOL)defaultBoolValue { id defaultValue = [self defaultValue]; if ([defaultValue isEqual:[self trueValue]]) { return YES; } if ([defaultValue isEqual:[self falseValue]]) { return NO; } return [defaultValue boolValue]; } - (id)trueValue { return [_specifierDict objectForKey:kIASKTrueValue]; } - (id)falseValue { return [_specifierDict objectForKey:kIASKFalseValue]; } - (float)minimumValue { return [[_specifierDict objectForKey:kIASKMinimumValue] floatValue]; } - (float)maximumValue { return [[_specifierDict objectForKey:kIASKMaximumValue] floatValue]; } - (NSString*)minimumValueImage { return [_specifierDict objectForKey:kIASKMinimumValueImage]; } - (NSString*)maximumValueImage { return [_specifierDict objectForKey:kIASKMaximumValueImage]; } - (BOOL)isSecure { return [[_specifierDict objectForKey:kIASKIsSecure] boolValue]; } - (UIKeyboardType)keyboardType { if ([[_specifierDict objectForKey:KIASKKeyboardType] isEqualToString:kIASKKeyboardAlphabet]) { return UIKeyboardTypeDefault; } else if ([[_specifierDict objectForKey:KIASKKeyboardType] isEqualToString:kIASKKeyboardNumbersAndPunctuation]) { return UIKeyboardTypeNumbersAndPunctuation; } else if ([[_specifierDict objectForKey:KIASKKeyboardType] isEqualToString:kIASKKeyboardNumberPad]) { return UIKeyboardTypeNumberPad; } else if ([[_specifierDict objectForKey:KIASKKeyboardType] isEqualToString:kIASKKeyboardPhonePad]) { return UIKeyboardTypePhonePad; } else if ([[_specifierDict objectForKey:KIASKKeyboardType] isEqualToString:kIASKKeyboardNamePhonePad]) { return UIKeyboardTypeNamePhonePad; } else if ([[_specifierDict objectForKey:KIASKKeyboardType] isEqualToString:kIASKKeyboardASCIICapable]) { return UIKeyboardTypeASCIICapable; } else if ([[_specifierDict objectForKey:KIASKKeyboardType] isEqualToString:kIASKKeyboardDecimalPad]) { return UIKeyboardTypeDecimalPad; } else if ([[_specifierDict objectForKey:KIASKKeyboardType] isEqualToString:KIASKKeyboardURL]) { return UIKeyboardTypeURL; } else if ([[_specifierDict objectForKey:KIASKKeyboardType] isEqualToString:kIASKKeyboardEmailAddress]) { return UIKeyboardTypeEmailAddress; } return UIKeyboardTypeDefault; } - (UITextAutocapitalizationType)autocapitalizationType { if ([[_specifierDict objectForKey:kIASKAutocapitalizationType] isEqualToString:kIASKAutoCapNone]) { return UITextAutocapitalizationTypeNone; } else if ([[_specifierDict objectForKey:kIASKAutocapitalizationType] isEqualToString:kIASKAutoCapSentences]) { return UITextAutocapitalizationTypeSentences; } else if ([[_specifierDict objectForKey:kIASKAutocapitalizationType] isEqualToString:kIASKAutoCapWords]) { return UITextAutocapitalizationTypeWords; } else if ([[_specifierDict objectForKey:kIASKAutocapitalizationType] isEqualToString:kIASKAutoCapAllCharacters]) { return UITextAutocapitalizationTypeAllCharacters; } return UITextAutocapitalizationTypeNone; } - (UITextAutocorrectionType)autoCorrectionType { if ([[_specifierDict objectForKey:kIASKAutoCorrectionType] isEqualToString:kIASKAutoCorrDefault]) { return UITextAutocorrectionTypeDefault; } else if ([[_specifierDict objectForKey:kIASKAutoCorrectionType] isEqualToString:kIASKAutoCorrNo]) { return UITextAutocorrectionTypeNo; } else if ([[_specifierDict objectForKey:kIASKAutoCorrectionType] isEqualToString:kIASKAutoCorrYes]) { return UITextAutocorrectionTypeYes; } return UITextAutocorrectionTypeDefault; } - (UIImage *)cellImage { NSString *imageName = [_specifierDict objectForKey:kIASKCellImage]; if( imageName.length == 0 ) return nil; return [UIImage imageNamed:imageName]; } - (UIImage *)highlightedCellImage { NSString *imageName = [[_specifierDict objectForKey:kIASKCellImage ] stringByAppendingString:@"Highlighted"]; if( imageName.length == 0 ) return nil; return [UIImage imageNamed:imageName]; } - (BOOL)adjustsFontSizeToFitWidth { NSNumber *boxedResult = [_specifierDict objectForKey:kIASKAdjustsFontSizeToFitWidth]; return !boxedResult || [boxedResult boolValue]; } - (NSTextAlignment)textAlignment { if (self.subtitle.length || [[_specifierDict objectForKey:kIASKTextLabelAlignment] isEqualToString:kIASKTextLabelAlignmentLeft]) { return NSTextAlignmentLeft; } else if ([[_specifierDict objectForKey:kIASKTextLabelAlignment] isEqualToString:kIASKTextLabelAlignmentCenter]) { return NSTextAlignmentCenter; } else if ([[_specifierDict objectForKey:kIASKTextLabelAlignment] isEqualToString:kIASKTextLabelAlignmentRight]) { return NSTextAlignmentRight; } if ([self.type isEqualToString:kIASKButtonSpecifier] && !self.cellImage) { return NSTextAlignmentCenter; } else if ([self.type isEqualToString:kIASKPSMultiValueSpecifier] || [self.type isEqualToString:kIASKPSTitleValueSpecifier] || [self.type isEqualToString:kIASKTextViewSpecifier]) { return NSTextAlignmentRight; } return NSTextAlignmentLeft; } - (NSArray *)userInterfaceIdioms { NSArray *idiomStrings = _specifierDict[kIASKSupportedUserInterfaceIdioms]; if (idiomStrings.count == 0) { return @[@(UIUserInterfaceIdiomPhone), @(UIUserInterfaceIdiomPad)]; } NSMutableArray *idioms = [NSMutableArray new]; for (NSString *idiomString in idiomStrings) { if ([idiomString isEqualToString:@"Phone"]) { [idioms addObject:@(UIUserInterfaceIdiomPhone)]; } else if ([idiomString isEqualToString:@"Pad"]) { [idioms addObject:@(UIUserInterfaceIdiomPad)]; } } return idioms; } - (id)valueForKey:(NSString *)key { return [_specifierDict objectForKey:key]; } @end ================================================ FILE: ArcBit/External/InAppSettingsKit/Resources/Base.lproj/IASKLocalizable.strings ================================================ /* IASKLocalizable.strings Where To Created by Ortwin Gentz on 20.10.14. Copyright (c) 2014 FutureTap. All rights reserved. */ "Privacy" = "Privacy"; // iOS 8+ Privacy cell: title "Open in Settings app" = ""; // iOS 8+ Privacy cell: subtitle (TODO) ================================================ FILE: ArcBit/External/InAppSettingsKit/Resources/de.lproj/IASKLocalizable.strings ================================================ /* IASKLocalizable.strings Where To Created by Ortwin Gentz on 20.10.14. Copyright (c) 2014 FutureTap. All rights reserved. */ "Privacy" = "Datenschutz"; // iOS 8+ Privacy cell: title "Open in Settings app" = "In “Einstellungen” App öffnen"; // iOS 8+ Privacy cell: subtitle ================================================ FILE: ArcBit/External/InAppSettingsKit/Resources/el.lproj/IASKLocalizable.strings ================================================ /* IASKLocalizable.strings Where To Created by Ortwin Gentz on 20.10.14. Copyright (c) 2014 FutureTap. All rights reserved. */ "Privacy" = "Απόρρητο"; // iOS 8+ Privacy cell: title "Open in Settings app" = ""; // iOS 8+ Privacy cell: subtitle (TODO) ================================================ FILE: ArcBit/External/InAppSettingsKit/Resources/en.lproj/IASKLocalizable.strings ================================================ /* IASKLocalizable.strings Where To Created by Ortwin Gentz on 20.10.14. Copyright (c) 2014 FutureTap. All rights reserved. */ "Privacy" = "Privacy"; // iOS 8+ Privacy cell: title "Open in Settings app" = "Open in “Settings” app"; // iOS 8+ Privacy cell: subtitle ================================================ FILE: ArcBit/External/InAppSettingsKit/Resources/es.lproj/IASKLocalizable.strings ================================================ /* IASKLocalizable.strings Where To Created by Ortwin Gentz on 20.10.14. Copyright (c) 2014 FutureTap. All rights reserved. */ "Privacy" = "Privacidad"; // iOS 8+ Privacy cell: title "Open in Settings app" = ""; // iOS 8+ Privacy cell: subtitle (TODO!) ================================================ FILE: ArcBit/External/InAppSettingsKit/Resources/fr.lproj/IASKLocalizable.strings ================================================ /* IASKLocalizable.strings Where To Created by Ortwin Gentz on 20.10.14. Copyright (c) 2014 FutureTap. All rights reserved. */ "Privacy" = "Confidentialité"; // iOS 8+ Privacy cell: title "Open in Settings app" = ""; // iOS 8+ Privacy cell: subtitle (TODO!) ================================================ FILE: ArcBit/External/InAppSettingsKit/Resources/it.lproj/IASKLocalizable.strings ================================================ /* IASKLocalizable.strings Where To Created by Ortwin Gentz on 20.10.14. Copyright (c) 2014 FutureTap. All rights reserved. */ "Privacy" = "Privacy"; // iOS 8+ Privacy cell: title "Open in Settings app" = ""; // iOS 8+ Privacy cell: subtitle (TODO) ================================================ FILE: ArcBit/External/InAppSettingsKit/Resources/ja.lproj/IASKLocalizable.strings ================================================ /* IASKLocalizable.strings Where To Created by Ortwin Gentz on 20.10.14. Copyright (c) 2014 FutureTap. All rights reserved. */ "Privacy" = "プライバシー"; // iOS 8+ Privacy cell: title "Open in Settings app" = ""; // iOS 8+ Privacy cell: subtitle (TODO) ================================================ FILE: ArcBit/External/InAppSettingsKit/Resources/nl.lproj/IASKLocalizable.strings ================================================ /* IASKLocalizable.strings Where To Created by Ortwin Gentz on 20.10.14. Copyright (c) 2014 FutureTap. All rights reserved. */ "Privacy" = "Privacy"; // iOS 8+ Privacy cell: title "Open in Settings app" = "In “Instellingen” app openen"; // iOS 8+ Privacy cell: subtitle ================================================ FILE: ArcBit/External/InAppSettingsKit/Resources/pt-PT.lproj/IASKLocalizable.strings ================================================ /* IASKLocalizable.strings Where To Created by Ortwin Gentz on 20.10.14. Copyright (c) 2014 FutureTap. All rights reserved. */ "Privacy" = "Privacidade"; // iOS 8+ Privacy cell: title "Open in Settings app" = ""; // iOS 8+ Privacy cell: subtitle (TODO) ================================================ FILE: ArcBit/External/InAppSettingsKit/Resources/pt.lproj/IASKLocalizable.strings ================================================ /* IASKLocalizable.strings Where To Created by Ortwin Gentz on 20.10.14. Copyright (c) 2014 FutureTap. All rights reserved. */ "Privacy" = "Privacidade"; // iOS 8+ Privacy cell: title "Open in Settings app" = ""; // iOS 8+ Privacy cell: subtitle (TODO) ================================================ FILE: ArcBit/External/InAppSettingsKit/Resources/ru.lproj/IASKLocalizable.strings ================================================ /* IASKLocalizable.strings Where To Created by Ortwin Gentz on 20.10.14. Copyright (c) 2014 FutureTap. All rights reserved. */ "Privacy" = "Приватность"; // iOS 8+ Privacy cell: title "Open in Settings app" = ""; // iOS 8+ Privacy cell: subtitle (TODO) ================================================ FILE: ArcBit/External/InAppSettingsKit/Resources/sv.lproj/IASKLocalizable.strings ================================================ /* IASKLocalizable.strings Where To Created by Ortwin Gentz on 20.10.14. Copyright (c) 2014 FutureTap. All rights reserved. */ "Privacy" = "Integritetsskydd"; // iOS 8+ Privacy cell: title "Open in Settings app" = ""; // iOS 8+ Privacy cell: subtitle (TODO) ================================================ FILE: ArcBit/External/InAppSettingsKit/Resources/th.lproj/IASKLocalizable.strings ================================================ /* IASKLocalizable.strings Where To Created by Ortwin Gentz on 20.10.14. Copyright (c) 2014 FutureTap. All rights reserved. */ "Privacy" = "ความเป็นส่วนตัว"; // iOS 8+ Privacy cell: title "Open in Settings app" = ""; // iOS 8+ Privacy cell: subtitle (TODO) ================================================ FILE: ArcBit/External/InAppSettingsKit/Resources/tr.lproj/IASKLocalizable.strings ================================================ /* IASKLocalizable.strings Where To Created by Ortwin Gentz on 20.10.14. Copyright (c) 2014 FutureTap. All rights reserved. */ "Privacy" = "Gizlilik"; // iOS 8+ Privacy cell: title "Open in Settings app" = ""; // iOS 8+ Privacy cell: subtitle (TODO) ================================================ FILE: ArcBit/External/InAppSettingsKit/Resources/zh-Hant.lproj/IASKLocalizable.strings ================================================ /* IASKLocalizable.strings Where To Created by Ortwin Gentz on 20.10.14. Copyright (c) 2014 FutureTap. All rights reserved. */ "Privacy" = "Privacy"; // iOS 8+ Privacy cell: title "Open in Settings app" = ""; // iOS 8+ Privacy cell: subtitle (TODO) ================================================ FILE: ArcBit/External/InAppSettingsKit/Views/IASKPSSliderSpecifierViewCell.h ================================================ // // IASKPSSliderSpecifierViewCell.h // http://www.inappsettingskit.com // // Copyright (c) 2009: // Luc Vandal, Edovia Inc., http://www.edovia.com // Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com // All rights reserved. // // It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz, // as the original authors of this code. You can give credit in a blog post, a tweet or on // a info page of your app. Also, the original authors appreciate letting them know if you use this code. // // This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php // #import @class IASKSlider; @interface IASKPSSliderSpecifierViewCell : UITableViewCell @property (nonatomic, strong) IASKSlider *slider; @property (nonatomic, strong) UIImageView *minImage; @property (nonatomic, strong) UIImageView *maxImage; @end ================================================ FILE: ArcBit/External/InAppSettingsKit/Views/IASKPSSliderSpecifierViewCell.m ================================================ // // IASKPSSliderSpecifierViewCell.m // http://www.inappsettingskit.com // // Copyright (c) 2009-2010: // Luc Vandal, Edovia Inc., http://www.edovia.com // Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com // All rights reserved. // // It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz, // as the original authors of this code. You can give credit in a blog post, a tweet or on // a info page of your app. Also, the original authors appreciate letting them know if you use this code. // // This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php // #import "IASKPSSliderSpecifierViewCell.h" #import "IASKSlider.h" #import "IASKSettingsReader.h" @implementation IASKPSSliderSpecifierViewCell - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; if (self) { // Setting only frame data that will not be overwritten by layoutSubviews // Slider _slider = [[IASKSlider alloc] initWithFrame:CGRectMake(0, 0, 0, 23)]; _slider.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleWidth; _slider.continuous = NO; [self.contentView addSubview:_slider]; // MinImage _minImage = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 21, 21)]; _minImage.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin; [self.contentView addSubview:_minImage]; // MaxImage _maxImage = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 21, 21)]; _maxImage.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin; [self.contentView addSubview:_maxImage]; self.selectionStyle = UITableViewCellSelectionStyleNone; } return self; } - (void)layoutSubviews { [super layoutSubviews]; UIEdgeInsets padding = (UIEdgeInsets) { 0, kIASKPaddingLeft, 0, kIASKPaddingRight }; if ([self respondsToSelector:@selector(layoutMargins)]) { padding = [self layoutMargins]; } CGRect sliderBounds = _slider.bounds; CGPoint sliderCenter = _slider.center; const CGFloat superViewWidth = _slider.superview.frame.size.width; sliderBounds.size.width = superViewWidth - (padding.left + padding.right); sliderCenter.x = padding.left + sliderBounds.size.width / 2; sliderCenter.y = self.contentView.center.y; _minImage.hidden = YES; _maxImage.hidden = YES; // Check if there are min and max images. If so, change the layout accordingly. if (_minImage.image) { // Min image _minImage.hidden = NO; sliderBounds.size.width -= _minImage.frame.size.width + kIASKSliderImageGap; sliderCenter.x += (_minImage.frame.size.width + kIASKSliderImageGap) / 2; _minImage.center = CGPointMake(_minImage.frame.size.width / 2 + padding.left, self.contentView.center.y); } if (_maxImage.image) { // Max image _maxImage.hidden = NO; sliderBounds.size.width -= kIASKSliderImageGap + _maxImage.frame.size.width; sliderCenter.x -= (kIASKSliderImageGap + _maxImage.frame.size.width) / 2; _maxImage.center = CGPointMake(superViewWidth - padding.right - _maxImage.frame.size.width /2, self.contentView.center.y ); } _slider.bounds = sliderBounds; _slider.center = sliderCenter; } - (void)prepareForReuse { [super prepareForReuse]; _minImage.image = nil; _maxImage.image = nil; } @end ================================================ FILE: ArcBit/External/InAppSettingsKit/Views/IASKPSTextFieldSpecifierViewCell.h ================================================ // // IASKPSTextFieldSpecifierViewCell.h // http://www.inappsettingskit.com // // Copyright (c) 2009: // Luc Vandal, Edovia Inc., http://www.edovia.com // Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com // All rights reserved. // // It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz, // as the original authors of this code. You can give credit in a blog post, a tweet or on // a info page of your app. Also, the original authors appreciate letting them know if you use this code. // // This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php // #import @class IASKTextField; @interface IASKPSTextFieldSpecifierViewCell : UITableViewCell @property (nonatomic, strong) IASKTextField *textField; @end ================================================ FILE: ArcBit/External/InAppSettingsKit/Views/IASKPSTextFieldSpecifierViewCell.m ================================================ // // IASKPSTextFieldSpecifierViewCell.m // http://www.inappsettingskit.com // // Copyright (c) 2009-2010: // Luc Vandal, Edovia Inc., http://www.edovia.com // Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com // All rights reserved. // // It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz, // as the original authors of this code. You can give credit in a blog post, a tweet or on // a info page of your app. Also, the original authors appreciate letting them know if you use this code. // // This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php // #import "IASKPSTextFieldSpecifierViewCell.h" #import "IASKTextField.h" #import "IASKSettingsReader.h" @implementation IASKPSTextFieldSpecifierViewCell - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; if (self) { self.textLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleRightMargin; // TextField _textField = [[IASKTextField alloc] initWithFrame:CGRectMake(0, 0, 200, self.frame.size.height)]; _textField.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleLeftMargin; _textField.font = [UIFont systemFontOfSize:kIASKLabelFontSize]; _textField.minimumFontSize = kIASKMinimumFontSize; IASK_IF_PRE_IOS7(_textField.textColor = [UIColor colorWithRed:0.275f green:0.376f blue:0.522f alpha:1.000f];); [self.contentView addSubview:_textField]; self.selectionStyle = UITableViewCellSelectionStyleNone; } return self; } - (void)layoutSubviews { [super layoutSubviews]; UIEdgeInsets padding = (UIEdgeInsets) { 0, kIASKPaddingLeft, 0, kIASKPaddingRight }; if ([self respondsToSelector:@selector(layoutMargins)]) { padding = [self layoutMargins]; } // Label CGFloat imageOffset = self.imageView.image ? self.imageView.bounds.size.width + padding.left : 0; CGSize labelSize = [self.textLabel sizeThatFits:CGSizeZero]; labelSize.width = MAX(labelSize.width, kIASKMinLabelWidth - imageOffset); self.textLabel.frame = (CGRect){self.textLabel.frame.origin, {MIN(kIASKMaxLabelWidth, labelSize.width), self.textLabel.frame.size.height}} ; // TextField _textField.center = CGPointMake(_textField.center.x, self.contentView.center.y); CGRect textFieldFrame = _textField.frame; textFieldFrame.origin.x = self.textLabel.frame.origin.x + MAX(kIASKMinLabelWidth - imageOffset, self.textLabel.frame.size.width) + kIASKSpacing; textFieldFrame.size.width = _textField.superview.frame.size.width - textFieldFrame.origin.x - padding.right; if (!self.textLabel.text.length) { textFieldFrame.origin.x = padding.left + imageOffset; textFieldFrame.size.width = self.contentView.bounds.size.width - padding.left - padding.right - imageOffset; } else if (_textField.textAlignment == NSTextAlignmentRight) { textFieldFrame.origin.x = self.textLabel.frame.origin.x + labelSize.width + kIASKSpacing; textFieldFrame.size.width = _textField.superview.frame.size.width - textFieldFrame.origin.x - padding.right; } _textField.frame = textFieldFrame; } @end ================================================ FILE: ArcBit/External/InAppSettingsKit/Views/IASKSlider.h ================================================ // // IASKSlider.h // http://www.inappsettingskit.com // // Copyright (c) 2009: // Luc Vandal, Edovia Inc., http://www.edovia.com // Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com // All rights reserved. // // It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz, // as the original authors of this code. You can give credit in a blog post, a tweet or on // a info page of your app. Also, the original authors appreciate letting them know if you use this code. // // This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php // #import @interface IASKSlider : UISlider @property (nonatomic, copy) NSString *key; @end ================================================ FILE: ArcBit/External/InAppSettingsKit/Views/IASKSlider.m ================================================ // // IASKSlider.m // http://www.inappsettingskit.com // // Copyright (c) 2009: // Luc Vandal, Edovia Inc., http://www.edovia.com // Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com // All rights reserved. // // It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz, // as the original authors of this code. You can give credit in a blog post, a tweet or on // a info page of your app. Also, the original authors appreciate letting them know if you use this code. // // This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php // #import "IASKSlider.h" @implementation IASKSlider @end ================================================ FILE: ArcBit/External/InAppSettingsKit/Views/IASKSwitch.h ================================================ // // IASKSwitch.h // http://www.inappsettingskit.com // // Copyright (c) 2009: // Luc Vandal, Edovia Inc., http://www.edovia.com // Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com // All rights reserved. // // It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz, // as the original authors of this code. You can give credit in a blog post, a tweet or on // a info page of your app. Also, the original authors appreciate letting them know if you use this code. // // This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php // #import @interface IASKSwitch : UISwitch @property (nonatomic, copy) NSString *key; @end ================================================ FILE: ArcBit/External/InAppSettingsKit/Views/IASKSwitch.m ================================================ // // IASKSwitch.m // http://www.inappsettingskit.com // // Copyright (c) 2009: // Luc Vandal, Edovia Inc., http://www.edovia.com // Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com // All rights reserved. // // It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz, // as the original authors of this code. You can give credit in a blog post, a tweet or on // a info page of your app. Also, the original authors appreciate letting them know if you use this code. // // This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php // #import "IASKSwitch.h" @implementation IASKSwitch @end ================================================ FILE: ArcBit/External/InAppSettingsKit/Views/IASKTextField.h ================================================ // // IASKTextField.h // http://www.inappsettingskit.com // // Copyright (c) 2009: // Luc Vandal, Edovia Inc., http://www.edovia.com // Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com // All rights reserved. // // It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz, // as the original authors of this code. You can give credit in a blog post, a tweet or on // a info page of your app. Also, the original authors appreciate letting them know if you use this code. // // This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php // #import @interface IASKTextField : UITextField @property (nonatomic, copy) NSString *key; @end ================================================ FILE: ArcBit/External/InAppSettingsKit/Views/IASKTextField.m ================================================ // // IASKTextField.m // http://www.inappsettingskit.com // // Copyright (c) 2009: // Luc Vandal, Edovia Inc., http://www.edovia.com // Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com // All rights reserved. // // It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz, // as the original authors of this code. You can give credit in a blog post, a tweet or on // a info page of your app. Also, the original authors appreciate letting them know if you use this code. // // This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php // #import "IASKTextField.h" @implementation IASKTextField @end ================================================ FILE: ArcBit/External/InAppSettingsKit/Views/IASKTextView.h ================================================ // // IASKTextView.h // http://www.inappsettingskit.com // // Copyright (c) 2009-2015: // Luc Vandal, Edovia Inc., http://www.edovia.com // Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com // All rights reserved. // // It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz, // as the original authors of this code. You can give credit in a blog post, a tweet or on // a info page of your app. Also, the original authors appreciate letting them know if you use this code. // // This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php // #import @interface IASKTextView : UITextView @property (nonatomic, copy) NSString *key; @end ================================================ FILE: ArcBit/External/InAppSettingsKit/Views/IASKTextView.m ================================================ // // IASKTextView.m // http://www.inappsettingskit.com // // Copyright (c) 2009-2015: // Luc Vandal, Edovia Inc., http://www.edovia.com // Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com // All rights reserved. // // It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz, // as the original authors of this code. You can give credit in a blog post, a tweet or on // a info page of your app. Also, the original authors appreciate letting them know if you use this code. // // This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php // #import "IASKTextView.h" @implementation IASKTextView @end ================================================ FILE: ArcBit/External/InAppSettingsKit/Views/IASKTextViewCell.h ================================================ // // IASKTextViewCell.h // http://www.inappsettingskit.com // // Copyright (c) 2009-2015: // Luc Vandal, Edovia Inc., http://www.edovia.com // Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com // All rights reserved. // // It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz, // as the original authors of this code. You can give credit in a blog post, a tweet or on // a info page of your app. Also, the original authors appreciate letting them know if you use this code. // // This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php // #import #import "IASKTextView.h" @interface IASKTextViewCell : UITableViewCell @property (nonatomic, strong) IASKTextView *textView; @end ================================================ FILE: ArcBit/External/InAppSettingsKit/Views/IASKTextViewCell.m ================================================ // // IASKTextViewCell.m // http://www.inappsettingskit.com // // Copyright (c) 2009-2015: // Luc Vandal, Edovia Inc., http://www.edovia.com // Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com // All rights reserved. // // It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz, // as the original authors of this code. You can give credit in a blog post, a tweet or on // a info page of your app. Also, the original authors appreciate letting them know if you use this code. // // This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php // #import "IASKTextViewCell.h" #import "IASKSettingsReader.h" @implementation IASKTextViewCell - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { if ((self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier])) { self.selectionStyle = UITableViewCellSelectionStyleNone; self.accessoryType = UITableViewCellAccessoryNone; IASKTextView *textView = [[IASKTextView alloc] initWithFrame:CGRectZero]; textView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; textView.scrollEnabled = NO; textView.font = [UIFont systemFontOfSize:17.0]; textView.backgroundColor = [UIColor whiteColor]; [self.contentView addSubview:textView]; self.textView = textView; } return self; } - (void)layoutSubviews { [super layoutSubviews]; UIEdgeInsets padding = (UIEdgeInsets) { 0, kIASKPaddingLeft, 0, kIASKPaddingRight }; if ([self respondsToSelector:@selector(layoutMargins)]) { padding = self.layoutMargins; padding.left -= 5; padding.right -= 5; padding.top -= 5; padding.bottom -= 5; } self.textView.frame = UIEdgeInsetsInsetRect(self.bounds, padding); } @end ================================================ FILE: ArcBit/External/JNKeychain-master/JNKeychain.h ================================================ // // JNKeychain.h // // Created by Jeremias Nunez on 5/10/13. // Copyright (c) 2013 Jeremias Nunez. All rights reserved. // // Based on Anomie's great answer - http://stackoverflow.com/a/5251820 // // jeremias.np@gmail.com #import @interface JNKeychain : NSObject + (void)saveValue:(id)data forKey:(NSString*)key; + (id)loadValueForKey:(NSString*)key; + (void)deleteValueForKey:(NSString *)key; @end ================================================ FILE: ArcBit/External/JNKeychain-master/JNKeychain.m ================================================ // // JNKeychain.m // // Created by Jeremias Nunez on 5/10/13. // Copyright (c) 2013 Jeremias Nunez. All rights reserved. // // jeremias.np@gmail.com #import "JNKeychain.h" @interface JNKeychain () + (NSMutableDictionary *)getKeychainQuery:(NSString *)key; @end @implementation JNKeychain + (NSMutableDictionary *)getKeychainQuery:(NSString *)key { // see http://developer.apple.com/library/ios/#DOCUMENTATION/Security/Reference/keychainservices/Reference/reference.html return [NSMutableDictionary dictionaryWithObjectsAndKeys: (__bridge id)kSecClassGenericPassword, (__bridge id)kSecClass, key, (__bridge id)kSecAttrService, key, (__bridge id)kSecAttrAccount, (__bridge id)kSecAttrAccessibleAfterFirstUnlock, (__bridge id)kSecAttrAccessible, nil]; } + (void)saveValue:(id)data forKey:(NSString*)key { NSMutableDictionary *keychainQuery = [self getKeychainQuery:key]; // delete any previous value with this key SecItemDelete((__bridge CFDictionaryRef)keychainQuery); [keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(__bridge id)kSecValueData]; SecItemAdd((__bridge CFDictionaryRef)keychainQuery, NULL); } + (id)loadValueForKey:(NSString *)key { id value = nil; NSMutableDictionary *keychainQuery = [self getKeychainQuery:key]; CFDataRef keyData = NULL; [keychainQuery setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData]; [keychainQuery setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit]; if (SecItemCopyMatching((__bridge CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr) { @try { value = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge NSData *)keyData]; } @catch (NSException *e) { NSLog(@"Unarchive of %@ failed: %@", key, e); } @finally {} } if (keyData) { CFRelease(keyData); } return value; } + (void)deleteValueForKey:(NSString *)key { NSMutableDictionary *keychainQuery = [self getKeychainQuery:key]; SecItemDelete((__bridge CFDictionaryRef)keychainQuery); } @end ================================================ FILE: ArcBit/External/KeychainItemWrapper/KeychainItemWrapper.h ================================================ /* File: KeychainItemWrapper.h Abstract: Objective-C wrapper for accessing a single keychain item. Version: 1.2 - ARCified Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. ("Apple") in consideration of your agreement to the following terms, and your use, installation, modification or redistribution of this Apple software constitutes acceptance of these terms. If you do not agree with these terms, please do not use, install, modify or redistribute this Apple software. In consideration of your agreement to abide by the following terms, and subject to these terms, Apple grants you a personal, non-exclusive license, under Apple's copyrights in this original Apple software (the "Apple Software"), to use, reproduce, modify and redistribute the Apple Software, with or without modifications, in source and/or binary forms; provided that if you redistribute the Apple Software in its entirety and without modifications, you must retain this notice and the following text and disclaimers in all such redistributions of the Apple Software. Neither the name, trademarks, service marks or logos of Apple Inc. may be used to endorse or promote products derived from the Apple Software without specific prior written permission from Apple. Except as expressly stated in this notice, no other rights or licenses, express or implied, are granted by Apple herein, including but not limited to any patent rights that may be infringed by your derivative works or by other works in which the Apple Software may be incorporated. The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Copyright (C) 2010 Apple Inc. All Rights Reserved. */ #import /* The KeychainItemWrapper class is an abstraction layer for the iPhone Keychain communication. It is merely a simple wrapper to provide a distinct barrier between all the idiosyncracies involved with the Keychain CF/NS container objects. */ @interface KeychainItemWrapper : NSObject // Designated initializer. - (id)initWithIdentifier: (NSString *)identifier accessGroup:(NSString *)accessGroup; - (void)setObject:(id)inObject forKey:(id)key; - (id)objectForKey:(id)key; // Initializes and resets the default generic keychain item data. - (void)resetKeychainItem; @end ================================================ FILE: ArcBit/External/KeychainItemWrapper/KeychainItemWrapper.m ================================================ /* File: KeychainItemWrapper.m Abstract: Objective-C wrapper for accessing a single keychain item. Version: 1.2 - ARCified Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. ("Apple") in consideration of your agreement to the following terms, and your use, installation, modification or redistribution of this Apple software constitutes acceptance of these terms. If you do not agree with these terms, please do not use, install, modify or redistribute this Apple software. In consideration of your agreement to abide by the following terms, and subject to these terms, Apple grants you a personal, non-exclusive license, under Apple's copyrights in this original Apple software (the "Apple Software"), to use, reproduce, modify and redistribute the Apple Software, with or without modifications, in source and/or binary forms; provided that if you redistribute the Apple Software in its entirety and without modifications, you must retain this notice and the following text and disclaimers in all such redistributions of the Apple Software. Neither the name, trademarks, service marks or logos of Apple Inc. may be used to endorse or promote products derived from the Apple Software without specific prior written permission from Apple. Except as expressly stated in this notice, no other rights or licenses, express or implied, are granted by Apple herein, including but not limited to any patent rights that may be infringed by your derivative works or by other works in which the Apple Software may be incorporated. The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Copyright (C) 2010 Apple Inc. All Rights Reserved. */ #define PASSWORD_USES_DATA #import "KeychainItemWrapper.h" #import /* These are the default constants and their respective types, available for the kSecClassGenericPassword Keychain Item class: kSecAttrAccessGroup - CFStringRef kSecAttrCreationDate - CFDateRef kSecAttrModificationDate - CFDateRef kSecAttrDescription - CFStringRef kSecAttrComment - CFStringRef kSecAttrCreator - CFNumberRef kSecAttrType - CFNumberRef kSecAttrLabel - CFStringRef kSecAttrIsInvisible - CFBooleanRef kSecAttrIsNegative - CFBooleanRef kSecAttrAccount - CFStringRef kSecAttrService - CFStringRef kSecAttrGeneric - CFDataRef See the header file Security/SecItem.h for more details. */ @interface KeychainItemWrapper (PrivateMethods) /* The decision behind the following two methods (secItemFormatToDictionary and dictionaryToSecItemFormat) was to encapsulate the transition between what the detail view controller was expecting (NSString *) and what the Keychain API expects as a validly constructed container class. */ - (NSMutableDictionary *)secItemFormatToDictionary:(NSDictionary *)dictionaryToConvert; - (NSMutableDictionary *)dictionaryToSecItemFormat:(NSDictionary *)dictionaryToConvert; // Updates the item in the keychain, or adds it if it doesn't exist. - (void)writeToKeychain; @end @implementation KeychainItemWrapper { NSMutableDictionary *keychainItemData; // The actual keychain item data backing store. NSMutableDictionary *genericPasswordQuery; // A placeholder for the generic keychain item query used to locate the item. } - (id)initWithIdentifier: (NSString *)identifier accessGroup:(NSString *) accessGroup; { if (self = [super init]) { // Begin Keychain search setup. The genericPasswordQuery leverages the special user // defined attribute kSecAttrGeneric to distinguish itself between other generic Keychain // items which may be included by the same application. genericPasswordQuery = [[NSMutableDictionary alloc] init]; [genericPasswordQuery setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass]; [genericPasswordQuery setObject:identifier forKey:(__bridge id)kSecAttrGeneric]; // The keychain access group attribute determines if this item can be shared // amongst multiple apps whose code signing entitlements contain the same keychain access group. if (accessGroup != nil) { #if TARGET_IPHONE_SIMULATOR // Ignore the access group if running on the iPhone simulator. // // Apps that are built for the simulator aren't signed, so there's no keychain access group // for the simulator to check. This means that all apps can see all keychain items when run // on the simulator. // // If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the // simulator will return -25243 (errSecNoAccessForItem). #else [genericPasswordQuery setObject:accessGroup forKey:(__bridge id)kSecAttrAccessGroup]; #endif } // Use the proper search constants, return only the attributes of the first match. [genericPasswordQuery setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit]; [genericPasswordQuery setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnAttributes]; NSDictionary *tempQuery = [NSDictionary dictionaryWithDictionary:genericPasswordQuery]; CFMutableDictionaryRef outDictionary = NULL; if (!SecItemCopyMatching((__bridge CFDictionaryRef)tempQuery, (CFTypeRef *)&outDictionary) == noErr) { // Stick these default values into keychain item if nothing found. [self resetKeychainItem]; // Add the generic attribute and the keychain access group. [keychainItemData setObject:identifier forKey:(__bridge id)kSecAttrGeneric]; if (accessGroup != nil) { #if TARGET_IPHONE_SIMULATOR // Ignore the access group if running on the iPhone simulator. // // Apps that are built for the simulator aren't signed, so there's no keychain access group // for the simulator to check. This means that all apps can see all keychain items when run // on the simulator. // // If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the // simulator will return -25243 (errSecNoAccessForItem). #else [keychainItemData setObject:accessGroup forKey:(__bridge id)kSecAttrAccessGroup]; #endif } } else { // load the saved data from Keychain. keychainItemData = [self secItemFormatToDictionary:(__bridge NSDictionary *)outDictionary]; } if(outDictionary) CFRelease(outDictionary); } return self; } - (void)setObject:(id)inObject forKey:(id)key { if (inObject == nil) return; id currentObject = [keychainItemData objectForKey:key]; if (![currentObject isEqual:inObject]) { [keychainItemData setObject:inObject forKey:key]; [self writeToKeychain]; } } - (id)objectForKey:(id)key { return [keychainItemData objectForKey:key]; } - (void)resetKeychainItem { if (!keychainItemData) { keychainItemData = [[NSMutableDictionary alloc] init]; } else if (keychainItemData) { NSMutableDictionary *tempDictionary = [self dictionaryToSecItemFormat:keychainItemData]; #ifndef NS_BLOCK_ASSERTIONS OSStatus junk = #endif SecItemDelete((__bridge CFDictionaryRef)tempDictionary); NSAssert( junk == noErr || junk == errSecItemNotFound, @"Problem deleting current dictionary." ); } // Default attributes for keychain item. [keychainItemData setObject:@"" forKey:(__bridge id)kSecAttrAccount]; [keychainItemData setObject:@"" forKey:(__bridge id)kSecAttrLabel]; [keychainItemData setObject:@"" forKey:(__bridge id)kSecAttrDescription]; // Default data for keychain item. #ifndef PASSWORD_USES_DATA [keychainItemData setObject:@"" forKey:(__bridge id)kSecValueData]; #else [keychainItemData setObject:[NSData data] forKey:(__bridge id)kSecValueData]; #endif } - (NSMutableDictionary *)dictionaryToSecItemFormat:(NSDictionary *)dictionaryToConvert { // The assumption is that this method will be called with a properly populated dictionary // containing all the right key/value pairs for a SecItem. // Create a dictionary to return populated with the attributes and data. NSMutableDictionary *returnDictionary = [NSMutableDictionary dictionaryWithDictionary:dictionaryToConvert]; // Add the Generic Password keychain item class attribute. [returnDictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass]; // Convert the NSString to NSData to meet the requirements for the value type kSecValueData. // This is where to store sensitive data that should be encrypted. #ifndef PASSWORD_USES_DATA // orig NSString *passwordString = [dictionaryToConvert objectForKey:(__bridge id)kSecValueData]; [returnDictionary setObject:[passwordString dataUsingEncoding:NSUTF8StringEncoding] forKey:(__bridge id)kSecValueData]; #else // DFH id val = [dictionaryToConvert objectForKey:(__bridge id)kSecValueData]; if([val isKindOfClass:[NSString class]]) { val = [(NSString *)val dataUsingEncoding:NSUTF8StringEncoding]; } [returnDictionary setObject:val forKey:(__bridge id)kSecValueData]; #endif return returnDictionary; } - (NSMutableDictionary *)secItemFormatToDictionary:(NSDictionary *)dictionaryToConvert { // The assumption is that this method will be called with a properly populated dictionary // containing all the right key/value pairs for the UI element. // Create a dictionary to return populated with the attributes and data. NSMutableDictionary *returnDictionary = [NSMutableDictionary dictionaryWithDictionary:dictionaryToConvert]; // Add the proper search key and class attribute. [returnDictionary setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData]; [returnDictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass]; // Acquire the password data from the attributes. CFDataRef passwordData = NULL; if (SecItemCopyMatching((__bridge CFDictionaryRef)returnDictionary, (CFTypeRef *)&passwordData) == noErr) { // Remove the search, class, and identifier key/value, we don't need them anymore. [returnDictionary removeObjectForKey:(__bridge id)kSecReturnData]; #ifndef PASSWORD_USES_DATA // Add the password to the dictionary, converting from NSData to NSString. NSString *password = [[NSString alloc] initWithBytes:[(__bridge NSData *)passwordData bytes] length:[(__bridge NSData *)passwordData length] encoding:NSUTF8StringEncoding]; #else NSData *password = (__bridge_transfer NSData *)passwordData; passwordData = NULL; #endif [returnDictionary setObject:password forKey:(__bridge id)kSecValueData]; } else { // Don't do anything if nothing is found. NSAssert(NO, @"Serious error, no matching item found in the keychain.\n"); } if(passwordData) CFRelease(passwordData); return returnDictionary; } - (void)writeToKeychain { CFDictionaryRef attributes = NULL; NSMutableDictionary *updateItem = nil; OSStatus result; if (SecItemCopyMatching((__bridge CFDictionaryRef)genericPasswordQuery, (CFTypeRef *)&attributes) == noErr) { // First we need the attributes from the Keychain. updateItem = [NSMutableDictionary dictionaryWithDictionary:(__bridge NSDictionary *)attributes]; // Second we need to add the appropriate search key/values. [updateItem setObject:[genericPasswordQuery objectForKey:(__bridge id)kSecClass] forKey:(__bridge id)kSecClass]; // Lastly, we need to set up the updated attribute list being careful to remove the class. NSMutableDictionary *tempCheck = [self dictionaryToSecItemFormat:keychainItemData]; [tempCheck removeObjectForKey:(__bridge id)kSecClass]; #if TARGET_IPHONE_SIMULATOR // Remove the access group if running on the iPhone simulator. // // Apps that are built for the simulator aren't signed, so there's no keychain access group // for the simulator to check. This means that all apps can see all keychain items when run // on the simulator. // // If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the // simulator will return -25243 (errSecNoAccessForItem). // // The access group attribute will be included in items returned by SecItemCopyMatching, // which is why we need to remove it before updating the item. [tempCheck removeObjectForKey:(__bridge id)kSecAttrAccessGroup]; #endif // An implicit assumption is that you can only update a single item at a time. #ifndef NDEBUG result = #endif SecItemUpdate((__bridge CFDictionaryRef)updateItem, (__bridge CFDictionaryRef)tempCheck); NSAssert( result == noErr, @"Couldn't update the Keychain Item." ); } else { // No previous item found; add the new one. result = SecItemAdd((__bridge CFDictionaryRef)[self dictionaryToSecItemFormat:keychainItemData], NULL); NSAssert( result == noErr, @"Couldn't add the Keychain Item." ); } if(attributes) CFRelease(attributes); } @end ================================================ FILE: ArcBit/External/LTHPasscodeViewController3.50/LTHKeychainUtils.h ================================================ // // SFHFKeychainUtils.h // // Created by Buzz Andersen on 10/20/08. // Based partly on code by Jonathan Wight, Jon Crosby, and Mike Malone. // Copyright 2008 Sci-Fi Hi-Fi. All rights reserved. // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation // files (the "Software"), to deal in the Software without // restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following // conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // #import /** Renamed it to LTHKeychainUtils because it could have conflicted with SFHFKeychainUtils already present in the project. This version is ARC-compliant, but all the rights and thanks go to Buzz Andersen for SFHFKeychainUtils. */ @interface LTHKeychainUtils : NSObject { } + (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error; + (BOOL) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error; + (BOOL) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error; @end ================================================ FILE: ArcBit/External/LTHPasscodeViewController3.50/LTHKeychainUtils.m ================================================ // // SFHFKeychainUtils.m // // Created by Buzz Andersen on 10/20/08. // Based partly on code by Jonathan Wight, Jon Crosby, and Mike Malone. // Copyright 2008 Sci-Fi Hi-Fi. All rights reserved. // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation // files (the "Software"), to deal in the Software without // restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following // conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // #import "LTHKeychainUtils.h" #import static NSString *SFHFKeychainUtilsErrorDomain = @"SFHFKeychainUtilsErrorDomain"; @implementation LTHKeychainUtils + (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error { if (!username || !serviceName) { if (error != nil) { *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil]; } return nil; } if (error != nil) { *error = nil; } // Set up a query dictionary with the base query attributes: item type (generic), username, and service NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass, kSecAttrAccount, kSecAttrService, nil]; NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword, username, serviceName, nil]; NSMutableDictionary *query = [[NSMutableDictionary alloc] initWithObjects: objects forKeys: keys]; // First do a query for attributes, in case we already have a Keychain item with no password data set. // One likely way such an incorrect item could have come about is due to the previous (incorrect) // version of this code (which set the password as a generic attribute instead of password data). NSMutableDictionary *attributeQuery = [query mutableCopy]; [attributeQuery setObject: (id) kCFBooleanTrue forKey:(__bridge_transfer id) kSecReturnAttributes]; CFTypeRef attrResult = NULL; OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef) attributeQuery, (CFTypeRef *) &attrResult); if (status != noErr) { // No existing item found--simply return nil for the password if (error != nil && status != errSecItemNotFound) { //Only return an error if a real exception happened--not simply for "not found." *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil]; } return nil; } // We have an existing item, now query for the password data associated with it. NSMutableDictionary *passwordQuery = [query mutableCopy]; [passwordQuery setObject: (id) kCFBooleanTrue forKey: (__bridge_transfer id) kSecReturnData]; CFTypeRef resData = NULL; status = SecItemCopyMatching((__bridge CFDictionaryRef) passwordQuery, (CFTypeRef *) &resData); NSData *resultData = (__bridge_transfer NSData *)resData; if (status != noErr) { if (status == errSecItemNotFound) { // We found attributes for the item previously, but no password now, so return a special error. // Users of this API will probably want to detect this error and prompt the user to // re-enter their credentials. When you attempt to store the re-entered credentials // using storeUsername:andPassword:forServiceName:updateExisting:error // the old, incorrect entry will be deleted and a new one with a properly encrypted // password will be added. if (error != nil) { *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -1999 userInfo: nil]; } } else { // Something else went wrong. Simply return the normal Keychain API error code. if (error != nil) { *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil]; } } return nil; } NSString *password = nil; if (resultData) { password = [[NSString alloc] initWithData: resultData encoding: NSUTF8StringEncoding]; } else { // There is an existing item, but we weren't able to get password data for it for some reason, // Possibly as a result of an item being incorrectly entered by the previous code. // Set the -1999 error so the code above us can prompt the user again. if (error != nil) { *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -1999 userInfo: nil]; } } return password; } + (BOOL) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error { if (!username || !password || !serviceName) { if (error != nil) { *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil]; } return NO; } // See if we already have a password entered for these credentials. NSError *getError = nil; NSString *existingPassword = [self getPasswordForUsername: username andServiceName: serviceName error:&getError]; if ([getError code] == -1999) { // There is an existing entry without a password properly stored (possibly as a result of the previous incorrect version of this code. // Delete the existing item before moving on entering a correct one. getError = nil; [self deleteItemForUsername: username andServiceName: serviceName error: &getError]; if ([getError code] != noErr) { if (error != nil) { *error = getError; } return NO; } } else if ([getError code] != noErr) { if (error != nil) { *error = getError; } return NO; } if (error != nil) { *error = nil; } OSStatus status = noErr; if (existingPassword) { // We have an existing, properly entered item with a password. // Update the existing item. if (![existingPassword isEqualToString:password] && updateExisting) { //Only update if we're allowed to update existing. If not, simply do nothing. NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass,kSecAttrService,kSecAttrLabel,kSecAttrAccount,nil]; NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword,serviceName,serviceName,username,nil]; NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys]; status = SecItemUpdate((__bridge CFDictionaryRef) query, (__bridge CFDictionaryRef) [NSDictionary dictionaryWithObject: [password dataUsingEncoding: NSUTF8StringEncoding] forKey: (__bridge_transfer NSString *) kSecValueData]); } } else { // No existing entry (or an existing, improperly entered, and therefore now // deleted, entry). Create a new entry. NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass,kSecAttrService,kSecAttrLabel,kSecAttrAccount,kSecValueData,nil]; NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword,serviceName,serviceName,username,[password dataUsingEncoding: NSUTF8StringEncoding],nil]; NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys]; status = SecItemAdd((__bridge CFDictionaryRef) query, NULL); } if (error != nil && status != noErr) { // Something went wrong with adding the new item. Return the Keychain error code. *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil]; return NO; } return YES; } + (BOOL) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error { if (!username || !serviceName) { if (error != nil) { *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil]; } return NO; } if (error != nil) { *error = nil; } NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass, kSecAttrAccount, kSecAttrService, kSecReturnAttributes, nil]; NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword, username, serviceName, kCFBooleanTrue, nil]; NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys]; OSStatus status = SecItemDelete((__bridge CFDictionaryRef) query); if (error != nil && status != noErr) { *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil]; return NO; } return YES; } @end ================================================ FILE: ArcBit/External/LTHPasscodeViewController3.50/LTHPasscodeViewController.h ================================================ // // PasscodeViewController.h // LTHPasscodeViewController // // Created by Roland Leth on 9/6/13. // Copyright (c) 2013 Roland Leth. All rights reserved. // #import @protocol LTHPasscodeViewControllerDelegate @optional /** @brief Called right before the passcode view controller will be dismissed or popped. */ - (void)passcodeViewControllerWillClose; /** @brief Called when the max number of failed attempts has been reached. */ - (void)maxNumberOfFailedAttemptsReached; /** @brief Called when the passcode was entered successfully. */ - (void)passcodeWasEnteredSuccessfully; /** @brief Called when the logout button was pressed. */ - (void)logoutButtonWasPressed; /** @brief Handle here the retrieval of the duration that needs to pass while app is in background for the lock to be displayed. @details Called when @c +timerDuration is called and @c +useKeychain:NO was used, but falls back to the Keychain anyway if not implemented. @return The duration. */ - (NSTimeInterval)timerDuration; /** @brief Handle here the saving of the duration that needs to pass while the app is in background for the lock to be displayed. @details Called when @c +saveTimerDuration: is called and @c +useKeychain:NO was used, but falls back to the Keychain anyway if not implemented. @param duration The duration. */ - (void)saveTimerDuration:(NSTimeInterval)duration; /** @brief Handle here the retrieval of the time at which the timer started. @details Called when @c +timerStartTime is called and @c +useKeychain:NO was used, but falls back to the Keychain anyway if not implemented. @return The time at which the timer started. */ - (NSTimeInterval)timerStartTime; /** @brief Handle here the saving of the current time. @details Called when @c +saveTimerStartTime is called and @c +useKeychain:NO was used, but falls back to the Keychain anyway if not implemented. */ - (void)saveTimerStartTime; /** @brief Handle here the check if the timer has ended and the lock has to be displayed. @details Called when @c +didPasscodeTimerEnd is called and @c +useKeychain:NO was used, but falls back to the Keychain anyway if not implemented. @return @c YES if the timer ended and the lock has to be displayed. */ - (BOOL)didPasscodeTimerEnd; /** @brief Handle here the passcode deletion. @details Called when @c +deletePasscode or @c +deletePasscodeAndClose are called and @c +useKeychain:NO was used, but falls back to the Keychain anyway if not implemented. */ - (void)deletePasscode; /** @brief Handle here the saving of the passcode. @details Called if @c +useKeychain:NO was used, but falls back to the Keychain anyway if not implemented. @param passcode The passcode. */ - (void)savePasscode:(NSString *)passcode; /** @brief Retrieve here the saved passcode. @details Called if @c +useKeychain:NO was used, but falls back to the Keychain anyway if not implemented. @return The passcode. */ - (NSString *)passcode; @end @interface LTHPasscodeViewController : UIViewController /** @brief The delegate. */ @property (nonatomic, weak) id delegate; /** @brief The gap between the passcode digits. */ @property (nonatomic, assign) CGFloat horizontalGap; /** @brief The gap between the top label and the passcode digits/field. */ @property (nonatomic, assign) CGFloat verticalGap; /** @brief The gap between the passcode digits and the failed label. */ @property (nonatomic, assign) CGFloat failedAttemptLabelGap; /** @brief The height for the complex passcode overlay. */ @property (nonatomic, assign) CGFloat passcodeOverlayHeight; /** @brief The font size for the top label. */ @property (nonatomic, assign) CGFloat labelFontSize; /** @brief The font size for the passcode digits. */ @property (nonatomic, assign) CGFloat passcodeFontSize; /** @brief The font for the top label. */ @property (nonatomic, strong) UIFont *labelFont; /** @brief The font for the passcode digits. */ @property (nonatomic, strong) UIFont *passcodeFont; /** @brief The background color for the top label. */ @property (nonatomic, strong) UIColor *enterPasscodeLabelBackgroundColor; /** @brief The background color for the view. */ @property (nonatomic, strong) UIColor *backgroundColor; /** @brief The background color for the cover view that appears on top of the app, visible in the multitasking. */ @property (nonatomic, strong) UIColor *coverViewBackgroundColor; /** @brief The background color for the passcode digits. */ @property (nonatomic, strong) UIColor *passcodeBackgroundColor; /** @brief The background color for the failed attempt label. */ @property (nonatomic, strong) UIColor *failedAttemptLabelBackgroundColor; /** @brief The text color for the top label. */ @property (nonatomic, strong) UIColor *labelTextColor; /** @brief The text color for the passcode digits. */ @property (nonatomic, strong) UIColor *passcodeTextColor; /** @brief The text color for the failed attempt label. */ @property (nonatomic, strong) UIColor *failedAttemptLabelTextColor; /** @brief The tint color to apply to the navigation items and bar button items. */ @property (nonatomic, strong) UIColor *navigationBarTintColor; /** @brief The tint color to apply to the navigation bar background. */ @property (nonatomic, strong) UIColor *navigationTintColor; /** @brief The color for te navigation bar's title. */ @property (nonatomic, strong) UIColor *navigationTitleColor; /** @brief The string to be used as username for the passcode in the Keychain. */ @property (nonatomic, strong) NSString *keychainPasscodeUsername; /** @brief The string to be used as username for the timer start time in the Keychain. */ @property (nonatomic, strong) NSString *keychainTimerStartUsername; /** @brief The string to be used as username for the timer duration in the Keychain. */ @property (nonatomic, strong) NSString *keychainTimerDurationUsername; /** @brief The string to be used as service name for all the Keychain entries. */ @property (nonatomic, strong) NSString *keychainServiceName; /** @brief The character for the passcode digit. */ @property (nonatomic, strong) NSString *passcodeCharacter; /** @brief The table name for NSLocalizedStringFromTable. */ @property (nonatomic, strong) NSString *localizationTableName; /** @brief The tag for the cover view. */ @property (nonatomic, assign) NSInteger coverViewTag; /** @brief The string displayed when entering your old passcode (while changing). */ @property (nonatomic, strong) NSString *enterOldPasscodeString; /** @brief The string displayed when entering your passcode. */ @property (nonatomic, strong) NSString *enterPasscodeString; /** @brief The string displayed when entering your new passcode (while changing). */ @property (nonatomic, strong) NSString *enterNewPasscodeString; /** @brief The string displayed when enabling your passcode. */ @property (nonatomic, strong) NSString *enablePasscodeString; /** @brief The string displayed when changing your passcode. */ @property (nonatomic, strong) NSString *changePasscodeString; /** @brief The string displayed when disabling your passcode. */ @property (nonatomic, strong) NSString *turnOffPasscodeString; /** @brief The string displayed when reentering your passcode. */ @property (nonatomic, strong) NSString *reenterPasscodeString; /** @brief The string displayed when reentering your new passcode (while changing). */ @property (nonatomic, strong) NSString *reenterNewPasscodeString; /** @brief The string displayed while user unlocks with Touch ID. */ @property (nonatomic, strong) NSString *touchIDString; /** @brief The duration of the lock animation. */ @property (nonatomic, assign) CGFloat lockAnimationDuration; /** @brief The duration of the slide animation. */ @property (nonatomic, assign) CGFloat slideAnimationDuration; /** @brief The maximum number of failed attempts allowed. */ @property (nonatomic, assign) NSInteger maxNumberOfAllowedFailedAttempts; /** @brief The navigation bar, if one was used. */ @property (nonatomic, strong) UINavigationBar *navBar; /** @brief A Boolean value that indicates whether the navigation bar is translucent (@c YES) or not (@c NO). */ @property (nonatomic, assign) BOOL navigationBarTranslucent; /** @brief A Boolean value that indicates whether the back bar button is hidden (@c YES) or not (@c NO). Default is @c YES. */ @property (nonatomic, assign) BOOL hidesBackButton; /** @brief A Boolean value that indicates whether Touch ID can be used (@c YES) or not (@c NO). Default is @c YES. */ @property (nonatomic, assign) BOOL allowUnlockWithTouchID; //BELOW PROPERTIES MADE PUBLIC BY TIMLEE @property (nonatomic, strong) UITextField *firstDigitTextField; @property (nonatomic, strong) UITextField *secondDigitTextField; @property (nonatomic, strong) UITextField *thirdDigitTextField; @property (nonatomic, strong) UITextField *fourthDigitTextField; @property (nonatomic, strong) UILabel *failedAttemptLabel; @property (nonatomic, strong) UILabel *enterPasscodeLabel; @property (nonatomic, strong) UIButton *OKButton; /** @brief Used for displaying the lock. The passcode view is added directly on the keyWindow. @param hasLogout Set to @c YES for a navBar with a Logout button, set to @c NO for no navBar. @param logoutTitle The title of the Logout button. */ - (void)showLockScreenWithAnimation:(BOOL)animated withLogout:(BOOL)hasLogout andLogoutTitle:(NSString*)logoutTitle; /** @brief Used for enabling the passcode. @details The back bar button is hidden by default. Set @c hidesBackButton to @c NO if you want it to be visible. @param viewController The view controller where the passcode view controller will be displayed. @param asModal Set to @c YES to present as a modal, or to @c NO to push on the current nav stack. */ - (void)showForEnablingPasscodeInViewController:(UIViewController *)viewController asModal:(BOOL)isModal; /** @brief Used for changing the passcode. @details The back bar button is hidden by default. Set @c hidesBackButton to @c NO if you want it to be visible. @param viewController The view controller where the passcode view controller will be displayed. @param asModal Set to @c YES to present as a modal, or to @c NO to push on the current nav stack. */ - (void)showForChangingPasscodeInViewController:(UIViewController *)viewController asModal:(BOOL)isModal; /** @brief Used for disabling the passcode. @details The back bar button is hidden by default. Set @c hidesBackButton to @c NO if you want it to be visible. @param viewController The view controller where the passcode view controller will be displayed. @param asModal Set to @c YES to present as a modal, or to @c NO to push on the current nav stack. */ - (void)showForDisablingPasscodeInViewController:(UIViewController *)viewController asModal:(BOOL)isModal; /** @brief Returns a Boolean value that indicates whether a simple, 4 digit (@c YES) or a complex passcode will be used (@c NO). @return @c YES if the passcode is simple, @c NO if the passcode is complex */ - (BOOL)isSimple; /** @brief Sets if the passcode should be simple (4 digits) or complex. @param isSimple Set to @c YES for a simple passcode, and to @c NO for a complex passcode. @param viewController The view controller where the passcode view controller will be displayed. @param isModal Set to @c YES to present as a modal, or to @c NO to push on the current nav stack. @details @c inViewController and @c asModal are needed because the delegate is of type id, and the passcode needs to be presented somewhere and with a specific style - modal or pushed. */ - (void)setIsSimple:(BOOL)isSimple inViewController:(UIViewController *)viewController asModal:(BOOL)isModal; /** @brief Returns a Boolean value that indicates whether a passcode exists (@c YES) or not (@c NO). @return @c YES if a passcode is enabled. This also means it is enabled, unless custom logic was added to the library. */ + (BOOL)doesPasscodeExist; /** @brief Retrieves from the keychain the duration while app is in background after which the lock has to be displayed. @return The duration. */ + (NSTimeInterval)timerDuration; /** @brief Saves in the keychain the duration that needs to pass while app is in background for the lock to be displayed. @param duration The duration. */ + (void)saveTimerDuration:(NSTimeInterval)duration; /** @brief Retrieves from the keychain the time at which the timer started. @return The time, as @c timeIntervalSinceReferenceDate, at which the timer started. */ + (NSTimeInterval)timerStartTime; /** @brief Saves the current time, as @c timeIntervalSinceReferenceDate. */ + (void)saveTimerStartTime; /** @brief Returns a Boolean value that indicates whether the timer has ended (@c YES) and the lock has to be displayed or not (@c NO). @return @c YES if the timer ended and the lock has to be displayed. */ + (BOOL)didPasscodeTimerEnd; /** @brief Removes the passcode from the keychain. */ + (void)deletePasscode; /** @brief Closes the passcode view controller. */ + (void)close; /** @brief Removes the passcode from the keychain and closes the passcode view controller. */ + (void)deletePasscodeAndClose; /** @brief Call this if you want to save and read the passcode and timers to and from somewhere else rather than the Keychain. @attention All the protocol methods will fall back to the Keychain if not implemented, even if calling this method with @c NO. This allows for flexibility over what and where you save. @param useKeychain Set to @c NO if you want to save and read the passcode and timers to and from somewhere else rather than the Keychain. Default is @c YES. */ + (void)useKeychain:(BOOL)useKeychain; /** @brief Returns the shared instance of the passcode view controller. */ + (instancetype)sharedUser; @end ================================================ FILE: ArcBit/External/LTHPasscodeViewController3.50/LTHPasscodeViewController.m ================================================ // // PasscodeViewController.m // LTHPasscodeViewController // // Created by Roland Leth on 9/6/13. // Copyright (c) 2013 Roland Leth. All rights reserved. // #import "LTHPasscodeViewController.h" #import "LTHKeychainUtils.h" #if !(TARGET_IPHONE_SIMULATOR) #import #endif #define DegreesToRadians(x) ((x) * M_PI / 180.0) #define LTHiOS8 ([[[UIDevice currentDevice] systemVersion] compare:@"8.0" \ options:NSNumericSearch] != NSOrderedAscending) #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 #define kPasscodeCharWidth [_passcodeCharacter sizeWithAttributes: @{NSFontAttributeName : _passcodeFont}].width #define kFailedAttemptLabelWidth (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ? [_failedAttemptLabel.text sizeWithAttributes: @{NSFontAttributeName : _labelFont}].width + 60.0f : [_failedAttemptLabel.text sizeWithAttributes: @{NSFontAttributeName : _labelFont}].width + 30.0f) #define kFailedAttemptLabelHeight [_failedAttemptLabel.text sizeWithAttributes: @{NSFontAttributeName : _labelFont}].height #define kEnterPasscodeLabelWidth [_enterPasscodeLabel.text sizeWithAttributes: @{NSFontAttributeName : _labelFont}].width #else // Thanks to Kent Nguyen - https://github.com/kentnguyen #define kPasscodeCharWidth [_passcodeCharacter sizeWithFont:_passcodeFont].width #define kFailedAttemptLabelWidth (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ? [_failedAttemptLabel.text sizeWithFont:_labelFont].width + 60.0f : [_failedAttemptLabel.text sizeWithFont:_labelFont].width + 20.0f) #define kFailedAttemptLabelHeight [_failedAttemptLabel.text sizeWithFont:_labelFont].height #define kEnterPasscodeLabelWidth [_enterPasscodeLabel.text sizeWithFont:_labelFont].width #endif @interface LTHPasscodeViewController () @property (nonatomic, strong) UIView *coverView; @property (nonatomic, strong) UIView *animatingView; @property (nonatomic, strong) UIView *complexPasscodeOverlayView; @property (nonatomic, strong) UITextField *passcodeTextField; //BELOW IS COMMENTED OUT BY TIMLEE //@property (nonatomic, strong) UITextField *firstDigitTextField; //@property (nonatomic, strong) UITextField *secondDigitTextField; //@property (nonatomic, strong) UITextField *thirdDigitTextField; //@property (nonatomic, strong) UITextField *fourthDigitTextField; //@property (nonatomic, strong) UILabel *failedAttemptLabel; //@property (nonatomic, strong) UILabel *enterPasscodeLabel; //@property (nonatomic, strong) UIButton *OKButton; @property (nonatomic, strong) NSString *tempPasscode; @property (nonatomic, assign) NSInteger failedAttempts; @property (nonatomic, assign) CGFloat modifierForBottomVerticalGap; @property (nonatomic, assign) CGFloat iPadFontSizeModifier; @property (nonatomic, assign) CGFloat iPhoneHorizontalGap; @property (nonatomic, assign) BOOL usesKeychain; @property (nonatomic, assign) BOOL displayedAsModal; @property (nonatomic, assign) BOOL displayedAsLockScreen; @property (nonatomic, assign) BOOL isCurrentlyOnScreen; @property (nonatomic, assign) BOOL isSimple;// YES by default @property (nonatomic, assign) BOOL isUserConfirmingPasscode; @property (nonatomic, assign) BOOL isUserBeingAskedForNewPasscode; @property (nonatomic, assign) BOOL isUserTurningPasscodeOff; @property (nonatomic, assign) BOOL isUserChangingPasscode; @property (nonatomic, assign) BOOL isUserEnablingPasscode; @property (nonatomic, assign) BOOL isUserSwitchingBetweenPasscodeModes;// simple/complex @property (nonatomic, assign) BOOL timerStartInSeconds; #if !(TARGET_IPHONE_SIMULATOR) @property (nonatomic, strong) LAContext *context; #endif @end @implementation LTHPasscodeViewController #pragma mark - Public, class methods + (BOOL)doesPasscodeExist { return [[LTHPasscodeViewController sharedUser] _doesPasscodeExist]; } + (NSString *)passcode { return [[LTHPasscodeViewController sharedUser] _passcode]; } + (NSTimeInterval)timerDuration { return [[LTHPasscodeViewController sharedUser] _timerDuration]; } + (void)saveTimerDuration:(NSTimeInterval)duration { [[LTHPasscodeViewController sharedUser] _saveTimerDuration:duration]; } + (NSTimeInterval)timerStartTime { return [[LTHPasscodeViewController sharedUser] _timerStartTime]; } + (void)saveTimerStartTime { [[LTHPasscodeViewController sharedUser] _saveTimerStartTime]; } + (BOOL)didPasscodeTimerEnd { return [[LTHPasscodeViewController sharedUser] _didPasscodeTimerEnd]; } + (void)deletePasscodeAndClose { [LTHPasscodeViewController deletePasscode]; [LTHPasscodeViewController close]; } + (void)close { [[LTHPasscodeViewController sharedUser] _close]; } + (void)deletePasscode { [[LTHPasscodeViewController sharedUser] _deletePasscode]; } + (void)useKeychain:(BOOL)useKeychain { [[LTHPasscodeViewController sharedUser] _useKeychain:useKeychain]; } #pragma mark - Private methods - (void)_close { if (_displayedAsLockScreen) [self _dismissMe]; else [self _cancelAndDismissMe]; } - (void)_useKeychain:(BOOL)useKeychain { _usesKeychain = useKeychain; } - (BOOL)_doesPasscodeExist { return [self _passcode].length != 0; } - (NSTimeInterval)_timerDuration { if (!_usesKeychain && [self.delegate respondsToSelector:@selector(timerDuration)]) { return [self.delegate timerDuration]; } NSString *keychainValue = [LTHKeychainUtils getPasswordForUsername:_keychainTimerDurationUsername andServiceName:_keychainServiceName error:nil]; if (!keychainValue) return -1; return keychainValue.doubleValue; } - (void)_saveTimerDuration:(NSTimeInterval) duration { if (!_usesKeychain && [self.delegate respondsToSelector:@selector(saveTimerDuration:)]) { [self.delegate saveTimerDuration:duration]; return; } [LTHKeychainUtils storeUsername:_keychainTimerDurationUsername andPassword:[NSString stringWithFormat: @"%.6f", duration] forServiceName:_keychainServiceName updateExisting:YES error:nil]; } - (NSTimeInterval)_timerStartTime { if (!_usesKeychain && [self.delegate respondsToSelector:@selector(timerStartTime)]) { return [self.delegate timerStartTime]; } NSString *keychainValue = [LTHKeychainUtils getPasswordForUsername:_keychainTimerStartUsername andServiceName:_keychainServiceName error:nil]; if (!keychainValue) return -1; return keychainValue.doubleValue; } - (void)_saveTimerStartTime { if (!_usesKeychain && [self.delegate respondsToSelector:@selector(saveTimerStartTime)]) { [self.delegate saveTimerStartTime]; return; } [LTHKeychainUtils storeUsername:_keychainTimerStartUsername andPassword:[NSString stringWithFormat: @"%.6f", [NSDate timeIntervalSinceReferenceDate]] forServiceName:_keychainServiceName updateExisting:YES error:nil]; } - (BOOL)_didPasscodeTimerEnd { if (!_usesKeychain && [self.delegate respondsToSelector:@selector(didPasscodeTimerEnd)]) { return [self.delegate didPasscodeTimerEnd]; } NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate]; // startTime wasn't saved yet (first app use and it crashed, phone force // closed, etc) if it returns -1. if (now - [self _timerStartTime] >= [self _timerDuration] || [self _timerStartTime] == -1) return YES; return NO; } - (void)_deletePasscode { if (!_usesKeychain && [self.delegate respondsToSelector:@selector(deletePasscode)]) { [self.delegate deletePasscode]; return; } [LTHKeychainUtils deleteItemForUsername:_keychainPasscodeUsername andServiceName:_keychainServiceName error:nil]; } - (void)_savePasscode:(NSString *)passcode { if (!_usesKeychain && [self.delegate respondsToSelector:@selector(savePasscode:)]) { [self.delegate savePasscode:passcode]; return; } [LTHKeychainUtils storeUsername:_keychainPasscodeUsername andPassword:passcode forServiceName:_keychainServiceName updateExisting:YES error:nil]; } - (NSString *)_passcode { if (!_usesKeychain && [self.delegate respondsToSelector:@selector(passcode)]) { return [self.delegate passcode]; } return [LTHKeychainUtils getPasswordForUsername:_keychainPasscodeUsername andServiceName:_keychainServiceName error:nil]; } #if !(TARGET_IPHONE_SIMULATOR) - (void)_setupFingerPrint { if (!self.context && _allowUnlockWithTouchID) { self.context = [[LAContext alloc] init]; NSError *error = nil; if ([self.context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]) { if (error) { return; } // Authenticate User [self.context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:NSLocalizedStringFromTable(self.touchIDString, _localizationTableName, @"") reply:^(BOOL success, NSError *error) { if (error) { dispatch_async(dispatch_get_main_queue(), ^{ [self _resetUI]; }); self.context = nil; return; } if (success) { dispatch_async(dispatch_get_main_queue(), ^{ [self _dismissMe]; if ([self.delegate respondsToSelector: @selector(passcodeWasEnteredSuccessfully)]) { [self.delegate performSelector: @selector(passcodeWasEnteredSuccessfully)]; } }); } else { dispatch_async(dispatch_get_main_queue(), ^{ [self _resetUI]; }); } self.context = nil; }]; } } } #endif #pragma mark - View life - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = _backgroundColor; _failedAttempts = 0; _animatingView = [[UIView alloc] initWithFrame: self.view.frame]; [self.view addSubview: _animatingView]; [self _setupViews]; [self _setupLabels]; [self _setupDigitFields]; [self _setupOKButton]; _passcodeTextField = [[UITextField alloc] initWithFrame: CGRectZero]; _passcodeTextField.delegate = self; _passcodeTextField.secureTextEntry = YES; _passcodeTextField.translatesAutoresizingMaskIntoConstraints = NO; [self.view setNeedsUpdateConstraints]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [_passcodeTextField becomeFirstResponder]; } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; if (!_passcodeTextField.isFirstResponder) [_passcodeTextField becomeFirstResponder]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; if (!_displayedAsModal && !_displayedAsLockScreen) { [self textFieldShouldEndEditing:_passcodeTextField]; } } - (void)_cancelAndDismissMe { _isCurrentlyOnScreen = NO; _isUserBeingAskedForNewPasscode = NO; _isUserChangingPasscode = NO; _isUserConfirmingPasscode = NO; _isUserEnablingPasscode = NO; _isUserTurningPasscodeOff = NO; _isUserSwitchingBetweenPasscodeModes = NO; [self _resetUI]; [_passcodeTextField resignFirstResponder]; if ([self.delegate respondsToSelector: @selector(passcodeViewControllerWillClose)]) { [self.delegate performSelector: @selector(passcodeViewControllerWillClose)]; } // Or, if you prefer by notifications: // [[NSNotificationCenter defaultCenter] postNotificationName: @"passcodeViewControllerWillClose" // object: self // userInfo: nil]; if (_displayedAsModal) [self dismissViewControllerAnimated:YES completion:nil]; else if (!_displayedAsLockScreen) [self.navigationController popViewControllerAnimated:YES]; } - (void)_dismissMe { _failedAttempts = 0; _isCurrentlyOnScreen = NO; [self _resetUI]; [_passcodeTextField resignFirstResponder]; [UIView animateWithDuration: _lockAnimationDuration animations: ^{ if (_displayedAsLockScreen) { if (LTHiOS8) { self.view.center = CGPointMake(self.view.center.x, self.view.center.y * 2.f); } else { if ([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationLandscapeLeft) { self.view.center = CGPointMake(self.view.center.x * -1.f, self.view.center.y); } else if ([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationLandscapeRight) { self.view.center = CGPointMake(self.view.center.x * 2.f, self.view.center.y); } else if ([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationPortrait) { self.view.center = CGPointMake(self.view.center.x, self.view.center.y * -1.f); } else { self.view.center = CGPointMake(self.view.center.x, self.view.center.y * 2.f); } } } else { // Delete from Keychain if (_isUserTurningPasscodeOff) { [self _deletePasscode]; } // Update the Keychain if adding or changing passcode else { [self _savePasscode:_tempPasscode]; //finalize type switching if (_isUserSwitchingBetweenPasscodeModes) { _isUserConfirmingPasscode = NO; [self setIsSimple:!self.isSimple inViewController:nil asModal:_displayedAsModal]; } } } } completion: ^(BOOL finished) { if ([self.delegate respondsToSelector: @selector(passcodeViewControllerWillClose)]) { [self.delegate performSelector: @selector(passcodeViewControllerWillClose)]; } // Or, if you prefer by notifications: // [[NSNotificationCenter defaultCenter] postNotificationName: @"passcodeViewControllerWillClose" // object: self // userInfo: nil]; if (_displayedAsLockScreen) { [self.view removeFromSuperview]; [self removeFromParentViewController]; } else if (_displayedAsModal) { [self dismissViewControllerAnimated:YES completion:nil]; } else if (!_displayedAsLockScreen) { [self.navigationController popViewControllerAnimated:NO]; } }]; [[NSNotificationCenter defaultCenter] removeObserver: self name: UIApplicationDidChangeStatusBarOrientationNotification object: nil]; [[NSNotificationCenter defaultCenter] removeObserver: self name: UIApplicationDidChangeStatusBarFrameNotification object: nil]; } #pragma mark - UI setup - (void)_setupViews { _coverView = [[UIView alloc] initWithFrame: CGRectZero]; _coverView.backgroundColor = _coverViewBackgroundColor; _coverView.frame = self.view.frame; _coverView.userInteractionEnabled = NO; _coverView.tag = _coverViewTag; _coverView.hidden = YES; [[UIApplication sharedApplication].keyWindow addSubview: _coverView]; _complexPasscodeOverlayView = [[UIView alloc] initWithFrame:CGRectZero]; _complexPasscodeOverlayView.backgroundColor = [UIColor whiteColor]; _complexPasscodeOverlayView.translatesAutoresizingMaskIntoConstraints = NO; [_animatingView addSubview:_complexPasscodeOverlayView]; } - (void)_setupLabels { _enterPasscodeLabel = [[UILabel alloc] initWithFrame: CGRectZero]; _enterPasscodeLabel.backgroundColor = _enterPasscodeLabelBackgroundColor; _enterPasscodeLabel.numberOfLines = 0; _enterPasscodeLabel.textColor = _labelTextColor; _enterPasscodeLabel.font = _labelFont; _enterPasscodeLabel.textAlignment = NSTextAlignmentCenter; [_animatingView addSubview: _enterPasscodeLabel]; // It is also used to display the "Passcodes did not match" error message // if the user fails to confirm the passcode. _failedAttemptLabel = [[UILabel alloc] initWithFrame: CGRectZero]; _failedAttemptLabel.text = @"1 Passcode Failed Attempt"; _failedAttemptLabel.numberOfLines = 0; _failedAttemptLabel.backgroundColor = _failedAttemptLabelBackgroundColor; _failedAttemptLabel.hidden = YES; _failedAttemptLabel.textColor = _failedAttemptLabelTextColor; _failedAttemptLabel.font = _labelFont; _failedAttemptLabel.textAlignment = NSTextAlignmentCenter; [_animatingView addSubview: _failedAttemptLabel]; _enterPasscodeLabel.text = _isUserChangingPasscode ? NSLocalizedStringFromTable(self.enterOldPasscodeString, _localizationTableName, @"") : NSLocalizedStringFromTable(self.enterPasscodeString, _localizationTableName, @""); _enterPasscodeLabel.translatesAutoresizingMaskIntoConstraints = NO; _failedAttemptLabel.translatesAutoresizingMaskIntoConstraints = NO; } - (void)_setupDigitFields { _firstDigitTextField = [self _makeDigitField]; [_animatingView addSubview:_firstDigitTextField]; _secondDigitTextField = [self _makeDigitField]; [_animatingView addSubview:_secondDigitTextField]; _thirdDigitTextField = [self _makeDigitField]; [_animatingView addSubview:_thirdDigitTextField]; _fourthDigitTextField = [self _makeDigitField]; [_animatingView addSubview:_fourthDigitTextField]; } - (UITextField *)_makeDigitField{ UITextField *field = [[UITextField alloc] initWithFrame:CGRectZero]; field.backgroundColor = _passcodeBackgroundColor; field.textAlignment = NSTextAlignmentCenter; field.text = _passcodeCharacter; field.textColor = _passcodeTextColor; field.font = _passcodeFont; field.secureTextEntry = NO; field.userInteractionEnabled = NO; field.translatesAutoresizingMaskIntoConstraints = NO; [field setBorderStyle:UITextBorderStyleNone]; return field; } - (void)_setupOKButton { _OKButton = [UIButton buttonWithType:UIButtonTypeCustom]; [_OKButton setTitle:NSLocalizedStringFromTable(@"OK", _localizationTableName, nil) forState:UIControlStateNormal]; _OKButton.titleLabel.font = _labelFont; _OKButton.backgroundColor = _enterPasscodeLabelBackgroundColor; [_OKButton setTitleColor:_labelTextColor forState:UIControlStateNormal]; [_OKButton setTitleColor:[UIColor blackColor] forState:UIControlStateHighlighted]; [_OKButton addTarget:self action:@selector(_validateComplexPasscode) forControlEvents:UIControlEventTouchUpInside]; [_complexPasscodeOverlayView addSubview:_OKButton]; _OKButton.hidden = YES; _OKButton.translatesAutoresizingMaskIntoConstraints = NO; } - (void)updateViewConstraints { [super updateViewConstraints]; [self.view removeConstraints:self.view.constraints]; [_animatingView removeConstraints:_animatingView.constraints]; _firstDigitTextField.hidden = !self.isSimple; _secondDigitTextField.hidden = !self.isSimple; _thirdDigitTextField.hidden = !self.isSimple; _fourthDigitTextField.hidden = !self.isSimple; _complexPasscodeOverlayView.hidden = self.isSimple; _passcodeTextField.hidden = self.isSimple; _passcodeTextField.keyboardType = self.isSimple ? UIKeyboardTypeNumberPad : UIKeyboardTypeASCIICapable; [_passcodeTextField reloadInputViews]; if (self.isSimple) { [_animatingView addSubview:_passcodeTextField]; } else { [_complexPasscodeOverlayView addSubview:_passcodeTextField]; // If we come from simple state some constraints are added even if // translatesAutoresizingMaskIntoConstraints = NO, // because no constraints are added manually in that case [_passcodeTextField removeConstraints:_passcodeTextField.constraints]; } // MARK: Please read // The controller works properly on all devices and orientations, but looks odd on iPhone's landscape. // Usually, lockscreens on iPhone are kept portrait-only, though. It also doesn't fit inside a modal when landscape. // That's why only portrait is selected for iPhone's supported orientations. // Modify this to fit your needs. CGFloat yOffsetFromCenter = -self.view.frame.size.height * 0.24; NSLayoutConstraint *enterPasscodeConstraintCenterX = [NSLayoutConstraint constraintWithItem: _enterPasscodeLabel attribute: NSLayoutAttributeCenterX relatedBy: NSLayoutRelationEqual toItem: _animatingView attribute: NSLayoutAttributeCenterX multiplier: 1.0f constant: 0.0f]; NSLayoutConstraint *enterPasscodeConstraintCenterY = [NSLayoutConstraint constraintWithItem: _enterPasscodeLabel attribute: NSLayoutAttributeCenterY relatedBy: NSLayoutRelationEqual toItem: _animatingView attribute: NSLayoutAttributeCenterY multiplier: 1.0f constant: yOffsetFromCenter]; [self.view addConstraint: enterPasscodeConstraintCenterX]; [self.view addConstraint: enterPasscodeConstraintCenterY]; if (self.isSimple) { NSLayoutConstraint *firstDigitX = [NSLayoutConstraint constraintWithItem: _firstDigitTextField attribute: NSLayoutAttributeLeft relatedBy: NSLayoutRelationEqual toItem: _animatingView attribute: NSLayoutAttributeCenterX multiplier: 1.0f constant: - _horizontalGap * 1.5f - 2.0f]; NSLayoutConstraint *secondDigitX = [NSLayoutConstraint constraintWithItem: _secondDigitTextField attribute: NSLayoutAttributeLeft relatedBy: NSLayoutRelationEqual toItem: _animatingView attribute: NSLayoutAttributeCenterX multiplier: 1.0f constant: - _horizontalGap * 2/3 - 2.0f]; NSLayoutConstraint *thirdDigitX = [NSLayoutConstraint constraintWithItem: _thirdDigitTextField attribute: NSLayoutAttributeLeft relatedBy: NSLayoutRelationEqual toItem: _animatingView attribute: NSLayoutAttributeCenterX multiplier: 1.0f constant: _horizontalGap * 1/6 - 2.0f]; NSLayoutConstraint *fourthDigitX = [NSLayoutConstraint constraintWithItem: _fourthDigitTextField attribute: NSLayoutAttributeLeft relatedBy: NSLayoutRelationEqual toItem: _animatingView attribute: NSLayoutAttributeCenterX multiplier: 1.0f constant: _horizontalGap - 2.0f]; NSLayoutConstraint *firstDigitY = [NSLayoutConstraint constraintWithItem: _firstDigitTextField attribute: NSLayoutAttributeCenterY relatedBy: NSLayoutRelationEqual toItem: _enterPasscodeLabel attribute: NSLayoutAttributeBottom multiplier: 1.0f constant: _verticalGap]; NSLayoutConstraint *secondDigitY = [NSLayoutConstraint constraintWithItem: _secondDigitTextField attribute: NSLayoutAttributeCenterY relatedBy: NSLayoutRelationEqual toItem: _enterPasscodeLabel attribute: NSLayoutAttributeBottom multiplier: 1.0f constant: _verticalGap]; NSLayoutConstraint *thirdDigitY = [NSLayoutConstraint constraintWithItem: _thirdDigitTextField attribute: NSLayoutAttributeCenterY relatedBy: NSLayoutRelationEqual toItem: _enterPasscodeLabel attribute: NSLayoutAttributeBottom multiplier: 1.0f constant: _verticalGap]; NSLayoutConstraint *fourthDigitY = [NSLayoutConstraint constraintWithItem: _fourthDigitTextField attribute: NSLayoutAttributeCenterY relatedBy: NSLayoutRelationEqual toItem: _enterPasscodeLabel attribute: NSLayoutAttributeBottom multiplier: 1.0f constant: _verticalGap]; [self.view addConstraint:firstDigitX]; [self.view addConstraint:secondDigitX]; [self.view addConstraint:thirdDigitX]; [self.view addConstraint:fourthDigitX]; [self.view addConstraint:firstDigitY]; [self.view addConstraint:secondDigitY]; [self.view addConstraint:thirdDigitY]; [self.view addConstraint:fourthDigitY]; } else { NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(_passcodeTextField, _OKButton); //TODO: specify different offsets through metrics NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-10-[_passcodeTextField]-5-[_OKButton]-10-|" options:0 metrics:nil views:viewsDictionary]; [self.view addConstraints:constraints]; constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-5-[_passcodeTextField]-5-|" options:0 metrics:nil views:viewsDictionary]; [self.view addConstraints:constraints]; NSLayoutConstraint *buttonY = [NSLayoutConstraint constraintWithItem: _OKButton attribute: NSLayoutAttributeCenterY relatedBy: NSLayoutRelationEqual toItem: _passcodeTextField attribute: NSLayoutAttributeCenterY multiplier: 1.0f constant: 0.0f]; [self.view addConstraint:buttonY]; NSLayoutConstraint *buttonHeight = [NSLayoutConstraint constraintWithItem: _OKButton attribute: NSLayoutAttributeHeight relatedBy: NSLayoutRelationEqual toItem: _passcodeTextField attribute: NSLayoutAttributeHeight multiplier: 1.0f constant: 0.0f]; [self.view addConstraint:buttonHeight]; NSLayoutConstraint *overlayViewLeftConstraint = [NSLayoutConstraint constraintWithItem: _complexPasscodeOverlayView attribute: NSLayoutAttributeLeft relatedBy: NSLayoutRelationEqual toItem: _animatingView attribute: NSLayoutAttributeLeft multiplier: 1.0f constant: 0.0f]; NSLayoutConstraint *overlayViewY = [NSLayoutConstraint constraintWithItem: _complexPasscodeOverlayView attribute: NSLayoutAttributeCenterY relatedBy: NSLayoutRelationEqual toItem: _enterPasscodeLabel attribute: NSLayoutAttributeBottom multiplier: 1.0f constant: _verticalGap]; NSLayoutConstraint *overlayViewHeight = [NSLayoutConstraint constraintWithItem: _complexPasscodeOverlayView attribute: NSLayoutAttributeHeight relatedBy: NSLayoutRelationEqual toItem: nil attribute: NSLayoutAttributeNotAnAttribute multiplier: 1.0f constant: _passcodeOverlayHeight]; NSLayoutConstraint *overlayViewWidth = [NSLayoutConstraint constraintWithItem: _complexPasscodeOverlayView attribute: NSLayoutAttributeWidth relatedBy: NSLayoutRelationEqual toItem: _animatingView attribute: NSLayoutAttributeWidth multiplier: 1.0f constant: 0.0f]; [self.view addConstraints:@[overlayViewLeftConstraint, overlayViewY, overlayViewHeight, overlayViewWidth]]; } NSLayoutConstraint *failedAttemptLabelCenterX = [NSLayoutConstraint constraintWithItem: _failedAttemptLabel attribute: NSLayoutAttributeCenterX relatedBy: NSLayoutRelationEqual toItem: _animatingView attribute: NSLayoutAttributeCenterX multiplier: 1.0f constant: 0.0f]; NSLayoutConstraint *failedAttemptLabelCenterY = [NSLayoutConstraint constraintWithItem: _failedAttemptLabel attribute: NSLayoutAttributeCenterY relatedBy: NSLayoutRelationEqual toItem: _enterPasscodeLabel attribute: NSLayoutAttributeBottom multiplier: 1.0f constant: _failedAttemptLabelGap]; NSLayoutConstraint *failedAttemptLabelWidth = [NSLayoutConstraint constraintWithItem: _failedAttemptLabel attribute: NSLayoutAttributeWidth relatedBy: NSLayoutRelationGreaterThanOrEqual toItem: nil attribute: NSLayoutAttributeNotAnAttribute multiplier: 1.0f constant: kFailedAttemptLabelWidth]; NSLayoutConstraint *failedAttemptLabelHeight = [NSLayoutConstraint constraintWithItem: _failedAttemptLabel attribute: NSLayoutAttributeHeight relatedBy: NSLayoutRelationEqual toItem: nil attribute: NSLayoutAttributeNotAnAttribute multiplier: 1.0f constant: kFailedAttemptLabelHeight + 6.0f]; [self.view addConstraint:failedAttemptLabelCenterX]; [self.view addConstraint:failedAttemptLabelCenterY]; [self.view addConstraint:failedAttemptLabelWidth]; [self.view addConstraint:failedAttemptLabelHeight]; // NSLog(@"constraints %@", self.view.constraints); // NSLog(@"_passcodeTextField %@", _passcodeTextField.constraints); } #pragma mark - Displaying - (void)showLockscreenWithoutAnimation { [self showLockScreenWithAnimation:NO withLogout:NO andLogoutTitle:nil]; } - (void)showLockScreenWithAnimation:(BOOL)animated withLogout:(BOOL)hasLogout andLogoutTitle:(NSString*)logoutTitle { [self _prepareAsLockScreen]; // In case the user leaves the app while the lockscreen is already active. if (!_isCurrentlyOnScreen) { // Usually, the app's window is the first on the stack. I'm doing this because if an alertView or actionSheet // is open when presenting the lockscreen causes problems, because the av/as has it's own window that replaces the keyWindow // and due to how Apple handles said window internally. // Currently the lockscreen appears behind the av/as, which is the best compromise for now. // You can read and/or give a hand following one of the links below // http://stackoverflow.com/questions/19816142/uialertviews-uiactionsheets-and-keywindow-problems // https://github.com/rolandleth/LTHPasscodeViewController/issues/16 // Usually not more than one window is needed, but your needs may vary; modify below. // Also, in case the control doesn't work properly, // try it with .keyWindow before anything else, it might work. // UIWindow *mainWindow = [UIApplication sharedApplication].keyWindow; UIWindow *mainWindow = [UIApplication sharedApplication].windows[0]; [mainWindow addSubview: self.view]; // [mainWindow.rootViewController addChildViewController: self]; // All this hassle because a view added to UIWindow does not rotate automatically // and if we would have added the view anywhere else, it wouldn't display properly // (having a modal on screen when the user leaves the app, for example). [self rotateAccordingToStatusBarOrientationAndSupportedOrientations]; CGPoint newCenter; [self statusBarFrameOrOrientationChanged:nil]; if (LTHiOS8) { self.view.center = CGPointMake(self.view.center.x, self.view.center.y * -1.f); newCenter = CGPointMake(mainWindow.center.x, mainWindow.center.y + self.navigationController.navigationBar.frame.size.height / 2); } else { if ([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationLandscapeLeft) { self.view.center = CGPointMake(self.view.center.x * -1.f, self.view.center.y); newCenter = CGPointMake(mainWindow.center.x - self.navigationController.navigationBar.frame.size.height / 2, mainWindow.center.y); } else if ([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationLandscapeRight) { self.view.center = CGPointMake(self.view.center.x * 2.f, self.view.center.y); newCenter = CGPointMake(mainWindow.center.x + self.navigationController.navigationBar.frame.size.height / 2, mainWindow.center.y); } else if ([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationPortrait) { self.view.center = CGPointMake(self.view.center.x, self.view.center.y * -1.f); newCenter = CGPointMake(mainWindow.center.x, mainWindow.center.y - self.navigationController.navigationBar.frame.size.height / 2); } else { self.view.center = CGPointMake(self.view.center.x, self.view.center.y * 2.f); newCenter = CGPointMake(mainWindow.center.x, mainWindow.center.y + self.navigationController.navigationBar.frame.size.height / 2); } } if (animated) { [UIView animateWithDuration: _lockAnimationDuration animations: ^{ self.view.center = newCenter; }]; } else { self.view.center = newCenter; } // Add nav bar & logout button if specified if (hasLogout) { // Navigation Bar with custom UI self.navBar = [[UINavigationBar alloc] initWithFrame:CGRectMake(0, mainWindow.frame.origin.y, mainWindow.frame.size.width, 64)]; self.navBar.tintColor = self.navigationTintColor; if ([self respondsToSelector:@selector(setEdgesForExtendedLayout:)]) { self.navBar.barTintColor = self.navigationBarTintColor; self.navBar.translucent = self.navigationBarTranslucent; } if (self.navigationTitleColor) { self.navBar.titleTextAttributes = @{ NSForegroundColorAttributeName : self.navigationTitleColor }; } // Navigation item UIBarButtonItem *leftButton = [[UIBarButtonItem alloc] initWithTitle:logoutTitle style:UIBarButtonItemStyleDone target:self action:@selector(_logoutWasPressed)]; UINavigationItem *item = [[UINavigationItem alloc] initWithTitle:self.title]; item.leftBarButtonItem = leftButton; item.hidesBackButton = YES; [self.navBar pushNavigationItem:item animated:NO]; [mainWindow addSubview:self.navBar]; } _isCurrentlyOnScreen = YES; } } - (void)_prepareNavigationControllerWithController:(UIViewController *)viewController { self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(_cancelAndDismissMe)]; if (!_displayedAsModal) { [viewController.navigationController pushViewController:self animated:YES]; self.navigationItem.hidesBackButton = _hidesBackButton; [self rotateAccordingToStatusBarOrientationAndSupportedOrientations]; return; } UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:self]; // Make sure nav bar for logout is off the screen [self.navBar removeFromSuperview]; self.navBar = nil; // Customize navigation bar // Make sure UITextAttributeTextColor is not set to nil // barTintColor & translucent is only called on iOS7+ navController.navigationBar.tintColor = self.navigationTintColor; if ([self respondsToSelector:@selector(setEdgesForExtendedLayout:)]) { navController.navigationBar.barTintColor = self.navigationBarTintColor; navController.navigationBar.translucent = self.navigationBarTranslucent; } if (self.navigationTitleColor) { navController.navigationBar.titleTextAttributes = @{ NSForegroundColorAttributeName : self.navigationTitleColor }; } [viewController presentViewController:navController animated:YES completion:nil]; [self rotateAccordingToStatusBarOrientationAndSupportedOrientations]; } - (void)showForEnablingPasscodeInViewController:(UIViewController *)viewController asModal:(BOOL)isModal { _displayedAsModal = isModal; [self _prepareForEnablingPasscode]; [self _prepareNavigationControllerWithController:viewController]; self.title = NSLocalizedStringFromTable(self.enablePasscodeString, _localizationTableName, @""); } - (void)showForChangingPasscodeInViewController:(UIViewController *)viewController asModal:(BOOL)isModal { _displayedAsModal = isModal; [self _prepareForChangingPasscode]; [self _prepareNavigationControllerWithController:viewController]; self.title = NSLocalizedStringFromTable(self.changePasscodeString, _localizationTableName, @""); } - (void)showForDisablingPasscodeInViewController:(UIViewController *)viewController asModal:(BOOL)isModal { _displayedAsModal = isModal; [self _prepareForTurningOffPasscode]; [self _prepareNavigationControllerWithController:viewController]; self.title = NSLocalizedStringFromTable(self.turnOffPasscodeString, _localizationTableName, @""); } #pragma mark - Preparing - (void)_prepareAsLockScreen { // In case the user leaves the app while changing/disabling Passcode. if (_isCurrentlyOnScreen && !_displayedAsLockScreen) { [self _cancelAndDismissMe]; } _displayedAsLockScreen = YES; _isUserTurningPasscodeOff = NO; _isUserChangingPasscode = NO; _isUserConfirmingPasscode = NO; _isUserEnablingPasscode = NO; _isUserSwitchingBetweenPasscodeModes = NO; [self _resetUI]; #if !(TARGET_IPHONE_SIMULATOR) [self _setupFingerPrint]; #endif } - (void)_prepareForChangingPasscode { _isCurrentlyOnScreen = YES; _displayedAsLockScreen = NO; _isUserTurningPasscodeOff = NO; _isUserChangingPasscode = YES; _isUserConfirmingPasscode = NO; _isUserEnablingPasscode = NO; [self _resetUI]; } - (void)_prepareForTurningOffPasscode { _isCurrentlyOnScreen = YES; _displayedAsLockScreen = NO; _isUserTurningPasscodeOff = YES; _isUserChangingPasscode = NO; _isUserConfirmingPasscode = NO; _isUserEnablingPasscode = NO; _isUserSwitchingBetweenPasscodeModes = NO; [self _resetUI]; } - (void)_prepareForEnablingPasscode { _isCurrentlyOnScreen = YES; _displayedAsLockScreen = NO; _isUserTurningPasscodeOff = NO; _isUserChangingPasscode = NO; _isUserConfirmingPasscode = NO; _isUserEnablingPasscode = YES; _isUserSwitchingBetweenPasscodeModes = NO; [self _resetUI]; } #pragma mark - UITextFieldDelegate - (BOOL)textFieldShouldEndEditing:(UITextField *)textField { if (!_displayedAsLockScreen && !_displayedAsModal) return YES; return !_isCurrentlyOnScreen; } - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { if ([string isEqualToString: @"\n"]) return NO; NSString *typedString = [textField.text stringByReplacingCharactersInRange: range withString: string]; if (self.isSimple) { if (typedString.length >= 1) _firstDigitTextField.secureTextEntry = YES; else _firstDigitTextField.secureTextEntry = NO; if (typedString.length >= 2) _secondDigitTextField.secureTextEntry = YES; else _secondDigitTextField.secureTextEntry = NO; if (typedString.length >= 3) _thirdDigitTextField.secureTextEntry = YES; else _thirdDigitTextField.secureTextEntry = NO; if (typedString.length >= 4) _fourthDigitTextField.secureTextEntry = YES; else _fourthDigitTextField.secureTextEntry = NO; if (typedString.length == 4) { // Make the last bullet show up [self performSelector: @selector(_validatePasscode:) withObject: typedString afterDelay: 0.15]; } if (typedString.length > 4) return NO; } else _OKButton.hidden = [typedString length] == 0; return YES; } #pragma mark - Validation - (void)_validateComplexPasscode { NSLog(@"isValid %@", [self _validatePasscode:_passcodeTextField.text] ? @"YES" : @"NO"); } - (BOOL)_validatePasscode:(NSString *)typedString { NSString *savedPasscode = [self _passcode]; // Entering from Settings. If savedPasscode is empty, it means // the user is setting a new Passcode now, or is changing his current Passcode. if ((_isUserChangingPasscode || savedPasscode.length == 0) && !_isUserTurningPasscodeOff) { // Either the user is being asked for a new passcode, confirmation comes next, // either he is setting up a new passcode, confirmation comes next, still. // We need the !_isUserConfirmingPasscode condition, because if he's adding a new Passcode, // then savedPasscode is still empty and the condition will always be true, not passing this point. if ((_isUserBeingAskedForNewPasscode || savedPasscode.length == 0) && !_isUserConfirmingPasscode) { _tempPasscode = typedString; // The delay is to give time for the last bullet to appear [self performSelector:@selector(_askForConfirmationPasscode) withObject:nil afterDelay:0.15f]; } // User entered his Passcode correctly and we are at the confirming screen. else if (_isUserConfirmingPasscode) { // User entered the confirmation Passcode correctly if ([typedString isEqualToString: _tempPasscode]) { [self _dismissMe]; } // User entered the confirmation Passcode incorrectly, start over. else { [self performSelector:@selector(_reAskForNewPasscode) withObject:nil afterDelay:_slideAnimationDuration]; } } // Changing Passcode and the entered Passcode is correct. else if ([typedString isEqualToString:savedPasscode]){ [self performSelector:@selector(_askForNewPasscode) withObject:nil afterDelay:_slideAnimationDuration]; _failedAttempts = 0; } // Acting as lockscreen and the entered Passcode is incorrect. else { [self performSelector: @selector(_denyAccess) withObject: nil afterDelay: _slideAnimationDuration]; return NO; } } // App launch/Turning passcode off: Passcode OK -> dismiss, Passcode incorrect -> deny access. else { if ([typedString isEqualToString: savedPasscode]) { // Or, if you prefer by notifications: // [[NSNotificationCenter defaultCenter] postNotificationName: @"passcodeWasEnteredSuccessfully" // object: self // userInfo: nil]; [self _dismissMe]; if ([self.delegate respondsToSelector: @selector(passcodeWasEnteredSuccessfully)]) { [self.delegate performSelector: @selector(passcodeWasEnteredSuccessfully)]; } } else { [self performSelector: @selector(_denyAccess) withObject: nil afterDelay: _slideAnimationDuration]; return NO; } } return YES; } #pragma mark - Actions - (void)_askForNewPasscode { _isUserBeingAskedForNewPasscode = YES; _isUserConfirmingPasscode = NO; // Update layout considering type [self.view setNeedsUpdateConstraints]; _failedAttemptLabel.hidden = YES; CATransition *transition = [CATransition animation]; [transition setDelegate: self]; [self performSelector: @selector(_resetUI) withObject: nil afterDelay: 0.1f]; [transition setType: kCATransitionPush]; [transition setSubtype: kCATransitionFromRight]; [transition setDuration: _slideAnimationDuration]; [transition setTimingFunction: [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseInEaseOut]]; [[_animatingView layer] addAnimation: transition forKey: @"swipe"]; } - (void)_reAskForNewPasscode { _isUserBeingAskedForNewPasscode = YES; _isUserConfirmingPasscode = NO; _tempPasscode = @""; CATransition *transition = [CATransition animation]; [transition setDelegate: self]; [self performSelector: @selector(_resetUIForReEnteringNewPasscode) withObject: nil afterDelay: 0.1f]; [transition setType: kCATransitionPush]; [transition setSubtype: kCATransitionFromRight]; [transition setDuration: _slideAnimationDuration]; [transition setTimingFunction: [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseInEaseOut]]; [[_animatingView layer] addAnimation: transition forKey: @"swipe"]; } - (void)_askForConfirmationPasscode { _isUserBeingAskedForNewPasscode = NO; _isUserConfirmingPasscode = YES; _failedAttemptLabel.hidden = YES; CATransition *transition = [CATransition animation]; [transition setDelegate: self]; [self performSelector: @selector(_resetUI) withObject: nil afterDelay: 0.1f]; [transition setType: kCATransitionPush]; [transition setSubtype: kCATransitionFromRight]; [transition setDuration: _slideAnimationDuration]; [transition setTimingFunction: [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseInEaseOut]]; [[_animatingView layer] addAnimation: transition forKey: @"swipe"]; } - (void)_denyAccess { [self _resetTextFields]; _passcodeTextField.text = @""; _OKButton.hidden = YES; _failedAttempts++; if (_maxNumberOfAllowedFailedAttempts > 0 && _failedAttempts == _maxNumberOfAllowedFailedAttempts && [self.delegate respondsToSelector: @selector(maxNumberOfFailedAttemptsReached)]) { [self.delegate maxNumberOfFailedAttemptsReached]; } // Or, if you prefer by notifications: // [[NSNotificationCenter defaultCenter] postNotificationName: @"maxNumberOfFailedAttemptsReached" // object: self // userInfo: nil]; if (_failedAttempts == 1) { _failedAttemptLabel.text = NSLocalizedStringFromTable(@"1 Passcode Failed Attempt", _localizationTableName, @""); } else { _failedAttemptLabel.text = [NSString stringWithFormat: NSLocalizedStringFromTable(@"%i Passcode Failed Attempts", _localizationTableName, @""), _failedAttempts]; } _failedAttemptLabel.layer.cornerRadius = kFailedAttemptLabelHeight * 0.65f; _failedAttemptLabel.clipsToBounds = true; _failedAttemptLabel.hidden = NO; } - (void)_logoutWasPressed { // Notify delegate that logout button was pressed if ([self.delegate respondsToSelector: @selector(logoutButtonWasPressed)]) { [self.delegate logoutButtonWasPressed]; } } - (void)_resetTextFields { if (![_passcodeTextField isFirstResponder]) [_passcodeTextField becomeFirstResponder]; _firstDigitTextField.secureTextEntry = NO; _secondDigitTextField.secureTextEntry = NO; _thirdDigitTextField.secureTextEntry = NO; _fourthDigitTextField.secureTextEntry = NO; } - (void)_resetUI { [self _resetTextFields]; _failedAttemptLabel.backgroundColor = _failedAttemptLabelBackgroundColor; _failedAttemptLabel.textColor = _failedAttemptLabelTextColor; if (_failedAttempts == 0) _failedAttemptLabel.hidden = YES; _passcodeTextField.text = @""; if (_isUserConfirmingPasscode) { if (_isUserEnablingPasscode) { _enterPasscodeLabel.text = NSLocalizedStringFromTable(self.reenterPasscodeString, _localizationTableName, @""); } else if (_isUserChangingPasscode) { _enterPasscodeLabel.text = NSLocalizedStringFromTable(self.reenterNewPasscodeString, _localizationTableName, @""); } } else if (_isUserBeingAskedForNewPasscode) { if (_isUserEnablingPasscode || _isUserChangingPasscode) { _enterPasscodeLabel.text = NSLocalizedStringFromTable(self.enterNewPasscodeString, _localizationTableName, @""); } } else { if (_isUserChangingPasscode) { _enterPasscodeLabel.text = NSLocalizedStringFromTable(self.enterOldPasscodeString, _localizationTableName, @""); } else { _enterPasscodeLabel.text = NSLocalizedStringFromTable(self.enterPasscodeString, _localizationTableName, @""); } } // Make sure nav bar for logout is off the screen [self.navBar removeFromSuperview]; self.navBar = nil; _OKButton.hidden = YES; } - (void)_resetUIForReEnteringNewPasscode { [self _resetTextFields]; _passcodeTextField.text = @""; // If there's no passcode saved in Keychain, // the user is adding one for the first time, otherwise he's changing his passcode. NSString *savedPasscode = [LTHKeychainUtils getPasswordForUsername: _keychainPasscodeUsername andServiceName: _keychainServiceName error: nil]; _enterPasscodeLabel.text = savedPasscode.length == 0 ? NSLocalizedStringFromTable(self.enterPasscodeString, _localizationTableName, @"") : NSLocalizedStringFromTable(self.enterNewPasscodeString, _localizationTableName, @""); _failedAttemptLabel.hidden = NO; _failedAttemptLabel.text = NSLocalizedStringFromTable(@"Passcodes did not match. Try again.", _localizationTableName, @""); _failedAttemptLabel.backgroundColor = [UIColor clearColor]; _failedAttemptLabel.layer.borderWidth = 0; _failedAttemptLabel.layer.borderColor = [UIColor clearColor].CGColor; _failedAttemptLabel.textColor = _labelTextColor; } - (void)setIsSimple:(BOOL)isSimple inViewController:(UIViewController *)viewController asModal:(BOOL)isModal{ if (!_isUserSwitchingBetweenPasscodeModes && !_isUserBeingAskedForNewPasscode && [self _doesPasscodeExist]) { // User trying to change passcode type while having passcode already _isUserSwitchingBetweenPasscodeModes = YES; // Display modified change passcode flow starting with input once passcode // of current type and then 2 times new one of another type [self showForChangingPasscodeInViewController:viewController asModal:isModal]; } else { _isUserSwitchingBetweenPasscodeModes = NO; _isSimple = isSimple; [self.view setNeedsUpdateConstraints]; } } - (BOOL)isSimple { // Is in process of changing, but not finished -> // we need to display UI accordingly if (_isUserSwitchingBetweenPasscodeModes && (_isUserBeingAskedForNewPasscode || _isUserConfirmingPasscode)) { return !_isSimple; } return _isSimple; } #pragma mark - Notification Observers - (void)_applicationDidEnterBackground { //BELOW IS COMMENTED OUT BY TIMLEE /* if ([self _doesPasscodeExist]) { if ([_passcodeTextField isFirstResponder]) [_passcodeTextField resignFirstResponder]; // Without animation because otherwise it won't come down fast enough, // so inside iOS' multitasking view the app won't be covered by anything. if ([self _timerDuration] <= 0) { // This is here and the rest in willEnterForeground because when self is pushed // instead of presented as a modal, // the app would be visible from the multitasking view. if (_isCurrentlyOnScreen && !_displayedAsModal) return; [self showLockScreenWithAnimation:NO withLogout:NO andLogoutTitle:nil]; } else { _coverView.hidden = NO; if (![[UIApplication sharedApplication].keyWindow viewWithTag: _coverViewTag]) [[UIApplication sharedApplication].keyWindow addSubview: _coverView]; } } //*/ } - (void)_applicationDidBecomeActive { _coverView.hidden = YES; } - (void)_applicationWillEnterForeground { /* if ([self _doesPasscodeExist] && [self _didPasscodeTimerEnd]) { // This is here instead of didEnterBackground because when self is pushed // instead of presented as a modal, // the app would be visible from the multitasking view. if (!_displayedAsModal && !_displayedAsLockScreen && _isCurrentlyOnScreen) { [_passcodeTextField resignFirstResponder]; [self.navigationController popViewControllerAnimated:NO]; // This is like this because it screws up the navigation stack otherwise [self performSelector:@selector(showLockscreenWithoutAnimation) withObject:nil afterDelay:0.0]; } else { [self showLockScreenWithAnimation:NO withLogout:NO andLogoutTitle:nil]; } } //*/ } - (void)_applicationWillResignActive { if ([self _doesPasscodeExist] && !([self isCurrentlyOnScreen] && [self displayedAsLockScreen])) { [self _saveTimerStartTime]; } } #pragma mark - Init + (instancetype)sharedUser { __strong static LTHPasscodeViewController *sharedObject = nil; static dispatch_once_t pred; dispatch_once(&pred, ^{ sharedObject = [[LTHPasscodeViewController alloc] init]; }); return sharedObject; } - (id)init { self = [super init]; if (self) { [self _commonInit]; } return self; } - (id)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) { [self _commonInit]; } return self; } - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { [self _commonInit]; } return self; } - (void)_commonInit { _isSimple = YES; [self _loadDefaults]; [self _addObservers]; } - (void)_loadDefaults { [self _loadMiscDefaults]; [self _loadStringDefaults]; [self _loadGapDefaults]; [self _loadFontDefaults]; [self _loadColorDefaults]; [self _loadKeychainDefaults]; } - (void)_loadMiscDefaults { _coverViewTag = 994499; _lockAnimationDuration = 0.25; _slideAnimationDuration = 0.15; _maxNumberOfAllowedFailedAttempts = 0; _usesKeychain = YES; _displayedAsModal = YES; _hidesBackButton = YES; _allowUnlockWithTouchID = YES; _passcodeCharacter = @"\u2014"; // A longer "-"; _localizationTableName = @"LTHPasscodeViewController"; } - (void)_loadStringDefaults { self.enterOldPasscodeString = @"Enter your old passcode"; self.enterPasscodeString = @"Enter your passcode"; self.enablePasscodeString = @"Enable Passcode"; self.changePasscodeString = @"Change Passcode"; self.turnOffPasscodeString = @"Turn Off Passcode"; self.reenterPasscodeString = @"Re-enter your passcode"; self.reenterNewPasscodeString = @"Re-enter your new passcode"; self.enterNewPasscodeString = @"Enter your new passcode"; self.touchIDString = @"Unlock using Touch ID"; } - (void)_loadGapDefaults { _iPadFontSizeModifier = 1.5; _iPhoneHorizontalGap = 40.0; _horizontalGap = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ? _iPhoneHorizontalGap * _iPadFontSizeModifier : _iPhoneHorizontalGap; _verticalGap = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ? 60.0f : 25.0f; _modifierForBottomVerticalGap = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ? 2.6f : 3.0f; _failedAttemptLabelGap = _verticalGap * _modifierForBottomVerticalGap - 2.0f; _passcodeOverlayHeight = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ? 96.0f : 40.0f; } - (void)_loadFontDefaults { _labelFontSize = 15.0; _passcodeFontSize = 33.0; _labelFont = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ? [UIFont fontWithName: @"AvenirNext-Regular" size:_labelFontSize * _iPadFontSizeModifier] : [UIFont fontWithName: @"AvenirNext-Regular" size:_labelFontSize]; _passcodeFont = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ? [UIFont fontWithName: @"AvenirNext-Regular" size: _passcodeFontSize * _iPadFontSizeModifier] : [UIFont fontWithName: @"AvenirNext-Regular" size: _passcodeFontSize]; } - (void)_loadColorDefaults { // Backgrounds _backgroundColor = [UIColor colorWithRed:0.97f green:0.97f blue:1.0f alpha:1.00f]; _passcodeBackgroundColor = [UIColor clearColor]; _coverViewBackgroundColor = [UIColor colorWithRed:0.97f green:0.97f blue:1.0f alpha:1.00f]; _failedAttemptLabelBackgroundColor = [UIColor colorWithRed:0.8f green:0.1f blue:0.2f alpha:1.000f]; _enterPasscodeLabelBackgroundColor = [UIColor clearColor]; // Text _labelTextColor = [UIColor colorWithWhite:0.31f alpha:1.0f]; _passcodeTextColor = [UIColor colorWithWhite:0.31f alpha:1.0f]; _failedAttemptLabelTextColor = [UIColor whiteColor]; } - (void)_loadKeychainDefaults { _keychainPasscodeUsername = @"demoPasscode"; _keychainTimerStartUsername = @"demoPasscodeTimerStart"; _keychainServiceName = @"demoServiceName"; _keychainTimerDurationUsername = @"passcodeTimerDuration"; } - (void)_addObservers { [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(_applicationDidEnterBackground) name: UIApplicationDidEnterBackgroundNotification object: nil]; [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(_applicationWillResignActive) name: UIApplicationWillResignActiveNotification object: nil]; [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(_applicationDidBecomeActive) name: UIApplicationDidBecomeActiveNotification object: nil]; [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(_applicationWillEnterForeground) name: UIApplicationWillEnterForegroundNotification object: nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarFrameOrOrientationChanged:) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarFrameOrOrientationChanged:) name:UIApplicationDidChangeStatusBarFrameNotification object:nil]; } #pragma mark - Handling rotation - (NSUInteger)supportedInterfaceOrientations { if (_displayedAsLockScreen) return LTHiOS8 ? UIInterfaceOrientationMaskPortrait : UIInterfaceOrientationMaskAll; // I'll be honest and mention I have no idea why this line of code below works. // Without it, if you present the passcode view as lockscreen (directly on the window) // and then inside of a modal, the orientation will be wrong. // If you could explain why, I'd be more than grateful :) return UIInterfaceOrientationPortraitUpsideDown; } // All of the rotation handling is thanks to Håvard Fossli's - https://github.com/hfossli // answer: http://stackoverflow.com/a/4960988/793916 - (void)statusBarFrameOrOrientationChanged:(NSNotification *)notification { /* This notification is most likely triggered inside an animation block, therefore no animation is needed to perform this nice transition. */ [self rotateAccordingToStatusBarOrientationAndSupportedOrientations]; if (LTHiOS8) { _animatingView.frame = self.view.frame; } else { if (UIInterfaceOrientationIsPortrait([UIApplication sharedApplication].statusBarOrientation)) { _animatingView.frame = CGRectMake(0, 0, [UIApplication sharedApplication].keyWindow.frame.size.width, [UIApplication sharedApplication].keyWindow.frame.size.height); } else { _animatingView.frame = CGRectMake(0, 0, [UIApplication sharedApplication].keyWindow.frame.size.height, [UIApplication sharedApplication].keyWindow.frame.size.width); } } } // And to his AGWindowView: https://github.com/hfossli/AGWindowView // Without the 'desiredOrientation' method, using showLockscreen in one orientation, // then presenting it inside a modal in another orientation would display // the view in the first orientation. - (UIInterfaceOrientation)desiredOrientation { UIInterfaceOrientation statusBarOrientation = [[UIApplication sharedApplication] statusBarOrientation]; UIInterfaceOrientationMask statusBarOrientationAsMask = UIInterfaceOrientationMaskFromOrientation(statusBarOrientation); if(self.supportedInterfaceOrientations & statusBarOrientationAsMask) { return statusBarOrientation; } else { if(self.supportedInterfaceOrientations & UIInterfaceOrientationMaskPortrait) { return UIInterfaceOrientationPortrait; } else if(self.supportedInterfaceOrientations & UIInterfaceOrientationMaskLandscapeLeft) { return UIInterfaceOrientationLandscapeLeft; } else if(self.supportedInterfaceOrientations & UIInterfaceOrientationMaskLandscapeRight) { return UIInterfaceOrientationLandscapeRight; } else { return UIInterfaceOrientationPortraitUpsideDown; } } } - (void)rotateAccordingToStatusBarOrientationAndSupportedOrientations { UIInterfaceOrientation orientation = [self desiredOrientation]; CGFloat angle = UIInterfaceOrientationAngleOfOrientation(orientation); CGAffineTransform transform = CGAffineTransformMakeRotation(angle); [self setIfNotEqualTransform: transform frame: self.view.window.bounds]; } - (void)setIfNotEqualTransform:(CGAffineTransform)transform frame:(CGRect)frame { if(!CGAffineTransformEqualToTransform(self.view.transform, transform)) { self.view.transform = transform; } if(!CGRectEqualToRect(self.view.frame, frame)) { self.view.frame = frame; } } + (CGFloat)getStatusBarHeight { UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; if (UIInterfaceOrientationIsLandscape(orientation)) { return [UIApplication sharedApplication].statusBarFrame.size.width; } else { return [UIApplication sharedApplication].statusBarFrame.size.height; } } CGFloat UIInterfaceOrientationAngleOfOrientation(UIInterfaceOrientation orientation) { CGFloat angle; switch (orientation) { case UIInterfaceOrientationPortraitUpsideDown: angle = M_PI; break; case UIInterfaceOrientationLandscapeLeft: angle = -M_PI_2; break; case UIInterfaceOrientationLandscapeRight: angle = M_PI_2; break; default: angle = 0.0; break; } return angle; } UIInterfaceOrientationMask UIInterfaceOrientationMaskFromOrientation(UIInterfaceOrientation orientation) { return 1 << orientation; } @end ================================================ FILE: ArcBit/External/Localizations/de.lproj/LTHPasscodeViewController.strings ================================================ /* LTHPasscodeViewController.strings */ "Enter Passcode" = "Pincode eingeben"; "Enter your old passcode" = "Alten Pincode eingeben"; "Enter your passcode" = "Geben Sie Ihren Pincode ein"; "Enable Passcode" = "Pincode aktivieren"; "Change Passcode" = "Pincode ändern"; "Turn Off Passcode" = "Pincode deaktivieren"; "Re-enter your passcode" = "Pincode wiederholen"; "Re-enter your new passcode" = "Neuen Pincode wiederholen"; "Enter your new passcode" = "Geben Sie Ihren neuen Pincode ein"; "Passcodes did not match. Try again." = "Pincodes nicht ident. Bitte nochmals versuchen."; "1 Passcode Failed Attempt" = "1 fehlgeschlagener Eingabeversuch"; "%i Passcode Failed Attempts" = "%i fehlgeschlagene Eingabeversuche"; ================================================ FILE: ArcBit/External/Localizations/en.lproj/LTHPasscodeViewController.strings ================================================ /* LTHPasscodeViewController.strings */ "Enter Passcode" = "Enter Passcode"; "Enter your old passcode" = "Enter your old passcode"; "Enter your passcode" = "Enter your passcode"; "Enable Passcode" = "Enable Passcode"; "Change Passcode" = "Change Passcode"; "Turn Off Passcode" = "Turn Off Passcode"; "Re-enter your passcode" = "Re-enter your passcode"; "Re-enter your new passcode" = "Re-enter your new passcode"; "Enter your new passcode" = "Enter your new passcode"; "Passcodes did not match. Try again." = "Passcodes did not match. Try again."; "1 Passcode Failed Attempt" = "1 Passcode Failed Attempt"; "%i Passcode Failed Attempts" = "%i Passcode Failed Attempts"; ================================================ FILE: ArcBit/External/Localizations/es.lproj/LTHPasscodeViewController.strings ================================================ /* LTHPasscodeViewController.strings */ "Enter Passcode" = "Insertar contraseña"; "Enter your old passcode" = "Insertar contraseña antigua"; "Enter your passcode" = "Inserte su contraseña"; "Enable Passcode" = "Activar contraseña"; "Change Passcode" = "Cambiar contraseña"; "Turn Off Passcode" = "Desactivar contraseña"; "Re-enter your passcode" = "Repetir contraseña"; "Re-enter your new passcode" = "Repetir contraseña nueva"; "Enter your new passcode" = "Inserte su contraseña nueva"; "Passcodes did not match. Try again." = "Contraseña no son iguales. Por favor tratalo otra vez."; "1 Passcode Failed Attempt" = "1 inteto de entrada falló"; "%i Passcode Failed Attempts" = "%i intentos de entrada fallaron"; ================================================ FILE: ArcBit/External/Localizations/fr.lproj/LTHPasscodeViewController.strings ================================================ /* LTHPasscodeViewController.strings */ "Enter Passcode" = "Entrer votre mot de passe"; "Enter your old passcode" = "Entrer votre ancien mot de passe"; "Enter your passcode" = "Entrer votre mot de passe"; "Enable Passcode" = "Enter votre mot de passe"; "Change Passcode" = "Changer votre mot de passe"; "Turn Off Passcode" = "Supprimer votre mot de passe"; "Re-enter your passcode" = "Réentrer votre mot de passe"; "Re-enter your new passcode" = "Réentrer votre nouveau mot de passe"; "Enter your new passcode" = "Entrer votre nouveau mot de passe"; "Passcodes did not match. Try again." = "Les mots de passe ne correspondent pas. Essayer à nouveau."; "1 Passcode Failed Attempt" = "1 échec"; "%i Passcode Failed Attempts" = "%i échecs"; ================================================ FILE: ArcBit/External/Localizations/ja.lproj/LTHPasscodeViewController.strings ================================================ /* LTHPasscodeViewController.strings */ "Enter Passcode" = "パスコードを入力"; "Enter your old passcode" = "古いパスコードを入力"; "Enter your passcode" = "パスコードを入力"; "Enable Passcode" = "パスコードを設定"; "Change Passcode" = "パスコードを変更"; "Turn Off Passcode" = "パスコードをオフ"; "Re-enter your passcode" = "パスコードを再入力"; "Re-enter your new passcode" = "新しいパスコードを再入力"; "Enter your new passcode" = "新しいパスコードを入力"; "Passcodes did not match. Try again." = "パスコードが一致しません。\nもう一度入力してください。"; "1 Passcode Failed Attempt" = "パスコード入力に1回失敗"; "%i Passcode Failed Attempts" = "パスコード入力に%i回失敗"; ================================================ FILE: ArcBit/External/Localizations/ro.lproj/LTHPasscodeViewController.strings ================================================ /* LTHPasscodeViewController.strings */ "Enter Passcode" = "Introduceți parola"; "Enter your old passcode" = "Introduceți parola veche"; "Enter your passcode" = "Introduceți parola"; "Enable Passcode" = "Activați parola"; "Change Passcode" = "Schimbați parola"; "Turn Off Passcode" = "Dezactivați parola"; "Re-enter your passcode" = "Reintroduceți parola"; "Re-enter your new passcode" = "Reintroduceți parola nouă"; "Enter your new passcode" = "Introduceți parola nouă"; "Passcodes did not match. Try again." = "Parolele nu coincid. Incercați din nou."; "1 Passcode Failed Attempt" = "1 încercare eșuată"; "%i Passcode Failed Attempts" = "%i încercări eșuate"; ================================================ FILE: ArcBit/External/Localizations/ru.lproj/LTHPasscodeViewController.strings ================================================ /* LTHPasscodeViewController.strings */ "Enter Passcode" = "Введите пароль"; "Enter your old passcode" = "Введите старый пароль"; "Enter your passcode" = "Введите свой пароль"; "Enable Passcode" = "Включить пароль"; "Change Passcode" = "Изменить пароль"; "Turn Off Passcode" = "Выключить пароль"; "Re-enter your passcode" = "Повторно введите пароль"; "Re-enter your new passcode" = "Повторно введите новый пароль"; "Enter your new passcode" = "Введите новый пароль"; "Passcodes did not match. Try again." = "Пароли не совпадают. Попробуйте снова."; "1 Passcode Failed Attempt" = "1 Неудачная попытка ввода пароля"; "%i Passcode Failed Attempts" = "%i Неудачная попытка "; ================================================ FILE: ArcBit/External/Localizations/zh-Hans-CN.lproj/LTHPasscodeViewController.strings ================================================ /* LTHPasscodeViewController.strings */ "Enter Passcode" = "输入密码"; "Enter your old passcode" = "输入老的密码"; "Enter your passcode" = "输入你的密码"; "Enable Passcode" = "启用密码"; "Change Passcode" = "改密码"; "Turn Off Passcode" = "不启用密码"; "Re-enter your passcode" = "重新输入你的密码"; "Re-enter your new passcode" = "重新输入你的新密码"; "Enter your new passcode" = "输入你的新密码"; "Passcodes did not match. Try again." = "密码不正确,请重试!"; "1 Passcode Failed Attempt" = "密码尝试失败一次"; "%i Passcode Failed Attempts" = "%i 次密码尝试失败"; ================================================ FILE: ArcBit/External/Localizations/zh-Hant.lproj/LTHPasscodeViewController.strings ================================================ /* LTHPasscodeViewController.strings */ "Enter Passcode" = "輸入密碼"; "Enter your old passcode" = "輸入您的舊密碼"; "Enter your passcode" = "輸入您的密碼"; "Enable Passcode" = "啟用密碼"; "Change Passcode" = "更改密碼"; "Turn Off Passcode" = "關閉密碼"; "Re-enter your passcode" = "重新輸入您的密碼"; "Re-enter your new passcode" = "重新輸入新的密碼"; "Enter your new passcode" = "輸入新的密碼"; "Passcodes did not match. Try again." = "密碼不匹配。再試一次"; "1 Passcode Failed Attempt" = "密碼嘗試失敗一次"; "%i Passcode Failed Attempts" = "%i 次密碼失敗嘗試"; ================================================ FILE: ArcBit/External/MySocketRocketExtras/SRWebSocket+Helpers.h ================================================ // // SRWebSocket+Helpers.h // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA #import "SRWebSocket.h" @interface SRWebSocket (Helpers) + (NSMutableURLRequest*)createURLRequest:(NSString*)urlString withPinnedCert:(NSData*)certData; @end ================================================ FILE: ArcBit/External/MySocketRocketExtras/SRWebSocket+Helpers.m ================================================ // // SRWebSocket+Helpers.m // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA #import "SRWebSocket+Helpers.h" @implementation SRWebSocket (Helpers) + (NSMutableURLRequest*)createURLRequest:(NSString*)urlString withPinnedCert:(NSData*)certData { NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:urlString]]; if (certData != nil) { CFDataRef certDataRef = (__bridge CFDataRef)certData; SecCertificateRef certRef = SecCertificateCreateWithData(NULL, certDataRef); id certificate = (__bridge id)certRef; [request setSR_SSLPinnedCertificates:@[certificate]]; } return request; } @end ================================================ FILE: ArcBit/External/NSDate-Extensions/NSDate-Utilities.h ================================================ /* Erica Sadun, http://ericasadun.com iPhone Developer's Cookbook 3.x and beyond BSD License, Use at your own risk */ #import #define D_MINUTE 60 #define D_HOUR 3600 #define D_DAY 86400 #define D_WEEK 604800 #define D_YEAR 31556926 @interface NSDate (Utilities) // Relative dates from the current date + (NSDate *) dateTomorrow; + (NSDate *) dateYesterday; + (NSDate *) dateWithDaysFromNow: (NSInteger) days; + (NSDate *) dateWithDaysBeforeNow: (NSInteger) days; + (NSDate *) dateWithHoursFromNow: (NSInteger) dHours; + (NSDate *) dateWithHoursBeforeNow: (NSInteger) dHours; + (NSDate *) dateWithMinutesFromNow: (NSInteger) dMinutes; + (NSDate *) dateWithMinutesBeforeNow: (NSInteger) dMinutes; // Comparing dates - (BOOL) isEqualToDateIgnoringTime: (NSDate *) aDate; - (BOOL) isToday; - (BOOL) isTomorrow; - (BOOL) isYesterday; - (BOOL) isSameWeekAsDate: (NSDate *) aDate; - (BOOL) isThisWeek; - (BOOL) isNextWeek; - (BOOL) isLastWeek; - (BOOL) isSameMonthAsDate: (NSDate *) aDate; - (BOOL) isThisMonth; - (BOOL) isSameYearAsDate: (NSDate *) aDate; - (BOOL) isThisYear; - (BOOL) isNextYear; - (BOOL) isLastYear; - (BOOL) isEarlierThanDate: (NSDate *) aDate; - (BOOL) isLaterThanDate: (NSDate *) aDate; - (BOOL) isInFuture; - (BOOL) isInPast; // Date roles - (BOOL) isTypicallyWorkday; - (BOOL) isTypicallyWeekend; // Adjusting dates - (NSDate *) dateByAddingDays: (NSInteger) dDays; - (NSDate *) dateBySubtractingDays: (NSInteger) dDays; - (NSDate *) dateByAddingHours: (NSInteger) dHours; - (NSDate *) dateBySubtractingHours: (NSInteger) dHours; - (NSDate *) dateByAddingMinutes: (NSInteger) dMinutes; - (NSDate *) dateBySubtractingMinutes: (NSInteger) dMinutes; - (NSDate *) dateAtStartOfDay; // Retrieving intervals - (NSInteger) minutesAfterDate: (NSDate *) aDate; - (NSInteger) minutesBeforeDate: (NSDate *) aDate; - (NSInteger) hoursAfterDate: (NSDate *) aDate; - (NSInteger) hoursBeforeDate: (NSDate *) aDate; - (NSInteger) daysAfterDate: (NSDate *) aDate; - (NSInteger) daysBeforeDate: (NSDate *) aDate; - (NSInteger)distanceInDaysToDate:(NSDate *)anotherDate; // Decomposing dates @property (readonly) NSInteger nearestHour; @property (readonly) NSInteger hour; @property (readonly) NSInteger minute; @property (readonly) NSInteger seconds; @property (readonly) NSInteger day; @property (readonly) NSInteger month; @property (readonly) NSInteger week; @property (readonly) NSInteger weekday; @property (readonly) NSInteger nthWeekday; // e.g. 2nd Tuesday of the month == 2 @property (readonly) NSInteger year; @end ================================================ FILE: ArcBit/External/NSDate-Extensions/NSDate-Utilities.m ================================================ /* Erica Sadun, http://ericasadun.com iPhone Developer's Cookbook 3.x and beyond BSD License, Use at your own risk */ /* #import : Not planning to implement: dateByAskingBoyOut and dateByGettingBabysitter ---- General Thanks: sstreza, Scott Lawrence, Kevin Ballard, NoOneButMe, Avi`, August Joki. Emanuele Vulcano, jcromartiej, Blagovest Dachev, Matthias Plappert, Slava Bushtruk, Ali Servet Donmez, Ricardo1980, pip8786, Danny Thuerin, Dennis Madsen */ #import "NSDate-Utilities.h" #define DATE_COMPONENTS (NSYearCalendarUnit| NSMonthCalendarUnit | NSDayCalendarUnit | NSWeekCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit | NSWeekdayCalendarUnit | NSWeekdayOrdinalCalendarUnit) #define CURRENT_CALENDAR [NSCalendar currentCalendar] @implementation NSDate (Utilities) #pragma mark Relative Dates + (NSDate *) dateWithDaysFromNow: (NSInteger) days { // Thanks, Jim Morrison return [[NSDate date] dateByAddingDays:days]; } + (NSDate *) dateWithDaysBeforeNow: (NSInteger) days { // Thanks, Jim Morrison return [[NSDate date] dateBySubtractingDays:days]; } + (NSDate *) dateTomorrow { return [NSDate dateWithDaysFromNow:1]; } + (NSDate *) dateYesterday { return [NSDate dateWithDaysBeforeNow:1]; } + (NSDate *) dateWithHoursFromNow: (NSInteger) dHours { NSTimeInterval aTimeInterval = [[NSDate date] timeIntervalSinceReferenceDate] + D_HOUR * dHours; NSDate *newDate = [NSDate dateWithTimeIntervalSinceReferenceDate:aTimeInterval]; return newDate; } + (NSDate *) dateWithHoursBeforeNow: (NSInteger) dHours { NSTimeInterval aTimeInterval = [[NSDate date] timeIntervalSinceReferenceDate] - D_HOUR * dHours; NSDate *newDate = [NSDate dateWithTimeIntervalSinceReferenceDate:aTimeInterval]; return newDate; } + (NSDate *) dateWithMinutesFromNow: (NSInteger) dMinutes { NSTimeInterval aTimeInterval = [[NSDate date] timeIntervalSinceReferenceDate] + D_MINUTE * dMinutes; NSDate *newDate = [NSDate dateWithTimeIntervalSinceReferenceDate:aTimeInterval]; return newDate; } + (NSDate *) dateWithMinutesBeforeNow: (NSInteger) dMinutes { NSTimeInterval aTimeInterval = [[NSDate date] timeIntervalSinceReferenceDate] - D_MINUTE * dMinutes; NSDate *newDate = [NSDate dateWithTimeIntervalSinceReferenceDate:aTimeInterval]; return newDate; } #pragma mark Comparing Dates - (BOOL) isEqualToDateIgnoringTime: (NSDate *) aDate { NSDateComponents *components1 = [CURRENT_CALENDAR components:DATE_COMPONENTS fromDate:self]; NSDateComponents *components2 = [CURRENT_CALENDAR components:DATE_COMPONENTS fromDate:aDate]; return ((components1.year == components2.year) && (components1.month == components2.month) && (components1.day == components2.day)); } - (BOOL) isToday { return [self isEqualToDateIgnoringTime:[NSDate date]]; } - (BOOL) isTomorrow { return [self isEqualToDateIgnoringTime:[NSDate dateTomorrow]]; } - (BOOL) isYesterday { return [self isEqualToDateIgnoringTime:[NSDate dateYesterday]]; } // This hard codes the assumption that a week is 7 days - (BOOL) isSameWeekAsDate: (NSDate *) aDate { NSDateComponents *components1 = [CURRENT_CALENDAR components:DATE_COMPONENTS fromDate:self]; NSDateComponents *components2 = [CURRENT_CALENDAR components:DATE_COMPONENTS fromDate:aDate]; // Must be same week. 12/31 and 1/1 will both be week "1" if they are in the same week if (components1.week != components2.week) return NO; // Must have a time interval under 1 week. Thanks @aclark return (abs([self timeIntervalSinceDate:aDate]) < D_WEEK); } - (BOOL) isThisWeek { return [self isSameWeekAsDate:[NSDate date]]; } - (BOOL) isNextWeek { NSTimeInterval aTimeInterval = [[NSDate date] timeIntervalSinceReferenceDate] + D_WEEK; NSDate *newDate = [NSDate dateWithTimeIntervalSinceReferenceDate:aTimeInterval]; return [self isSameWeekAsDate:newDate]; } - (BOOL) isLastWeek { NSTimeInterval aTimeInterval = [[NSDate date] timeIntervalSinceReferenceDate] - D_WEEK; NSDate *newDate = [NSDate dateWithTimeIntervalSinceReferenceDate:aTimeInterval]; return [self isSameWeekAsDate:newDate]; } // Thanks, mspasov - (BOOL) isSameMonthAsDate: (NSDate *) aDate { NSDateComponents *components1 = [CURRENT_CALENDAR components:NSYearCalendarUnit | NSMonthCalendarUnit fromDate:self]; NSDateComponents *components2 = [CURRENT_CALENDAR components:NSYearCalendarUnit | NSMonthCalendarUnit fromDate:aDate]; return ((components1.month == components2.month) && (components1.year == components2.year)); } - (BOOL) isThisMonth { return [self isSameMonthAsDate:[NSDate date]]; } - (BOOL) isSameYearAsDate: (NSDate *) aDate { NSDateComponents *components1 = [CURRENT_CALENDAR components:NSYearCalendarUnit fromDate:self]; NSDateComponents *components2 = [CURRENT_CALENDAR components:NSYearCalendarUnit fromDate:aDate]; return (components1.year == components2.year); } - (BOOL) isThisYear { // Thanks, baspellis return [self isSameYearAsDate:[NSDate date]]; } - (BOOL) isNextYear { NSDateComponents *components1 = [CURRENT_CALENDAR components:NSYearCalendarUnit fromDate:self]; NSDateComponents *components2 = [CURRENT_CALENDAR components:NSYearCalendarUnit fromDate:[NSDate date]]; return (components1.year == (components2.year + 1)); } - (BOOL) isLastYear { NSDateComponents *components1 = [CURRENT_CALENDAR components:NSYearCalendarUnit fromDate:self]; NSDateComponents *components2 = [CURRENT_CALENDAR components:NSYearCalendarUnit fromDate:[NSDate date]]; return (components1.year == (components2.year - 1)); } - (BOOL) isEarlierThanDate: (NSDate *) aDate { return ([self compare:aDate] == NSOrderedAscending); } - (BOOL) isLaterThanDate: (NSDate *) aDate { return ([self compare:aDate] == NSOrderedDescending); } // Thanks, markrickert - (BOOL) isInFuture { return ([self isLaterThanDate:[NSDate date]]); } // Thanks, markrickert - (BOOL) isInPast { return ([self isEarlierThanDate:[NSDate date]]); } #pragma mark Roles - (BOOL) isTypicallyWeekend { NSDateComponents *components = [CURRENT_CALENDAR components:NSWeekdayCalendarUnit fromDate:self]; if ((components.weekday == 1) || (components.weekday == 7)) return YES; return NO; } - (BOOL) isTypicallyWorkday { return ![self isTypicallyWeekend]; } #pragma mark Adjusting Dates - (NSDate *) dateByAddingDays: (NSInteger) dDays { NSTimeInterval aTimeInterval = [self timeIntervalSinceReferenceDate] + D_DAY * dDays; NSDate *newDate = [NSDate dateWithTimeIntervalSinceReferenceDate:aTimeInterval]; return newDate; } - (NSDate *) dateBySubtractingDays: (NSInteger) dDays { return [self dateByAddingDays: (dDays * -1)]; } - (NSDate *) dateByAddingHours: (NSInteger) dHours { NSTimeInterval aTimeInterval = [self timeIntervalSinceReferenceDate] + D_HOUR * dHours; NSDate *newDate = [NSDate dateWithTimeIntervalSinceReferenceDate:aTimeInterval]; return newDate; } - (NSDate *) dateBySubtractingHours: (NSInteger) dHours { return [self dateByAddingHours: (dHours * -1)]; } - (NSDate *) dateByAddingMinutes: (NSInteger) dMinutes { NSTimeInterval aTimeInterval = [self timeIntervalSinceReferenceDate] + D_MINUTE * dMinutes; NSDate *newDate = [NSDate dateWithTimeIntervalSinceReferenceDate:aTimeInterval]; return newDate; } - (NSDate *) dateBySubtractingMinutes: (NSInteger) dMinutes { return [self dateByAddingMinutes: (dMinutes * -1)]; } - (NSDate *) dateAtStartOfDay { NSDateComponents *components = [CURRENT_CALENDAR components:DATE_COMPONENTS fromDate:self]; components.hour = 0; components.minute = 0; components.second = 0; return [CURRENT_CALENDAR dateFromComponents:components]; } - (NSDateComponents *) componentsWithOffsetFromDate: (NSDate *) aDate { NSDateComponents *dTime = [CURRENT_CALENDAR components:DATE_COMPONENTS fromDate:aDate toDate:self options:0]; return dTime; } #pragma mark Retrieving Intervals - (NSInteger) minutesAfterDate: (NSDate *) aDate { NSTimeInterval ti = [self timeIntervalSinceDate:aDate]; return (NSInteger) (ti / D_MINUTE); } - (NSInteger) minutesBeforeDate: (NSDate *) aDate { NSTimeInterval ti = [aDate timeIntervalSinceDate:self]; return (NSInteger) (ti / D_MINUTE); } - (NSInteger) hoursAfterDate: (NSDate *) aDate { NSTimeInterval ti = [self timeIntervalSinceDate:aDate]; return (NSInteger) (ti / D_HOUR); } - (NSInteger) hoursBeforeDate: (NSDate *) aDate { NSTimeInterval ti = [aDate timeIntervalSinceDate:self]; return (NSInteger) (ti / D_HOUR); } - (NSInteger) daysAfterDate: (NSDate *) aDate { NSTimeInterval ti = [self timeIntervalSinceDate:aDate]; return (NSInteger) (ti / D_DAY); } - (NSInteger) daysBeforeDate: (NSDate *) aDate { NSTimeInterval ti = [aDate timeIntervalSinceDate:self]; return (NSInteger) (ti / D_DAY); } // Thanks, dmitrydims // I have not yet thoroughly tested this - (NSInteger)distanceInDaysToDate:(NSDate *)anotherDate { NSCalendar *gregorianCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; NSDateComponents *components = [gregorianCalendar components:NSDayCalendarUnit fromDate:self toDate:anotherDate options:0]; return components.day; } #pragma mark Decomposing Dates - (NSInteger) nearestHour { NSTimeInterval aTimeInterval = [[NSDate date] timeIntervalSinceReferenceDate] + D_MINUTE * 30; NSDate *newDate = [NSDate dateWithTimeIntervalSinceReferenceDate:aTimeInterval]; NSDateComponents *components = [CURRENT_CALENDAR components:NSHourCalendarUnit fromDate:newDate]; return components.hour; } - (NSInteger) hour { NSDateComponents *components = [CURRENT_CALENDAR components:DATE_COMPONENTS fromDate:self]; return components.hour; } - (NSInteger) minute { NSDateComponents *components = [CURRENT_CALENDAR components:DATE_COMPONENTS fromDate:self]; return components.minute; } - (NSInteger) seconds { NSDateComponents *components = [CURRENT_CALENDAR components:DATE_COMPONENTS fromDate:self]; return components.second; } - (NSInteger) day { NSDateComponents *components = [CURRENT_CALENDAR components:DATE_COMPONENTS fromDate:self]; return components.day; } - (NSInteger) month { NSDateComponents *components = [CURRENT_CALENDAR components:DATE_COMPONENTS fromDate:self]; return components.month; } - (NSInteger) week { NSDateComponents *components = [CURRENT_CALENDAR components:DATE_COMPONENTS fromDate:self]; return components.week; } - (NSInteger) weekday { NSDateComponents *components = [CURRENT_CALENDAR components:DATE_COMPONENTS fromDate:self]; return components.weekday; } - (NSInteger) nthWeekday // e.g. 2nd Tuesday of the month is 2 { NSDateComponents *components = [CURRENT_CALENDAR components:DATE_COMPONENTS fromDate:self]; return components.weekdayOrdinal; } - (NSInteger) year { NSDateComponents *components = [CURRENT_CALENDAR components:DATE_COMPONENTS fromDate:self]; return components.year; } @end ================================================ FILE: ArcBit/External/QRCodeEncoderObjectiveCAtGithub/DataMatrix.h ================================================ #import @interface DataMatrix : NSObject { int dim; bool** data; } - (id)initWith:(int)dimension; - (int)dimension; - (void)set:(bool)value x:(int)x y:(int)y; - (bool)valueAt:(int)x y:(int)y; - (NSString*)toString; @end ================================================ FILE: ArcBit/External/QRCodeEncoderObjectiveCAtGithub/DataMatrix.mm ================================================ #import "DataMatrix.h" @implementation DataMatrix - (id)initWith:(int)dimension { if ([super init]) { self->dim = dimension; self->data = (bool**)malloc(sizeof(bool*) * self->dim); for (int y=0; ydim; y++) { self->data[y] = (bool*)malloc(sizeof(bool) * self->dim); if (self->data[y]==NULL) { NSLog(@"null!"); } } } return self; } - (int)dimension { return self->dim; } - (void)set:(bool)value x:(int)x y:(int)y { self->data[y][x] = value; } - (bool)valueAt:(int)x y:(int)y { return self->data[y][x]; } - (NSString*)toString { NSString* string = [NSString string]; for (int y=0; ydim; y++) { for (int x=0; xdim; x++) { bool value = self->data[y][x]; string = [string stringByAppendingFormat:@"%d", value]; } string = [string stringByAppendingString:@"\n"]; } return string; } - (void)dealloc { for (int y=0; ydim; y++) { free(self->data[y]); } free(self->data); [super dealloc]; } @end ================================================ FILE: ArcBit/External/QRCodeEncoderObjectiveCAtGithub/QRCodeEncoderDemoViewController.h ================================================ // // QRCodeDemoViewController.h // QRCodeEncoderObjectiveCAtGithub // // #import #import "QREncoder.h" #import "DataMatrix.h" @interface QRCodeEncoderDemoViewController : UIViewController { } @end ================================================ FILE: ArcBit/External/QRCodeEncoderObjectiveCAtGithub/QRCodeEncoderDemoViewController.mm ================================================ #import "QRCodeEncoderDemoViewController.h" @implementation QRCodeEncoderDemoViewController - (void)dealloc { [super dealloc]; } #pragma mark - View lifecycle - (void)viewDidLoad { [super viewDidLoad]; //the qrcode is square. now we make it 250 pixels wide int qrcodeImageDimension = 250; //the string can be very long NSString* aVeryLongURL = @"http://thelongestlistofthelongeststuffatthelongestdomainnameatlonglast.com/"; //first encode the string into a matrix of bools, TRUE for black dot and FALSE for white. Let the encoder decide the error correction level and version DataMatrix* qrMatrix = [QREncoder encodeWithECLevel:QR_ECLEVEL_AUTO version:QR_VERSION_AUTO string:aVeryLongURL]; //then render the matrix UIImage* qrcodeImage = [QREncoder renderDataMatrix:qrMatrix imageDimension:qrcodeImageDimension]; //put the image into the view UIImageView* qrcodeImageView = [[UIImageView alloc] initWithImage:qrcodeImage]; CGRect parentFrame = self.view.frame; CGRect tabBarFrame = self.tabBarController.tabBar.frame; //center the image CGFloat x = (parentFrame.size.width - qrcodeImageDimension) / 2.0; CGFloat y = (parentFrame.size.height - qrcodeImageDimension - tabBarFrame.size.height) / 2.0; CGRect qrcodeImageViewFrame = CGRectMake(x, y, qrcodeImageDimension, qrcodeImageDimension); [qrcodeImageView setFrame:qrcodeImageViewFrame]; //and that's it! [self.view addSubview:qrcodeImageView]; [qrcodeImageView release]; } @end ================================================ FILE: ArcBit/External/QRCodeEncoderObjectiveCAtGithub/QRCodeEncoderObjectiveCAtGithub-Prefix.pch ================================================ // // Prefix header for all source files of the 'QRCodeEncoderObjectiveCAtGithub' target in the 'QRCodeEncoderObjectiveCAtGithub' project // #ifdef __OBJC__ #import #endif ================================================ FILE: ArcBit/External/QRCodeEncoderObjectiveCAtGithub/QREncoder-Prefix.pch ================================================ // // Prefix header for all source files of the 'QREncoder' target in the 'QREncoder' project // #ifdef __OBJC__ #import #endif ================================================ FILE: ArcBit/External/QRCodeEncoderObjectiveCAtGithub/QREncoder.h ================================================ #import #import #import //#include "QR_Encode.h" // moved to QREncoder.mm #import "DataMatrix.h" const static int QR_ECLEVEL_AUTO = 0; //const static int QR_ECLEVEL_H = QR_LEVEL_H; //const static int QR_ECLEVEL_M = QR_LEVEL_M; //const static int QR_ECLEVEL_L = QR_LEVEL_L; //const static int QR_ECLEVEL_Q = QR_LEVEL_Q; const static int QR_VERSION_AUTO = -1; const static int BITS_PER_BYTE = 8; const static int BYTES_PER_PIXEL = 4; const static unsigned char WHITE = 0xff; @interface QREncoder : NSObject { } + (DataMatrix*)encodeWithECLevel:(int)ecLevel version:(int)version string:(NSString *)string AESPassphrase:(NSString*)AESPassphrase; + (DataMatrix*)encodeWithECLevel:(int)ecLevel version:(int)version string:(NSString*)string; + (UIImage*)renderDataMatrix:(DataMatrix*)matrix imageDimension:(int)imageDimension; @end void FLProviderReleaseData(void *info, const void *data, size_t size); ================================================ FILE: ArcBit/External/QRCodeEncoderObjectiveCAtGithub/QREncoder.mm ================================================ #import "QREncoder.h" #include "QR_Encode.h" @implementation QREncoder + (NSData*)AESEncryptString:(NSString*)string withPassphrase:(NSString*)passphrase { if (passphrase.length>kCCKeySizeAES256) { throw [NSException exceptionWithName:@"invalid passphrase exception" reason:[NSString stringWithFormat:@"passphrase too long: %d", passphrase.length] userInfo:nil]; } const char* cstrPassphraseOriginal = [passphrase cStringUsingEncoding:NSUTF8StringEncoding]; char cstrPassphrasePadded[kCCKeySizeAES256 + 1]; memset(cstrPassphrasePadded, 0, sizeof(cstrPassphrasePadded)); memcpy(cstrPassphrasePadded, cstrPassphraseOriginal, [passphrase length]); const char* dataIn = [string cStringUsingEncoding:NSUTF8StringEncoding]; size_t dataInLength = [string length]; size_t dataOutLength = dataInLength + kCCBlockSizeAES128; void* dataOut = malloc(dataOutLength); size_t encryptedDataLength = 0; CCCryptorStatus status = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, cstrPassphrasePadded, kCCKeySizeAES256, NULL, dataIn, dataInLength, dataOut, dataOutLength, &encryptedDataLength); NSData* encryptedData = nil; if (status==kCCSuccess) { encryptedData = [NSData dataWithBytes:dataOut length:encryptedDataLength]; } free(dataOut); return encryptedData; } + (NSString*)AESDecryptString:(NSData*)string withPassphrase:(NSString*)passphrase { if (passphrase.length>kCCKeySizeAES256) { throw [NSException exceptionWithName:@"invalid passphrase exception" reason:[NSString stringWithFormat:@"passphrase too long: %d", passphrase.length] userInfo:nil]; } const char* cstrPassphraseOriginal = [passphrase cStringUsingEncoding:NSASCIIStringEncoding]; char cstrPassphrase[kCCKeySizeAES256 + 1]; memset(cstrPassphrase, 0, sizeof(cstrPassphrase)); memcpy(cstrPassphrase, cstrPassphraseOriginal, [passphrase length]); const void* dataIn = [string bytes]; size_t dataInLength = [string length]; size_t dataOutLength = dataInLength + kCCBlockSizeAES128; void* dataOut = malloc(dataOutLength); size_t decryptedDataLength = 0; CCCryptorStatus status = CCCrypt(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, cstrPassphrase, kCCKeySizeAES256, NULL, dataIn, dataInLength, dataOut, dataOutLength, &decryptedDataLength); NSString* decryptedString = nil; if (status==kCCSuccess) { NSData* data = [NSData dataWithBytes:dataOut length:decryptedDataLength]; decryptedString = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]; } free(dataOut); return decryptedString; } + (DataMatrix*)encodeCStringWithECLevel:(int)ecLevel version:(int)version cstring:(const char*)cstring { CQR_Encode* encoder = new CQR_Encode; encoder->EncodeData(ecLevel, version, true, -1, cstring); int dimension = encoder->m_nSymbleSize; DataMatrix* matrix = [[[DataMatrix alloc] initWith:dimension] autorelease]; for (int y=0; ym_byModuleData[y][x]; bool bk = v==1; [matrix set:bk x:y y:x]; } } delete encoder; return matrix; } + (DataMatrix*)encodeWithECLevel:(int)ecLevel version:(int)version string:(NSString *)string AESPassphrase:(NSString*)AESPassphrase { NSData* encryptedString = [QREncoder AESEncryptString:string withPassphrase:AESPassphrase]; const unsigned int len = [encryptedString length]; char cstring[len + 1]; bzero(cstring, len + 1); [encryptedString getBytes:cstring length:len]; DataMatrix* matrix = [QREncoder encodeCStringWithECLevel:ecLevel version:version cstring:cstring]; return matrix; } + (DataMatrix*)encodeWithECLevel:(int)ecLevel version:(int)version string:(NSString *)string { const char* cstring = [string cStringUsingEncoding:NSUTF8StringEncoding]; DataMatrix* matrix = [QREncoder encodeCStringWithECLevel:ecLevel version:version cstring:cstring]; return matrix; } + (UIImage*)renderDataMatrix:(DataMatrix*)matrix imageDimension:(int)imageDimension { const int bitsPerPixel = BITS_PER_BYTE * BYTES_PER_PIXEL; const int bytesPerLine = BYTES_PER_PIXEL * imageDimension; const int rawDataSize = imageDimension * imageDimension * BYTES_PER_PIXEL; unsigned char* rawData = (unsigned char*)malloc(rawDataSize); int matrixDimension = [matrix dimension]; int pixelPerDot = imageDimension / matrixDimension; int offsetTopAndLeft = (int)((imageDimension - pixelPerDot * matrixDimension) / 2); int offsetBottomAndRight = (imageDimension - pixelPerDot * matrixDimension - offsetTopAndLeft); // alpha, blue, green, red const uint32_t white = 0xFFFFFFFF, black = 0xFF000000, transp = 0x00FFFFFF; uint32_t *ptrData = (uint32_t *)rawData; // top offset for(int c=offsetTopAndLeft*imageDimension; c>0; c--) *(ptrData++) = transp; for(int my=0; my0; c--) *(ptrData++) = transp; for(int mx=0; mx0; c--) *(ptrData++) = clr; } // right offset for(int c=offsetBottomAndRight; c>0; c--) *(ptrData++) = transp; // then copy that row pixelPerDot-1 times for(int c=(pixelPerDot-1)*imageDimension; c>0; c--) *(ptrData++) = *(ptrDataSouce++); } // bottom offset for(int c=offsetBottomAndRight*imageDimension; c>0; c--) *(ptrData++) = transp; CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, rawData, rawDataSize, FLProviderReleaseData); CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB(); CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaLast; CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault; CGImageRef imageRef = CGImageCreate(imageDimension, imageDimension, BITS_PER_BYTE, bitsPerPixel, bytesPerLine, colorSpaceRef, bitmapInfo, provider, NULL,NO,renderingIntent); UIImage *newImage = [UIImage imageWithCGImage:imageRef]; CGImageRelease(imageRef); CGColorSpaceRelease(colorSpaceRef); CGDataProviderRelease(provider); return newImage; } void FLProviderReleaseData(void *info, const void *data, size_t size) { free((void *)data); } @end ================================================ FILE: ArcBit/External/QRCodeEncoderObjectiveCAtGithub/QR_Encode.cpp ================================================ // QR_Encode.cpp : CQR_Encode NX Cve[V t@C // Date 2006/05/17 Ver. 1.22 Psytec Inc. #include "QR_Encode.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // QRR[ho[W(^) static QR_VERSIONINFO QR_VersonInfo[] = {{0}, // (_~[:Ver.0) { 1, // Ver.1 26, 19, 16, 13, 9, 0, 0, 0, 0, 0, 0, 0, 1, 26, 19, 1, 26, 16, 1, 26, 13, 1, 26, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, { 2, // Ver.2 44, 34, 28, 22, 16, 1, 18, 0, 0, 0, 0, 0, 1, 44, 34, 1, 44, 28, 1, 44, 22, 1, 44, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, { 3, // Ver.3 70, 55, 44, 34, 26, 1, 22, 0, 0, 0, 0, 0, 1, 70, 55, 1, 70, 44, 2, 35, 17, 2, 35, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, { 4, // Ver.4 100, 80, 64, 48, 36, 1, 26, 0, 0, 0, 0, 0, 1, 100, 80, 2, 50, 32, 2, 50, 24, 4, 25, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, { 5, // Ver.5 134, 108, 86, 62, 46, 1, 30, 0, 0, 0, 0, 0, 1, 134, 108, 2, 67, 43, 2, 33, 15, 2, 33, 11, 0, 0, 0, 0, 0, 0, 2, 34, 16, 2, 34, 12}, { 6, // Ver.6 172, 136, 108, 76, 60, 1, 34, 0, 0, 0, 0, 0, 2, 86, 68, 4, 43, 27, 4, 43, 19, 4, 43, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, { 7, // Ver.7 196, 156, 124, 88, 66, 2, 22, 38, 0, 0, 0, 0, 2, 98, 78, 4, 49, 31, 2, 32, 14, 4, 39, 13, 0, 0, 0, 0, 0, 0, 4, 33, 15, 1, 40, 14}, { 8, // Ver.8 242, 194, 154, 110, 86, 2, 24, 42, 0, 0, 0, 0, 2, 121, 97, 2, 60, 38, 4, 40, 18, 4, 40, 14, 0, 0, 0, 2, 61, 39, 2, 41, 19, 2, 41, 15}, { 9, // Ver.9 292, 232, 182, 132, 100, 2, 26, 46, 0, 0, 0, 0, 2, 146, 116, 3, 58, 36, 4, 36, 16, 4, 36, 12, 0, 0, 0, 2, 59, 37, 4, 37, 17, 4, 37, 13}, {10, // Ver.10 346, 274, 216, 154, 122, 2, 28, 50, 0, 0, 0, 0, 2, 86, 68, 4, 69, 43, 6, 43, 19, 6, 43, 15, 2, 87, 69, 1, 70, 44, 2, 44, 20, 2, 44, 16}, {11, // Ver.11 404, 324, 254, 180, 140, 2, 30, 54, 0, 0, 0, 0, 4, 101, 81, 1, 80, 50, 4, 50, 22, 3, 36, 12, 0, 0, 0, 4, 81, 51, 4, 51, 23, 8, 37, 13}, {12, // Ver.12 466, 370, 290, 206, 158, 2, 32, 58, 0, 0, 0, 0, 2, 116, 92, 6, 58, 36, 4, 46, 20, 7, 42, 14, 2, 117, 93, 2, 59, 37, 6, 47, 21, 4, 43, 15}, {13, // Ver.13 532, 428, 334, 244, 180, 2, 34, 62, 0, 0, 0, 0, 4, 133, 107, 8, 59, 37, 8, 44, 20, 12, 33, 11, 0, 0, 0, 1, 60, 38, 4, 45, 21, 4, 34, 12}, {14, // Ver.14 581, 461, 365, 261, 197, 3, 26, 46, 66, 0, 0, 0, 3, 145, 115, 4, 64, 40, 11, 36, 16, 11, 36, 12, 1, 146, 116, 5, 65, 41, 5, 37, 17, 5, 37, 13}, {15, // Ver.15 655, 523, 415, 295, 223, 3, 26, 48, 70, 0, 0, 0, 5, 109, 87, 5, 65, 41, 5, 54, 24, 11, 36, 12, 1, 110, 88, 5, 66, 42, 7, 55, 25, 7, 37, 13}, {16, // Ver.16 733, 589, 453, 325, 253, 3, 26, 50, 74, 0, 0, 0, 5, 122, 98, 7, 73, 45, 15, 43, 19, 3, 45, 15, 1, 123, 99, 3, 74, 46, 2, 44, 20, 13, 46, 16}, {17, // Ver.17 815, 647, 507, 367, 283, 3, 30, 54, 78, 0, 0, 0, 1, 135, 107, 10, 74, 46, 1, 50, 22, 2, 42, 14, 5, 136, 108, 1, 75, 47, 15, 51, 23, 17, 43, 15}, {18, // Ver.18 901, 721, 563, 397, 313, 3, 30, 56, 82, 0, 0, 0, 5, 150, 120, 9, 69, 43, 17, 50, 22, 2, 42, 14, 1, 151, 121, 4, 70, 44, 1, 51, 23, 19, 43, 15}, {19, // Ver.19 991, 795, 627, 445, 341, 3, 30, 58, 86, 0, 0, 0, 3, 141, 113, 3, 70, 44, 17, 47, 21, 9, 39, 13, 4, 142, 114, 11, 71, 45, 4, 48, 22, 16, 40, 14}, {20, // Ver.20 1085, 861, 669, 485, 385, 3, 34, 62, 90, 0, 0, 0, 3, 135, 107, 3, 67, 41, 15, 54, 24, 15, 43, 15, 5, 136, 108, 13, 68, 42, 5, 55, 25, 10, 44, 16}, {21, // Ver.21 1156, 932, 714, 512, 406, 4, 28, 50, 72, 94, 0, 0, 4, 144, 116, 17, 68, 42, 17, 50, 22, 19, 46, 16, 4, 145, 117, 0, 0, 0, 6, 51, 23, 6, 47, 17}, {22, // Ver.22 1258, 1006, 782, 568, 442, 4, 26, 50, 74, 98, 0, 0, 2, 139, 111, 17, 74, 46, 7, 54, 24, 34, 37, 13, 7, 140, 112, 0, 0, 0, 16, 55, 25, 0, 0, 0}, {23, // Ver.23 1364, 1094, 860, 614, 464, 4, 30, 54, 78, 102, 0, 0, 4, 151, 121, 4, 75, 47, 11, 54, 24, 16, 45, 15, 5, 152, 122, 14, 76, 48, 14, 55, 25, 14, 46, 16}, {24, // Ver.24 1474, 1174, 914, 664, 514, 4, 28, 54, 80, 106, 0, 0, 6, 147, 117, 6, 73, 45, 11, 54, 24, 30, 46, 16, 4, 148, 118, 14, 74, 46, 16, 55, 25, 2, 47, 17}, {25, // Ver.25 1588, 1276, 1000, 718, 538, 4, 32, 58, 84, 110, 0, 0, 8, 132, 106, 8, 75, 47, 7, 54, 24, 22, 45, 15, 4, 133, 107, 13, 76, 48, 22, 55, 25, 13, 46, 16}, {26, // Ver.26 1706, 1370, 1062, 754, 596, 4, 30, 58, 86, 114, 0, 0, 10, 142, 114, 19, 74, 46, 28, 50, 22, 33, 46, 16, 2, 143, 115, 4, 75, 47, 6, 51, 23, 4, 47, 17}, {27, // Ver.27 1828, 1468, 1128, 808, 628, 4, 34, 62, 90, 118, 0, 0, 8, 152, 122, 22, 73, 45, 8, 53, 23, 12, 45, 15, 4, 153, 123, 3, 74, 46, 26, 54, 24, 28, 46, 16}, {28, // Ver.28 1921, 1531, 1193, 871, 661, 5, 26, 50, 74, 98, 122, 0, 3, 147, 117, 3, 73, 45, 4, 54, 24, 11, 45, 15, 10, 148, 118, 23, 74, 46, 31, 55, 25, 31, 46, 16}, {29, // Ver.29 2051, 1631, 1267, 911, 701, 5, 30, 54, 78, 102, 126, 0, 7, 146, 116, 21, 73, 45, 1, 53, 23, 19, 45, 15, 7, 147, 117, 7, 74, 46, 37, 54, 24, 26, 46, 16}, {30, // Ver.30 2185, 1735, 1373, 985, 745, 5, 26, 52, 78, 104, 130, 0, 5, 145, 115, 19, 75, 47, 15, 54, 24, 23, 45, 15, 10, 146, 116, 10, 76, 48, 25, 55, 25, 25, 46, 16}, {31, // Ver.31 2323, 1843, 1455, 1033, 793, 5, 30, 56, 82, 108, 134, 0, 13, 145, 115, 2, 74, 46, 42, 54, 24, 23, 45, 15, 3, 146, 116, 29, 75, 47, 1, 55, 25, 28, 46, 16}, {32, // Ver.32 2465, 1955, 1541, 1115, 845, 5, 34, 60, 86, 112, 138, 0, 17, 145, 115, 10, 74, 46, 10, 54, 24, 19, 45, 15, 0, 0, 0, 23, 75, 47, 35, 55, 25, 35, 46, 16}, {33, // Ver.33 2611, 2071, 1631, 1171, 901, 5, 30, 58, 86, 114, 142, 0, 17, 145, 115, 14, 74, 46, 29, 54, 24, 11, 45, 15, 1, 146, 116, 21, 75, 47, 19, 55, 25, 46, 46, 16}, {34, // Ver.34 2761, 2191, 1725, 1231, 961, 5, 34, 62, 90, 118, 146, 0, 13, 145, 115, 14, 74, 46, 44, 54, 24, 59, 46, 16, 6, 146, 116, 23, 75, 47, 7, 55, 25, 1, 47, 17}, {35, // Ver.35 2876, 2306, 1812, 1286, 986, 6, 30, 54, 78, 102, 126, 150, 12, 151, 121, 12, 75, 47, 39, 54, 24, 22, 45, 15, 7, 152, 122, 26, 76, 48, 14, 55, 25, 41, 46, 16}, {36, // Ver.36 3034, 2434, 1914, 1354, 1054, 6, 24, 50, 76, 102, 128, 154, 6, 151, 121, 6, 75, 47, 46, 54, 24, 2, 45, 15, 14, 152, 122, 34, 76, 48, 10, 55, 25, 64, 46, 16}, {37, // Ver.37 3196, 2566, 1992, 1426, 1096, 6, 28, 54, 80, 106, 132, 158, 17, 152, 122, 29, 74, 46, 49, 54, 24, 24, 45, 15, 4, 153, 123, 14, 75, 47, 10, 55, 25, 46, 46, 16}, {38, // Ver.38 3362, 2702, 2102, 1502, 1142, 6, 32, 58, 84, 110, 136, 162, 4, 152, 122, 13, 74, 46, 48, 54, 24, 42, 45, 15, 18, 153, 123, 32, 75, 47, 14, 55, 25, 32, 46, 16}, {39, // Ver.39 3532, 2812, 2216, 1582, 1222, 6, 26, 54, 82, 110, 138, 166, 20, 147, 117, 40, 75, 47, 43, 54, 24, 10, 45, 15, 4, 148, 118, 7, 76, 48, 22, 55, 25, 67, 46, 16}, {40, // Ver.40 3706, 2956, 2334, 1666, 1276, 6, 30, 58, 86, 114, 142, 170, 19, 148, 118, 18, 75, 47, 34, 54, 24, 20, 45, 15, 6, 149, 119, 31, 76, 48, 34, 55, 25, 61, 46, 16} }; ///////////////////////////////////////////////////////////////////////////// // GF(2^8)wϊe[u static BYTE byExpToInt[] = { 1, 2, 4, 8, 16, 32, 64, 128, 29, 58, 116, 232, 205, 135, 19, 38, 76, 152, 45, 90, 180, 117, 234, 201, 143, 3, 6, 12, 24, 48, 96, 192, 157, 39, 78, 156, 37, 74, 148, 53, 106, 212, 181, 119, 238, 193, 159, 35, 70, 140, 5, 10, 20, 40, 80, 160, 93, 186, 105, 210, 185, 111, 222, 161, 95, 190, 97, 194, 153, 47, 94, 188, 101, 202, 137, 15, 30, 60, 120, 240, 253, 231, 211, 187, 107, 214, 177, 127, 254, 225, 223, 163, 91, 182, 113, 226, 217, 175, 67, 134, 17, 34, 68, 136, 13, 26, 52, 104, 208, 189, 103, 206, 129, 31, 62, 124, 248, 237, 199, 147, 59, 118, 236, 197, 151, 51, 102, 204, 133, 23, 46, 92, 184, 109, 218, 169, 79, 158, 33, 66, 132, 21, 42, 84, 168, 77, 154, 41, 82, 164, 85, 170, 73, 146, 57, 114, 228, 213, 183, 115, 230, 209, 191, 99, 198, 145, 63, 126, 252, 229, 215, 179, 123, 246, 241, 255, 227, 219, 171, 75, 150, 49, 98, 196, 149, 55, 110, 220, 165, 87, 174, 65, 130, 25, 50, 100, 200, 141, 7, 14, 28, 56, 112, 224, 221, 167, 83, 166, 81, 162, 89, 178, 121, 242, 249, 239, 195, 155, 43, 86, 172, 69, 138, 9, 18, 36, 72, 144, 61, 122, 244, 245, 247, 243, 251, 235, 203, 139, 11, 22, 44, 88, 176, 125, 250, 233, 207, 131, 27, 54, 108, 216, 173, 71, 142, 1}; ///////////////////////////////////////////////////////////////////////////// // GF(2^8)wϊe[u static BYTE byIntToExp[] = { 0, 0, 1, 25, 2, 50, 26, 198, 3, 223, 51, 238, 27, 104, 199, 75, 4, 100, 224, 14, 52, 141, 239, 129, 28, 193, 105, 248, 200, 8, 76, 113, 5, 138, 101, 47, 225, 36, 15, 33, 53, 147, 142, 218, 240, 18, 130, 69, 29, 181, 194, 125, 106, 39, 249, 185, 201, 154, 9, 120, 77, 228, 114, 166, 6, 191, 139, 98, 102, 221, 48, 253, 226, 152, 37, 179, 16, 145, 34, 136, 54, 208, 148, 206, 143, 150, 219, 189, 241, 210, 19, 92, 131, 56, 70, 64, 30, 66, 182, 163, 195, 72, 126, 110, 107, 58, 40, 84, 250, 133, 186, 61, 202, 94, 155, 159, 10, 21, 121, 43, 78, 212, 229, 172, 115, 243, 167, 87, 7, 112, 192, 247, 140, 128, 99, 13, 103, 74, 222, 237, 49, 197, 254, 24, 227, 165, 153, 119, 38, 184, 180, 124, 17, 68, 146, 217, 35, 32, 137, 46, 55, 63, 209, 91, 149, 188, 207, 205, 144, 135, 151, 178, 220, 252, 190, 97, 242, 86, 211, 171, 20, 42, 93, 158, 132, 60, 57, 83, 71, 109, 65, 162, 31, 45, 67, 216, 183, 123, 164, 118, 196, 23, 73, 236, 127, 12, 111, 246, 108, 161, 59, 82, 41, 157, 85, 170, 251, 96, 134, 177, 187, 204, 62, 90, 203, 89, 95, 176, 156, 169, 160, 81, 11, 245, 22, 235, 122, 117, 44, 215, 79, 174, 213, 233, 230, 231, 173, 232, 116, 214, 244, 234, 168, 80, 88, 175}; ///////////////////////////////////////////////////////////////////////////// // W static BYTE byRSExp7[] = {87, 229, 146, 149, 238, 102, 21}; static BYTE byRSExp10[] = {251, 67, 46, 61, 118, 70, 64, 94, 32, 45}; static BYTE byRSExp13[] = { 74, 152, 176, 100, 86, 100, 106, 104, 130, 218, 206, 140, 78}; static BYTE byRSExp15[] = { 8, 183, 61, 91, 202, 37, 51, 58, 58, 237, 140, 124, 5, 99, 105}; static BYTE byRSExp16[] = {120, 104, 107, 109, 102, 161, 76, 3, 91, 191, 147, 169, 182, 194, 225, 120}; static BYTE byRSExp17[] = { 43, 139, 206, 78, 43, 239, 123, 206, 214, 147, 24, 99, 150, 39, 243, 163, 136}; static BYTE byRSExp18[] = {215, 234, 158, 94, 184, 97, 118, 170, 79, 187, 152, 148, 252, 179, 5, 98, 96, 153}; static BYTE byRSExp20[] = { 17, 60, 79, 50, 61, 163, 26, 187, 202, 180, 221, 225, 83, 239, 156, 164, 212, 212, 188, 190}; static BYTE byRSExp22[] = {210, 171, 247, 242, 93, 230, 14, 109, 221, 53, 200, 74, 8, 172, 98, 80, 219, 134, 160, 105, 165, 231}; static BYTE byRSExp24[] = {229, 121, 135, 48, 211, 117, 251, 126, 159, 180, 169, 152, 192, 226, 228, 218, 111, 0, 117, 232, 87, 96, 227, 21}; static BYTE byRSExp26[] = {173, 125, 158, 2, 103, 182, 118, 17, 145, 201, 111, 28, 165, 53, 161, 21, 245, 142, 13, 102, 48, 227, 153, 145, 218, 70}; static BYTE byRSExp28[] = {168, 223, 200, 104, 224, 234, 108, 180, 110, 190, 195, 147, 205, 27, 232, 201, 21, 43, 245, 87, 42, 195, 212, 119, 242, 37, 9, 123}; static BYTE byRSExp30[] = { 41, 173, 145, 152, 216, 31, 179, 182, 50, 48, 110, 86, 239, 96, 222, 125, 42, 173, 226, 193, 224, 130, 156, 37, 251, 216, 238, 40, 192, 180}; static BYTE byRSExp32[] = { 10, 6, 106, 190, 249, 167, 4, 67, 209, 138, 138, 32, 242, 123, 89, 27, 120, 185, 80, 156, 38, 69, 171, 60, 28, 222, 80, 52, 254, 185, 220, 241}; static BYTE byRSExp34[] = {111, 77, 146, 94, 26, 21, 108, 19, 105, 94, 113, 193, 86, 140, 163, 125, 58, 158, 229, 239, 218, 103, 56, 70, 114, 61, 183, 129, 167, 13, 98, 62, 129, 51}; static BYTE byRSExp36[] = {200, 183, 98, 16, 172, 31, 246, 234, 60, 152, 115, 0, 167, 152, 113, 248, 238, 107, 18, 63, 218, 37, 87, 210, 105, 177, 120, 74, 121, 196, 117, 251, 113, 233, 30, 120}; static BYTE byRSExp38[] = {159, 34, 38, 228, 230, 59, 243, 95, 49, 218, 176, 164, 20, 65, 45, 111, 39, 81, 49, 118, 113, 222, 193, 250, 242, 168, 217, 41, 164, 247, 177, 30, 238, 18, 120, 153, 60, 193}; static BYTE byRSExp40[] = { 59, 116, 79, 161, 252, 98, 128, 205, 128, 161, 247, 57, 163, 56, 235, 106, 53, 26, 187, 174, 226, 104, 170, 7, 175, 35, 181, 114, 88, 41, 47, 163, 125, 134, 72, 20, 232, 53, 35, 15}; static BYTE byRSExp42[] = {250, 103, 221, 230, 25, 18, 137, 231, 0, 3, 58, 242, 221, 191, 110, 84, 230, 8, 188, 106, 96, 147, 15, 131, 139, 34, 101, 223, 39, 101, 213, 199, 237, 254, 201, 123, 171, 162, 194, 117, 50, 96}; static BYTE byRSExp44[] = {190, 7, 61, 121, 71, 246, 69, 55, 168, 188, 89, 243, 191, 25, 72, 123, 9, 145, 14, 247, 1, 238, 44, 78, 143, 62, 224, 126, 118, 114, 68, 163, 52, 194, 217, 147, 204, 169, 37, 130, 113, 102, 73, 181}; static BYTE byRSExp46[] = {112, 94, 88, 112, 253, 224, 202, 115, 187, 99, 89, 5, 54, 113, 129, 44, 58, 16, 135, 216, 169, 211, 36, 1, 4, 96, 60, 241, 73, 104, 234, 8, 249, 245, 119, 174, 52, 25, 157, 224, 43, 202, 223, 19, 82, 15}; static BYTE byRSExp48[] = {228, 25, 196, 130, 211, 146, 60, 24, 251, 90, 39, 102, 240, 61, 178, 63, 46, 123, 115, 18, 221, 111, 135, 160, 182, 205, 107, 206, 95, 150, 120, 184, 91, 21, 247, 156, 140, 238, 191, 11, 94, 227, 84, 50, 163, 39, 34, 108}; static BYTE byRSExp50[] = {232, 125, 157, 161, 164, 9, 118, 46, 209, 99, 203, 193, 35, 3, 209, 111, 195, 242, 203, 225, 46, 13, 32, 160, 126, 209, 130, 160, 242, 215, 242, 75, 77, 42, 189, 32, 113, 65, 124, 69, 228, 114, 235, 175, 124, 170, 215, 232, 133, 205}; static BYTE byRSExp52[] = {116, 50, 86, 186, 50, 220, 251, 89, 192, 46, 86, 127, 124, 19, 184, 233, 151, 215, 22, 14, 59, 145, 37, 242, 203, 134, 254, 89, 190, 94, 59, 65, 124, 113, 100, 233, 235, 121, 22, 76, 86, 97, 39, 242, 200, 220, 101, 33, 239, 254, 116, 51}; static BYTE byRSExp54[] = {183, 26, 201, 87, 210, 221, 113, 21, 46, 65, 45, 50, 238, 184, 249, 225, 102, 58, 209, 218, 109, 165, 26, 95, 184, 192, 52, 245, 35, 254, 238, 175, 172, 79, 123, 25, 122, 43, 120, 108, 215, 80, 128, 201, 235, 8, 153, 59, 101, 31, 198, 76, 31, 156}; static BYTE byRSExp56[] = {106, 120, 107, 157, 164, 216, 112, 116, 2, 91, 248, 163, 36, 201, 202, 229, 6, 144, 254, 155, 135, 208, 170, 209, 12, 139, 127, 142, 182, 249, 177, 174, 190, 28, 10, 85, 239, 184, 101, 124, 152, 206, 96, 23, 163, 61, 27, 196, 247, 151, 154, 202, 207, 20, 61, 10}; static BYTE byRSExp58[] = { 82, 116, 26, 247, 66, 27, 62, 107, 252, 182, 200, 185, 235, 55, 251, 242, 210, 144, 154, 237, 176, 141, 192, 248, 152, 249, 206, 85, 253, 142, 65, 165, 125, 23, 24, 30, 122, 240, 214, 6, 129, 218, 29, 145, 127, 134, 206, 245, 117, 29, 41, 63, 159, 142, 233, 125, 148, 123}; static BYTE byRSExp60[] = {107, 140, 26, 12, 9, 141, 243, 197, 226, 197, 219, 45, 211, 101, 219, 120, 28, 181, 127, 6, 100, 247, 2, 205, 198, 57, 115, 219, 101, 109, 160, 82, 37, 38, 238, 49, 160, 209, 121, 86, 11, 124, 30, 181, 84, 25, 194, 87, 65, 102, 190, 220, 70, 27, 209, 16, 89, 7, 33, 240}; static BYTE byRSExp62[] = { 65, 202, 113, 98, 71, 223, 248, 118, 214, 94, 0, 122, 37, 23, 2, 228, 58, 121, 7, 105, 135, 78, 243, 118, 70, 76, 223, 89, 72, 50, 70, 111, 194, 17, 212, 126, 181, 35, 221, 117, 235, 11, 229, 149, 147, 123, 213, 40, 115, 6, 200, 100, 26, 246, 182, 218, 127, 215, 36, 186, 110, 106}; static BYTE byRSExp64[] = { 45, 51, 175, 9, 7, 158, 159, 49, 68, 119, 92, 123, 177, 204, 187, 254, 200, 78, 141, 149, 119, 26, 127, 53, 160, 93, 199, 212, 29, 24, 145, 156, 208, 150, 218, 209, 4, 216, 91, 47, 184, 146, 47, 140, 195, 195, 125, 242, 238, 63, 99, 108, 140, 230, 242, 31, 204, 11, 178, 243, 217, 156, 213, 231}; static BYTE byRSExp66[] = { 5, 118, 222, 180, 136, 136, 162, 51, 46, 117, 13, 215, 81, 17, 139, 247, 197, 171, 95, 173, 65, 137, 178, 68, 111, 95, 101, 41, 72, 214, 169, 197, 95, 7, 44, 154, 77, 111, 236, 40, 121, 143, 63, 87, 80, 253, 240, 126, 217, 77, 34, 232, 106, 50, 168, 82, 76, 146, 67, 106, 171, 25, 132, 93, 45, 105}; static BYTE byRSExp68[] = {247, 159, 223, 33, 224, 93, 77, 70, 90, 160, 32, 254, 43, 150, 84, 101, 190, 205, 133, 52, 60, 202, 165, 220, 203, 151, 93, 84, 15, 84, 253, 173, 160, 89, 227, 52, 199, 97, 95, 231, 52, 177, 41, 125, 137, 241, 166, 225, 118, 2, 54, 32, 82, 215, 175, 198, 43, 238, 235, 27, 101, 184, 127, 3, 5, 8, 163, 238}; static LPBYTE byRSExp[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, byRSExp7, NULL, NULL, byRSExp10, NULL, NULL, byRSExp13, NULL, byRSExp15, byRSExp16, byRSExp17, byRSExp18, NULL, byRSExp20, NULL, byRSExp22, NULL, byRSExp24, NULL, byRSExp26, NULL, byRSExp28, NULL, byRSExp30, NULL, byRSExp32, NULL, byRSExp34, NULL, byRSExp36, NULL, byRSExp38, NULL, byRSExp40, NULL, byRSExp42, NULL, byRSExp44, NULL, byRSExp46, NULL, byRSExp48, NULL, byRSExp50, NULL, byRSExp52, NULL, byRSExp54, NULL, byRSExp56, NULL, byRSExp58, NULL, byRSExp60, NULL, byRSExp62, NULL, byRSExp64, NULL, byRSExp66, NULL, byRSExp68}; // CWP[^rbg(o[WO[v, {S, M, L}) static int nIndicatorLenNumeral[] = {10, 12, 14}; static int nIndicatorLenAlphabet[] = { 9, 11, 13}; static int nIndicatorLen8Bit[] = { 8, 16, 16}; static int nIndicatorLenKanji[] = { 8, 10, 12}; ///////////////////////////////////////////////////////////////////////////// // QR_Encode NX̍\z/ CQR_Encode::CQR_Encode() { } CQR_Encode::~CQR_Encode() { } int min(int a, int b); int min(int a, int b) { if (a<=b) { return a; } else { return b; } } ///////////////////////////////////////////////////////////////////////////// // CQR_Encode::EncodeData // p rFf[^GR[h // FxA^(0=)A^ԎgtOA}XLOԍ(-1=)AGR[hf[^AGR[hf[^ // ߂lFGR[h=trueAf[^ȂA܂͗eʃI[o[=false bool CQR_Encode::EncodeData(int nLevel, int nVersion, bool bAutoExtent, int nMaskingNo, LPCSTR lpsSource, int ncSource) { int i, j; m_nLevel = nLevel; m_nMaskingNo = nMaskingNo; // f[^w肳ĂȂꍇ lstrlen ɂĎ擾 int ncLength = ncSource > 0 ? ncSource : strlen(lpsSource); if (ncLength == 0) return false; // f[^Ȃ // o[W(^)`FbN int nEncodeVersion = GetEncodeVersion(nVersion, lpsSource, ncLength); if (nEncodeVersion == 0) return false; // eʃI[o[ if (nVersion == 0) { // ^Ԏ m_nVersion = nEncodeVersion; } else { if (nEncodeVersion <= nVersion) { m_nVersion = nVersion; } else { if (bAutoExtent) m_nVersion = nEncodeVersion; // o[W(^)g else return false; // eʃI[o[ } } // ^[~l[^R[h"0000"t int ncDataCodeWord = QR_VersonInfo[m_nVersion].ncDataCodeWord[nLevel]; int ncTerminater = min(4, (ncDataCodeWord * 8) - m_ncDataCodeWordBit); if (ncTerminater > 0) m_ncDataCodeWordBit = SetBitStream(m_ncDataCodeWordBit, 0, ncTerminater); // pfBOR[h"11101100, 00010001"t BYTE byPaddingCode = 0xec; for (i = (m_ncDataCodeWordBit + 7) / 8; i < ncDataCodeWord; ++i) { m_byDataCodeWord[i] = byPaddingCode; byPaddingCode = (BYTE)(byPaddingCode == 0xec ? 0x11 : 0xec); } // R[h[hZoGANA m_ncAllCodeWord = QR_VersonInfo[m_nVersion].ncAllCodeWord; ZeroMemory(m_byAllCodeWord, m_ncAllCodeWord); int nDataCwIndex = 0; // f[^R[h[hʒu // f[^ubN int ncBlock1 = QR_VersonInfo[m_nVersion].RS_BlockInfo1[nLevel].ncRSBlock; int ncBlock2 = QR_VersonInfo[m_nVersion].RS_BlockInfo2[nLevel].ncRSBlock; int ncBlockSum = ncBlock1 + ncBlock2; int nBlockNo = 0; // ubNԍ // ubNʃf[^R[h[h int ncDataCw1 = QR_VersonInfo[m_nVersion].RS_BlockInfo1[nLevel].ncDataCodeWord; int ncDataCw2 = QR_VersonInfo[m_nVersion].RS_BlockInfo2[nLevel].ncDataCodeWord; // f[^R[h[hC^[[uzu for (i = 0; i < ncBlock1; ++i) { for (j = 0; j < ncDataCw1; ++j) { m_byAllCodeWord[(ncBlockSum * j) + nBlockNo] = m_byDataCodeWord[nDataCwIndex++]; } ++nBlockNo; } for (i = 0; i < ncBlock2; ++i) { for (j = 0; j < ncDataCw2; ++j) { if (j < ncDataCw1) { m_byAllCodeWord[(ncBlockSum * j) + nBlockNo] = m_byDataCodeWord[nDataCwIndex++]; } else { // QڃubN[zu m_byAllCodeWord[(ncBlockSum * ncDataCw1) + i] = m_byDataCodeWord[nDataCwIndex++]; } } ++nBlockNo; } // ubNʂqrR[h[h(ł͓) int ncRSCw1 = QR_VersonInfo[m_nVersion].RS_BlockInfo1[nLevel].ncAllCodeWord - ncDataCw1; int ncRSCw2 = QR_VersonInfo[m_nVersion].RS_BlockInfo2[nLevel].ncAllCodeWord - ncDataCw2; ///////////////////////////////////////////////////////////////////////// // qrR[h[hZo nDataCwIndex = 0; nBlockNo = 0; for (i = 0; i < ncBlock1; ++i) { ZeroMemory(m_byRSWork, sizeof(m_byRSWork)); memmove(m_byRSWork, m_byDataCodeWord + nDataCwIndex, ncDataCw1); GetRSCodeWord(m_byRSWork, ncDataCw1, ncRSCw1); // qrR[h[hzu for (j = 0; j < ncRSCw1; ++j) { m_byAllCodeWord[ncDataCodeWord + (ncBlockSum * j) + nBlockNo] = m_byRSWork[j]; } nDataCwIndex += ncDataCw1; ++nBlockNo; } for (i = 0; i < ncBlock2; ++i) { ZeroMemory(m_byRSWork, sizeof(m_byRSWork)); memmove(m_byRSWork, m_byDataCodeWord + nDataCwIndex, ncDataCw2); GetRSCodeWord(m_byRSWork, ncDataCw2, ncRSCw2); // qrR[h[hzu for (j = 0; j < ncRSCw2; ++j) { m_byAllCodeWord[ncDataCodeWord + (ncBlockSum * j) + nBlockNo] = m_byRSWork[j]; } nDataCwIndex += ncDataCw2; ++nBlockNo; } m_nSymbleSize = m_nVersion * 4 + 17; // W[zu FormatModule(); return true; } ///////////////////////////////////////////////////////////////////////////// // CQR_Encode::GetEncodeVersion // p rFGR[ho[W(^)擾 // FJno[WAGR[hf[^AGR[hf[^ // ߂lFo[WԍieʃI[o[=0j int CQR_Encode::GetEncodeVersion(int nVersion, LPCSTR lpsSource, int ncLength) { int nVerGroup = nVersion >= 27 ? QR_VRESION_L : (nVersion >= 10 ? QR_VRESION_M : QR_VRESION_S); int i, j; for (i = nVerGroup; i <= QR_VRESION_L; ++i) { if (EncodeSourceData(lpsSource, ncLength, i)) { if (i == QR_VRESION_S) { for (j = 1; j <= 9; ++j) { if ((m_ncDataCodeWordBit + 7) / 8 <= QR_VersonInfo[j].ncDataCodeWord[m_nLevel]) return j; } } else if (i == QR_VRESION_M) { for (j = 10; j <= 26; ++j) { if ((m_ncDataCodeWordBit + 7) / 8 <= QR_VersonInfo[j].ncDataCodeWord[m_nLevel]) return j; } } else if (i == QR_VRESION_L) { for (j = 27; j <= 40; ++j) { if ((m_ncDataCodeWordBit + 7) / 8 <= QR_VersonInfo[j].ncDataCodeWord[m_nLevel]) return j; } } } } return 0; } ///////////////////////////////////////////////////////////////////////////// // CQR_Encode::EncodeSourceData // p rF̓f[^GR[h // F̓f[^A̓f[^Ao[W(^)O[v // ߂lFGR[h=true bool CQR_Encode::EncodeSourceData(LPCSTR lpsSource, int ncLength, int nVerGroup) { ZeroMemory(m_nBlockLength, sizeof(m_nBlockLength)); int i, j; // ǂ̃[h(oCg)pĂ邩𒲍 for (m_ncDataBlock = i = 0; i < ncLength; ++i) { BYTE byMode; if (i < ncLength - 1 && IsKanjiData(lpsSource[i], lpsSource[i + 1])) byMode = QR_MODE_KANJI; else if (IsNumeralData(lpsSource[i])) byMode = QR_MODE_NUMERAL; else if (IsAlphabetData(lpsSource[i])) byMode = QR_MODE_ALPHABET; else byMode = QR_MODE_8BIT; if (i == 0) m_byBlockMode[0] = byMode; if (m_byBlockMode[m_ncDataBlock] != byMode) m_byBlockMode[++m_ncDataBlock] = byMode; ++m_nBlockLength[m_ncDataBlock]; if (byMode == QR_MODE_KANJI) { // ͕ł͂Ȃ ŋL^ ++m_nBlockLength[m_ncDataBlock]; ++i; } } ++m_ncDataBlock; ///////////////////////////////////////////////////////////////////////// // אڂp[hubNƐ[hubN̕тɂ茋 int ncSrcBits, ncDstBits; // ̃rbgƒP̉p[hubNꍇ̃rbg int nBlock = 0; while (nBlock < m_ncDataBlock - 1) { int ncJoinFront, ncJoinBehind; // OWrbgoCg[hubNƌꍇ̃rbg int nJoinPosition = 0; // WrbgoCg[hubNƂ̌F-1=OƌA0=ȂA1=ƌ // u|pv܂́up|v̕ if ((m_byBlockMode[nBlock] == QR_MODE_NUMERAL && m_byBlockMode[nBlock + 1] == QR_MODE_ALPHABET) || (m_byBlockMode[nBlock] == QR_MODE_ALPHABET && m_byBlockMode[nBlock + 1] == QR_MODE_NUMERAL)) { // ̃rbgƒP̉p[hubNꍇ̃rbgr ncSrcBits = GetBitLength(m_byBlockMode[nBlock], m_nBlockLength[nBlock], nVerGroup) + GetBitLength(m_byBlockMode[nBlock + 1], m_nBlockLength[nBlock + 1], nVerGroup); ncDstBits = GetBitLength(QR_MODE_ALPHABET, m_nBlockLength[nBlock] + m_nBlockLength[nBlock + 1], nVerGroup); if (ncSrcBits > ncDstBits) { // OɂWrbgoCg[hubNꍇAƂ̌Lǂ`FbN if (nBlock >= 1 && m_byBlockMode[nBlock - 1] == QR_MODE_8BIT) { // OɂWrbgoCg[hubN ncJoinFront = GetBitLength(QR_MODE_8BIT, m_nBlockLength[nBlock - 1] + m_nBlockLength[nBlock], nVerGroup) + GetBitLength(m_byBlockMode[nBlock + 1], m_nBlockLength[nBlock + 1], nVerGroup); if (ncJoinFront > ncDstBits + GetBitLength(QR_MODE_8BIT, m_nBlockLength[nBlock - 1], nVerGroup)) ncJoinFront = 0; // WrbgoCg[hubNƂ͌Ȃ } else ncJoinFront = 0; if (nBlock < m_ncDataBlock - 2 && m_byBlockMode[nBlock + 2] == QR_MODE_8BIT) { // ɂWrbgoCg[hubN ncJoinBehind = GetBitLength(m_byBlockMode[nBlock], m_nBlockLength[nBlock], nVerGroup) + GetBitLength(QR_MODE_8BIT, m_nBlockLength[nBlock + 1] + m_nBlockLength[nBlock + 2], nVerGroup); if (ncJoinBehind > ncDstBits + GetBitLength(QR_MODE_8BIT, m_nBlockLength[nBlock + 2], nVerGroup)) ncJoinBehind = 0; // WrbgoCg[hubNƂ͌Ȃ } else ncJoinBehind = 0; if (ncJoinFront != 0 && ncJoinBehind != 0) { // O㗼ɂWrbgoCg[hubNꍇ̓f[^ZȂD nJoinPosition = (ncJoinFront < ncJoinBehind) ? -1 : 1; } else { nJoinPosition = (ncJoinFront != 0) ? -1 : ((ncJoinBehind != 0) ? 1 : 0); } if (nJoinPosition != 0) { // WrbgoCg[hubNƂ̌ if (nJoinPosition == -1) { m_nBlockLength[nBlock - 1] += m_nBlockLength[nBlock]; // 㑱Vtg for (i = nBlock; i < m_ncDataBlock - 1; ++i) { m_byBlockMode[i] = m_byBlockMode[i + 1]; m_nBlockLength[i] = m_nBlockLength[i + 1]; } } else { m_byBlockMode[nBlock + 1] = QR_MODE_8BIT; m_nBlockLength[nBlock + 1] += m_nBlockLength[nBlock + 2]; // 㑱Vtg for (i = nBlock + 2; i < m_ncDataBlock - 1; ++i) { m_byBlockMode[i] = m_byBlockMode[i + 1]; m_nBlockLength[i] = m_nBlockLength[i + 1]; } } --m_ncDataBlock; } else { // pƐ̕тP̉p[hubNɓ if (nBlock < m_ncDataBlock - 2 && m_byBlockMode[nBlock + 2] == QR_MODE_ALPHABET) { // 悤ƂubŇɑp[hubN m_nBlockLength[nBlock + 1] += m_nBlockLength[nBlock + 2]; // 㑱Vtg for (i = nBlock + 2; i < m_ncDataBlock - 1; ++i) { m_byBlockMode[i] = m_byBlockMode[i + 1]; m_nBlockLength[i] = m_nBlockLength[i + 1]; } --m_ncDataBlock; } m_byBlockMode[nBlock] = QR_MODE_ALPHABET; m_nBlockLength[nBlock] += m_nBlockLength[nBlock + 1]; // 㑱Vtg for (i = nBlock + 1; i < m_ncDataBlock - 1; ++i) { m_byBlockMode[i] = m_byBlockMode[i + 1]; m_nBlockLength[i] = m_nBlockLength[i + 1]; } --m_ncDataBlock; if (nBlock >= 1 && m_byBlockMode[nBlock - 1] == QR_MODE_ALPHABET) { // ubN̑Ỏp[hubN m_nBlockLength[nBlock - 1] += m_nBlockLength[nBlock]; // 㑱Vtg for (i = nBlock; i < m_ncDataBlock - 1; ++i) { m_byBlockMode[i] = m_byBlockMode[i + 1]; m_nBlockLength[i] = m_nBlockLength[i + 1]; } --m_ncDataBlock; } } continue; // ݈ʒũubNĒ } } ++nBlock; // ubN𒲍 } ///////////////////////////////////////////////////////////////////////// // AZ[hubNWrbgoCg[hubN nBlock = 0; while (nBlock < m_ncDataBlock - 1) { ncSrcBits = GetBitLength(m_byBlockMode[nBlock], m_nBlockLength[nBlock], nVerGroup) + GetBitLength(m_byBlockMode[nBlock + 1], m_nBlockLength[nBlock + 1], nVerGroup); ncDstBits = GetBitLength(QR_MODE_8BIT, m_nBlockLength[nBlock] + m_nBlockLength[nBlock + 1], nVerGroup); // OɂWrbgoCg[hubNꍇAdCWP[^Z if (nBlock >= 1 && m_byBlockMode[nBlock - 1] == QR_MODE_8BIT) ncDstBits -= (4 + nIndicatorLen8Bit[nVerGroup]); // ɂWrbgoCg[hubNꍇAdCWP[^Z if (nBlock < m_ncDataBlock - 2 && m_byBlockMode[nBlock + 2] == QR_MODE_8BIT) ncDstBits -= (4 + nIndicatorLen8Bit[nVerGroup]); if (ncSrcBits > ncDstBits) { if (nBlock >= 1 && m_byBlockMode[nBlock - 1] == QR_MODE_8BIT) { // ubN̑OɂWrbgoCg[hubN m_nBlockLength[nBlock - 1] += m_nBlockLength[nBlock]; // 㑱Vtg for (i = nBlock; i < m_ncDataBlock - 1; ++i) { m_byBlockMode[i] = m_byBlockMode[i + 1]; m_nBlockLength[i] = m_nBlockLength[i + 1]; } --m_ncDataBlock; --nBlock; } if (nBlock < m_ncDataBlock - 2 && m_byBlockMode[nBlock + 2] == QR_MODE_8BIT) { // ubŇɂWrbgoCg[hubN m_nBlockLength[nBlock + 1] += m_nBlockLength[nBlock + 2]; // 㑱Vtg for (i = nBlock + 2; i < m_ncDataBlock - 1; ++i) { m_byBlockMode[i] = m_byBlockMode[i + 1]; m_nBlockLength[i] = m_nBlockLength[i + 1]; } --m_ncDataBlock; } m_byBlockMode[nBlock] = QR_MODE_8BIT; m_nBlockLength[nBlock] += m_nBlockLength[nBlock + 1]; // 㑱Vtg for (i = nBlock + 1; i < m_ncDataBlock - 1; ++i) { m_byBlockMode[i] = m_byBlockMode[i + 1]; m_nBlockLength[i] = m_nBlockLength[i + 1]; } --m_ncDataBlock; // ubN̑OĒ if (nBlock >= 1) --nBlock; continue; } ++nBlock; // ubN𒲍 } ///////////////////////////////////////////////////////////////////////// // rbgz int ncComplete = 0; // σf[^JE^ WORD wBinCode; m_ncDataCodeWordBit = 0; // rbgPʏJE^ ZeroMemory(m_byDataCodeWord, MAX_DATACODEWORD); for (i = 0; i < m_ncDataBlock && m_ncDataCodeWordBit != -1; ++i) { if (m_byBlockMode[i] == QR_MODE_NUMERAL) { ///////////////////////////////////////////////////////////////// // [h // CWP[^(0001b) m_ncDataCodeWordBit = SetBitStream(m_ncDataCodeWordBit, 1, 4); // Zbg m_ncDataCodeWordBit = SetBitStream(m_ncDataCodeWordBit, (WORD)m_nBlockLength[i], nIndicatorLenNumeral[nVerGroup]); // rbgۑ for (j = 0; j < m_nBlockLength[i]; j += 3) { if (j < m_nBlockLength[i] - 2) { wBinCode = (WORD)(((lpsSource[ncComplete + j] - '0') * 100) + ((lpsSource[ncComplete + j + 1] - '0') * 10) + (lpsSource[ncComplete + j + 2] - '0')); m_ncDataCodeWordBit = SetBitStream(m_ncDataCodeWordBit, wBinCode, 10); } else if (j == m_nBlockLength[i] - 2) { // [QoCg wBinCode = (WORD)(((lpsSource[ncComplete + j] - '0') * 10) + (lpsSource[ncComplete + j + 1] - '0')); m_ncDataCodeWordBit = SetBitStream(m_ncDataCodeWordBit, wBinCode, 7); } else if (j == m_nBlockLength[i] - 1) { // [PoCg wBinCode = (WORD)(lpsSource[ncComplete + j] - '0'); m_ncDataCodeWordBit = SetBitStream(m_ncDataCodeWordBit, wBinCode, 4); } } ncComplete += m_nBlockLength[i]; } else if (m_byBlockMode[i] == QR_MODE_ALPHABET) { ///////////////////////////////////////////////////////////////// // p[h // [hCWP[^(0010b) m_ncDataCodeWordBit = SetBitStream(m_ncDataCodeWordBit, 2, 4); // Zbg m_ncDataCodeWordBit = SetBitStream(m_ncDataCodeWordBit, (WORD)m_nBlockLength[i], nIndicatorLenAlphabet[nVerGroup]); // rbgۑ for (j = 0; j < m_nBlockLength[i]; j += 2) { if (j < m_nBlockLength[i] - 1) { wBinCode = (WORD)((AlphabetToBinaly(lpsSource[ncComplete + j]) * 45) + AlphabetToBinaly(lpsSource[ncComplete + j + 1])); m_ncDataCodeWordBit = SetBitStream(m_ncDataCodeWordBit, wBinCode, 11); } else { // [PoCg wBinCode = (WORD)AlphabetToBinaly(lpsSource[ncComplete + j]); m_ncDataCodeWordBit = SetBitStream(m_ncDataCodeWordBit, wBinCode, 6); } } ncComplete += m_nBlockLength[i]; } else if (m_byBlockMode[i] == QR_MODE_8BIT) { ///////////////////////////////////////////////////////////////// // WrbgoCg[h // [hCWP[^(0100b) m_ncDataCodeWordBit = SetBitStream(m_ncDataCodeWordBit, 4, 4); // Zbg m_ncDataCodeWordBit = SetBitStream(m_ncDataCodeWordBit, (WORD)m_nBlockLength[i], nIndicatorLen8Bit[nVerGroup]); // rbgۑ for (j = 0; j < m_nBlockLength[i]; ++j) { m_ncDataCodeWordBit = SetBitStream(m_ncDataCodeWordBit, (WORD)lpsSource[ncComplete + j], 8); } ncComplete += m_nBlockLength[i]; } else // m_byBlockMode[i] == QR_MODE_KANJI { ///////////////////////////////////////////////////////////////// // [h // [hCWP[^(1000b) m_ncDataCodeWordBit = SetBitStream(m_ncDataCodeWordBit, 8, 4); // Zbg m_ncDataCodeWordBit = SetBitStream(m_ncDataCodeWordBit, (WORD)(m_nBlockLength[i] / 2), nIndicatorLenKanji[nVerGroup]); // [hŃrbgۑ for (j = 0; j < m_nBlockLength[i] / 2; ++j) { WORD wBinCode = KanjiToBinaly((WORD)(((BYTE)lpsSource[ncComplete + (j * 2)] << 8) + (BYTE)lpsSource[ncComplete + (j * 2) + 1])); m_ncDataCodeWordBit = SetBitStream(m_ncDataCodeWordBit, wBinCode, 13); } ncComplete += m_nBlockLength[i]; } } return (m_ncDataCodeWordBit != -1); } ///////////////////////////////////////////////////////////////////////////// // CQR_Encode::GetBitLength // p rFrbg擾 // Ff[^[hʁAf[^Ao[W(^)O[v // ߂lFf[^rbg // lF[hł̃f[^͕ł͂ȂoCg int CQR_Encode::GetBitLength(BYTE nMode, int ncData, int nVerGroup) { int ncBits = 0; switch (nMode) { case QR_MODE_NUMERAL: ncBits = 4 + nIndicatorLenNumeral[nVerGroup] + (10 * (ncData / 3)); switch (ncData % 3) { case 1: ncBits += 4; break; case 2: ncBits += 7; break; default: // case 0: break; } break; case QR_MODE_ALPHABET: ncBits = 4 + nIndicatorLenAlphabet[nVerGroup] + (11 * (ncData / 2)) + (6 * (ncData % 2)); break; case QR_MODE_8BIT: ncBits = 4 + nIndicatorLen8Bit[nVerGroup] + (8 * ncData); break; default: // case QR_MODE_KANJI: ncBits = 4 + nIndicatorLenKanji[nVerGroup] + (13 * (ncData / 2)); break; } return ncBits; } ///////////////////////////////////////////////////////////////////////////// // CQR_Encode::SetBitStream // p rFrbgZbg // F}ʒuArbgzf[^Af[^rbg(ő16) // ߂lF}ʒu(obt@I[o[=-1) // lFm_byDataCodeWord ɌʂZbg(v[) int CQR_Encode::SetBitStream(int nIndex, WORD wData, int ncData) { int i; if (nIndex == -1 || nIndex + ncData > MAX_DATACODEWORD * 8) return -1; for (i = 0; i < ncData; ++i) { if (wData & (1 << (ncData - i - 1))) { m_byDataCodeWord[(nIndex + i) / 8] |= 1 << (7 - ((nIndex + i) % 8)); } } return nIndex + ncData; } ///////////////////////////////////////////////////////////////////////////// // CQR_Encode::IsNumeralData // p rF[hY`FbN // F // ߂lFY=true bool CQR_Encode::IsNumeralData(unsigned char c) { if (c >= '0' && c <= '9') return true; return false; } ///////////////////////////////////////////////////////////////////////////// // CQR_Encode::IsAlphabetData // p rFp[hY`FbN // F // ߂lFY=true bool CQR_Encode::IsAlphabetData(unsigned char c) { if (c >= '0' && c <= '9') return true; if (c >= 'A' && c <= 'Z') return true; if (c == ' ' || c == '$' || c == '%' || c == '*' || c == '+' || c == '-' || c == '.' || c == '/' || c == ':') return true; return false; } ///////////////////////////////////////////////////////////////////////////// // CQR_Encode::IsKanjiData // p rF[hY`FbN // Fi16rbgj // ߂lFY=true // lFEBBFh ȍ~ S-JIS ͑ΏۊO bool CQR_Encode::IsKanjiData(unsigned char c1, unsigned char c2) { if (((c1 >= 0x81 && c1 <= 0x9f) || (c1 >= 0xe0 && c1 <= 0xeb)) && (c2 >= 0x40)) { if ((c1 == 0x9f && c2 > 0xfc) || (c1 == 0xeb && c2 > 0xbf)) return false; return true; } return false; } ///////////////////////////////////////////////////////////////////////////// // CQR_Encode::AlphabetToBinaly // p rFp[h̃oCi // FΏە // ߂lFoCil BYTE CQR_Encode::AlphabetToBinaly(unsigned char c) { if (c >= '0' && c <= '9') return (unsigned char)(c - '0'); if (c >= 'A' && c <= 'Z') return (unsigned char)(c - 'A' + 10); if (c == ' ') return 36; if (c == '$') return 37; if (c == '%') return 38; if (c == '*') return 39; if (c == '+') return 40; if (c == '-') return 41; if (c == '.') return 42; if (c == '/') return 43; return 44; // c == ':' } ///////////////////////////////////////////////////////////////////////////// // CQR_Encode::KanjiToBinaly // p rF[h̃oCi // FΏە // ߂lFoCil WORD CQR_Encode::KanjiToBinaly(WORD wc) { if (wc >= 0x8140 && wc <= 0x9ffc) wc -= 0x8140; else // (wc >= 0xe040 && wc <= 0xebbf) wc -= 0xc140; return (WORD)(((wc >> 8) * 0xc0) + (wc & 0x00ff)); } ///////////////////////////////////////////////////////////////////////////// // CQR_Encode::GetRSCodeWord // p rFqrR[h[h擾 // Ff[^R[h[hAhXAf[^R[h[hAqrR[h[h // lFR[h[h̃GAmۂĂĂяo void CQR_Encode::GetRSCodeWord(LPBYTE lpbyRSWork, int ncDataCodeWord, int ncRSCodeWord) { int i, j; for (i = 0; i < ncDataCodeWord ; ++i) { if (lpbyRSWork[0] != 0) { BYTE nExpFirst = byIntToExp[lpbyRSWork[0]]; // W搔Zo for (j = 0; j < ncRSCodeWord; ++j) { // e搔ɏ搔Zi% 255 ^255 = 1j BYTE nExpElement = (BYTE)(((int)(byRSExp[ncRSCodeWord][j] + nExpFirst)) % 255); // r_aɂ]Zo lpbyRSWork[j] = (BYTE)(lpbyRSWork[j + 1] ^ byExpToInt[nExpElement]); } // c茅Vtg for (j = ncRSCodeWord; j < ncDataCodeWord + ncRSCodeWord - 1; ++j) lpbyRSWork[j] = lpbyRSWork[j + 1]; } else { // c茅Vtg for (j = 0; j < ncDataCodeWord + ncRSCodeWord - 1; ++j) lpbyRSWork[j] = lpbyRSWork[j + 1]; } } } ///////////////////////////////////////////////////////////////////////////// // CQR_Encode::FormatModule // p rFW[ւ̃f[^zu // ߂lFӂ̃W[ void CQR_Encode::FormatModule() { int i, j; ZeroMemory(m_byModuleData, sizeof(m_byModuleData)); // @\W[zu SetFunctionModule(); // f[^p^[zu SetCodeWordPattern(); if (m_nMaskingNo == -1) { // œK}XLOp^[I m_nMaskingNo = 0; SetMaskingPattern(m_nMaskingNo); // }XLO SetFormatInfoPattern(m_nMaskingNo); // tH[}bgp^[zu int nMinPenalty = CountPenalty(); for (i = 1; i <= 7; ++i) { SetMaskingPattern(i); // }XLO SetFormatInfoPattern(i); // tH[}bgp^[zu int nPenalty = CountPenalty(); if (nPenalty < nMinPenalty) { nMinPenalty = nPenalty; m_nMaskingNo = i; } } } SetMaskingPattern(m_nMaskingNo); // }XLO SetFormatInfoPattern(m_nMaskingNo); // tH[}bgp^[zu // W[p^[u[lɕϊ for (i = 0; i < m_nSymbleSize; ++i) { for (j = 0; j < m_nSymbleSize; ++j) { m_byModuleData[i][j] = (BYTE)((m_byModuleData[i][j] & 0x11) != 0); } } } ///////////////////////////////////////////////////////////////////////////// // CQR_Encode::SetFunctionModule // p rF@\W[zu // lFtH[}bg͋@\W[o^̂(f[^͋) void CQR_Encode::SetFunctionModule() { int i, j; // ʒuop^[ SetFinderPattern(0, 0); SetFinderPattern(m_nSymbleSize - 7, 0); SetFinderPattern(0, m_nSymbleSize - 7); // ʒuop^[Zp[^ for (i = 0; i < 8; ++i) { m_byModuleData[i][7] = m_byModuleData[7][i] = '\x20'; m_byModuleData[m_nSymbleSize - 8][i] = m_byModuleData[m_nSymbleSize - 8 + i][7] = '\x20'; m_byModuleData[i][m_nSymbleSize - 8] = m_byModuleData[7][m_nSymbleSize - 8 + i] = '\x20'; } // tH[}bgLqʒu@\W[Ƃēo^ for (i = 0; i < 9; ++i) { m_byModuleData[i][8] = m_byModuleData[8][i] = '\x20'; } for (i = 0; i < 8; ++i) { m_byModuleData[m_nSymbleSize - 8 + i][8] = m_byModuleData[8][m_nSymbleSize - 8 + i] = '\x20'; } // o[Wp^[ SetVersionPattern(); // ʒu킹p^[ for (i = 0; i < QR_VersonInfo[m_nVersion].ncAlignPoint; ++i) { SetAlignmentPattern(QR_VersonInfo[m_nVersion].nAlignPoint[i], 6); SetAlignmentPattern(6, QR_VersonInfo[m_nVersion].nAlignPoint[i]); for (j = 0; j < QR_VersonInfo[m_nVersion].ncAlignPoint; ++j) { SetAlignmentPattern(QR_VersonInfo[m_nVersion].nAlignPoint[i], QR_VersonInfo[m_nVersion].nAlignPoint[j]); } } // ^C~Op^[ for (i = 8; i <= m_nSymbleSize - 9; ++i) { m_byModuleData[i][6] = (i % 2) == 0 ? '\x30' : '\x20'; m_byModuleData[6][i] = (i % 2) == 0 ? '\x30' : '\x20'; } } ///////////////////////////////////////////////////////////////////////////// // CQR_Encode::SetFinderPattern // p rFʒuop^[zu // FzuW void CQR_Encode::SetFinderPattern(int x, int y) { static BYTE byPattern[] = {0x7f, // 1111111b 0x41, // 1000001b 0x5d, // 1011101b 0x5d, // 1011101b 0x5d, // 1011101b 0x41, // 1000001b 0x7f}; // 1111111b int i, j; for (i = 0; i < 7; ++i) { for (j = 0; j < 7; ++j) { m_byModuleData[x + j][y + i] = (byPattern[i] & (1 << (6 - j))) ? '\x30' : '\x20'; } } } ///////////////////////////////////////////////////////////////////////////// // CQR_Encode::SetAlignmentPattern // p rFʒu킹p^[zu // FzuW void CQR_Encode::SetAlignmentPattern(int x, int y) { static BYTE byPattern[] = {0x1f, // 11111b 0x11, // 10001b 0x15, // 10101b 0x11, // 10001b 0x1f}; // 11111b int i, j; if (m_byModuleData[x][y] & 0x20) return; // @\W[Əd邽ߏO x -= 2; y -= 2; // Wɕϊ for (i = 0; i < 5; ++i) { for (j = 0; j < 5; ++j) { m_byModuleData[x + j][y + i] = (byPattern[i] & (1 << (4 - j))) ? '\x30' : '\x20'; } } } ///////////////////////////////////////////////////////////////////////////// // CQR_Encode::SetVersionPattern // p rFo[W(^)p^[zu // lFgabg(18,6)ƂĎgp void CQR_Encode::SetVersionPattern() { int i, j; if (m_nVersion <= 6) return; int nVerData = m_nVersion << 12; // ]rbgZo for (i = 0; i < 6; ++i) { if (nVerData & (1 << (17 - i))) { nVerData ^= (0x1f25 << (5 - i)); } } nVerData += m_nVersion << 12; for (i = 0; i < 6; ++i) { for (j = 0; j < 3; ++j) { m_byModuleData[m_nSymbleSize - 11 + j][i] = m_byModuleData[i][m_nSymbleSize - 11 + j] = (nVerData & (1 << (i * 3 + j))) ? '\x30' : '\x20'; } } } ///////////////////////////////////////////////////////////////////////////// // CQR_Encode::SetCodeWordPattern // p rFf[^p^[zu void CQR_Encode::SetCodeWordPattern() { int x = m_nSymbleSize; int y = m_nSymbleSize - 1; int nCoef_x = 1; // zu int nCoef_y = 1; // zu int i, j; for (i = 0; i < m_ncAllCodeWord; ++i) { for (j = 0; j < 8; ++j) { do { x += nCoef_x; nCoef_x *= -1; if (nCoef_x < 0) { y += nCoef_y; if (y < 0 || y == m_nSymbleSize) { y = (y < 0) ? 0 : m_nSymbleSize - 1; nCoef_y *= -1; x -= 2; if (x == 6) // ^C~Op^[ --x; } } } while (m_byModuleData[x][y] & 0x20); // @\W[O m_byModuleData[x][y] = (m_byAllCodeWord[i] & (1 << (7 - j))) ? '\x02' : '\x00'; } } } ///////////////////////////////////////////////////////////////////////////// // CQR_Encode::SetMaskingPattern // p rF}XLOp^[zu // F}XLOp^[ԍ void CQR_Encode::SetMaskingPattern(int nPatternNo) { int i, j; for (i = 0; i < m_nSymbleSize; ++i) { for (j = 0; j < m_nSymbleSize; ++j) { if (! (m_byModuleData[j][i] & 0x20)) // @\W[O { bool bMask; switch (nPatternNo) { case 0: bMask = ((i + j) % 2 == 0); break; case 1: bMask = (i % 2 == 0); break; case 2: bMask = (j % 3 == 0); break; case 3: bMask = ((i + j) % 3 == 0); break; case 4: bMask = (((i / 2) + (j / 3)) % 2 == 0); break; case 5: bMask = (((i * j) % 2) + ((i * j) % 3) == 0); break; case 6: bMask = ((((i * j) % 2) + ((i * j) % 3)) % 2 == 0); break; default: // case 7: bMask = ((((i * j) % 3) + ((i + j) % 2)) % 2 == 0); break; } m_byModuleData[j][i] = (BYTE)((m_byModuleData[j][i] & 0xfe) | (((m_byModuleData[j][i] & 0x02) > 1) ^ bMask)); } } } } ///////////////////////////////////////////////////////////////////////////// // CQR_Encode::SetFormatInfoPattern // p rFtH[}bgzu // F}XLOp^[ԍ void CQR_Encode::SetFormatInfoPattern(int nPatternNo) { int nFormatInfo; int i; switch (m_nLevel) { case QR_LEVEL_M: nFormatInfo = 0x00; // 00nnnb break; case QR_LEVEL_L: nFormatInfo = 0x08; // 01nnnb break; case QR_LEVEL_Q: nFormatInfo = 0x18; // 11nnnb break; default: // case QR_LEVEL_H: nFormatInfo = 0x10; // 10nnnb break; } nFormatInfo += nPatternNo; int nFormatData = nFormatInfo << 10; // ]rbgZo for (i = 0; i < 5; ++i) { if (nFormatData & (1 << (14 - i))) { nFormatData ^= (0x0537 << (4 - i)); // 10100110111b } } nFormatData += nFormatInfo << 10; // }XLO nFormatData ^= 0x5412; // 101010000010010b // ʒuop^[zu for (i = 0; i <= 5; ++i) m_byModuleData[8][i] = (nFormatData & (1 << i)) ? '\x30' : '\x20'; m_byModuleData[8][7] = (nFormatData & (1 << 6)) ? '\x30' : '\x20'; m_byModuleData[8][8] = (nFormatData & (1 << 7)) ? '\x30' : '\x20'; m_byModuleData[7][8] = (nFormatData & (1 << 8)) ? '\x30' : '\x20'; for (i = 9; i <= 14; ++i) m_byModuleData[14 - i][8] = (nFormatData & (1 << i)) ? '\x30' : '\x20'; // Eʒuop^[zu for (i = 0; i <= 7; ++i) m_byModuleData[m_nSymbleSize - 1 - i][8] = (nFormatData & (1 << i)) ? '\x30' : '\x20'; // ʒuop^[Ezu m_byModuleData[8][m_nSymbleSize - 8] = '\x30'; // ŒÃW[ for (i = 8; i <= 14; ++i) m_byModuleData[8][m_nSymbleSize - 15 + i] = (nFormatData & (1 << i)) ? '\x30' : '\x20'; } ///////////////////////////////////////////////////////////////////////////// // CQR_Encode::CountPenalty // p rF}XNyieBXRAZo int CQR_Encode::CountPenalty() { int nPenalty = 0; int i, j, k; // F̗̗אڃW[ for (i = 0; i < m_nSymbleSize; ++i) { for (j = 0; j < m_nSymbleSize - 4; ++j) { int nCount = 1; for (k = j + 1; k < m_nSymbleSize; k++) { if (((m_byModuleData[i][j] & 0x11) == 0) == ((m_byModuleData[i][k] & 0x11) == 0)) ++nCount; else break; } if (nCount >= 5) { nPenalty += 3 + (nCount - 5); } j = k - 1; } } // F̍s̗אڃW[ for (i = 0; i < m_nSymbleSize; ++i) { for (j = 0; j < m_nSymbleSize - 4; ++j) { int nCount = 1; for (k = j + 1; k < m_nSymbleSize; k++) { if (((m_byModuleData[j][i] & 0x11) == 0) == ((m_byModuleData[k][i] & 0x11) == 0)) ++nCount; else break; } if (nCount >= 5) { nPenalty += 3 + (nCount - 5); } j = k - 1; } } // F̃W[ubNiQ~Qj for (i = 0; i < m_nSymbleSize - 1; ++i) { for (j = 0; j < m_nSymbleSize - 1; ++j) { if ((((m_byModuleData[i][j] & 0x11) == 0) == ((m_byModuleData[i + 1][j] & 0x11) == 0)) && (((m_byModuleData[i][j] & 0x11) == 0) == ((m_byModuleData[i] [j + 1] & 0x11) == 0)) && (((m_byModuleData[i][j] & 0x11) == 0) == ((m_byModuleData[i + 1][j + 1] & 0x11) == 0))) { nPenalty += 3; } } } // ɂ 1:1:3:1:1 䗦i::::Áj̃p^[ for (i = 0; i < m_nSymbleSize; ++i) { for (j = 0; j < m_nSymbleSize - 6; ++j) { if (((j == 0) || (! (m_byModuleData[i][j - 1] & 0x11))) && // ܂ V{O ( m_byModuleData[i][j] & 0x11) && // - 1 (! (m_byModuleData[i][j + 1] & 0x11)) && // - 1 ( m_byModuleData[i][j + 2] & 0x11) && // ( m_byModuleData[i][j + 3] & 0x11) && // 3 ( m_byModuleData[i][j + 4] & 0x11) && // (! (m_byModuleData[i][j + 5] & 0x11)) && // - 1 ( m_byModuleData[i][j + 6] & 0x11) && // - 1 ((j == m_nSymbleSize - 7) || (! (m_byModuleData[i][j + 7] & 0x11)))) // ܂ V{O { // O܂͌4ȏ̖p^[ if (((j < 2 || ! (m_byModuleData[i][j - 2] & 0x11)) && (j < 3 || ! (m_byModuleData[i][j - 3] & 0x11)) && (j < 4 || ! (m_byModuleData[i][j - 4] & 0x11))) || ((j >= m_nSymbleSize - 8 || ! (m_byModuleData[i][j + 8] & 0x11)) && (j >= m_nSymbleSize - 9 || ! (m_byModuleData[i][j + 9] & 0x11)) && (j >= m_nSymbleSize - 10 || ! (m_byModuleData[i][j + 10] & 0x11)))) { nPenalty += 40; } } } } // sɂ 1:1:3:1:1 䗦i::::Áj̃p^[ for (i = 0; i < m_nSymbleSize; ++i) { for (j = 0; j < m_nSymbleSize - 6; ++j) { if (((j == 0) || (! (m_byModuleData[j - 1][i] & 0x11))) && // ܂ V{O ( m_byModuleData[j] [i] & 0x11) && // - 1 (! (m_byModuleData[j + 1][i] & 0x11)) && // - 1 ( m_byModuleData[j + 2][i] & 0x11) && // ( m_byModuleData[j + 3][i] & 0x11) && // 3 ( m_byModuleData[j + 4][i] & 0x11) && // (! (m_byModuleData[j + 5][i] & 0x11)) && // - 1 ( m_byModuleData[j + 6][i] & 0x11) && // - 1 ((j == m_nSymbleSize - 7) || (! (m_byModuleData[j + 7][i] & 0x11)))) // ܂ V{O { // O܂͌4ȏ̖p^[ if (((j < 2 || ! (m_byModuleData[j - 2][i] & 0x11)) && (j < 3 || ! (m_byModuleData[j - 3][i] & 0x11)) && (j < 4 || ! (m_byModuleData[j - 4][i] & 0x11))) || ((j >= m_nSymbleSize - 8 || ! (m_byModuleData[j + 8][i] & 0x11)) && (j >= m_nSymbleSize - 9 || ! (m_byModuleData[j + 9][i] & 0x11)) && (j >= m_nSymbleSize - 10 || ! (m_byModuleData[j + 10][i] & 0x11)))) { nPenalty += 40; } } } } // Ŝɑ΂ÃW[̐߂銄 int nCount = 0; for (i = 0; i < m_nSymbleSize; ++i) { for (j = 0; j < m_nSymbleSize; ++j) { if (! (m_byModuleData[i][j] & 0x11)) { ++nCount; } } } nPenalty += (abs(50 - ((nCount * 100) / (m_nSymbleSize * m_nSymbleSize))) / 5) * 10; return nPenalty; } ================================================ FILE: ArcBit/External/QRCodeEncoderObjectiveCAtGithub/QR_Encode.h ================================================ // QR_Encode.h : CQR_Encode NX錾уC^[tFCX` // Date 2006/05/17 Ver. 1.22 Psytec Inc. #ifndef _QR_ENCODE_H #define _QR_ENCODE_H #include #include ///////////////////////////////////////////////////////////////////////////// // 萔 // x #define QR_LEVEL_L 0 #define QR_LEVEL_M 1 #define QR_LEVEL_Q 2 #define QR_LEVEL_H 3 // f[^[h #define QR_MODE_NUMERAL 0 #define QR_MODE_ALPHABET 1 #define QR_MODE_8BIT 2 #define QR_MODE_KANJI 3 // o[W(^)O[v #define QR_VRESION_S 0 // 1 ` 9 #define QR_VRESION_M 1 // 10 ` 26 #define QR_VRESION_L 2 // 27 ` 40 #define MAX_ALLCODEWORD 3706 // R[h[hől #define MAX_DATACODEWORD 2956 // f[^R[h[hől(o[W40-L) #define MAX_CODEBLOCK 153 // ubNf[^R[h[hől(qrR[h[h܂) #define MAX_MODULESIZE 177 // ӃW[ől // rbg}bv`掞}[W #define QR_MARGIN 4 ///////////////////////////////////////////////////////////////////////////// typedef struct tagRS_BLOCKINFO { int ncRSBlock; // qrubN int ncAllCodeWord; // ubNR[h[h int ncDataCodeWord; // f[^R[h[h(R[h[h - qrR[h[h) } RS_BLOCKINFO, *LPRS_BLOCKINFO; ///////////////////////////////////////////////////////////////////////////// // QRR[ho[W(^)֘A typedef struct tagQR_VERSIONINFO { int nVersionNo; // o[W(^)ԍ(1`40) int ncAllCodeWord; // R[h[h // ȉzY͌(0 = L, 1 = M, 2 = Q, 3 = H) int ncDataCodeWord[4]; // f[^R[h[h(R[h[h - qrR[h[h) int ncAlignPoint; // ACgp^[W int nAlignPoint[6]; // ACgp^[SW RS_BLOCKINFO RS_BlockInfo1[4]; // qrubN(1) RS_BLOCKINFO RS_BlockInfo2[4]; // qrubN(2) } QR_VERSIONINFO, *LPQR_VERSIONINFO; typedef unsigned short WORD; typedef unsigned char BYTE; typedef BYTE* LPBYTE; typedef const char* LPCSTR; #define ZeroMemory(Destination,Length) memset((Destination),0,(Length)) class CQR_Encode { // \z/ public: CQR_Encode(); ~CQR_Encode(); public: int m_nLevel; // x int m_nVersion; // o[W(^) bool m_bAutoExtent; // o[W(^)gwtO int m_nMaskingNo; // }XLOp^[ԍ public: int m_nSymbleSize; BYTE m_byModuleData[MAX_MODULESIZE][MAX_MODULESIZE]; // [x][y] // bit5:@\W[i}XLOΏۊOjtO // bit4:@\W[`f[^ // bit1:GR[hf[^ // bit0:}XNGR[h`f[^ // 20hƂ̘_aɂ@\W[A11hƂ̘_aɂ`iŏIIɂboollj private: int m_ncDataCodeWordBit; // f[^R[h[hrbg BYTE m_byDataCodeWord[MAX_DATACODEWORD]; // ̓f[^GR[hGA int m_ncDataBlock; BYTE m_byBlockMode[MAX_DATACODEWORD]; int m_nBlockLength[MAX_DATACODEWORD]; int m_ncAllCodeWord; // R[h[h(qrf[^܂) BYTE m_byAllCodeWord[MAX_ALLCODEWORD]; // R[h[hZoGA BYTE m_byRSWork[MAX_CODEBLOCK]; // qrR[h[hZo[N // f[^GR[h֘At@NV public: bool EncodeData(int nLevel, int nVersion, bool bAutoExtent, int nMaskingNo, LPCSTR lpsSource, int ncSource = 0); private: int GetEncodeVersion(int nVersion, LPCSTR lpsSource, int ncLength); bool EncodeSourceData(LPCSTR lpsSource, int ncLength, int nVerGroup); int GetBitLength(BYTE nMode, int ncData, int nVerGroup); int SetBitStream(int nIndex, WORD wData, int ncData); bool IsNumeralData(unsigned char c); bool IsAlphabetData(unsigned char c); bool IsKanjiData(unsigned char c1, unsigned char c2); BYTE AlphabetToBinaly(unsigned char c); WORD KanjiToBinaly(WORD wc); void GetRSCodeWord(LPBYTE lpbyRSWork, int ncDataCodeWord, int ncRSCodeWord); // W[zu֘At@NV private: void FormatModule(); void SetFunctionModule(); void SetFinderPattern(int x, int y); void SetAlignmentPattern(int x, int y); void SetVersionPattern(); void SetCodeWordPattern(); void SetMaskingPattern(int nPatternNo); void SetFormatInfoPattern(int nPatternNo); int CountPenalty(); }; ///////////////////////////////////////////////////////////////////////////// #endif ================================================ FILE: ArcBit/External/SocketRocket/NSData+SRB64Additions.h ================================================ // // Copyright 2012 Square Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #import @interface NSData (SRB64Additions) - (NSString *)SR_stringByBase64Encoding; @end ================================================ FILE: ArcBit/External/SocketRocket/NSData+SRB64Additions.m ================================================ // // Copyright 2012 Square Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #import "NSData+SRB64Additions.h" #import "base64.h" @implementation NSData (SRB64Additions) - (NSString *)SR_stringByBase64Encoding; { size_t buffer_size = (([self length] * 3 + 2) / 2); char *buffer = (char *)malloc(buffer_size); int len = b64_ntop([self bytes], [self length], buffer, buffer_size); if (len == -1) { free(buffer); return nil; } else{ return [[NSString alloc] initWithBytesNoCopy:buffer length:len encoding:NSUTF8StringEncoding freeWhenDone:YES]; } } @end ================================================ FILE: ArcBit/External/SocketRocket/SRWebSocket.h ================================================ // // Copyright 2012 Square Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #import #import typedef enum { SR_CONNECTING = 0, SR_OPEN = 1, SR_CLOSING = 2, SR_CLOSED = 3, } SRReadyState; @class SRWebSocket; extern NSString *const SRWebSocketErrorDomain; #pragma mark - SRWebSocketDelegate @protocol SRWebSocketDelegate; #pragma mark - SRWebSocket @interface SRWebSocket : NSObject @property (nonatomic, assign) id delegate; @property (nonatomic, readonly) SRReadyState readyState; @property (nonatomic, readonly, retain) NSURL *url; // This returns the negotiated protocol. // It will be nil until after the handshake completes. @property (nonatomic, readonly, copy) NSString *protocol; // Protocols should be an array of strings that turn into Sec-WebSocket-Protocol. - (id)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols; - (id)initWithURLRequest:(NSURLRequest *)request; // Some helper constructors. - (id)initWithURL:(NSURL *)url protocols:(NSArray *)protocols; - (id)initWithURL:(NSURL *)url; // Delegate queue will be dispatch_main_queue by default. // You cannot set both OperationQueue and dispatch_queue. - (void)setDelegateOperationQueue:(NSOperationQueue*) queue; - (void)setDelegateDispatchQueue:(dispatch_queue_t) queue; // By default, it will schedule itself on +[NSRunLoop SR_networkRunLoop] using defaultModes. - (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; - (void)unscheduleFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; // SRWebSockets are intended for one-time-use only. Open should be called once and only once. - (void)open; - (void)close; - (void)closeWithCode:(NSInteger)code reason:(NSString *)reason; // Send a UTF8 String or Data. - (void)send:(id)data; @end #pragma mark - SRWebSocketDelegate @protocol SRWebSocketDelegate // message will either be an NSString if the server is using text // or NSData if the server is using binary. - (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message; @optional - (void)webSocketDidOpen:(SRWebSocket *)webSocket; - (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error; - (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean; @end #pragma mark - NSURLRequest (CertificateAdditions) @interface NSURLRequest (CertificateAdditions) @property (nonatomic, retain, readonly) NSArray *SR_SSLPinnedCertificates; @end #pragma mark - NSMutableURLRequest (CertificateAdditions) @interface NSMutableURLRequest (CertificateAdditions) @property (nonatomic, retain) NSArray *SR_SSLPinnedCertificates; @end #pragma mark - NSRunLoop (SRWebSocket) @interface NSRunLoop (SRWebSocket) + (NSRunLoop *)SR_networkRunLoop; @end ================================================ FILE: ArcBit/External/SocketRocket/SRWebSocket.m ================================================ // // Copyright 2012 Square Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #import "SRWebSocket.h" #if TARGET_OS_IPHONE #define HAS_ICU #endif #ifdef HAS_ICU #import #endif #if TARGET_OS_IPHONE #import #else #import #endif #import #import #import "base64.h" #import "NSData+SRB64Additions.h" #if OS_OBJECT_USE_OBJC_RETAIN_RELEASE #define sr_dispatch_retain(x) #define sr_dispatch_release(x) #define maybe_bridge(x) ((__bridge void *) x) #else #define sr_dispatch_retain(x) dispatch_retain(x) #define sr_dispatch_release(x) dispatch_release(x) #define maybe_bridge(x) (x) #endif #if !__has_feature(objc_arc) #error SocketRocket muust be compiled with ARC enabled #endif typedef enum { SROpCodeTextFrame = 0x1, SROpCodeBinaryFrame = 0x2, // 3-7 reserved. SROpCodeConnectionClose = 0x8, SROpCodePing = 0x9, SROpCodePong = 0xA, // B-F reserved. } SROpCode; typedef enum { SRStatusCodeNormal = 1000, SRStatusCodeGoingAway = 1001, SRStatusCodeProtocolError = 1002, SRStatusCodeUnhandledType = 1003, // 1004 reserved. SRStatusNoStatusReceived = 1005, // 1004-1006 reserved. SRStatusCodeInvalidUTF8 = 1007, SRStatusCodePolicyViolated = 1008, SRStatusCodeMessageTooBig = 1009, } SRStatusCode; typedef struct { BOOL fin; // BOOL rsv1; // BOOL rsv2; // BOOL rsv3; uint8_t opcode; BOOL masked; uint64_t payload_length; } frame_header; static NSString *const SRWebSocketAppendToSecKeyString = @"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; static inline int32_t validate_dispatch_data_partial_string(NSData *data); static inline dispatch_queue_t log_queue(); static inline void SRFastLog(NSString *format, ...); @interface NSData (SRWebSocket) - (NSString *)stringBySHA1ThenBase64Encoding; @end @interface NSString (SRWebSocket) - (NSString *)stringBySHA1ThenBase64Encoding; @end @interface NSURL (SRWebSocket) // The origin isn't really applicable for a native application. // So instead, just map ws -> http and wss -> https. - (NSString *)SR_origin; @end @interface _SRRunLoopThread : NSThread @property (nonatomic, readonly) NSRunLoop *runLoop; @end static NSString *newSHA1String(const char *bytes, size_t length) { uint8_t md[CC_SHA1_DIGEST_LENGTH]; CC_SHA1(bytes, length, md); size_t buffer_size = ((sizeof(md) * 3 + 2) / 2); char *buffer = (char *)malloc(buffer_size); int len = b64_ntop(md, CC_SHA1_DIGEST_LENGTH, buffer, buffer_size); if (len == -1) { free(buffer); return nil; } else{ return [[NSString alloc] initWithBytesNoCopy:buffer length:len encoding:NSASCIIStringEncoding freeWhenDone:YES]; } } @implementation NSData (SRWebSocket) - (NSString *)stringBySHA1ThenBase64Encoding; { return newSHA1String(self.bytes, self.length); } @end @implementation NSString (SRWebSocket) - (NSString *)stringBySHA1ThenBase64Encoding; { return newSHA1String(self.UTF8String, self.length); } @end NSString *const SRWebSocketErrorDomain = @"SRWebSocketErrorDomain"; // Returns number of bytes consumed. Returning 0 means you didn't match. // Sends bytes to callback handler; typedef size_t (^stream_scanner)(NSData *collected_data); typedef void (^data_callback)(SRWebSocket *webSocket, NSData *data); @interface SRIOConsumer : NSObject { stream_scanner _scanner; data_callback _handler; size_t _bytesNeeded; BOOL _readToCurrentFrame; BOOL _unmaskBytes; } @property (nonatomic, copy, readonly) stream_scanner consumer; @property (nonatomic, copy, readonly) data_callback handler; @property (nonatomic, assign) size_t bytesNeeded; @property (nonatomic, assign, readonly) BOOL readToCurrentFrame; @property (nonatomic, assign, readonly) BOOL unmaskBytes; @end // This class is not thread-safe, and is expected to always be run on the same queue. @interface SRIOConsumerPool : NSObject - (id)initWithBufferCapacity:(NSUInteger)poolSize; - (SRIOConsumer *)consumerWithScanner:(stream_scanner)scanner handler:(data_callback)handler bytesNeeded:(size_t)bytesNeeded readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; - (void)returnConsumer:(SRIOConsumer *)consumer; @end @interface SRWebSocket () - (void)_writeData:(NSData *)data; - (void)_closeWithProtocolError:(NSString *)message; - (void)_failWithError:(NSError *)error; - (void)_disconnect; - (void)_readFrameNew; - (void)_readFrameContinue; - (void)_pumpScanner; - (void)_pumpWriting; - (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback; - (void)_addConsumerWithDataLength:(size_t)dataLength callback:(data_callback)callback readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; - (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback dataLength:(size_t)dataLength; - (void)_readUntilBytes:(const void *)bytes length:(size_t)length callback:(data_callback)dataHandler; - (void)_readUntilHeaderCompleteWithCallback:(data_callback)dataHandler; - (void)_sendFrameWithOpcode:(SROpCode)opcode data:(id)data; - (BOOL)_checkHandshake:(CFHTTPMessageRef)httpMessage; - (void)_SR_commonInit; - (void)_initializeStreams; - (void)_connect; @property (nonatomic) SRReadyState readyState; @property (nonatomic) NSOperationQueue *delegateOperationQueue; @property (nonatomic) dispatch_queue_t delegateDispatchQueue; @end @implementation SRWebSocket { NSInteger _webSocketVersion; NSOperationQueue *_delegateOperationQueue; dispatch_queue_t _delegateDispatchQueue; dispatch_queue_t _workQueue; NSMutableArray *_consumers; NSInputStream *_inputStream; NSOutputStream *_outputStream; NSMutableData *_readBuffer; NSUInteger _readBufferOffset; NSMutableData *_outputBuffer; NSUInteger _outputBufferOffset; uint8_t _currentFrameOpcode; size_t _currentFrameCount; size_t _readOpCount; uint32_t _currentStringScanPosition; NSMutableData *_currentFrameData; NSString *_closeReason; NSString *_secKey; BOOL _pinnedCertFound; uint8_t _currentReadMaskKey[4]; size_t _currentReadMaskOffset; BOOL _consumerStopped; BOOL _closeWhenFinishedWriting; BOOL _failed; BOOL _secure; NSURLRequest *_urlRequest; CFHTTPMessageRef _receivedHTTPHeaders; BOOL _sentClose; BOOL _didFail; int _closeCode; BOOL _isPumping; NSMutableSet *_scheduledRunloops; // We use this to retain ourselves. __strong SRWebSocket *_selfRetain; NSArray *_requestedProtocols; SRIOConsumerPool *_consumerPool; } @synthesize delegate = _delegate; @synthesize url = _url; @synthesize readyState = _readyState; @synthesize protocol = _protocol; static __strong NSData *CRLFCRLF; + (void)initialize; { CRLFCRLF = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4]; } - (id)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols; { self = [super init]; if (self) { assert(request.URL); _url = request.URL; _urlRequest = request; _requestedProtocols = [protocols copy]; [self _SR_commonInit]; } return self; } - (id)initWithURLRequest:(NSURLRequest *)request; { return [self initWithURLRequest:request protocols:nil]; } - (id)initWithURL:(NSURL *)url; { return [self initWithURL:url protocols:nil]; } - (id)initWithURL:(NSURL *)url protocols:(NSArray *)protocols; { NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; return [self initWithURLRequest:request protocols:protocols]; } - (void)_SR_commonInit; { NSString *scheme = _url.scheme.lowercaseString; assert([scheme isEqualToString:@"ws"] || [scheme isEqualToString:@"http"] || [scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]); if ([scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]) { _secure = YES; } _readyState = SR_CONNECTING; _consumerStopped = YES; _webSocketVersion = 13; _workQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL); // Going to set a specific on the queue so we can validate we're on the work queue dispatch_queue_set_specific(_workQueue, (__bridge void *)self, maybe_bridge(_workQueue), NULL); _delegateDispatchQueue = dispatch_get_main_queue(); sr_dispatch_retain(_delegateDispatchQueue); _readBuffer = [[NSMutableData alloc] init]; _outputBuffer = [[NSMutableData alloc] init]; _currentFrameData = [[NSMutableData alloc] init]; _consumers = [[NSMutableArray alloc] init]; _consumerPool = [[SRIOConsumerPool alloc] init]; _scheduledRunloops = [[NSMutableSet alloc] init]; [self _initializeStreams]; // default handlers } - (void)assertOnWorkQueue; { assert(dispatch_get_specific((__bridge void *)self) == maybe_bridge(_workQueue)); } - (void)dealloc { _inputStream.delegate = nil; _outputStream.delegate = nil; [_inputStream close]; [_outputStream close]; sr_dispatch_release(_workQueue); _workQueue = NULL; if (_receivedHTTPHeaders) { CFRelease(_receivedHTTPHeaders); _receivedHTTPHeaders = NULL; } if (_delegateDispatchQueue) { sr_dispatch_release(_delegateDispatchQueue); _delegateDispatchQueue = NULL; } } #ifndef NDEBUG - (void)setReadyState:(SRReadyState)aReadyState; { [self willChangeValueForKey:@"readyState"]; assert(aReadyState > _readyState); _readyState = aReadyState; [self didChangeValueForKey:@"readyState"]; } #endif - (void)open; { assert(_url); NSAssert(_readyState == SR_CONNECTING, @"Cannot call -(void)open on SRWebSocket more than once"); _selfRetain = self; [self _connect]; } // Calls block on delegate queue - (void)_performDelegateBlock:(dispatch_block_t)block; { if (_delegateOperationQueue) { [_delegateOperationQueue addOperationWithBlock:block]; } else { assert(_delegateDispatchQueue); dispatch_async(_delegateDispatchQueue, block); } } - (void)setDelegateDispatchQueue:(dispatch_queue_t)queue; { if (queue) { sr_dispatch_retain(queue); } if (_delegateDispatchQueue) { sr_dispatch_release(_delegateDispatchQueue); } _delegateDispatchQueue = queue; } - (BOOL)_checkHandshake:(CFHTTPMessageRef)httpMessage; { NSString *acceptHeader = CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(httpMessage, CFSTR("Sec-WebSocket-Accept"))); if (acceptHeader == nil) { return NO; } NSString *concattedString = [_secKey stringByAppendingString:SRWebSocketAppendToSecKeyString]; NSString *expectedAccept = [concattedString stringBySHA1ThenBase64Encoding]; return [acceptHeader isEqualToString:expectedAccept]; } - (void)_HTTPHeadersDidFinish; { NSInteger responseCode = CFHTTPMessageGetResponseStatusCode(_receivedHTTPHeaders); if (responseCode >= 400) { SRFastLog(@"Request failed with response code %d", responseCode); [self _failWithError:[NSError errorWithDomain:@"org.lolrus.SocketRocket" code:2132 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"received bad response code from server %d", responseCode] forKey:NSLocalizedDescriptionKey]]]; return; } if(![self _checkHandshake:_receivedHTTPHeaders]) { [self _failWithError:[NSError errorWithDomain:SRWebSocketErrorDomain code:2133 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Invalid Sec-WebSocket-Accept response"] forKey:NSLocalizedDescriptionKey]]]; return; } NSString *negotiatedProtocol = CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(_receivedHTTPHeaders, CFSTR("Sec-WebSocket-Protocol"))); if (negotiatedProtocol) { // Make sure we requested the protocol if ([_requestedProtocols indexOfObject:negotiatedProtocol] == NSNotFound) { [self _failWithError:[NSError errorWithDomain:SRWebSocketErrorDomain code:2133 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Server specified Sec-WebSocket-Protocol that wasn't requested"] forKey:NSLocalizedDescriptionKey]]]; return; } _protocol = negotiatedProtocol; } self.readyState = SR_OPEN; if (!_didFail) { [self _readFrameNew]; } [self _performDelegateBlock:^{ if ([self.delegate respondsToSelector:@selector(webSocketDidOpen:)]) { [self.delegate webSocketDidOpen:self]; }; }]; } - (void)_readHTTPHeader; { if (_receivedHTTPHeaders == NULL) { _receivedHTTPHeaders = CFHTTPMessageCreateEmpty(NULL, NO); } [self _readUntilHeaderCompleteWithCallback:^(SRWebSocket *self, NSData *data) { CFHTTPMessageAppendBytes(_receivedHTTPHeaders, (const UInt8 *)data.bytes, data.length); if (CFHTTPMessageIsHeaderComplete(_receivedHTTPHeaders)) { SRFastLog(@"Finished reading headers %@", CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(_receivedHTTPHeaders))); [self _HTTPHeadersDidFinish]; } else { [self _readHTTPHeader]; } }]; } - (void)didConnect { SRFastLog(@"Connected"); CFHTTPMessageRef request = CFHTTPMessageCreateRequest(NULL, CFSTR("GET"), (__bridge CFURLRef)_url, kCFHTTPVersion1_1); // Set host first so it defaults CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Host"), (__bridge CFStringRef)(_url.port ? [NSString stringWithFormat:@"%@:%@", _url.host, _url.port] : _url.host)); NSMutableData *keyBytes = [[NSMutableData alloc] initWithLength:16]; SecRandomCopyBytes(kSecRandomDefault, keyBytes.length, keyBytes.mutableBytes); _secKey = [keyBytes SR_stringByBase64Encoding]; assert([_secKey length] == 24); CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Upgrade"), CFSTR("websocket")); CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Connection"), CFSTR("Upgrade")); CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Key"), (__bridge CFStringRef)_secKey); CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Version"), (__bridge CFStringRef)[NSString stringWithFormat:@"%d", _webSocketVersion]); CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Origin"), (__bridge CFStringRef)_url.SR_origin); if (_requestedProtocols) { CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Protocol"), (__bridge CFStringRef)[_requestedProtocols componentsJoinedByString:@", "]); } [_urlRequest.allHTTPHeaderFields enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { CFHTTPMessageSetHeaderFieldValue(request, (__bridge CFStringRef)key, (__bridge CFStringRef)obj); }]; NSData *message = CFBridgingRelease(CFHTTPMessageCopySerializedMessage(request)); CFRelease(request); [self _writeData:message]; [self _readHTTPHeader]; } - (void)_initializeStreams; { NSInteger port = _url.port.integerValue; if (port == 0) { if (!_secure) { port = 80; } else { port = 443; } } NSString *host = _url.host; CFReadStreamRef readStream = NULL; CFWriteStreamRef writeStream = NULL; CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)host, port, &readStream, &writeStream); _outputStream = CFBridgingRelease(writeStream); _inputStream = CFBridgingRelease(readStream); if (_secure) { NSMutableDictionary *SSLOptions = [[NSMutableDictionary alloc] init]; [_outputStream setProperty:(__bridge id)kCFStreamSocketSecurityLevelNegotiatedSSL forKey:(__bridge id)kCFStreamPropertySocketSecurityLevel]; // If we're using pinned certs, don't validate the certificate chain if ([_urlRequest SR_SSLPinnedCertificates].count) { [SSLOptions setValue:[NSNumber numberWithBool:NO] forKey:(__bridge id)kCFStreamSSLValidatesCertificateChain]; } #if DEBUG [SSLOptions setValue:[NSNumber numberWithBool:NO] forKey:(__bridge id)kCFStreamSSLValidatesCertificateChain]; NSLog(@"SocketRocket: In debug mode. Allowing connection to any root cert"); #endif [_outputStream setProperty:SSLOptions forKey:(__bridge id)kCFStreamPropertySSLSettings]; } _inputStream.delegate = self; _outputStream.delegate = self; } - (void)_connect; { if (!_scheduledRunloops.count) { [self scheduleInRunLoop:[NSRunLoop SR_networkRunLoop] forMode:NSDefaultRunLoopMode]; } [_outputStream open]; [_inputStream open]; } - (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; { [_outputStream scheduleInRunLoop:aRunLoop forMode:mode]; [_inputStream scheduleInRunLoop:aRunLoop forMode:mode]; [_scheduledRunloops addObject:@[aRunLoop, mode]]; } - (void)unscheduleFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; { [_outputStream removeFromRunLoop:aRunLoop forMode:mode]; [_inputStream removeFromRunLoop:aRunLoop forMode:mode]; [_scheduledRunloops removeObject:@[aRunLoop, mode]]; } - (void)close; { [self closeWithCode:-1 reason:nil]; } - (void)closeWithCode:(NSInteger)code reason:(NSString *)reason; { assert(code); dispatch_async(_workQueue, ^{ if (self.readyState == SR_CLOSING || self.readyState == SR_CLOSED) { return; } BOOL wasConnecting = self.readyState == SR_CONNECTING; self.readyState = SR_CLOSING; SRFastLog(@"Closing with code %d reason %@", code, reason); if (wasConnecting) { [self _disconnect]; return; } size_t maxMsgSize = [reason maximumLengthOfBytesUsingEncoding:NSUTF8StringEncoding]; NSMutableData *mutablePayload = [[NSMutableData alloc] initWithLength:sizeof(uint16_t) + maxMsgSize]; NSData *payload = mutablePayload; ((uint16_t *)mutablePayload.mutableBytes)[0] = EndianU16_BtoN(code); if (reason) { NSRange remainingRange = {0}; NSUInteger usedLength = 0; BOOL success = [reason getBytes:(char *)mutablePayload.mutableBytes + sizeof(uint16_t) maxLength:payload.length - sizeof(uint16_t) usedLength:&usedLength encoding:NSUTF8StringEncoding options:NSStringEncodingConversionExternalRepresentation range:NSMakeRange(0, reason.length) remainingRange:&remainingRange]; assert(success); assert(remainingRange.length == 0); if (usedLength != maxMsgSize) { payload = [payload subdataWithRange:NSMakeRange(0, usedLength + sizeof(uint16_t))]; } } [self _sendFrameWithOpcode:SROpCodeConnectionClose data:payload]; }); } - (void)_closeWithProtocolError:(NSString *)message; { // Need to shunt this on the _callbackQueue first to see if they received any messages [self _performDelegateBlock:^{ [self closeWithCode:SRStatusCodeProtocolError reason:message]; dispatch_async(_workQueue, ^{ [self _disconnect]; }); }]; } - (void)_failWithError:(NSError *)error; { dispatch_async(_workQueue, ^{ if (self.readyState != SR_CLOSED) { _failed = YES; [self _performDelegateBlock:^{ if ([self.delegate respondsToSelector:@selector(webSocket:didFailWithError:)]) { [self.delegate webSocket:self didFailWithError:error]; } }]; self.readyState = SR_CLOSED; _selfRetain = nil; SRFastLog(@"Failing with error %@", error.localizedDescription); [self _disconnect]; } }); } - (void)_writeData:(NSData *)data; { [self assertOnWorkQueue]; if (_closeWhenFinishedWriting) { return; } [_outputBuffer appendData:data]; [self _pumpWriting]; } - (void)send:(id)data; { NSAssert(self.readyState != SR_CONNECTING, @"Invalid State: Cannot call send: until connection is open"); // TODO: maybe not copy this for performance data = [data copy]; dispatch_async(_workQueue, ^{ if ([data isKindOfClass:[NSString class]]) { [self _sendFrameWithOpcode:SROpCodeTextFrame data:[(NSString *)data dataUsingEncoding:NSUTF8StringEncoding]]; } else if ([data isKindOfClass:[NSData class]]) { [self _sendFrameWithOpcode:SROpCodeBinaryFrame data:data]; } else if (data == nil) { [self _sendFrameWithOpcode:SROpCodeTextFrame data:data]; } else { assert(NO); } }); } - (void)handlePing:(NSData *)pingData; { // Need to pingpong this off _callbackQueue first to make sure messages happen in order [self _performDelegateBlock:^{ dispatch_async(_workQueue, ^{ [self _sendFrameWithOpcode:SROpCodePong data:pingData]; }); }]; } - (void)handlePong; { // NOOP } - (void)_handleMessage:(id)message { SRFastLog(@"Received message"); [self _performDelegateBlock:^{ [self.delegate webSocket:self didReceiveMessage:message]; }]; } static inline BOOL closeCodeIsValid(int closeCode) { if (closeCode < 1000) { return NO; } if (closeCode >= 1000 && closeCode <= 1011) { if (closeCode == 1004 || closeCode == 1005 || closeCode == 1006) { return NO; } return YES; } if (closeCode >= 3000 && closeCode <= 3999) { return YES; } if (closeCode >= 4000 && closeCode <= 4999) { return YES; } return NO; } // Note from RFC: // // If there is a body, the first two // bytes of the body MUST be a 2-byte unsigned integer (in network byte // order) representing a status code with value /code/ defined in // Section 7.4. Following the 2-byte integer the body MAY contain UTF-8 // encoded data with value /reason/, the interpretation of which is not // defined by this specification. - (void)handleCloseWithData:(NSData *)data; { size_t dataSize = data.length; __block uint16_t closeCode = 0; SRFastLog(@"Received close frame"); if (dataSize == 1) { // TODO handle error [self _closeWithProtocolError:@"Payload for close must be larger than 2 bytes"]; return; } else if (dataSize >= 2) { [data getBytes:&closeCode length:sizeof(closeCode)]; _closeCode = EndianU16_BtoN(closeCode); if (!closeCodeIsValid(_closeCode)) { [self _closeWithProtocolError:[NSString stringWithFormat:@"Cannot have close code of %d", _closeCode]]; return; } if (dataSize > 2) { _closeReason = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(2, dataSize - 2)] encoding:NSUTF8StringEncoding]; if (!_closeReason) { [self _closeWithProtocolError:@"Close reason MUST be valid UTF-8"]; return; } } } else { _closeCode = SRStatusNoStatusReceived; } [self assertOnWorkQueue]; if (self.readyState == SR_OPEN) { [self closeWithCode:1000 reason:nil]; } dispatch_async(_workQueue, ^{ [self _disconnect]; }); } - (void)_disconnect; { [self assertOnWorkQueue]; SRFastLog(@"Trying to disconnect"); _closeWhenFinishedWriting = YES; [self _pumpWriting]; } - (void)_handleFrameWithData:(NSData *)frameData opCode:(NSInteger)opcode; { // Check that the current data is valid UTF8 BOOL isControlFrame = (opcode == SROpCodePing || opcode == SROpCodePong || opcode == SROpCodeConnectionClose); if (!isControlFrame) { [self _readFrameNew]; } else { dispatch_async(_workQueue, ^{ [self _readFrameContinue]; }); } switch (opcode) { case SROpCodeTextFrame: { NSString *str = [[NSString alloc] initWithData:frameData encoding:NSUTF8StringEncoding]; if (str == nil && frameData) { [self closeWithCode:SRStatusCodeInvalidUTF8 reason:@"Text frames must be valid UTF-8"]; dispatch_async(_workQueue, ^{ [self _disconnect]; }); return; } [self _handleMessage:str]; break; } case SROpCodeBinaryFrame: [self _handleMessage:[frameData copy]]; break; case SROpCodeConnectionClose: [self handleCloseWithData:frameData]; break; case SROpCodePing: [self handlePing:frameData]; break; case SROpCodePong: [self handlePong]; break; default: [self _closeWithProtocolError:[NSString stringWithFormat:@"Unknown opcode %d", opcode]]; // TODO: Handle invalid opcode break; } } - (void)_handleFrameHeader:(frame_header)frame_header curData:(NSData *)curData; { assert(frame_header.opcode != 0); if (self.readyState != SR_OPEN) { return; } BOOL isControlFrame = (frame_header.opcode == SROpCodePing || frame_header.opcode == SROpCodePong || frame_header.opcode == SROpCodeConnectionClose); if (isControlFrame && !frame_header.fin) { [self _closeWithProtocolError:@"Fragmented control frames not allowed"]; return; } if (isControlFrame && frame_header.payload_length >= 126) { [self _closeWithProtocolError:@"Control frames cannot have payloads larger than 126 bytes"]; return; } if (!isControlFrame) { _currentFrameOpcode = frame_header.opcode; _currentFrameCount += 1; } if (frame_header.payload_length == 0) { if (isControlFrame) { [self _handleFrameWithData:curData opCode:frame_header.opcode]; } else { if (frame_header.fin) { [self _handleFrameWithData:_currentFrameData opCode:frame_header.opcode]; } else { // TODO add assert that opcode is not a control; [self _readFrameContinue]; } } } else { [self _addConsumerWithDataLength:frame_header.payload_length callback:^(SRWebSocket *self, NSData *newData) { if (isControlFrame) { [self _handleFrameWithData:newData opCode:frame_header.opcode]; } else { if (frame_header.fin) { [self _handleFrameWithData:self->_currentFrameData opCode:frame_header.opcode]; } else { // TODO add assert that opcode is not a control; [self _readFrameContinue]; } } } readToCurrentFrame:!isControlFrame unmaskBytes:frame_header.masked]; } } /* From RFC: 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-------+-+-------------+-------------------------------+ |F|R|R|R| opcode|M| Payload len | Extended payload length | |I|S|S|S| (4) |A| (7) | (16/64) | |N|V|V|V| |S| | (if payload len==126/127) | | |1|2|3| |K| | | +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + | Extended payload length continued, if payload len == 127 | + - - - - - - - - - - - - - - - +-------------------------------+ | |Masking-key, if MASK set to 1 | +-------------------------------+-------------------------------+ | Masking-key (continued) | Payload Data | +-------------------------------- - - - - - - - - - - - - - - - + : Payload Data continued ... : + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Payload Data continued ... | +---------------------------------------------------------------+ */ static const uint8_t SRFinMask = 0x80; static const uint8_t SROpCodeMask = 0x0F; static const uint8_t SRRsvMask = 0x70; static const uint8_t SRMaskMask = 0x80; static const uint8_t SRPayloadLenMask = 0x7F; - (void)_readFrameContinue; { assert((_currentFrameCount == 0 && _currentFrameOpcode == 0) || (_currentFrameCount > 0 && _currentFrameOpcode > 0)); [self _addConsumerWithDataLength:2 callback:^(SRWebSocket *self, NSData *data) { __block frame_header header = {0}; const uint8_t *headerBuffer = data.bytes; assert(data.length >= 2); if (headerBuffer[0] & SRRsvMask) { [self _closeWithProtocolError:@"Server used RSV bits"]; return; } uint8_t receivedOpcode = (SROpCodeMask & headerBuffer[0]); BOOL isControlFrame = (receivedOpcode == SROpCodePing || receivedOpcode == SROpCodePong || receivedOpcode == SROpCodeConnectionClose); if (!isControlFrame && receivedOpcode != 0 && self->_currentFrameCount > 0) { [self _closeWithProtocolError:@"all data frames after the initial data frame must have opcode 0"]; return; } if (receivedOpcode == 0 && self->_currentFrameCount == 0) { [self _closeWithProtocolError:@"cannot continue a message"]; return; } header.opcode = receivedOpcode == 0 ? self->_currentFrameOpcode : receivedOpcode; header.fin = !!(SRFinMask & headerBuffer[0]); header.masked = !!(SRMaskMask & headerBuffer[1]); header.payload_length = SRPayloadLenMask & headerBuffer[1]; headerBuffer = NULL; if (header.masked) { [self _closeWithProtocolError:@"Client must receive unmasked data"]; } size_t extra_bytes_needed = header.masked ? sizeof(_currentReadMaskKey) : 0; if (header.payload_length == 126) { extra_bytes_needed += sizeof(uint16_t); } else if (header.payload_length == 127) { extra_bytes_needed += sizeof(uint64_t); } if (extra_bytes_needed == 0) { [self _handleFrameHeader:header curData:self->_currentFrameData]; } else { [self _addConsumerWithDataLength:extra_bytes_needed callback:^(SRWebSocket *self, NSData *data) { size_t mapped_size = data.length; const void *mapped_buffer = data.bytes; size_t offset = 0; if (header.payload_length == 126) { assert(mapped_size >= sizeof(uint16_t)); uint16_t newLen = EndianU16_BtoN(*(uint16_t *)(mapped_buffer)); header.payload_length = newLen; offset += sizeof(uint16_t); } else if (header.payload_length == 127) { assert(mapped_size >= sizeof(uint64_t)); header.payload_length = EndianU64_BtoN(*(uint64_t *)(mapped_buffer)); offset += sizeof(uint64_t); } else { assert(header.payload_length < 126 && header.payload_length >= 0); } if (header.masked) { assert(mapped_size >= sizeof(_currentReadMaskOffset) + offset); memcpy(self->_currentReadMaskKey, ((uint8_t *)mapped_buffer) + offset, sizeof(self->_currentReadMaskKey)); } [self _handleFrameHeader:header curData:self->_currentFrameData]; } readToCurrentFrame:NO unmaskBytes:NO]; } } readToCurrentFrame:NO unmaskBytes:NO]; } - (void)_readFrameNew; { dispatch_async(_workQueue, ^{ [_currentFrameData setLength:0]; _currentFrameOpcode = 0; _currentFrameCount = 0; _readOpCount = 0; _currentStringScanPosition = 0; [self _readFrameContinue]; }); } - (void)_pumpWriting; { [self assertOnWorkQueue]; NSUInteger dataLength = _outputBuffer.length; if (dataLength - _outputBufferOffset > 0 && _outputStream.hasSpaceAvailable) { NSInteger bytesWritten = [_outputStream write:_outputBuffer.bytes + _outputBufferOffset maxLength:dataLength - _outputBufferOffset]; if (bytesWritten == -1) { [self _failWithError:[NSError errorWithDomain:@"org.lolrus.SocketRocket" code:2145 userInfo:[NSDictionary dictionaryWithObject:@"Error writing to stream" forKey:NSLocalizedDescriptionKey]]]; return; } _outputBufferOffset += bytesWritten; if (_outputBufferOffset > 4096 && _outputBufferOffset > (_outputBuffer.length >> 1)) { _outputBuffer = [[NSMutableData alloc] initWithBytes:(char *)_outputBuffer.bytes + _outputBufferOffset length:_outputBuffer.length - _outputBufferOffset]; _outputBufferOffset = 0; } } if (_closeWhenFinishedWriting && _outputBuffer.length - _outputBufferOffset == 0 && (_inputStream.streamStatus != NSStreamStatusNotOpen && _inputStream.streamStatus != NSStreamStatusClosed) && !_sentClose) { _sentClose = YES; [_outputStream close]; [_inputStream close]; for (NSArray *runLoop in [_scheduledRunloops copy]) { [self unscheduleFromRunLoop:[runLoop objectAtIndex:0] forMode:[runLoop objectAtIndex:1]]; } if (!_failed) { [self _performDelegateBlock:^{ if ([self.delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)]) { [self.delegate webSocket:self didCloseWithCode:_closeCode reason:_closeReason wasClean:YES]; } }]; } _selfRetain = nil; } } - (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback; { [self assertOnWorkQueue]; [self _addConsumerWithScanner:consumer callback:callback dataLength:0]; } - (void)_addConsumerWithDataLength:(size_t)dataLength callback:(data_callback)callback readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; { [self assertOnWorkQueue]; assert(dataLength); [_consumers addObject:[_consumerPool consumerWithScanner:nil handler:callback bytesNeeded:dataLength readToCurrentFrame:readToCurrentFrame unmaskBytes:unmaskBytes]]; [self _pumpScanner]; } - (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback dataLength:(size_t)dataLength; { [self assertOnWorkQueue]; [_consumers addObject:[_consumerPool consumerWithScanner:consumer handler:callback bytesNeeded:dataLength readToCurrentFrame:NO unmaskBytes:NO]]; [self _pumpScanner]; } static const char CRLFCRLFBytes[] = {'\r', '\n', '\r', '\n'}; - (void)_readUntilHeaderCompleteWithCallback:(data_callback)dataHandler; { [self _readUntilBytes:CRLFCRLFBytes length:sizeof(CRLFCRLFBytes) callback:dataHandler]; } - (void)_readUntilBytes:(const void *)bytes length:(size_t)length callback:(data_callback)dataHandler; { // TODO optimize so this can continue from where we last searched stream_scanner consumer = ^size_t(NSData *data) { __block size_t found_size = 0; __block size_t match_count = 0; size_t size = data.length; const unsigned char *buffer = data.bytes; for (size_t i = 0; i < size; i++ ) { if (((const unsigned char *)buffer)[i] == ((const unsigned char *)bytes)[match_count]) { match_count += 1; if (match_count == length) { found_size = i + 1; break; } } else { match_count = 0; } } return found_size; }; [self _addConsumerWithScanner:consumer callback:dataHandler]; } // Returns true if did work - (BOOL)_innerPumpScanner { BOOL didWork = NO; if (self.readyState >= SR_CLOSING) { return didWork; } if (!_consumers.count) { return didWork; } size_t curSize = _readBuffer.length - _readBufferOffset; if (!curSize) { return didWork; } SRIOConsumer *consumer = [_consumers objectAtIndex:0]; size_t bytesNeeded = consumer.bytesNeeded; size_t foundSize = 0; if (consumer.consumer) { NSData *tempView = [NSData dataWithBytesNoCopy:(char *)_readBuffer.bytes + _readBufferOffset length:_readBuffer.length - _readBufferOffset freeWhenDone:NO]; foundSize = consumer.consumer(tempView); } else { assert(consumer.bytesNeeded); if (curSize >= bytesNeeded) { foundSize = bytesNeeded; } else if (consumer.readToCurrentFrame) { foundSize = curSize; } } NSData *slice = nil; if (consumer.readToCurrentFrame || foundSize) { NSRange sliceRange = NSMakeRange(_readBufferOffset, foundSize); slice = [_readBuffer subdataWithRange:sliceRange]; _readBufferOffset += foundSize; if (_readBufferOffset > 4096 && _readBufferOffset > (_readBuffer.length >> 1)) { _readBuffer = [[NSMutableData alloc] initWithBytes:(char *)_readBuffer.bytes + _readBufferOffset length:_readBuffer.length - _readBufferOffset]; _readBufferOffset = 0; } if (consumer.unmaskBytes) { NSMutableData *mutableSlice = [slice mutableCopy]; NSUInteger len = mutableSlice.length; uint8_t *bytes = mutableSlice.mutableBytes; for (NSUInteger i = 0; i < len; i++) { bytes[i] = bytes[i] ^ _currentReadMaskKey[_currentReadMaskOffset % sizeof(_currentReadMaskKey)]; _currentReadMaskOffset += 1; } slice = mutableSlice; } if (consumer.readToCurrentFrame) { [_currentFrameData appendData:slice]; _readOpCount += 1; if (_currentFrameOpcode == SROpCodeTextFrame) { // Validate UTF8 stuff. size_t currentDataSize = _currentFrameData.length; if (_currentFrameOpcode == SROpCodeTextFrame && currentDataSize > 0) { // TODO: Optimize the crap out of this. Don't really have to copy all the data each time size_t scanSize = currentDataSize - _currentStringScanPosition; NSData *scan_data = [_currentFrameData subdataWithRange:NSMakeRange(_currentStringScanPosition, scanSize)]; int32_t valid_utf8_size = validate_dispatch_data_partial_string(scan_data); if (valid_utf8_size == -1) { [self closeWithCode:SRStatusCodeInvalidUTF8 reason:@"Text frames must be valid UTF-8"]; dispatch_async(_workQueue, ^{ [self _disconnect]; }); return didWork; } else { _currentStringScanPosition += valid_utf8_size; } } } consumer.bytesNeeded -= foundSize; if (consumer.bytesNeeded == 0) { [_consumers removeObjectAtIndex:0]; consumer.handler(self, nil); [_consumerPool returnConsumer:consumer]; didWork = YES; } } else if (foundSize) { [_consumers removeObjectAtIndex:0]; consumer.handler(self, slice); [_consumerPool returnConsumer:consumer]; didWork = YES; } } return didWork; } -(void)_pumpScanner; { [self assertOnWorkQueue]; if (!_isPumping) { _isPumping = YES; } else { return; } while ([self _innerPumpScanner]) { } _isPumping = NO; } //#define NOMASK static const size_t SRFrameHeaderOverhead = 32; - (void)_sendFrameWithOpcode:(SROpCode)opcode data:(id)data; { [self assertOnWorkQueue]; NSAssert(data == nil || [data isKindOfClass:[NSData class]] || [data isKindOfClass:[NSString class]], @"Function expects nil, NSString or NSData"); size_t payloadLength = [data isKindOfClass:[NSString class]] ? [(NSString *)data lengthOfBytesUsingEncoding:NSUTF8StringEncoding] : [data length]; NSMutableData *frame = [[NSMutableData alloc] initWithLength:payloadLength + SRFrameHeaderOverhead]; if (!frame) { [self closeWithCode:SRStatusCodeMessageTooBig reason:@"Message too big"]; return; } uint8_t *frame_buffer = (uint8_t *)[frame mutableBytes]; // set fin frame_buffer[0] = SRFinMask | opcode; BOOL useMask = YES; #ifdef NOMASK useMask = NO; #endif if (useMask) { // set the mask and header frame_buffer[1] |= SRMaskMask; } size_t frame_buffer_size = 2; const uint8_t *unmasked_payload = NULL; if ([data isKindOfClass:[NSData class]]) { unmasked_payload = (uint8_t *)[data bytes]; } else if ([data isKindOfClass:[NSString class]]) { unmasked_payload = (const uint8_t *)[data UTF8String]; } else { assert(NO); } if (payloadLength < 126) { frame_buffer[1] |= payloadLength; } else if (payloadLength <= UINT16_MAX) { frame_buffer[1] |= 126; *((uint16_t *)(frame_buffer + frame_buffer_size)) = EndianU16_BtoN((uint16_t)payloadLength); frame_buffer_size += sizeof(uint16_t); } else { frame_buffer[1] |= 127; *((uint64_t *)(frame_buffer + frame_buffer_size)) = EndianU64_BtoN((uint64_t)payloadLength); frame_buffer_size += sizeof(uint64_t); } if (!useMask) { for (size_t i = 0; i < payloadLength; i++) { frame_buffer[frame_buffer_size] = unmasked_payload[i]; frame_buffer_size += 1; } } else { uint8_t *mask_key = frame_buffer + frame_buffer_size; SecRandomCopyBytes(kSecRandomDefault, sizeof(uint32_t), (uint8_t *)mask_key); frame_buffer_size += sizeof(uint32_t); // TODO: could probably optimize this with SIMD for (size_t i = 0; i < payloadLength; i++) { frame_buffer[frame_buffer_size] = unmasked_payload[i] ^ mask_key[i % sizeof(uint32_t)]; frame_buffer_size += 1; } } assert(frame_buffer_size <= [frame length]); frame.length = frame_buffer_size; [self _writeData:frame]; } - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode; { if (_secure && !_pinnedCertFound && (eventCode == NSStreamEventHasBytesAvailable || eventCode == NSStreamEventHasSpaceAvailable)) { NSArray *sslCerts = [_urlRequest SR_SSLPinnedCertificates]; if (sslCerts) { SecTrustRef secTrust = (__bridge SecTrustRef)[aStream propertyForKey:(__bridge id)kCFStreamPropertySSLPeerTrust]; if (secTrust) { NSInteger numCerts = SecTrustGetCertificateCount(secTrust); for (NSInteger i = 0; i < numCerts && !_pinnedCertFound; i++) { SecCertificateRef cert = SecTrustGetCertificateAtIndex(secTrust, i); NSData *certData = CFBridgingRelease(SecCertificateCopyData(cert)); for (id ref in sslCerts) { SecCertificateRef trustedCert = (__bridge SecCertificateRef)ref; NSData *trustedCertData = CFBridgingRelease(SecCertificateCopyData(trustedCert)); if ([trustedCertData isEqualToData:certData]) { _pinnedCertFound = YES; break; } } } } if (!_pinnedCertFound) { dispatch_async(_workQueue, ^{ [self _failWithError:[NSError errorWithDomain:@"org.lolrus.SocketRocket" code:23556 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Invalid server cert"] forKey:NSLocalizedDescriptionKey]]]; }); return; } } } dispatch_async(_workQueue, ^{ switch (eventCode) { case NSStreamEventOpenCompleted: { SRFastLog(@"NSStreamEventOpenCompleted %@", aStream); if (self.readyState >= SR_CLOSING) { return; } assert(_readBuffer); if (self.readyState == SR_CONNECTING && aStream == _inputStream) { [self didConnect]; } [self _pumpWriting]; [self _pumpScanner]; break; } case NSStreamEventErrorOccurred: { SRFastLog(@"NSStreamEventErrorOccurred %@ %@", aStream, [[aStream streamError] copy]); /// TODO specify error better! [self _failWithError:aStream.streamError]; _readBufferOffset = 0; [_readBuffer setLength:0]; break; } case NSStreamEventEndEncountered: { [self _pumpScanner]; SRFastLog(@"NSStreamEventEndEncountered %@", aStream); if (aStream.streamError) { [self _failWithError:aStream.streamError]; } else { if (self.readyState != SR_CLOSED) { self.readyState = SR_CLOSED; _selfRetain = nil; } if (!_sentClose && !_failed) { _sentClose = YES; // If we get closed in this state it's probably not clean because we should be sending this when we send messages [self _performDelegateBlock:^{ if ([self.delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)]) { [self.delegate webSocket:self didCloseWithCode:0 reason:@"Stream end encountered" wasClean:NO]; } }]; } } break; } case NSStreamEventHasBytesAvailable: { SRFastLog(@"NSStreamEventHasBytesAvailable %@", aStream); const int bufferSize = 2048; uint8_t buffer[bufferSize]; while (_inputStream.hasBytesAvailable) { int bytes_read = [_inputStream read:buffer maxLength:bufferSize]; if (bytes_read > 0) { [_readBuffer appendBytes:buffer length:bytes_read]; } else if (bytes_read < 0) { [self _failWithError:_inputStream.streamError]; } if (bytes_read != bufferSize) { break; } }; [self _pumpScanner]; break; } case NSStreamEventHasSpaceAvailable: { SRFastLog(@"NSStreamEventHasSpaceAvailable %@", aStream); [self _pumpWriting]; break; } default: SRFastLog(@"(default) %@", aStream); break; } }); } @end @implementation SRIOConsumer @synthesize bytesNeeded = _bytesNeeded; @synthesize consumer = _scanner; @synthesize handler = _handler; @synthesize readToCurrentFrame = _readToCurrentFrame; @synthesize unmaskBytes = _unmaskBytes; - (void)setupWithScanner:(stream_scanner)scanner handler:(data_callback)handler bytesNeeded:(size_t)bytesNeeded readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; { _scanner = [scanner copy]; _handler = [handler copy]; _bytesNeeded = bytesNeeded; _readToCurrentFrame = readToCurrentFrame; _unmaskBytes = unmaskBytes; assert(_scanner || _bytesNeeded); } @end @implementation SRIOConsumerPool { NSUInteger _poolSize; NSMutableArray *_bufferedConsumers; } - (id)initWithBufferCapacity:(NSUInteger)poolSize; { self = [super init]; if (self) { _poolSize = poolSize; _bufferedConsumers = [[NSMutableArray alloc] initWithCapacity:poolSize]; } return self; } - (id)init { return [self initWithBufferCapacity:8]; } - (SRIOConsumer *)consumerWithScanner:(stream_scanner)scanner handler:(data_callback)handler bytesNeeded:(size_t)bytesNeeded readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; { SRIOConsumer *consumer = nil; if (_bufferedConsumers.count) { consumer = [_bufferedConsumers lastObject]; [_bufferedConsumers removeLastObject]; } else { consumer = [[SRIOConsumer alloc] init]; } [consumer setupWithScanner:scanner handler:handler bytesNeeded:bytesNeeded readToCurrentFrame:readToCurrentFrame unmaskBytes:unmaskBytes]; return consumer; } - (void)returnConsumer:(SRIOConsumer *)consumer; { if (_bufferedConsumers.count < _poolSize) { [_bufferedConsumers addObject:consumer]; } } @end @implementation NSURLRequest (CertificateAdditions) - (NSArray *)SR_SSLPinnedCertificates; { return [NSURLProtocol propertyForKey:@"SR_SSLPinnedCertificates" inRequest:self]; } @end @implementation NSMutableURLRequest (CertificateAdditions) - (NSArray *)SR_SSLPinnedCertificates; { return [NSURLProtocol propertyForKey:@"SR_SSLPinnedCertificates" inRequest:self]; } - (void)setSR_SSLPinnedCertificates:(NSArray *)SR_SSLPinnedCertificates; { [NSURLProtocol setProperty:SR_SSLPinnedCertificates forKey:@"SR_SSLPinnedCertificates" inRequest:self]; } @end @implementation NSURL (SRWebSocket) - (NSString *)SR_origin; { NSString *scheme = [self.scheme lowercaseString]; if ([scheme isEqualToString:@"wss"]) { scheme = @"https"; } else if ([scheme isEqualToString:@"ws"]) { scheme = @"http"; } if (self.port) { return [NSString stringWithFormat:@"%@://%@:%@/", scheme, self.host, self.port]; } else { return [NSString stringWithFormat:@"%@://%@/", scheme, self.host]; } } @end static inline dispatch_queue_t log_queue() { static dispatch_queue_t queue = 0; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ queue = dispatch_queue_create("fast log queue", DISPATCH_QUEUE_SERIAL); }); return queue; } //#define SR_ENABLE_LOG static inline void SRFastLog(NSString *format, ...) { #ifdef SR_ENABLE_LOG __block va_list arg_list; va_start (arg_list, format); NSString *formattedString = [[NSString alloc] initWithFormat:format arguments:arg_list]; va_end(arg_list); NSLog(@"[SR] %@", formattedString); #endif } #ifdef HAS_ICU static inline int32_t validate_dispatch_data_partial_string(NSData *data) { const void * contents = [data bytes]; long size = [data length]; const uint8_t *str = (const uint8_t *)contents; UChar32 codepoint = 1; int32_t offset = 0; int32_t lastOffset = 0; while(offset < size && codepoint > 0) { lastOffset = offset; U8_NEXT(str, offset, size, codepoint); } if (codepoint == -1) { // Check to see if the last byte is valid or whether it was just continuing if (!U8_IS_LEAD(str[lastOffset]) || U8_COUNT_TRAIL_BYTES(str[lastOffset]) + lastOffset < (int32_t)size) { size = -1; } else { uint8_t leadByte = str[lastOffset]; U8_MASK_LEAD_BYTE(leadByte, U8_COUNT_TRAIL_BYTES(leadByte)); for (int i = lastOffset + 1; i < offset; i++) { if (U8_IS_SINGLE(str[i]) || U8_IS_LEAD(str[i]) || !U8_IS_TRAIL(str[i])) { size = -1; } } if (size != -1) { size = lastOffset; } } } if (size != -1 && ![[NSString alloc] initWithBytesNoCopy:(char *)[data bytes] length:size encoding:NSUTF8StringEncoding freeWhenDone:NO]) { size = -1; } return size; } #else // This is a hack, and probably not optimal static inline int32_t validate_dispatch_data_partial_string(NSData *data) { static const int maxCodepointSize = 3; for (int i = 0; i < maxCodepointSize; i++) { NSString *str = [[NSString alloc] initWithBytesNoCopy:(char *)data.bytes length:data.length - i encoding:NSUTF8StringEncoding freeWhenDone:NO]; if (str) { return data.length - i; } } return -1; } #endif static _SRRunLoopThread *networkThread = nil; static NSRunLoop *networkRunLoop = nil; @implementation NSRunLoop (SRWebSocket) + (NSRunLoop *)SR_networkRunLoop { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ networkThread = [[_SRRunLoopThread alloc] init]; networkThread.name = @"com.squareup.SocketRocket.NetworkThread"; [networkThread start]; networkRunLoop = networkThread.runLoop; }); return networkRunLoop; } @end @implementation _SRRunLoopThread { dispatch_group_t _waitGroup; } @synthesize runLoop = _runLoop; - (void)dealloc { sr_dispatch_release(_waitGroup); } - (id)init { self = [super init]; if (self) { _waitGroup = dispatch_group_create(); dispatch_group_enter(_waitGroup); } return self; } - (void)main; { @autoreleasepool { _runLoop = [NSRunLoop currentRunLoop]; dispatch_group_leave(_waitGroup); NSTimer *timer = [[NSTimer alloc] initWithFireDate:[NSDate distantFuture] interval:0.0 target:nil selector:nil userInfo:nil repeats:NO]; [_runLoop addTimer:timer forMode:NSDefaultRunLoopMode]; while ([_runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) { } assert(NO); } } - (NSRunLoop *)runLoop; { dispatch_group_wait(_waitGroup, DISPATCH_TIME_FOREVER); return _runLoop; } @end ================================================ FILE: ArcBit/External/SocketRocket/SocketRocket-Prefix.pch ================================================ // // Copyright 2012 Square Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #ifdef __cplusplus extern "C" { #endif #ifdef __OBJC__ #import #endif #ifdef __cplusplus } #endif ================================================ FILE: ArcBit/External/SocketRocket/base64.c ================================================ /* $OpenBSD: base64.c,v 1.5 2006/10/21 09:55:03 otto Exp $ */ /* * Copyright (c) 1996 by Internet Software Consortium. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS * SOFTWARE. */ /* * Portions Copyright (c) 1995 by International Business Machines, Inc. * * International Business Machines, Inc. (hereinafter called IBM) grants * permission under its copyrights to use, copy, modify, and distribute this * Software with or without fee, provided that the above copyright notice and * all paragraphs of this notice appear in all copies, and that the name of IBM * not be used in connection with the marketing of any product incorporating * the Software or modifications thereof, without specific, written prior * permission. * * To the extent it has a right to do so, IBM grants an immunity from suit * under its patents, if any, for the use, sale or manufacture of products to * the extent that such products are used for performing Domain Name System * dynamic updates in TCP/IP networks by means of the Software. No immunity is * granted for any product per se or for any other function of any product. * * THE SOFTWARE IS PROVIDED "AS IS", AND IBM DISCLAIMS ALL WARRANTIES, * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE. IN NO EVENT SHALL IBM BE LIABLE FOR ANY SPECIAL, * DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE, EVEN * IF IBM IS APPRISED OF THE POSSIBILITY OF SUCH DAMAGES. */ /* OPENBSD ORIGINAL: lib/libc/net/base64.c */ #if (!defined(HAVE_B64_NTOP) && !defined(HAVE___B64_NTOP)) || (!defined(HAVE_B64_PTON) && !defined(HAVE___B64_PTON)) #include #include #include #include #include #include #include #include #include #include "base64.h" static const char Base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; static const char Pad64 = '='; /* (From RFC1521 and draft-ietf-dnssec-secext-03.txt) The following encoding technique is taken from RFC 1521 by Borenstein and Freed. It is reproduced here in a slightly edited form for convenience. A 65-character subset of US-ASCII is used, enabling 6 bits to be represented per printable character. (The extra 65th character, "=", is used to signify a special processing function.) The encoding process represents 24-bit groups of input bits as output strings of 4 encoded characters. Proceeding from left to right, a 24-bit input group is formed by concatenating 3 8-bit input groups. These 24 bits are then treated as 4 concatenated 6-bit groups, each of which is translated into a single digit in the base64 alphabet. Each 6-bit group is used as an index into an array of 64 printable characters. The character referenced by the index is placed in the output string. Table 1: The Base64 Alphabet Value Encoding Value Encoding Value Encoding Value Encoding 0 A 17 R 34 i 51 z 1 B 18 S 35 j 52 0 2 C 19 T 36 k 53 1 3 D 20 U 37 l 54 2 4 E 21 V 38 m 55 3 5 F 22 W 39 n 56 4 6 G 23 X 40 o 57 5 7 H 24 Y 41 p 58 6 8 I 25 Z 42 q 59 7 9 J 26 a 43 r 60 8 10 K 27 b 44 s 61 9 11 L 28 c 45 t 62 + 12 M 29 d 46 u 63 / 13 N 30 e 47 v 14 O 31 f 48 w (pad) = 15 P 32 g 49 x 16 Q 33 h 50 y Special processing is performed if fewer than 24 bits are available at the end of the data being encoded. A full encoding quantum is always completed at the end of a quantity. When fewer than 24 input bits are available in an input group, zero bits are added (on the right) to form an integral number of 6-bit groups. Padding at the end of the data is performed using the '=' character. Since all base64 input is an integral number of octets, only the ------------------------------------------------- following cases can arise: (1) the final quantum of encoding input is an integral multiple of 24 bits; here, the final unit of encoded output will be an integral multiple of 4 characters with no "=" padding, (2) the final quantum of encoding input is exactly 8 bits; here, the final unit of encoded output will be two characters followed by two "=" padding characters, or (3) the final quantum of encoding input is exactly 16 bits; here, the final unit of encoded output will be three characters followed by one "=" padding character. */ #if !defined(HAVE_B64_NTOP) && !defined(HAVE___B64_NTOP) int b64_ntop(u_char const *src, size_t srclength, char *target, size_t targsize) { size_t datalength = 0; u_char input[3]; u_char output[4]; u_int i; while (2 < srclength) { input[0] = *src++; input[1] = *src++; input[2] = *src++; srclength -= 3; output[0] = input[0] >> 2; output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4); output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6); output[3] = input[2] & 0x3f; if (datalength + 4 > targsize) return (-1); target[datalength++] = Base64[output[0]]; target[datalength++] = Base64[output[1]]; target[datalength++] = Base64[output[2]]; target[datalength++] = Base64[output[3]]; } /* Now we worry about padding. */ if (0 != srclength) { /* Get what's left. */ input[0] = input[1] = input[2] = '\0'; for (i = 0; i < srclength; i++) input[i] = *src++; output[0] = input[0] >> 2; output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4); output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6); if (datalength + 4 > targsize) return (-1); target[datalength++] = Base64[output[0]]; target[datalength++] = Base64[output[1]]; if (srclength == 1) target[datalength++] = Pad64; else target[datalength++] = Base64[output[2]]; target[datalength++] = Pad64; } if (datalength >= targsize) return (-1); target[datalength] = '\0'; /* Returned value doesn't count \0. */ return (datalength); } #endif /* !defined(HAVE_B64_NTOP) && !defined(HAVE___B64_NTOP) */ #if !defined(HAVE_B64_PTON) && !defined(HAVE___B64_PTON) /* skips all whitespace anywhere. converts characters, four at a time, starting at (or after) src from base - 64 numbers into three 8 bit bytes in the target area. it returns the number of data bytes stored at the target, or -1 on error. */ int b64_pton(char const *src, u_char *target, size_t targsize) { u_int tarindex, state; int ch; char *pos; state = 0; tarindex = 0; while ((ch = *src++) != '\0') { if (isspace(ch)) /* Skip whitespace anywhere. */ continue; if (ch == Pad64) break; pos = strchr(Base64, ch); if (pos == 0) /* A non-base64 character. */ return (-1); switch (state) { case 0: if (target) { if (tarindex >= targsize) return (-1); target[tarindex] = (pos - Base64) << 2; } state = 1; break; case 1: if (target) { if (tarindex + 1 >= targsize) return (-1); target[tarindex] |= (pos - Base64) >> 4; target[tarindex+1] = ((pos - Base64) & 0x0f) << 4 ; } tarindex++; state = 2; break; case 2: if (target) { if (tarindex + 1 >= targsize) return (-1); target[tarindex] |= (pos - Base64) >> 2; target[tarindex+1] = ((pos - Base64) & 0x03) << 6; } tarindex++; state = 3; break; case 3: if (target) { if (tarindex >= targsize) return (-1); target[tarindex] |= (pos - Base64); } tarindex++; state = 0; break; } } /* * We are done decoding Base-64 chars. Let's see if we ended * on a byte boundary, and/or with erroneous trailing characters. */ if (ch == Pad64) { /* We got a pad char. */ ch = *src++; /* Skip it, get next. */ switch (state) { case 0: /* Invalid = in first position */ case 1: /* Invalid = in second position */ return (-1); case 2: /* Valid, means one byte of info */ /* Skip any number of spaces. */ for (; ch != '\0'; ch = *src++) if (!isspace(ch)) break; /* Make sure there is another trailing = sign. */ if (ch != Pad64) return (-1); ch = *src++; /* Skip the = */ /* Fall through to "single trailing =" case. */ /* FALLTHROUGH */ case 3: /* Valid, means two bytes of info */ /* * We know this char is an =. Is there anything but * whitespace after it? */ for (; ch != '\0'; ch = *src++) if (!isspace(ch)) return (-1); /* * Now make sure for cases 2 and 3 that the "extra" * bits that slopped past the last full byte were * zeros. If we don't check them, they become a * subliminal channel. */ if (target && target[tarindex] != 0) return (-1); } } else { /* * We ended by seeing the end of the string. Make sure we * have no partial bytes lying around. */ if (state != 0) return (-1); } return (tarindex); } #endif /* !defined(HAVE_B64_PTON) && !defined(HAVE___B64_PTON) */ #endif ================================================ FILE: ArcBit/External/SocketRocket/base64.h ================================================ // Copyright 2012 Square Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #ifndef SocketRocket_base64_h #define SocketRocket_base64_h #include extern int b64_ntop(u_char const *src, size_t srclength, char *target, size_t targsize); extern int b64_pton(char const *src, u_char *target, size_t targsize); #endif ================================================ FILE: ArcBit/External/TLCloudDocumentSyncWrapper/TLCloudDocumentSyncWrapper.h ================================================ // // TLCloudDocumentSyncWrapper.h // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA #import #import @interface TLCloudDocumentSyncWrapper : NSObject + (TLCloudDocumentSyncWrapper *)instance; - (BOOL)checkCloudAvailability; - (void)saveFileToCloud:(NSString*)fileName content:(NSString*)content completion:(void (^)(UIDocument *cloudDocument, NSData *documentData, NSError *error))completion; - (void)getFileFromCloud:(NSString*)fileName completion:(void (^)(UIDocument *cloudDocument, NSData *documentData, NSError *error))completion; @end ================================================ FILE: ArcBit/External/TLCloudDocumentSyncWrapper/TLCloudDocumentSyncWrapper.m ================================================ // // TLCloudDocumentSyncWrapper.m // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA #import "TLCloudDocumentSyncWrapper.h" static NSString* WALLET_JSON_CLOUD_BACKUP_FILE_EXTENSION = @"backup"; @implementation TLCloudDocumentSyncWrapper + (TLCloudDocumentSyncWrapper *)instance { static id _instance = nil; static dispatch_once_t oncePredicate; dispatch_once(&oncePredicate, ^{ _instance = [[self alloc] init]; }); return _instance; } - (id)init { self = [super init]; if (self) { [[iCloud sharedCloud] setDelegate:self]; [[iCloud sharedCloud] setVerboseLogging:YES]; [[iCloud sharedCloud] setupiCloudDocumentSyncWithUbiquityContainer:nil]; } return self; } - (BOOL)checkCloudAvailability { BOOL cloudIsAvailable = [[iCloud sharedCloud] checkCloudAvailability]; return cloudIsAvailable; } - (void)saveFileToCloud:(NSString*)fileName content:(NSString*)content completion:(void (^)(UIDocument *cloudDocument, NSData *documentData, NSError *error))completion { NSData* data = [content dataUsingEncoding:NSUTF8StringEncoding]; [[iCloud sharedCloud] saveAndCloseDocumentWithName:fileName withContent:data completion:^(UIDocument *cloudDocument, NSData *documentData, NSError *error) { completion(cloudDocument, documentData, error); }]; } - (void)getFileFromCloud:(NSString*)fileName completion:(void (^)(UIDocument *cloudDocument, NSData *documentData, NSError *error))completion { [[iCloud sharedCloud] retrieveCloudDocumentWithName:fileName completion:^(UIDocument *cloudDocument, NSData *documentData, NSError *error) { completion(cloudDocument, documentData, error); }]; } #pragma mark - iCloud Methods - (void)iCloudDidFinishInitializingWitUbiquityToken:(id)cloudToken withUbiquityContainer:(NSURL *)ubiquityContainer { NSLog(@"Ubiquity container initialized. You may proceed to perform document operations."); } - (void)iCloudAvailabilityDidChangeToState:(BOOL)cloudIsAvailable withUbiquityToken:(id)ubiquityToken withUbiquityContainer:(NSURL *)ubiquityContainer { if (!cloudIsAvailable) { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"iCloud Unavailable" message:@"iCloud is no longer available. Make sure that you are signed into a valid iCloud account." delegate:nil cancelButtonTitle:@"Okay" otherButtonTitles:nil]; [alert show]; } } - (void)iCloudFilesDidChange:(NSMutableArray *)files withNewFileNames:(NSMutableArray *)fileNames { } - (NSString *)iCloudQueryLimitedToFileExtension { return WALLET_JSON_CLOUD_BACKUP_FILE_EXTENSION; } - (void)iCloudFileConflictBetweenCloudFile:(NSDictionary *)cloudFile andLocalFile:(NSDictionary *)localFile { } - (void)iCloudFileUpdateDidBegin { } - (void)iCloudFileUpdateDidEnd { } - (void)refreshCloudList { [[iCloud sharedCloud] updateFiles]; } - (void)refreshCloudListAfterSetup { [[iCloud sharedCloud] setDelegate:self]; [[iCloud sharedCloud] updateFiles]; } @end ================================================ FILE: ArcBit/External/UIAlertController+Blocks/UIAlertConrtoller+Blocks.m ================================================ // // UIAlertController+Blocks.m // UIAlertControllerBlocks // // Created by Ryan Maxwell on 11/09/14. // // The MIT License (MIT) // // Copyright (c) 2014 Ryan Maxwell // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of // the Software, and to permit persons to whom the Software is furnished to do so, // subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // #import "UIAlertController+Blocks.h" static NSInteger const UIAlertControllerBlocksCancelButtonIndex = 0; static NSInteger const UIAlertControllerBlocksDestructiveButtonIndex = 1; static NSInteger const UIAlertControllerBlocksFirstOtherButtonIndex = 2; @implementation UIAlertController (Blocks) + (instancetype)showInViewController:(UIViewController *)viewController withTitle:(NSString *)title message:(NSString *)message preferredStyle:(UIAlertControllerStyle)preferredStyle cancelButtonTitle:(NSString *)cancelButtonTitle destructiveButtonTitle:(NSString *)destructiveButtonTitle otherButtonTitles:(NSArray *)otherButtonTitles popoverPresentationControllerBlock:(void(^)(UIPopoverPresentationController *popover))popoverPresentationControllerBlock tapBlock:(UIAlertControllerCompletionBlock)tapBlock preShowBlock:(UIAlertControllerPreshowBlock)preShowBlock { UIAlertController *strongController = [self alertControllerWithTitle:title message:message preferredStyle:preferredStyle]; __weak UIAlertController *controller = strongController; if (cancelButtonTitle) { UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:cancelButtonTitle style:UIAlertActionStyleCancel handler:^(UIAlertAction *action){ if (tapBlock) { tapBlock(controller, action, UIAlertControllerBlocksCancelButtonIndex); } }]; [controller addAction:cancelAction]; } if (destructiveButtonTitle) { UIAlertAction *destructiveAction = [UIAlertAction actionWithTitle:destructiveButtonTitle style:UIAlertActionStyleDestructive handler:^(UIAlertAction *action){ if (tapBlock) { tapBlock(controller, action, UIAlertControllerBlocksDestructiveButtonIndex); } }]; [controller addAction:destructiveAction]; } for (NSUInteger i = 0; i < otherButtonTitles.count; i++) { NSString *otherButtonTitle = otherButtonTitles[i]; UIAlertAction *otherAction = [UIAlertAction actionWithTitle:otherButtonTitle style:UIAlertActionStyleDefault handler:^(UIAlertAction *action){ if (tapBlock) { tapBlock(controller, action, UIAlertControllerBlocksFirstOtherButtonIndex + i); } }]; [controller addAction:otherAction]; } if (popoverPresentationControllerBlock) { popoverPresentationControllerBlock(controller.popoverPresentationController); } if(preShowBlock) { preShowBlock(controller); } [viewController presentViewController:controller animated:YES completion:nil]; return controller; } + (instancetype)showInViewController:(UIViewController *)viewController withTitle:(NSString *)title message:(NSString *)message preferredStyle:(UIAlertControllerStyle)preferredStyle cancelButtonTitle:(NSString *)cancelButtonTitle destructiveButtonTitle:(NSString *)destructiveButtonTitle otherButtonTitles:(NSArray *)otherButtonTitles popoverPresentationControllerBlock:(void(^)(UIPopoverPresentationController *popover))popoverPresentationControllerBlock tapBlock:(UIAlertControllerCompletionBlock)tapBlock { UIAlertController *strongController = [self alertControllerWithTitle:title message:message preferredStyle:preferredStyle]; __weak UIAlertController *controller = strongController; if (cancelButtonTitle) { UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:cancelButtonTitle style:UIAlertActionStyleCancel handler:^(UIAlertAction *action){ if (tapBlock) { tapBlock(controller, action, UIAlertControllerBlocksCancelButtonIndex); } }]; [controller addAction:cancelAction]; } if (destructiveButtonTitle) { UIAlertAction *destructiveAction = [UIAlertAction actionWithTitle:destructiveButtonTitle style:UIAlertActionStyleDestructive handler:^(UIAlertAction *action){ if (tapBlock) { tapBlock(controller, action, UIAlertControllerBlocksDestructiveButtonIndex); } }]; [controller addAction:destructiveAction]; } for (NSUInteger i = 0; i < otherButtonTitles.count; i++) { NSString *otherButtonTitle = otherButtonTitles[i]; UIAlertAction *otherAction = [UIAlertAction actionWithTitle:otherButtonTitle style:UIAlertActionStyleDefault handler:^(UIAlertAction *action){ if (tapBlock) { tapBlock(controller, action, UIAlertControllerBlocksFirstOtherButtonIndex + i); } }]; [controller addAction:otherAction]; } if (popoverPresentationControllerBlock) { popoverPresentationControllerBlock(controller.popoverPresentationController); } [viewController presentViewController:controller animated:YES completion:nil]; return controller; } + (instancetype)showAlertInViewController:(UIViewController *)viewController withTitle:(NSString *)title message:(NSString *)message cancelButtonTitle:(NSString *)cancelButtonTitle destructiveButtonTitle:(NSString *)destructiveButtonTitle otherButtonTitles:(NSArray *)otherButtonTitles tapBlock:(UIAlertControllerCompletionBlock)tapBlock { return [self showInViewController:viewController withTitle:title message:message preferredStyle:UIAlertControllerStyleAlert cancelButtonTitle:cancelButtonTitle destructiveButtonTitle:destructiveButtonTitle otherButtonTitles:otherButtonTitles popoverPresentationControllerBlock:nil tapBlock:tapBlock preShowBlock:nil]; } + (instancetype)showAlertInViewController:(UIViewController *)viewController withTitle:(NSString *)title message:(NSString *)message preferredStyle:(UIAlertControllerStyle)preferredStyle cancelButtonTitle:(NSString *)cancelButtonTitle destructiveButtonTitle:(NSString *)destructiveButtonTitle otherButtonTitles:(NSArray *)otherButtonTitles preShowBlock:(UIAlertControllerPreshowBlock)preShowBlock tapBlock:(UIAlertControllerCompletionBlock)tapBlock { return [self showInViewController:viewController withTitle:title message:message preferredStyle:preferredStyle cancelButtonTitle:cancelButtonTitle destructiveButtonTitle:destructiveButtonTitle otherButtonTitles:otherButtonTitles popoverPresentationControllerBlock:nil tapBlock:tapBlock preShowBlock:preShowBlock ]; } + (instancetype)showAlertInViewController:(UIViewController *)viewController withTitle:(NSString *)title message:(NSString *)message preferredStyle:(UIAlertControllerStyle)preferredStyle cancelButtonTitle:(NSString *)cancelButtonTitle destructiveButtonTitle:(NSString *)destructiveButtonTitle otherButtonTitles:(NSArray *)otherButtonTitles tapBlock:(UIAlertControllerCompletionBlock)tapBlock { return [self showInViewController:viewController withTitle:title message:message preferredStyle:preferredStyle cancelButtonTitle:cancelButtonTitle destructiveButtonTitle:destructiveButtonTitle otherButtonTitles:otherButtonTitles popoverPresentationControllerBlock:nil tapBlock:tapBlock preShowBlock:nil ]; } + (instancetype)showActionSheetInViewController:(UIViewController *)viewController withTitle:(NSString *)title message:(NSString *)message cancelButtonTitle:(NSString *)cancelButtonTitle destructiveButtonTitle:(NSString *)destructiveButtonTitle otherButtonTitles:(NSArray *)otherButtonTitles tapBlock:(UIAlertControllerCompletionBlock)tapBlock { return [self showActionSheetInViewController:viewController withTitle:title message:message cancelButtonTitle:cancelButtonTitle destructiveButtonTitle:destructiveButtonTitle otherButtonTitles:otherButtonTitles popoverPresentationControllerBlock:nil tapBlock:tapBlock]; } + (instancetype)showActionSheetInViewController:(UIViewController *)viewController withTitle:(NSString *)title message:(NSString *)message cancelButtonTitle:(NSString *)cancelButtonTitle destructiveButtonTitle:(NSString *)destructiveButtonTitle otherButtonTitles:(NSArray *)otherButtonTitles popoverPresentationControllerBlock:(void(^)(UIPopoverPresentationController *popover))popoverPresentationControllerBlock tapBlock:(UIAlertControllerCompletionBlock)tapBlock { return [self showInViewController:viewController withTitle:title message:message preferredStyle:UIAlertControllerStyleActionSheet cancelButtonTitle:cancelButtonTitle destructiveButtonTitle:destructiveButtonTitle otherButtonTitles:otherButtonTitles popoverPresentationControllerBlock:popoverPresentationControllerBlock tapBlock:tapBlock preShowBlock:nil ]; } #pragma mark - - (BOOL)visible { return self.view.superview != nil; } - (NSInteger)cancelButtonIndex { return UIAlertControllerBlocksCancelButtonIndex; } - (NSInteger)firstOtherButtonIndex { return UIAlertControllerBlocksFirstOtherButtonIndex; } - (NSInteger)destructiveButtonIndex { return UIAlertControllerBlocksDestructiveButtonIndex; } @end ================================================ FILE: ArcBit/External/UIAlertController+Blocks/UIAlertController+Blocks.h ================================================ // // UIAlertController+Blocks.h // UIAlertControllerBlocks // // Created by Ryan Maxwell on 11/09/14. // // The MIT License (MIT) // // Copyright (c) 2014 Ryan Maxwell // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of // the Software, and to permit persons to whom the Software is furnished to do so, // subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // #import typedef void (^UIAlertControllerCompletionBlock) (UIAlertController *controller, UIAlertAction *action, NSInteger buttonIndex); typedef void (^UIAlertControllerPreshowBlock) (UIAlertController *controller); @interface UIAlertController (Blocks) + (instancetype)showInViewController:(UIViewController *)viewController withTitle:(NSString *)title message:(NSString *)message preferredStyle:(UIAlertControllerStyle)preferredStyle cancelButtonTitle:(NSString *)cancelButtonTitle destructiveButtonTitle:(NSString *)destructiveButtonTitle otherButtonTitles:(NSArray *)otherButtonTitles popoverPresentationControllerBlock:(void(^)(UIPopoverPresentationController *popover))popoverPresentationControllerBlock tapBlock:(UIAlertControllerCompletionBlock)tapBlock preShowBlock:(UIAlertControllerPreshowBlock)preShowBlock; + (instancetype)showAlertInViewController:(UIViewController *)viewController withTitle:(NSString *)title message:(NSString *)message cancelButtonTitle:(NSString *)cancelButtonTitle destructiveButtonTitle:(NSString *)destructiveButtonTitle otherButtonTitles:(NSArray *)otherButtonTitles tapBlock:(UIAlertControllerCompletionBlock)tapBlock; + (instancetype)showActionSheetInViewController:(UIViewController *)viewController withTitle:(NSString *)title message:(NSString *)message cancelButtonTitle:(NSString *)cancelButtonTitle destructiveButtonTitle:(NSString *)destructiveButtonTitle otherButtonTitles:(NSArray *)otherButtonTitles popoverPresentationControllerBlock:(void(^)(UIPopoverPresentationController *popover))popoverPresentationControllerBlock tapBlock:(UIAlertControllerCompletionBlock)tapBlock; + (instancetype)showAlertInViewController:(UIViewController *)viewController withTitle:(NSString *)title message:(NSString *)message preferredStyle:(UIAlertControllerStyle)preferredStyle cancelButtonTitle:(NSString *)cancelButtonTitle destructiveButtonTitle:(NSString *)destructiveButtonTitle otherButtonTitles:(NSArray *)otherButtonTitles tapBlock:(UIAlertControllerCompletionBlock)tapBlock; + (instancetype)showAlertInViewController:(UIViewController *)viewController withTitle:(NSString *)title message:(NSString *)message preferredStyle:(UIAlertControllerStyle)preferredStyle cancelButtonTitle:(NSString *)cancelButtonTitle destructiveButtonTitle:(NSString *)destructiveButtonTitle otherButtonTitles:(NSArray *)otherButtonTitles preShowBlock:(UIAlertControllerPreshowBlock)preShowBlock tapBlock:(UIAlertControllerCompletionBlock)tapBlock; @property (readonly, nonatomic) BOOL visible; @property (readonly, nonatomic) NSInteger cancelButtonIndex; @property (readonly, nonatomic) NSInteger firstOtherButtonIndex; @property (readonly, nonatomic) NSInteger destructiveButtonIndex; @end ================================================ FILE: ArcBit/External/UINavigationBar-FixedHeightWhenStatusBarHidden-master/UINavigationBar+FixedHeightWhenStatusBarHidden.h ================================================ // // UINavigationBar+FixedHeightWhenStatusBarHidden.h // // Created by Vitaliy Ivanov on 7/30/14. // Copyright (c) 2014 Factorial Complexity. All rights reserved. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import @interface UINavigationBar (FixedHeightWhenStatusBarHidden) /** * If set to YES, UINavigationBar height will not change after status bar was hidden. * Normally on iOS 7+ navigation bar height equals to 64 px, when status bar is shown. * After it is hidden, its height is changed to 44 px by default. */ @property (readwrite, nonatomic) BOOL fixedHeightWhenStatusBarHidden; @end ================================================ FILE: ArcBit/External/UINavigationBar-FixedHeightWhenStatusBarHidden-master/UINavigationBar+FixedHeightWhenStatusBarHidden.m ================================================ // // UINavigationBar+FixedHeightWhenStatusBarHidden.m // // Created by Vitaliy Ivanov on 7/30/14. // Copyright (c) 2014 Factorial Complexity. All rights reserved. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // #import "UINavigationBar+FixedHeightWhenStatusBarHidden.h" #import #define FYIsIOSVersionGreaterThanOrEqualTo(v) \ ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending) static char const* const FixedNavigationBarSize = "FixedNavigationBarSize"; @implementation UINavigationBar (FixedHeightWhenStatusBarHidden) - (CGSize)sizeThatFits_FixedHeightWhenStatusBarHidden:(CGSize)size { if ([UIApplication sharedApplication].statusBarHidden && FYIsIOSVersionGreaterThanOrEqualTo(@"7.0") && self.fixedHeightWhenStatusBarHidden) { CGSize newSize = CGSizeMake(self.frame.size.width, 64); return newSize; } else { return [self sizeThatFits_FixedHeightWhenStatusBarHidden:size]; } } - (BOOL)fixedHeightWhenStatusBarHidden { return [objc_getAssociatedObject(self, FixedNavigationBarSize) boolValue]; } - (void)setFixedHeightWhenStatusBarHidden:(BOOL)fixedHeightWhenStatusBarHidden { objc_setAssociatedObject(self, FixedNavigationBarSize, [NSNumber numberWithBool:fixedHeightWhenStatusBarHidden], OBJC_ASSOCIATION_RETAIN); } + (void)load { method_exchangeImplementations(class_getInstanceMethod(self, @selector(sizeThatFits:)), class_getInstanceMethod(self, @selector(sizeThatFits_FixedHeightWhenStatusBarHidden:))); } @end ================================================ FILE: ArcBit/External/iToast/iToast.h ================================================ /* iToast.h MIT LICENSE Copyright (c) 2012 Guru Software Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import #import typedef enum iToastGravity { iToastGravityTop = 1000001, iToastGravityBottom, iToastGravityCenter }iToastGravity; enum iToastDuration { iToastDurationLong = 10000, iToastDurationShort = 1000, iToastDurationNormal = 3000 }iToastDuration; typedef enum iToastType { iToastTypeInfo = -100000, iToastTypeNotice, iToastTypeWarning, iToastTypeError, iToastTypeNone // For internal use only (to force no image) }iToastType; typedef enum { iToastImageLocationTop, iToastImageLocationLeft } iToastImageLocation; @class iToastSettings; @interface iToast : NSObject { iToastSettings *_settings; NSTimer *timer; UIView *view; NSString *text; } - (void) show; - (void) show:(iToastType) type; - (iToast *) setDuration:(NSInteger ) duration; - (iToast *) setGravity:(iToastGravity) gravity offsetLeft:(NSInteger) left offsetTop:(NSInteger) top; - (iToast *) setGravity:(iToastGravity) gravity; - (iToast *) setPostion:(CGPoint) position; - (iToast *) setFontSize:(CGFloat) fontSize; - (iToast *) setUseShadow:(BOOL) useShadow; - (iToast *) setCornerRadius:(CGFloat) cornerRadius; - (iToast *) setBgRed:(CGFloat) bgRed; - (iToast *) setBgGreen:(CGFloat) bgGreen; - (iToast *) setBgBlue:(CGFloat) bgBlue; - (iToast *) setBgAlpha:(CGFloat) bgAlpha; + (iToast *) makeText:(NSString *) text; -(iToastSettings *) theSettings; @end @interface iToastSettings : NSObject{ NSInteger duration; iToastGravity gravity; CGPoint postition; iToastType toastType; CGFloat fontSize; BOOL useShadow; CGFloat cornerRadius; CGFloat bgRed; CGFloat bgGreen; CGFloat bgBlue; CGFloat bgAlpha; NSInteger offsetLeft; NSInteger offsetTop; NSDictionary *images; BOOL positionIsSet; } @property(assign) NSInteger duration; @property(assign) iToastGravity gravity; @property(assign) CGPoint postition; @property(assign) CGFloat fontSize; @property(assign) BOOL useShadow; @property(assign) CGFloat cornerRadius; @property(assign) CGFloat bgRed; @property(assign) CGFloat bgGreen; @property(assign) CGFloat bgBlue; @property(assign) CGFloat bgAlpha; @property(assign) NSInteger offsetLeft; @property(assign) NSInteger offsetTop; @property(readonly) NSDictionary *images; @property(assign) iToastImageLocation imageLocation; - (void) setImage:(UIImage *)img forType:(iToastType) type; - (void) setImage:(UIImage *)img withLocation:(iToastImageLocation)location forType:(iToastType)type; + (iToastSettings *) getSharedSettings; @end ================================================ FILE: ArcBit/External/iToast/iToast.m ================================================ /* iToast.m MIT LICENSE Copyright (c) 2011 Guru Software Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "iToast.h" #import #define CURRENT_TOAST_TAG 6984678 static const CGFloat kComponentPadding = 5; static iToastSettings *sharedSettings = nil; @interface iToast(private) - (iToast *) settings; - (CGRect)_toastFrameForImageSize:(CGSize)imageSize withLocation:(iToastImageLocation)location andTextSize:(CGSize)textSize; - (CGRect)_frameForImage:(iToastType)type inToastFrame:(CGRect)toastFrame; @end @implementation iToast - (id) initWithText:(NSString *) tex{ if (self = [super init]) { text = [tex copy]; } return self; } - (void) show{ [self show:iToastTypeNone]; } - (void) show:(iToastType) type { iToastSettings *theSettings = _settings; if (!theSettings) { theSettings = [iToastSettings getSharedSettings]; } UIImage *image = [theSettings.images valueForKey:[NSString stringWithFormat:@"%i", type]]; UIFont *font = [UIFont systemFontOfSize:theSettings.fontSize]; CGSize textSize = [text sizeWithFont:font constrainedToSize:CGSizeMake(280, 60)]; UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, textSize.width + kComponentPadding, textSize.height + kComponentPadding)]; label.backgroundColor = [UIColor clearColor]; label.textColor = [UIColor whiteColor]; label.font = font; label.text = text; label.numberOfLines = 0; if (theSettings.useShadow) { label.shadowColor = [UIColor darkGrayColor]; label.shadowOffset = CGSizeMake(1, 1); } UIButton *v = [UIButton buttonWithType:UIButtonTypeCustom]; if (image) { v.frame = [self _toastFrameForImageSize:image.size withLocation:[theSettings imageLocation] andTextSize:textSize]; switch ([theSettings imageLocation]) { case iToastImageLocationLeft: [label setTextAlignment:UITextAlignmentLeft]; label.center = CGPointMake(image.size.width + kComponentPadding * 2 + (v.frame.size.width - image.size.width - kComponentPadding * 2) / 2, v.frame.size.height / 2); break; case iToastImageLocationTop: [label setTextAlignment:UITextAlignmentCenter]; label.center = CGPointMake(v.frame.size.width / 2, (image.size.height + kComponentPadding * 2 + (v.frame.size.height - image.size.height - kComponentPadding * 2) / 2)); break; default: break; } } else { v.frame = CGRectMake(0, 0, textSize.width + kComponentPadding * 2, textSize.height + kComponentPadding * 2); label.center = CGPointMake(v.frame.size.width / 2, v.frame.size.height / 2); } CGRect lbfrm = label.frame; lbfrm.origin.x = ceil(lbfrm.origin.x); lbfrm.origin.y = ceil(lbfrm.origin.y); label.frame = lbfrm; [v addSubview:label]; [label release]; if (image) { UIImageView *imageView = [[UIImageView alloc] initWithImage:image]; imageView.frame = [self _frameForImage:type inToastFrame:v.frame]; [v addSubview:imageView]; [imageView release]; } v.backgroundColor = [UIColor colorWithRed:theSettings.bgRed green:theSettings.bgGreen blue:theSettings.bgBlue alpha:theSettings.bgAlpha]; v.layer.cornerRadius = theSettings.cornerRadius; UIWindow *window = [[[UIApplication sharedApplication] windows] objectAtIndex:0]; CGPoint point; // Set correct orientation/location regarding device orientation UIInterfaceOrientation orientation = (UIInterfaceOrientation)[[UIApplication sharedApplication] statusBarOrientation]; switch (orientation) { case UIDeviceOrientationPortrait: { if (theSettings.gravity == iToastGravityTop) { point = CGPointMake(window.frame.size.width / 2, 45); } else if (theSettings.gravity == iToastGravityBottom) { point = CGPointMake(window.frame.size.width / 2, window.frame.size.height - 45); } else if (theSettings.gravity == iToastGravityCenter) { point = CGPointMake(window.frame.size.width/2, window.frame.size.height/2); } else { point = theSettings.postition; } point = CGPointMake(point.x + theSettings.offsetLeft, point.y + theSettings.offsetTop); break; } case UIDeviceOrientationPortraitUpsideDown: { v.transform = CGAffineTransformMakeRotation(M_PI); float width = window.frame.size.width; float height = window.frame.size.height; if (theSettings.gravity == iToastGravityTop) { point = CGPointMake(width / 2, height - 45); } else if (theSettings.gravity == iToastGravityBottom) { point = CGPointMake(width / 2, 45); } else if (theSettings.gravity == iToastGravityCenter) { point = CGPointMake(width/2, height/2); } else { // TODO : handle this case point = theSettings.postition; } point = CGPointMake(point.x - theSettings.offsetLeft, point.y - theSettings.offsetTop); break; } case UIDeviceOrientationLandscapeLeft: { v.transform = CGAffineTransformMakeRotation(M_PI/2); //rotation in radians if (theSettings.gravity == iToastGravityTop) { point = CGPointMake(window.frame.size.width - 45, window.frame.size.height / 2); } else if (theSettings.gravity == iToastGravityBottom) { point = CGPointMake(45,window.frame.size.height / 2); } else if (theSettings.gravity == iToastGravityCenter) { point = CGPointMake(window.frame.size.width/2, window.frame.size.height/2); } else { // TODO : handle this case point = theSettings.postition; } point = CGPointMake(point.x - theSettings.offsetTop, point.y - theSettings.offsetLeft); break; } case UIDeviceOrientationLandscapeRight: { v.transform = CGAffineTransformMakeRotation(-M_PI/2); if (theSettings.gravity == iToastGravityTop) { point = CGPointMake(45, window.frame.size.height / 2); } else if (theSettings.gravity == iToastGravityBottom) { point = CGPointMake(window.frame.size.width - 45, window.frame.size.height/2); } else if (theSettings.gravity == iToastGravityCenter) { point = CGPointMake(window.frame.size.width/2, window.frame.size.height/2); } else { // TODO : handle this case point = theSettings.postition; } point = CGPointMake(point.x + theSettings.offsetTop, point.y + theSettings.offsetLeft); break; } default: break; } v.center = point; v.frame = CGRectIntegral(v.frame); NSTimer *timer1 = [NSTimer timerWithTimeInterval:((float)theSettings.duration)/1000 target:self selector:@selector(hideToast:) userInfo:nil repeats:NO]; [[NSRunLoop mainRunLoop] addTimer:timer1 forMode:NSDefaultRunLoopMode]; v.tag = CURRENT_TOAST_TAG; UIView *currentToast = [window viewWithTag:CURRENT_TOAST_TAG]; if (currentToast != nil) { [currentToast removeFromSuperview]; } v.alpha = 0; [window addSubview:v]; [UIView beginAnimations:nil context:nil]; v.alpha = 1; [UIView commitAnimations]; view = [v retain]; [v addTarget:self action:@selector(hideToast:) forControlEvents:UIControlEventTouchDown]; } - (CGRect)_toastFrameForImageSize:(CGSize)imageSize withLocation:(iToastImageLocation)location andTextSize:(CGSize)textSize { CGRect theRect = CGRectZero; switch (location) { case iToastImageLocationLeft: theRect = CGRectMake(0, 0, imageSize.width + textSize.width + kComponentPadding * 3, MAX(textSize.height, imageSize.height) + kComponentPadding * 2); break; case iToastImageLocationTop: theRect = CGRectMake(0, 0, MAX(textSize.width, imageSize.width) + kComponentPadding * 2, imageSize.height + textSize.height + kComponentPadding * 3); default: break; } return theRect; } - (CGRect)_frameForImage:(iToastType)type inToastFrame:(CGRect)toastFrame { iToastSettings *theSettings = _settings; UIImage *image = [theSettings.images valueForKey:[NSString stringWithFormat:@"%i", type]]; if (!image) return CGRectZero; CGRect imageFrame = CGRectZero; switch ([theSettings imageLocation]) { case iToastImageLocationLeft: imageFrame = CGRectMake(kComponentPadding, (toastFrame.size.height - image.size.height) / 2, image.size.width, image.size.height); break; case iToastImageLocationTop: imageFrame = CGRectMake((toastFrame.size.width - image.size.width) / 2, kComponentPadding, image.size.width, image.size.height); break; default: break; } return imageFrame; } - (void) hideToast:(NSTimer*)theTimer{ [UIView beginAnimations:nil context:NULL]; view.alpha = 0; [UIView commitAnimations]; NSTimer *timer2 = [NSTimer timerWithTimeInterval:500 target:self selector:@selector(hideToast:) userInfo:nil repeats:NO]; [[NSRunLoop mainRunLoop] addTimer:timer2 forMode:NSDefaultRunLoopMode]; } - (void) removeToast:(NSTimer*)theTimer{ [view removeFromSuperview]; } + (iToast *) makeText:(NSString *) _text{ iToast *toast = [[[iToast alloc] initWithText:_text] autorelease]; return toast; } - (iToast *) setDuration:(NSInteger ) duration{ [self theSettings].duration = duration; return self; } - (iToast *) setGravity:(iToastGravity) gravity offsetLeft:(NSInteger) left offsetTop:(NSInteger) top{ [self theSettings].gravity = gravity; [self theSettings].offsetLeft = left; [self theSettings].offsetTop = top; return self; } - (iToast *) setGravity:(iToastGravity) gravity{ [self theSettings].gravity = gravity; return self; } - (iToast *) setPostion:(CGPoint) _position{ [self theSettings].postition = CGPointMake(_position.x, _position.y); return self; } - (iToast *) setFontSize:(CGFloat) fontSize{ [self theSettings].fontSize = fontSize; return self; } - (iToast *) setUseShadow:(BOOL) useShadow{ [self theSettings].useShadow = useShadow; return self; } - (iToast *) setCornerRadius:(CGFloat) cornerRadius{ [self theSettings].cornerRadius = cornerRadius; return self; } - (iToast *) setBgRed:(CGFloat) bgRed{ [self theSettings].bgRed = bgRed; return self; } - (iToast *) setBgGreen:(CGFloat) bgGreen{ [self theSettings].bgGreen = bgGreen; return self; } - (iToast *) setBgBlue:(CGFloat) bgBlue{ [self theSettings].bgBlue = bgBlue; return self; } - (iToast *) setBgAlpha:(CGFloat) bgAlpha{ [self theSettings].bgAlpha = bgAlpha; return self; } -(iToastSettings *) theSettings{ if (!_settings) { _settings = [[iToastSettings getSharedSettings] copy]; } return _settings; } @end @implementation iToastSettings @synthesize offsetLeft; @synthesize offsetTop; @synthesize duration; @synthesize gravity; @synthesize postition; @synthesize fontSize; @synthesize useShadow; @synthesize cornerRadius; @synthesize bgRed; @synthesize bgGreen; @synthesize bgBlue; @synthesize bgAlpha; @synthesize images; @synthesize imageLocation; - (void) setImage:(UIImage *) img withLocation:(iToastImageLocation)location forType:(iToastType) type { if (type == iToastTypeNone) { // This should not be used, internal use only (to force no image) return; } if (!images) { images = [[NSMutableDictionary alloc] initWithCapacity:4]; } if (img) { NSString *key = [NSString stringWithFormat:@"%i", type]; [images setValue:img forKey:key]; } [self setImageLocation:location]; } - (void)setImage:(UIImage *)img forType:(iToastType)type { [self setImage:img withLocation:iToastImageLocationLeft forType:type]; } + (iToastSettings *) getSharedSettings{ if (!sharedSettings) { sharedSettings = [iToastSettings new]; sharedSettings.gravity = iToastGravityCenter; sharedSettings.duration = iToastDurationShort; sharedSettings.fontSize = 16.0; sharedSettings.useShadow = YES; sharedSettings.cornerRadius = 5.0; sharedSettings.bgRed = 0; sharedSettings.bgGreen = 0; sharedSettings.bgBlue = 0; sharedSettings.bgAlpha = 0.7; sharedSettings.offsetLeft = 0; sharedSettings.offsetTop = 0; } return sharedSettings; } - (id) copyWithZone:(NSZone *)zone{ iToastSettings *copy = [iToastSettings new]; copy.gravity = self.gravity; copy.duration = self.duration; copy.postition = self.postition; copy.fontSize = self.fontSize; copy.useShadow = self.useShadow; copy.cornerRadius = self.cornerRadius; copy.bgRed = self.bgRed; copy.bgGreen = self.bgGreen; copy.bgBlue = self.bgBlue; copy.bgAlpha = self.bgAlpha; copy.offsetLeft = self.offsetLeft; copy.offsetTop = self.offsetTop; NSArray *keys = [self.images allKeys]; for (NSString *key in keys){ [copy setImage:[images valueForKey:key] forType:[key intValue]]; } [copy setImageLocation:imageLocation]; return copy; } @end ================================================ FILE: ArcBit/External/socket.io-client-swift-10.0.0/Source/SSLSecurity.swift ================================================ ////////////////////////////////////////////////////////////////////////////////////////////////// // // SSLSecurity.swift // Starscream // // Created by Dalton Cherry on 5/16/15. // Copyright (c) 2014-2016 Dalton Cherry. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ////////////////////////////////////////////////////////////////////////////////////////////////// import Foundation import Security public protocol SSLTrustValidator { func isValid(_ trust: SecTrust, domain: String?) -> Bool } open class SSLCert : NSObject { var certData: Data? var key: SecKey? /** Designated init for certificates - parameter data: is the binary data of the certificate - returns: a representation security object to be used with */ public init(data: Data) { self.certData = data } /** Designated init for public keys - parameter key: is the public key to be used - returns: a representation security object to be used with */ public init(key: SecKey) { self.key = key } } open class SSLSecurity : SSLTrustValidator { public var validatedDN = true //should the domain name be validated? var isReady = false //is the key processing done? var certificates: [Data]? //the certificates @nonobjc var pubKeys: [SecKey]? //the public keys var usePublicKeys = false //use public keys or certificate validation? /** Use certs from main app bundle - parameter usePublicKeys: is to specific if the publicKeys or certificates should be used for SSL pinning validation - returns: a representation security object to be used with */ public convenience init(usePublicKeys: Bool = false) { let paths = Bundle.main.paths(forResourcesOfType: "cer", inDirectory: ".") let certs = paths.reduce([SSLCert]()) { (certs: [SSLCert], path: String) -> [SSLCert] in var certs = certs if let data = NSData(contentsOfFile: path) { certs.append(SSLCert(data: data as Data)) } return certs } self.init(certs: certs, usePublicKeys: usePublicKeys) } /** Designated init - parameter certs: is the certificates or public keys to use - parameter usePublicKeys: is to specific if the publicKeys or certificates should be used for SSL pinning validation - returns: a representation security object to be used with */ public init(certs: [SSLCert], usePublicKeys: Bool) { self.usePublicKeys = usePublicKeys if self.usePublicKeys { DispatchQueue.global(qos: .default).async { let pubKeys = certs.reduce([SecKey]()) { (pubKeys: [SecKey], cert: SSLCert) -> [SecKey] in var pubKeys = pubKeys if let data = cert.certData, cert.key == nil { cert.key = self.extractPublicKey(data) } if let key = cert.key { pubKeys.append(key) } return pubKeys } self.pubKeys = pubKeys self.isReady = true } } else { let certificates = certs.reduce([Data]()) { (certificates: [Data], cert: SSLCert) -> [Data] in var certificates = certificates if let data = cert.certData { certificates.append(data) } return certificates } self.certificates = certificates self.isReady = true } } /** Valid the trust and domain name. - parameter trust: is the serverTrust to validate - parameter domain: is the CN domain to validate - returns: if the key was successfully validated */ public func isValid(_ trust: SecTrust, domain: String?) -> Bool { var tries = 0 while !self.isReady { usleep(1000) tries += 1 if tries > 5 { return false //doesn't appear it is going to ever be ready... } } var policy: SecPolicy if self.validatedDN { policy = SecPolicyCreateSSL(true, domain as NSString?) } else { policy = SecPolicyCreateBasicX509() } SecTrustSetPolicies(trust,policy) if self.usePublicKeys { if let keys = self.pubKeys { let serverPubKeys = publicKeyChain(trust) for serverKey in serverPubKeys as [AnyObject] { for key in keys as [AnyObject] { if serverKey.isEqual(key) { return true } } } } } else if let certs = self.certificates { let serverCerts = certificateChain(trust) var collect = [SecCertificate]() for cert in certs { collect.append(SecCertificateCreateWithData(nil,cert as CFData)!) } SecTrustSetAnchorCertificates(trust,collect as NSArray) var result: SecTrustResultType = .unspecified SecTrustEvaluate(trust,&result) if result == .unspecified || result == .proceed { var trustedCount = 0 for serverCert in serverCerts { for cert in certs { if cert == serverCert { trustedCount += 1 break } } } if trustedCount == serverCerts.count { return true } } } return false } /** Get the public key from a certificate data - parameter data: is the certificate to pull the public key from - returns: a public key */ func extractPublicKey(_ data: Data) -> SecKey? { guard let cert = SecCertificateCreateWithData(nil, data as CFData) else { return nil } return extractPublicKey(cert, policy: SecPolicyCreateBasicX509()) } /** Get the public key from a certificate - parameter data: is the certificate to pull the public key from - returns: a public key */ func extractPublicKey(_ cert: SecCertificate, policy: SecPolicy) -> SecKey? { var possibleTrust: SecTrust? SecTrustCreateWithCertificates(cert, policy, &possibleTrust) guard let trust = possibleTrust else { return nil } var result: SecTrustResultType = .unspecified SecTrustEvaluate(trust, &result) return SecTrustCopyPublicKey(trust) } /** Get the certificate chain for the trust - parameter trust: is the trust to lookup the certificate chain for - returns: the certificate chain for the trust */ func certificateChain(_ trust: SecTrust) -> [Data] { let certificates = (0.. [Data] in var certificates = certificates let cert = SecTrustGetCertificateAtIndex(trust, index) certificates.append(SecCertificateCopyData(cert!) as Data) return certificates } return certificates } /** Get the public key chain for the trust - parameter trust: is the trust to lookup the certificate chain and extract the public keys - returns: the public keys from the certifcate chain for the trust */ @nonobjc func publicKeyChain(_ trust: SecTrust) -> [SecKey] { let policy = SecPolicyCreateBasicX509() let keys = (0.. [SecKey] in var keys = keys let cert = SecTrustGetCertificateAtIndex(trust, index) if let key = extractPublicKey(cert!, policy: policy) { keys.append(key) } return keys } return keys } } ================================================ FILE: ArcBit/External/socket.io-client-swift-10.0.0/Source/SocketAckEmitter.swift ================================================ // // SocketAckEmitter.swift // Socket.IO-Client-Swift // // Created by Erik Little on 9/16/15. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Dispatch import Foundation /// A class that represents a waiting ack call. /// /// **NOTE**: You should not store this beyond the life of the event handler. public final class SocketAckEmitter : NSObject { let socket: SocketIOClient let ackNum: Int // MARK: Properties /// If true, this handler is expecting to be acked. Call `with(_: SocketData...)` to ack. public var expected: Bool { return ackNum != -1 } init(socket: SocketIOClient, ackNum: Int) { self.socket = socket self.ackNum = ackNum } // MARK: Methods /// Call to ack receiving this event. /// /// If an error occurs trying to transform `items` into their socket representation, a `SocketClientEvent.error` /// will be emitted. The structure of the error data is `[ackNum, items, theError]` /// /// - parameter items: A variable number of items to send when acking. public func with(_ items: SocketData...) { guard ackNum != -1 else { return } do { socket.emitAck(ackNum, with: try items.map({ try $0.socketRepresentation() })) } catch let err { socket.handleClientEvent(.error, data: [ackNum, items, err]) } } /// Call to ack receiving this event. /// /// - parameter items: An array of items to send when acking. Use `[]` to send nothing. public func with(_ items: [Any]) { guard ackNum != -1 else { return } socket.emitAck(ackNum, with: items) } } /// A class that represents an emit that will request an ack that has not yet been sent. /// Call `timingOut(after:callback:)` to complete the emit /// Example: /// /// ```swift /// socket.emitWithAck("myEvent").timingOut(after: 1) {data in /// ... /// } /// ``` public final class OnAckCallback : NSObject { private let ackNumber: Int private let items: [Any] private weak var socket: SocketIOClient? init(ackNumber: Int, items: [Any], socket: SocketIOClient) { self.ackNumber = ackNumber self.items = items self.socket = socket } deinit { DefaultSocketLogger.Logger.log("OnAckCallback for \(ackNumber) being released", type: "OnAckCallback") } // MARK: Methods /// Completes an emitWithAck. If this isn't called, the emit never happens. /// /// - parameter after: The number of seconds before this emit times out if an ack hasn't been received. /// - parameter callback: The callback called when an ack is received, or when a timeout happens. /// To check for timeout, use `SocketAckStatus`'s `noAck` case. public func timingOut(after seconds: Int, callback: @escaping AckCallback) { guard let socket = self.socket, ackNumber != -1 else { return } socket.ackHandlers.addAck(ackNumber, callback: callback) socket._emit(items, ack: ackNumber) guard seconds != 0 else { return } socket.handleQueue.asyncAfter(deadline: DispatchTime.now() + Double(seconds)) { socket.ackHandlers.timeoutAck(self.ackNumber, onQueue: socket.handleQueue) } } } ================================================ FILE: ArcBit/External/socket.io-client-swift-10.0.0/Source/SocketAckManager.swift ================================================ // // SocketAckManager.swift // Socket.IO-Client-Swift // // Created by Erik Little on 4/3/15. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Dispatch import Foundation /// The status of an ack. public enum SocketAckStatus : String { /// The ack timed out. case noAck = "NO ACK" } private struct SocketAck : Hashable { let ack: Int var callback: AckCallback! var hashValue: Int { return ack.hashValue } init(ack: Int) { self.ack = ack } init(ack: Int, callback: @escaping AckCallback) { self.ack = ack self.callback = callback } fileprivate static func <(lhs: SocketAck, rhs: SocketAck) -> Bool { return lhs.ack < rhs.ack } fileprivate static func ==(lhs: SocketAck, rhs: SocketAck) -> Bool { return lhs.ack == rhs.ack } } struct SocketAckManager { private var acks = Set(minimumCapacity: 1) private let ackSemaphore = DispatchSemaphore(value: 1) mutating func addAck(_ ack: Int, callback: @escaping AckCallback) { acks.insert(SocketAck(ack: ack, callback: callback)) } /// Should be called on handle queue mutating func executeAck(_ ack: Int, with items: [Any], onQueue: DispatchQueue) { ackSemaphore.wait() defer { ackSemaphore.signal() } let ack = acks.remove(SocketAck(ack: ack)) onQueue.async() { ack?.callback(items) } } /// Should be called on handle queue mutating func timeoutAck(_ ack: Int, onQueue: DispatchQueue) { ackSemaphore.wait() defer { ackSemaphore.signal() } let ack = acks.remove(SocketAck(ack: ack)) onQueue.async() { ack?.callback?([SocketAckStatus.noAck.rawValue]) } } } ================================================ FILE: ArcBit/External/socket.io-client-swift-10.0.0/Source/SocketAnyEvent.swift ================================================ // // SocketAnyEvent.swift // Socket.IO-Client-Swift // // Created by Erik Little on 3/28/15. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation /// Represents some event that was received. public final class SocketAnyEvent : NSObject { // MARK: Properties /// The event name. public let event: String /// The data items for this event. public let items: [Any]? /// The description of this event. override public var description: String { return "SocketAnyEvent: Event: \(event) items: \(String(describing: items))" } init(event: String, items: [Any]?) { self.event = event self.items = items } } ================================================ FILE: ArcBit/External/socket.io-client-swift-10.0.0/Source/SocketClientManager.swift ================================================ // // SocketClientManager.swift // Socket.IO-Client-Swift // // Created by Erik Little on 6/11/16. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation /** Experimental socket manager. API subject to change. Can be used to persist sockets across ViewControllers. Sockets are strongly stored, so be sure to remove them once they are no longer needed. Example usage: ``` let manager = SocketClientManager.sharedManager manager["room1"] = socket1 manager["room2"] = socket2 manager.removeSocket(socket: socket2) manager["room1"]?.emit("hello") ``` */ open class SocketClientManager : NSObject { // MARK: Properties. /// The shared manager. open static let sharedManager = SocketClientManager() private var sockets = [String: SocketIOClient]() /// Gets a socket by its name. /// /// - returns: The socket, if one had the given name. open subscript(string: String) -> SocketIOClient? { get { return sockets[string] } set(socket) { sockets[string] = socket } } // MARK: Methods. /// Adds a socket. /// /// - parameter socket: The socket to add. /// - parameter labeledAs: The label for this socket. open func addSocket(_ socket: SocketIOClient, labeledAs label: String) { sockets[label] = socket } /// Removes a socket by a given name. /// /// - parameter withLabel: The label of the socket to remove. /// - returns: The socket for the given label, if one was present. @discardableResult open func removeSocket(withLabel label: String) -> SocketIOClient? { return sockets.removeValue(forKey: label) } /// Removes a socket. /// /// - parameter socket: The socket to remove. /// - returns: The socket if it was in the manager. @discardableResult open func removeSocket(_ socket: SocketIOClient) -> SocketIOClient? { var returnSocket: SocketIOClient? for (label, dictSocket) in sockets where dictSocket === socket { returnSocket = sockets.removeValue(forKey: label) } return returnSocket } /// Removes all the sockets in the manager. open func removeSockets() { sockets.removeAll() } } ================================================ FILE: ArcBit/External/socket.io-client-swift-10.0.0/Source/SocketEngine.swift ================================================ // // SocketEngine.swift // Socket.IO-Client-Swift // // Created by Erik Little on 3/3/15. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Dispatch import Foundation /// The class that handles the engine.io protocol and transports. /// See `SocketEnginePollable` and `SocketEngineWebsocket` for transport specific methods. public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePollable, SocketEngineWebsocket { // MARK: Properties /// The queue that all engine actions take place on. public let engineQueue = DispatchQueue(label: "com.socketio.engineHandleQueue") /// The connect parameters sent during a connect. public var connectParams: [String: Any]? { didSet { (urlPolling, urlWebSocket) = createURLs() } } /// A queue of engine.io messages waiting for POSTing /// /// **You should not touch this directly** public var postWait = [String]() /// `true` if there is an outstanding poll. Trying to poll before the first is done will cause socket.io to /// disconnect us. /// /// **Do not touch this directly** public var waitingForPoll = false /// `true` if there is an outstanding post. Trying to post before the first is done will cause socket.io to /// disconnect us. /// /// **Do not touch this directly** public var waitingForPost = false /// `true` if this engine is closed. public private(set) var closed = false /// `true` if this engine is connected. Connected means that the initial poll connect has succeeded. public private(set) var connected = false /// An array of HTTPCookies that are sent during the connection. public private(set) var cookies: [HTTPCookie]? /// A dictionary of extra http headers that will be set during connection. public private(set) var extraHeaders: [String: String]? /// When `true`, the engine is in the process of switching to WebSockets. /// /// **Do not touch this directly** public private(set) var fastUpgrade = false /// When `true`, the engine will only use HTTP long-polling as a transport. public private(set) var forcePolling = false /// When `true`, the engine will only use WebSockets as a transport. public private(set) var forceWebsockets = false /// `true` If engine's session has been invalidated. public private(set) var invalidated = false /// If `true`, the engine is currently in HTTP long-polling mode. public private(set) var polling = true /// If `true`, the engine is currently seeing whether it can upgrade to WebSockets. public private(set) var probing = false /// The URLSession that will be used for polling. public private(set) var session: URLSession? /// The session id for this engine. public private(set) var sid = "" /// The path to engine.io. public private(set) var socketPath = "/engine.io/" /// The url for polling. public private(set) var urlPolling = URL(string: "http://localhost/")! /// The url for WebSockets. public private(set) var urlWebSocket = URL(string: "http://localhost/")! /// If `true`, then the engine is currently in WebSockets mode. public private(set) var websocket = false /// The WebSocket for this engine. public private(set) var ws: WebSocket? /// The client for this engine. public weak var client: SocketEngineClient? private weak var sessionDelegate: URLSessionDelegate? private let logType = "SocketEngine" private let url: URL private var pingInterval: Double? private var pingTimeout = 0.0 { didSet { pongsMissedMax = Int(pingTimeout / (pingInterval ?? 25)) } } private var pongsMissed = 0 private var pongsMissedMax = 0 private var probeWait = ProbeWaitQueue() private var secure = false private var security: SSLSecurity? private var selfSigned = false private var voipEnabled = false // MARK: Initializers /// Creates a new engine. /// /// - parameter client: The client for this engine. /// - parameter url: The url for this engine. /// - parameter config: An array of configuration options for this engine. public init(client: SocketEngineClient, url: URL, config: SocketIOClientConfiguration) { self.client = client self.url = url for option in config { switch option { case let .connectParams(params): connectParams = params case let .cookies(cookies): self.cookies = cookies case let .extraHeaders(headers): extraHeaders = headers case let .sessionDelegate(delegate): sessionDelegate = delegate case let .forcePolling(force): forcePolling = force case let .forceWebsockets(force): forceWebsockets = force case let .path(path): socketPath = path if !socketPath.hasSuffix("/") { socketPath += "/" } case let .voipEnabled(enable): voipEnabled = enable case let .secure(secure): self.secure = secure case let .selfSigned(selfSigned): self.selfSigned = selfSigned case let .security(security): self.security = security default: continue } } super.init() sessionDelegate = sessionDelegate ?? self (urlPolling, urlWebSocket) = createURLs() } /// Creates a new engine. /// /// - parameter client: The client for this engine. /// - parameter url: The url for this engine. /// - parameter options: The options for this engine. public convenience init(client: SocketEngineClient, url: URL, options: NSDictionary?) { self.init(client: client, url: url, config: options?.toSocketConfiguration() ?? []) } deinit { DefaultSocketLogger.Logger.log("Engine is being released", type: logType) closed = true stopPolling() } // MARK: Methods private func checkAndHandleEngineError(_ msg: String) { do { let dict = try msg.toNSDictionary() guard let error = dict["message"] as? String else { return } /* 0: Unknown transport 1: Unknown sid 2: Bad handshake request 3: Bad request */ didError(reason: error) } catch { client?.engineDidError(reason: "Got unknown error from server \(msg)") } } private func handleBase64(message: String) { // binary in base64 string let noPrefix = message[message.index(message.startIndex, offsetBy: 2).. (URL, URL) { if client == nil { return (URL(string: "http://localhost/")!, URL(string: "http://localhost/")!) } var urlPolling = URLComponents(string: url.absoluteString)! var urlWebSocket = URLComponents(string: url.absoluteString)! var queryString = "" urlWebSocket.path = socketPath urlPolling.path = socketPath if secure { urlPolling.scheme = "https" urlWebSocket.scheme = "wss" } else { urlPolling.scheme = "http" urlWebSocket.scheme = "ws" } if connectParams != nil { for (key, value) in connectParams! { let keyEsc = key.urlEncode()! let valueEsc = "\(value)".urlEncode()! queryString += "&\(keyEsc)=\(valueEsc)" } } urlWebSocket.percentEncodedQuery = "transport=websocket" + queryString urlPolling.percentEncodedQuery = "transport=polling&b64=1" + queryString return (urlPolling.url!, urlWebSocket.url!) } private func createWebsocketAndConnect() { ws?.delegate = nil ws = WebSocket(url: urlWebSocketWithSid as URL) if cookies != nil { let headers = HTTPCookie.requestHeaderFields(with: cookies!) for (key, value) in headers { ws?.headers[key] = value } } if extraHeaders != nil { for (headerName, value) in extraHeaders! { ws?.headers[headerName] = value } } ws?.callbackQueue = engineQueue ws?.voipEnabled = voipEnabled ws?.delegate = self ws?.disableSSLCertValidation = selfSigned ws?.security = security ws?.connect() } /// Called when an error happens during execution. Causes a disconnection. public func didError(reason: String) { DefaultSocketLogger.Logger.error("%@", type: logType, args: reason) client?.engineDidError(reason: reason) disconnect(reason: reason) } /// Disconnects from the server. /// /// - parameter reason: The reason for the disconnection. This is communicated up to the client. public func disconnect(reason: String) { engineQueue.async { self._disconnect(reason: reason) } } private func _disconnect(reason: String) { guard connected else { return closeOutEngine(reason: reason) } DefaultSocketLogger.Logger.log("Engine is being closed.", type: logType) if closed { return closeOutEngine(reason: reason) } if websocket { sendWebSocketMessage("", withType: .close, withData: []) closeOutEngine(reason: reason) } else { disconnectPolling(reason: reason) } } // We need to take special care when we're polling that we send it ASAP // Also make sure we're on the emitQueue since we're touching postWait private func disconnectPolling(reason: String) { postWait.append(String(SocketEnginePacketType.close.rawValue)) doRequest(for: createRequestForPostWithPostWait()) {_, _, _ in } closeOutEngine(reason: reason) } /// Called to switch from HTTP long-polling to WebSockets. After calling this method the engine will be in /// WebSocket mode. /// /// **You shouldn't call this directly** public func doFastUpgrade() { if waitingForPoll { DefaultSocketLogger.Logger.error("Outstanding poll when switched to WebSockets," + "we'll probably disconnect soon. You should report this.", type: logType) } sendWebSocketMessage("", withType: .upgrade, withData: []) websocket = true polling = false fastUpgrade = false probing = false flushProbeWait() } private func flushProbeWait() { DefaultSocketLogger.Logger.log("Flushing probe wait", type: logType) for waiter in probeWait { write(waiter.msg, withType: waiter.type, withData: waiter.data) } probeWait.removeAll(keepingCapacity: false) if postWait.count != 0 { flushWaitingForPostToWebSocket() } } /// Causes any packets that were waiting for POSTing to be sent through the WebSocket. This happens because when /// the engine is attempting to upgrade to WebSocket it does not do any POSTing. /// /// **You shouldn't call this directly** public func flushWaitingForPostToWebSocket() { guard let ws = self.ws else { return } for msg in postWait { ws.write(string: msg) } postWait.removeAll(keepingCapacity: false) } private func handleClose(_ reason: String) { client?.engineDidClose(reason: reason) } private func handleMessage(_ message: String) { client?.parseEngineMessage(message) } private func handleNOOP() { doPoll() } private func handleOpen(openData: String) { guard let json = try? openData.toNSDictionary() else { didError(reason: "Error parsing open packet") return } guard let sid = json["sid"] as? String else { didError(reason: "Open packet contained no sid") return } let upgradeWs: Bool self.sid = sid connected = true pongsMissed = 0 if let upgrades = json["upgrades"] as? [String] { upgradeWs = upgrades.contains("websocket") } else { upgradeWs = false } if let pingInterval = json["pingInterval"] as? Double, let pingTimeout = json["pingTimeout"] as? Double { self.pingInterval = pingInterval / 1000.0 self.pingTimeout = pingTimeout / 1000.0 } if !forcePolling && !forceWebsockets && upgradeWs { createWebsocketAndConnect() } sendPing() if !forceWebsockets { doPoll() } client?.engineDidOpen(reason: "Connect") } private func handlePong(with message: String) { pongsMissed = 0 // We should upgrade if message == "3probe" { upgradeTransport() } } /// Parses raw binary received from engine.io. /// /// - parameter data: The data to parse. public func parseEngineData(_ data: Data) { DefaultSocketLogger.Logger.log("Got binary data: %@", type: "SocketEngine", args: data) client?.parseEngineBinaryData(data.subdata(in: 1.. pongsMissedMax { client?.engineDidClose(reason: "Ping timeout") return } guard let pingInterval = pingInterval else { return } pongsMissed += 1 write("", withType: .ping, withData: []) engineQueue.asyncAfter(deadline: DispatchTime.now() + Double(pingInterval)) {[weak self] in self?.sendPing() } } // Moves from long-polling to websockets private func upgradeTransport() { if ws?.isConnected ?? false { DefaultSocketLogger.Logger.log("Upgrading transport to WebSockets", type: logType) fastUpgrade = true sendPollMessage("", withType: .noop, withData: []) // After this point, we should not send anymore polling messages } } /// Writes a message to engine.io, independent of transport. /// /// - parameter msg: The message to send. /// - parameter withType: The type of this message. /// - parameter withData: Any data that this message has. public func write(_ msg: String, withType type: SocketEnginePacketType, withData data: [Data]) { engineQueue.async { guard self.connected else { return } if self.websocket { DefaultSocketLogger.Logger.log("Writing ws: %@ has data: %@", type: self.logType, args: msg, data.count != 0) self.sendWebSocketMessage(msg, withType: type, withData: data) } else if !self.probing { DefaultSocketLogger.Logger.log("Writing poll: %@ has data: %@", type: self.logType, args: msg, data.count != 0) self.sendPollMessage(msg, withType: type, withData: data) } else { self.probeWait.append((msg, type, data)) } } } // MARK: Starscream delegate conformance /// Delegate method for connection. public func websocketDidConnect(socket: WebSocket) { if !forceWebsockets { probing = true probeWebSocket() } else { connected = true probing = false polling = false } } /// Delegate method for disconnection. public func websocketDidDisconnect(socket: WebSocket, error: NSError?) { probing = false if closed { client?.engineDidClose(reason: "Disconnect") return } guard websocket else { flushProbeWait() return } connected = false websocket = false if let reason = error?.localizedDescription { didError(reason: reason) } else { client?.engineDidClose(reason: "Socket Disconnected") } } } extension SocketEngine { // MARK: URLSessionDelegate methods /// Delegate called when the session becomes invalid. public func URLSession(session: URLSession, didBecomeInvalidWithError error: NSError?) { DefaultSocketLogger.Logger.error("Engine URLSession became invalid", type: "SocketEngine") didError(reason: "Engine URLSession became invalid") } } ================================================ FILE: ArcBit/External/socket.io-client-swift-10.0.0/Source/SocketEngineClient.swift ================================================ // // SocketEngineClient.swift // Socket.IO-Client-Swift // // Created by Erik Little on 3/19/15. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation /// Declares that a type will be a delegate to an engine. @objc public protocol SocketEngineClient { // MARK: Methods /// Called when the engine errors. /// /// - parameter reason: The reason the engine errored. func engineDidError(reason: String) /// Called when the engine closes. /// /// - parameter reason: The reason that the engine closed. func engineDidClose(reason: String) /// Called when the engine opens. /// /// - parameter reason: The reason the engine opened. func engineDidOpen(reason: String) /// Called when the engine has a message that must be parsed. /// /// - parameter msg: The message that needs parsing. func parseEngineMessage(_ msg: String) /// Called when the engine receives binary data. /// /// - parameter data: The data the engine received. func parseEngineBinaryData(_ data: Data) } ================================================ FILE: ArcBit/External/socket.io-client-swift-10.0.0/Source/SocketEnginePacketType.swift ================================================ // // SocketEnginePacketType.swift // Socket.IO-Client-Swift // // Created by Erik Little on 10/7/15. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation /// Represents the type of engine.io packet types. @objc public enum SocketEnginePacketType : Int { /// Open message. case open /// Close message. case close /// Ping message. case ping /// Pong message. case pong /// Regular message. case message /// Upgrade message. case upgrade /// NOOP. case noop } ================================================ FILE: ArcBit/External/socket.io-client-swift-10.0.0/Source/SocketEnginePollable.swift ================================================ // // SocketEnginePollable.swift // Socket.IO-Client-Swift // // Created by Erik Little on 1/15/16. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation /// Protocol that is used to implement socket.io polling support public protocol SocketEnginePollable : SocketEngineSpec { /// MARK: Properties /// `true` If engine's session has been invalidated. var invalidated: Bool { get } /// A queue of engine.io messages waiting for POSTing /// /// **You should not touch this directly** var postWait: [String] { get set } /// The URLSession that will be used for polling. var session: URLSession? { get } /// `true` if there is an outstanding poll. Trying to poll before the first is done will cause socket.io to /// disconnect us. /// /// **Do not touch this directly** var waitingForPoll: Bool { get set } /// `true` if there is an outstanding post. Trying to post before the first is done will cause socket.io to /// disconnect us. /// /// **Do not touch this directly** var waitingForPost: Bool { get set } /// Call to send a long-polling request. /// /// You shouldn't need to call this directly, the engine should automatically maintain a long-poll request. func doPoll() /// Sends an engine.io message through the polling transport. /// /// You shouldn't call this directly, instead call the `write` method on `SocketEngine`. /// /// - parameter message: The message to send. /// - parameter withType: The type of message to send. /// - parameter withData: The data associated with this message. func sendPollMessage(_ message: String, withType type: SocketEnginePacketType, withData datas: [Data]) /// Call to stop polling and invalidate the URLSession. func stopPolling() } // Default polling methods extension SocketEnginePollable { private func addHeaders(to req: inout URLRequest) { if cookies != nil { let headers = HTTPCookie.requestHeaderFields(with: cookies!) req.allHTTPHeaderFields = headers } if extraHeaders != nil { for (headerName, value) in extraHeaders! { req.setValue(value, forHTTPHeaderField: headerName) } } } func createRequestForPostWithPostWait() -> URLRequest { defer { postWait.removeAll(keepingCapacity: true) } var postStr = "" for packet in postWait { postStr += "\(packet.utf16.count):\(packet)" } DefaultSocketLogger.Logger.log("Created POST string: %@", type: "SocketEnginePolling", args: postStr) var req = URLRequest(url: urlPollingWithSid) let postData = postStr.data(using: .utf8, allowLossyConversion: false)! addHeaders(to: &req) req.httpMethod = "POST" req.setValue("text/plain; charset=UTF-8", forHTTPHeaderField: "Content-Type") req.httpBody = postData req.setValue(String(postData.count), forHTTPHeaderField: "Content-Length") return req } /// Call to send a long-polling request. /// /// You shouldn't need to call this directly, the engine should automatically maintain a long-poll request. public func doPoll() { guard !websocket && !waitingForPoll && connected && !closed else { return } var req = URLRequest(url: urlPollingWithSid) addHeaders(to: &req) doLongPoll(for: req) } func doRequest(for req: URLRequest, callbackWith callback: @escaping (Data?, URLResponse?, Error?) -> Void) { guard polling && !closed && !invalidated && !fastUpgrade else { return } DefaultSocketLogger.Logger.log("Doing polling %@ %@", type: "SocketEnginePolling", args: req.httpMethod ?? "", req) session?.dataTask(with: req, completionHandler: callback).resume() } func doLongPoll(for req: URLRequest) { waitingForPoll = true doRequest(for: req) {[weak self] data, res, err in guard let this = self, this.polling else { return } if err != nil || data == nil { DefaultSocketLogger.Logger.error(err?.localizedDescription ?? "Error", type: "SocketEnginePolling") if this.polling { this.didError(reason: err?.localizedDescription ?? "Error") } return } DefaultSocketLogger.Logger.log("Got polling response", type: "SocketEnginePolling") if let str = String(data: data!, encoding: String.Encoding.utf8) { this.parsePollingMessage(str) } this.waitingForPoll = false if this.fastUpgrade { this.doFastUpgrade() } else if !this.closed && this.polling { this.doPoll() } } } private func flushWaitingForPost() { guard postWait.count != 0 && connected else { return } guard !websocket else { flushWaitingForPostToWebSocket() return } let req = createRequestForPostWithPostWait() waitingForPost = true DefaultSocketLogger.Logger.log("POSTing", type: "SocketEnginePolling") doRequest(for: req) {[weak self] data, res, err in guard let this = self else { return } if err != nil { DefaultSocketLogger.Logger.error(err?.localizedDescription ?? "Error", type: "SocketEnginePolling") if this.polling { this.didError(reason: err?.localizedDescription ?? "Error") } return } this.waitingForPost = false if !this.fastUpgrade { this.flushWaitingForPost() this.doPoll() } } } func parsePollingMessage(_ str: String) { guard str.characters.count != 1 else { return } DefaultSocketLogger.Logger.log("Got poll message: %@", type: "SocketEnginePolling", args: str) var reader = SocketStringReader(message: str) while reader.hasNext { if let n = Int(reader.readUntilOccurence(of: ":")) { parseEngineMessage(reader.read(count: n)) } else { parseEngineMessage(str) break } } } /// Sends an engine.io message through the polling transport. /// /// You shouldn't call this directly, instead call the `write` method on `SocketEngine`. /// /// - parameter message: The message to send. /// - parameter withType: The type of message to send. /// - parameter withData: The data associated with this message. public func sendPollMessage(_ message: String, withType type: SocketEnginePacketType, withData datas: [Data]) { DefaultSocketLogger.Logger.log("Sending poll: %@ as type: %@", type: "SocketEnginePolling", args: message, type.rawValue) postWait.append(String(type.rawValue) + message) for data in datas { if case let .right(bin) = createBinaryDataForSend(using: data) { postWait.append(bin) } } if !waitingForPost { flushWaitingForPost() } } /// Call to stop polling and invalidate the URLSession. public func stopPolling() { waitingForPoll = false waitingForPost = false session?.finishTasksAndInvalidate() } } ================================================ FILE: ArcBit/External/socket.io-client-swift-10.0.0/Source/SocketEngineSpec.swift ================================================ // // SocketEngineSpec.swift // Socket.IO-Client-Swift // // Created by Erik Little on 10/7/15. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation /// Specifies a SocketEngine. @objc public protocol SocketEngineSpec { /// The client for this engine. weak var client: SocketEngineClient? { get set } /// `true` if this engine is closed. var closed: Bool { get } /// `true` if this engine is connected. Connected means that the initial poll connect has succeeded. var connected: Bool { get } /// The connect parameters sent during a connect. var connectParams: [String: Any]? { get set } /// An array of HTTPCookies that are sent during the connection. var cookies: [HTTPCookie]? { get } /// The queue that all engine actions take place on. var engineQueue: DispatchQueue { get } /// A dictionary of extra http headers that will be set during connection. var extraHeaders: [String: String]? { get } /// When `true`, the engine is in the process of switching to WebSockets. var fastUpgrade: Bool { get } /// When `true`, the engine will only use HTTP long-polling as a transport. var forcePolling: Bool { get } /// When `true`, the engine will only use WebSockets as a transport. var forceWebsockets: Bool { get } /// If `true`, the engine is currently in HTTP long-polling mode. var polling: Bool { get } /// If `true`, the engine is currently seeing whether it can upgrade to WebSockets. var probing: Bool { get } /// The session id for this engine. var sid: String { get } /// The path to engine.io. var socketPath: String { get } /// The url for polling. var urlPolling: URL { get } /// The url for WebSockets. var urlWebSocket: URL { get } /// If `true`, then the engine is currently in WebSockets mode. var websocket: Bool { get } /// The WebSocket for this engine. var ws: WebSocket? { get } /// Creates a new engine. /// /// - parameter client: The client for this engine. /// - parameter url: The url for this engine. /// - parameter options: The options for this engine. init(client: SocketEngineClient, url: URL, options: NSDictionary?) /// Starts the connection to the server. func connect() /// Called when an error happens during execution. Causes a disconnection. func didError(reason: String) /// Disconnects from the server. /// /// - parameter reason: The reason for the disconnection. This is communicated up to the client. func disconnect(reason: String) /// Called to switch from HTTP long-polling to WebSockets. After calling this method the engine will be in /// WebSocket mode. /// /// **You shouldn't call this directly** func doFastUpgrade() /// Causes any packets that were waiting for POSTing to be sent through the WebSocket. This happens because when /// the engine is attempting to upgrade to WebSocket it does not do any POSTing. /// /// **You shouldn't call this directly** func flushWaitingForPostToWebSocket() /// Parses raw binary received from engine.io. /// /// - parameter data: The data to parse. func parseEngineData(_ data: Data) /// Parses a raw engine.io packet. /// /// - parameter message: The message to parse. /// - parameter fromPolling: Whether this message is from long-polling. /// If `true` we might have to fix utf8 encoding. func parseEngineMessage(_ message: String) /// Writes a message to engine.io, independent of transport. /// /// - parameter msg: The message to send. /// - parameter withType: The type of this message. /// - parameter withData: Any data that this message has. func write(_ msg: String, withType type: SocketEnginePacketType, withData data: [Data]) } extension SocketEngineSpec { var urlPollingWithSid: URL { var com = URLComponents(url: urlPolling, resolvingAgainstBaseURL: false)! com.percentEncodedQuery = com.percentEncodedQuery! + "&sid=\(sid.urlEncode()!)" return com.url! } var urlWebSocketWithSid: URL { var com = URLComponents(url: urlWebSocket, resolvingAgainstBaseURL: false)! com.percentEncodedQuery = com.percentEncodedQuery! + (sid == "" ? "" : "&sid=\(sid.urlEncode()!)") return com.url! } func createBinaryDataForSend(using data: Data) -> Either { if websocket { var byteArray = [UInt8](repeating: 0x4, count: 1) let mutData = NSMutableData(bytes: &byteArray, length: 1) mutData.append(data) return .left(mutData as Data) } else { let str = "b4" + data.base64EncodedString(options: Data.Base64EncodingOptions(rawValue: 0)) return .right(str) } } /// Send an engine message (4) func send(_ msg: String, withData datas: [Data]) { write(msg, withType: .message, withData: datas) } } ================================================ FILE: ArcBit/External/socket.io-client-swift-10.0.0/Source/SocketEngineWebsocket.swift ================================================ // // SocketEngineWebsocket.swift // Socket.IO-Client-Swift // // Created by Erik Little on 1/15/16. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation /// Protocol that is used to implement socket.io WebSocket support public protocol SocketEngineWebsocket : SocketEngineSpec, WebSocketDelegate { /// Sends an engine.io message through the WebSocket transport. /// /// You shouldn't call this directly, instead call the `write` method on `SocketEngine`. /// /// - parameter message: The message to send. /// - parameter withType: The type of message to send. /// - parameter withData: The data associated with this message. func sendWebSocketMessage(_ str: String, withType type: SocketEnginePacketType, withData datas: [Data]) } // WebSocket methods extension SocketEngineWebsocket { func probeWebSocket() { if ws?.isConnected ?? false { sendWebSocketMessage("probe", withType: .ping, withData: []) } } /// Sends an engine.io message through the WebSocket transport. /// /// You shouldn't call this directly, instead call the `write` method on `SocketEngine`. /// /// - parameter message: The message to send. /// - parameter withType: The type of message to send. /// - parameter withData: The data associated with this message. public func sendWebSocketMessage(_ str: String, withType type: SocketEnginePacketType, withData datas: [Data]) { DefaultSocketLogger.Logger.log("Sending ws: %@ as type: %@", type: "SocketEngine", args: str, type.rawValue) ws?.write(string: "\(type.rawValue)\(str)") for data in datas { if case let .left(bin) = createBinaryDataForSend(using: data) { ws?.write(data: bin) } } } // MARK: Starscream delegate methods /// Delegate method for when a message is received. public func websocketDidReceiveMessage(socket: WebSocket, text: String) { parseEngineMessage(text) } /// Delegate method for when binary is received. public func websocketDidReceiveData(socket: WebSocket, data: Data) { parseEngineData(data) } } ================================================ FILE: ArcBit/External/socket.io-client-swift-10.0.0/Source/SocketEventHandler.swift ================================================ // // EventHandler.swift // Socket.IO-Client-Swift // // Created by Erik Little on 1/18/15. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation struct SocketEventHandler { let event: String let id: UUID let callback: NormalCallback func executeCallback(with items: [Any], withAck ack: Int, withSocket socket: SocketIOClient) { callback(items, SocketAckEmitter(socket: socket, ackNum: ack)) } } ================================================ FILE: ArcBit/External/socket.io-client-swift-10.0.0/Source/SocketExtensions.swift ================================================ // // SocketExtensions.swift // Socket.IO-Client-Swift // // Created by Erik Little on 7/1/2016. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation enum JSONError : Error { case notArray case notNSDictionary } extension Array { func toJSON() throws -> Data { return try JSONSerialization.data(withJSONObject: self, options: JSONSerialization.WritingOptions(rawValue: 0)) } } extension CharacterSet { static var allowedURLCharacterSet: CharacterSet { return CharacterSet(charactersIn: "!*'();:@&=+$,/?%#[]\" {}").inverted } } extension NSDictionary { private static func keyValueToSocketIOClientOption(key: String, value: Any) -> SocketIOClientOption? { switch (key, value) { case let ("connectParams", params as [String: Any]): return .connectParams(params) case let ("cookies", cookies as [HTTPCookie]): return .cookies(cookies) case let ("extraHeaders", headers as [String: String]): return .extraHeaders(headers) case let ("forceNew", force as Bool): return .forceNew(force) case let ("forcePolling", force as Bool): return .forcePolling(force) case let ("forceWebsockets", force as Bool): return .forceWebsockets(force) case let ("handleQueue", queue as DispatchQueue): return .handleQueue(queue) case let ("log", log as Bool): return .log(log) case let ("logger", logger as SocketLogger): return .logger(logger) case let ("nsp", nsp as String): return .nsp(nsp) case let ("path", path as String): return .path(path) case let ("reconnects", reconnects as Bool): return .reconnects(reconnects) case let ("reconnectAttempts", attempts as Int): return .reconnectAttempts(attempts) case let ("reconnectWait", wait as Int): return .reconnectWait(wait) case let ("secure", secure as Bool): return .secure(secure) case let ("security", security as SSLSecurity): return .security(security) case let ("selfSigned", selfSigned as Bool): return .selfSigned(selfSigned) case let ("sessionDelegate", delegate as URLSessionDelegate): return .sessionDelegate(delegate) case let ("voipEnabled", enable as Bool): return .voipEnabled(enable) default: return nil } } func toSocketConfiguration() -> SocketIOClientConfiguration { var options = [] as SocketIOClientConfiguration for (rawKey, value) in self { if let key = rawKey as? String, let opt = NSDictionary.keyValueToSocketIOClientOption(key: key, value: value) { options.insert(opt) } } return options } } extension String { func toArray() throws -> [Any] { guard let stringData = data(using: .utf16, allowLossyConversion: false) else { return [] } guard let array = try JSONSerialization.jsonObject(with: stringData, options: .mutableContainers) as? [Any] else { throw JSONError.notArray } return array } func toNSDictionary() throws -> NSDictionary { guard let binData = data(using: .utf16, allowLossyConversion: false) else { return [:] } guard let json = try JSONSerialization.jsonObject(with: binData, options: .allowFragments) as? NSDictionary else { throw JSONError.notNSDictionary } return json } func urlEncode() -> String? { return addingPercentEncoding(withAllowedCharacters: .allowedURLCharacterSet) } } ================================================ FILE: ArcBit/External/socket.io-client-swift-10.0.0/Source/SocketIOClient.swift ================================================ // // SocketIOClient.swift // Socket.IO-Client-Swift // // Created by Erik Little on 11/23/14. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Dispatch import Foundation /// The main class for SocketIOClientSwift. /// /// Represents a socket.io-client. Most interaction with socket.io will be through this class. open class SocketIOClient : NSObject, SocketIOClientSpec, SocketEngineClient, SocketParsable { // MARK: Properties /// The URL of the socket.io server. This is set in the initializer. public let socketURL: URL /// The engine for this client. public private(set) var engine: SocketEngineSpec? /// The status of this client. public private(set) var status = SocketIOClientStatus.notConnected { didSet { switch status { case .connected: reconnecting = false currentReconnectAttempt = 0 default: break } handleClientEvent(.statusChange, data: [status]) } } /// If `true` then every time `connect` is called, a new engine will be created. public var forceNew = false /// The queue that all interaction with the client should occur on. This is the queue that event handlers are /// called on. public var handleQueue = DispatchQueue.main /// The namespace for this client. public var nsp = "/" /// The configuration for this client. public var config: SocketIOClientConfiguration /// If `true`, this client will try and reconnect on any disconnects. public var reconnects = true /// The number of seconds to wait before attempting to reconnect. public var reconnectWait = 10 /// The session id of this client. public var sid: String? { return engine?.sid } private let logType = "SocketIOClient" private var anyHandler: ((SocketAnyEvent) -> Void)? private var currentReconnectAttempt = 0 private var handlers = [SocketEventHandler]() private var reconnecting = false private(set) var currentAck = -1 private(set) var reconnectAttempts = -1 var ackHandlers = SocketAckManager() var waitingPackets = [SocketPacket]() // MARK: Initializers /// Type safe way to create a new SocketIOClient. `opts` can be omitted. /// /// - parameter socketURL: The url of the socket.io server. /// - parameter config: The config for this socket. public init(socketURL: URL, config: SocketIOClientConfiguration = []) { self.config = config self.socketURL = socketURL if socketURL.absoluteString.hasPrefix("https://") { self.config.insert(.secure(true)) } for option in config { switch option { case let .reconnects(reconnects): self.reconnects = reconnects case let .reconnectAttempts(attempts): reconnectAttempts = attempts case let .reconnectWait(wait): reconnectWait = abs(wait) case let .nsp(nsp): self.nsp = nsp case let .log(log): DefaultSocketLogger.Logger.log = log case let .logger(logger): DefaultSocketLogger.Logger = logger case let .handleQueue(queue): handleQueue = queue case let .forceNew(force): forceNew = force default: continue } } self.config.insert(.path("/socket.io/"), replacing: false) super.init() } /// Not so type safe way to create a SocketIOClient, meant for Objective-C compatiblity. /// If using Swift it's recommended to use `init(socketURL: NSURL, options: Set)` /// /// - parameter socketURL: The url of the socket.io server. /// - parameter config: The config for this socket. public convenience init(socketURL: NSURL, config: NSDictionary?) { self.init(socketURL: socketURL as URL, config: config?.toSocketConfiguration() ?? []) } deinit { DefaultSocketLogger.Logger.log("Client is being released", type: logType) engine?.disconnect(reason: "Client Deinit") } // MARK: Methods private func addEngine() -> SocketEngineSpec { DefaultSocketLogger.Logger.log("Adding engine", type: logType, args: "") engine?.client = nil engine = SocketEngine(client: self, url: socketURL, config: config) return engine! } /// Connect to the server. open func connect() { connect(timeoutAfter: 0, withHandler: nil) } /// Connect to the server. If we aren't connected after `timeoutAfter` seconds, then `withHandler` is called. /// /// - parameter timeoutAfter: The number of seconds after which if we are not connected we assume the connection /// has failed. Pass 0 to never timeout. /// - parameter withHandler: The handler to call when the client fails to connect. open func connect(timeoutAfter: Int, withHandler handler: (() -> Void)?) { assert(timeoutAfter >= 0, "Invalid timeout: \(timeoutAfter)") guard status != .connected else { DefaultSocketLogger.Logger.log("Tried connecting on an already connected socket", type: logType) return } status = .connecting if engine == nil || forceNew { addEngine().connect() } else { engine?.connect() } guard timeoutAfter != 0 else { return } handleQueue.asyncAfter(deadline: DispatchTime.now() + Double(timeoutAfter)) {[weak self] in guard let this = self, this.status == .connecting || this.status == .notConnected else { return } this.status = .disconnected this.engine?.disconnect(reason: "Connect timeout") handler?() } } private func createOnAck(_ items: [Any]) -> OnAckCallback { currentAck += 1 return OnAckCallback(ackNumber: currentAck, items: items, socket: self) } func didConnect() { DefaultSocketLogger.Logger.log("Socket connected", type: logType) status = .connected handleClientEvent(.connect, data: []) } func didDisconnect(reason: String) { guard status != .disconnected else { return } DefaultSocketLogger.Logger.log("Disconnected: %@", type: logType, args: reason) reconnecting = false status = .disconnected // Make sure the engine is actually dead. engine?.disconnect(reason: reason) handleClientEvent(.disconnect, data: [reason]) } /// Disconnects the socket. open func disconnect() { DefaultSocketLogger.Logger.log("Closing socket", type: logType) didDisconnect(reason: "Disconnect") } /// Send an event to the server, with optional data items. /// /// If an error occurs trying to transform `items` into their socket representation, a `SocketClientEvent.error` /// will be emitted. The structure of the error data is `[eventName, items, theError]` /// /// - parameter event: The event to send. /// - parameter items: The items to send with this event. May be left out. open func emit(_ event: String, _ items: SocketData...) { do { try emit(event, with: items.map({ try $0.socketRepresentation() })) } catch let err { DefaultSocketLogger.Logger.error("Error creating socketRepresentation for emit: \(event), \(items)", type: logType) handleClientEvent(.error, data: [event, items, err]) } } /// Same as emit, but meant for Objective-C /// /// - parameter event: The event to send. /// - parameter with: The items to send with this event. May be left out. open func emit(_ event: String, with items: [Any]) { guard status == .connected else { handleClientEvent(.error, data: ["Tried emitting \(event) when not connected"]) return } _emit([event] + items) } /// Sends a message to the server, requesting an ack. /// /// **NOTE**: It is up to the server send an ack back, just calling this method does not mean the server will ack. /// Check that your server's api will ack the event being sent. /// /// If an error occurs trying to transform `items` into their socket representation, a `SocketClientEvent.error` /// will be emitted. The structure of the error data is `[eventName, items, theError]` /// /// Example: /// /// ```swift /// socket.emitWithAck("myEvent", 1).timingOut(after: 1) {data in /// ... /// } /// ``` /// /// - parameter event: The event to send. /// - parameter items: The items to send with this event. May be left out. /// - returns: An `OnAckCallback`. You must call the `timingOut(after:)` method before the event will be sent. open func emitWithAck(_ event: String, _ items: SocketData...) -> OnAckCallback { do { return emitWithAck(event, with: try items.map({ try $0.socketRepresentation() })) } catch let err { DefaultSocketLogger.Logger.error("Error creating socketRepresentation for emit: \(event), \(items)", type: logType) handleClientEvent(.error, data: [event, items, err]) return OnAckCallback(ackNumber: -1, items: [], socket: self) } } /// Same as emitWithAck, but for Objective-C /// /// **NOTE**: It is up to the server send an ack back, just calling this method does not mean the server will ack. /// Check that your server's api will ack the event being sent. /// /// Example: /// /// ```swift /// socket.emitWithAck("myEvent", with: [1]).timingOut(after: 1) {data in /// ... /// } /// ``` /// /// - parameter event: The event to send. /// - parameter with: The items to send with this event. Use `[]` to send nothing. /// - returns: An `OnAckCallback`. You must call the `timingOut(after:)` method before the event will be sent. open func emitWithAck(_ event: String, with items: [Any]) -> OnAckCallback { return createOnAck([event] + items) } func _emit(_ data: [Any], ack: Int? = nil) { guard status == .connected else { handleClientEvent(.error, data: ["Tried emitting when not connected"]) return } let packet = SocketPacket.packetFromEmit(data, id: ack ?? -1, nsp: nsp, ack: false) let str = packet.packetString DefaultSocketLogger.Logger.log("Emitting: %@", type: logType, args: str) engine?.send(str, withData: packet.binary) } // If the server wants to know that the client received data func emitAck(_ ack: Int, with items: [Any]) { guard status == .connected else { return } let packet = SocketPacket.packetFromEmit(items, id: ack, nsp: nsp, ack: true) let str = packet.packetString DefaultSocketLogger.Logger.log("Emitting Ack: %@", type: logType, args: str) engine?.send(str, withData: packet.binary) } /// Called when the engine closes. /// /// - parameter reason: The reason that the engine closed. open func engineDidClose(reason: String) { handleQueue.async { self._engineDidClose(reason: reason) } } private func _engineDidClose(reason: String) { waitingPackets.removeAll() if status != .disconnected { status = .notConnected } if status == .disconnected || !reconnects { didDisconnect(reason: reason) } else if !reconnecting { reconnecting = true tryReconnect(reason: reason) } } /// Called when the engine errors. /// /// - parameter reason: The reason the engine errored. open func engineDidError(reason: String) { handleQueue.async { self._engineDidError(reason: reason) } } private func _engineDidError(reason: String) { DefaultSocketLogger.Logger.error("%@", type: logType, args: reason) handleClientEvent(.error, data: [reason]) } /// Called when the engine opens. /// /// - parameter reason: The reason the engine opened. open func engineDidOpen(reason: String) { DefaultSocketLogger.Logger.log(reason, type: "SocketEngineClient") } // Called when the socket gets an ack for something it sent func handleAck(_ ack: Int, data: [Any]) { guard status == .connected else { return } DefaultSocketLogger.Logger.log("Handling ack: %@ with data: %@", type: logType, args: ack, data) ackHandlers.executeAck(ack, with: data, onQueue: handleQueue) } /// Causes an event to be handled, and any event handlers for that event to be called. /// /// - parameter event: The event that is to be handled. /// - parameter data: the data associated with this event. /// - parameter isInternalMessage: If `true` event handlers for this event will be called regardless of status. /// - parameter withAck: The ack number for this event. May be left out. open func handleEvent(_ event: String, data: [Any], isInternalMessage: Bool, withAck ack: Int = -1) { guard status == .connected || isInternalMessage else { return } DefaultSocketLogger.Logger.log("Handling event: %@ with data: %@", type: logType, args: event, data) anyHandler?(SocketAnyEvent(event: event, items: data)) for handler in handlers where handler.event == event { handler.executeCallback(with: data, withAck: ack, withSocket: self) } } func handleClientEvent(_ event: SocketClientEvent, data: [Any]) { handleEvent(event.rawValue, data: data, isInternalMessage: true) } /// Leaves nsp and goes back to the default namespace. open func leaveNamespace() { if nsp != "/" { engine?.send("1\(nsp)", withData: []) nsp = "/" } } /// Joins `namespace`. /// /// **Do not use this to join the default namespace.** Instead call `leaveNamespace`. /// /// - parameter namespace: The namespace to join. open func joinNamespace(_ namespace: String) { nsp = namespace if nsp != "/" { DefaultSocketLogger.Logger.log("Joining namespace", type: logType) engine?.send("0\(nsp)", withData: []) } } /// Removes handler(s) based on an event name. /// /// If you wish to remove a specific event, call the `off(id:)` with the UUID received from its `on` call. /// /// - parameter event: The event to remove handlers for. open func off(_ event: String) { DefaultSocketLogger.Logger.log("Removing handler for event: %@", type: logType, args: event) handlers = handlers.filter({ $0.event != event }) } /// Removes a handler with the specified UUID gotten from an `on` or `once` /// /// If you want to remove all events for an event, call the off `off(_:)` method with the event name. /// /// - parameter id: The UUID of the handler you wish to remove. open func off(id: UUID) { DefaultSocketLogger.Logger.log("Removing handler with id: %@", type: logType, args: id) handlers = handlers.filter({ $0.id != id }) } /// Adds a handler for an event. /// /// - parameter event: The event name for this handler. /// - parameter callback: The callback that will execute when this event is received. /// - returns: A unique id for the handler that can be used to remove it. @discardableResult open func on(_ event: String, callback: @escaping NormalCallback) -> UUID { DefaultSocketLogger.Logger.log("Adding handler for event: %@", type: logType, args: event) let handler = SocketEventHandler(event: event, id: UUID(), callback: callback) handlers.append(handler) return handler.id } /// Adds a handler for a client event. /// /// Example: /// /// ```swift /// socket.on(clientEvent: .connect) {data, ack in /// ... /// } /// ``` /// /// - parameter event: The event for this handler. /// - parameter callback: The callback that will execute when this event is received. /// - returns: A unique id for the handler that can be used to remove it. @discardableResult open func on(clientEvent event: SocketClientEvent, callback: @escaping NormalCallback) -> UUID { DefaultSocketLogger.Logger.log("Adding handler for event: %@", type: logType, args: event) let handler = SocketEventHandler(event: event.rawValue, id: UUID(), callback: callback) handlers.append(handler) return handler.id } /// Adds a single-use handler for an event. /// /// - parameter event: The event name for this handler. /// - parameter callback: The callback that will execute when this event is received. /// - returns: A unique id for the handler that can be used to remove it. @discardableResult open func once(_ event: String, callback: @escaping NormalCallback) -> UUID { DefaultSocketLogger.Logger.log("Adding once handler for event: %@", type: logType, args: event) let id = UUID() let handler = SocketEventHandler(event: event, id: id) {[weak self] data, ack in guard let this = self else { return } this.off(id: id) callback(data, ack) } handlers.append(handler) return handler.id } /// Adds a handler that will be called on every event. /// /// - parameter handler: The callback that will execute whenever an event is received. open func onAny(_ handler: @escaping (SocketAnyEvent) -> Void) { anyHandler = handler } /// Called when the engine has a message that must be parsed. /// /// - parameter msg: The message that needs parsing. public func parseEngineMessage(_ msg: String) { DefaultSocketLogger.Logger.log("Should parse message: %@", type: "SocketIOClient", args: msg) handleQueue.async { self.parseSocketMessage(msg) } } /// Called when the engine receives binary data. /// /// - parameter data: The data the engine received. public func parseEngineBinaryData(_ data: Data) { handleQueue.async { self.parseBinaryData(data) } } /// Tries to reconnect to the server. /// /// This will cause a `disconnect` event to be emitted, as well as an `reconnectAttempt` event. open func reconnect() { guard !reconnecting else { return } engine?.disconnect(reason: "manual reconnect") } /// Removes all handlers. /// Can be used after disconnecting to break any potential remaining retain cycles. open func removeAllHandlers() { handlers.removeAll(keepingCapacity: false) } private func tryReconnect(reason: String) { guard reconnecting else { return } DefaultSocketLogger.Logger.log("Starting reconnect", type: logType) handleClientEvent(.reconnect, data: [reason]) _tryReconnect() } private func _tryReconnect() { guard reconnects && reconnecting && status != .disconnected else { return } if reconnectAttempts != -1 && currentReconnectAttempt + 1 > reconnectAttempts { return didDisconnect(reason: "Reconnect Failed") } DefaultSocketLogger.Logger.log("Trying to reconnect", type: logType) handleClientEvent(.reconnectAttempt, data: [(reconnectAttempts - currentReconnectAttempt)]) currentReconnectAttempt += 1 connect() handleQueue.asyncAfter(deadline: DispatchTime.now() + Double(reconnectWait), execute: _tryReconnect) } // Test properties var testHandlers: [SocketEventHandler] { return handlers } func setTestable() { status = .connected } func setTestStatus(_ status: SocketIOClientStatus) { self.status = status } func setTestEngine(_ engine: SocketEngineSpec?) { self.engine = engine } func emitTest(event: String, _ data: Any...) { _emit([event] + data) } } ================================================ FILE: ArcBit/External/socket.io-client-swift-10.0.0/Source/SocketIOClientConfiguration.swift ================================================ // // SocketIOClientConfiguration.swift // Socket.IO-Client-Swift // // Created by Erik Little on 8/13/16. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. /// An array-like type that holds `SocketIOClientOption`s public struct SocketIOClientConfiguration : ExpressibleByArrayLiteral, Collection, MutableCollection { // MARK: Typealiases /// Type of element stored. public typealias Element = SocketIOClientOption /// Index type. public typealias Index = Array.Index /// Iterator type. public typealias Iterator = Array.Iterator /// SubSequence type. public typealias SubSequence = Array.SubSequence // MARK: Properties private var backingArray = [SocketIOClientOption]() /// The start index of this collection. public var startIndex: Index { return backingArray.startIndex } /// The end index of this collection. public var endIndex: Index { return backingArray.endIndex } /// Whether this collection is empty. public var isEmpty: Bool { return backingArray.isEmpty } /// The number of elements stored in this collection. public var count: Index.Stride { return backingArray.count } /// The first element in this collection. public var first: Element? { return backingArray.first } public subscript(position: Index) -> Element { get { return backingArray[position] } set { backingArray[position] = newValue } } public subscript(bounds: Range) -> SubSequence { get { return backingArray[bounds] } set { backingArray[bounds] = newValue } } // MARK: Initializers /// Creates a new `SocketIOClientConfiguration` from an array literal. /// /// - parameter arrayLiteral: The elements. public init(arrayLiteral elements: Element...) { backingArray = elements } // MARK: Methods /// Creates an iterator for this collection. /// /// - returns: An iterator over this collection. public func makeIterator() -> Iterator { return backingArray.makeIterator() } /// - returns: The index after index. public func index(after i: Index) -> Index { return backingArray.index(after: i) } /// Special method that inserts `element` into the collection, replacing any other instances of `element`. /// /// - parameter element: The element to insert. /// - parameter replacing: Whether to replace any occurrences of element to the new item. Default is `true`. public mutating func insert(_ element: Element, replacing replace: Bool = true) { for i in 0.. Any } /// The options for a client. public enum SocketIOClientOption : ClientOption { /// A dictionary of GET parameters that will be included in the connect url. case connectParams([String: Any]) /// An array of cookies that will be sent during the initial connection. case cookies([HTTPCookie]) /// Deprecated @available(*, deprecated, message: "No longer needed in socket.io 2.0+") case doubleEncodeUTF8(Bool) /// Any extra HTTP headers that should be sent during the initial connection. case extraHeaders([String: String]) /// If passed `true`, will cause the client to always create a new engine. Useful for debugging, /// or when you want to be sure no state from previous engines is being carried over. case forceNew(Bool) /// If passed `true`, the only transport that will be used will be HTTP long-polling. case forcePolling(Bool) /// If passed `true`, the only transport that will be used will be WebSockets. case forceWebsockets(Bool) /// The queue that all interaction with the client should occur on. This is the queue that event handlers are /// called on. case handleQueue(DispatchQueue) /// If passed `true`, the client will log debug information. This should be turned off in production code. case log(Bool) /// Used to pass in a custom logger. case logger(SocketLogger) /// The namespace that this client should connect to. Can be changed during use using the `joinNamespace` /// and `leaveNamespace` methods on `SocketIOClient`. case nsp(String) /// A custom path to socket.io. Only use this if the socket.io server is configured to look for this path. case path(String) /// If passed `false`, the client will not reconnect when it loses connection. Useful if you want full control /// over when reconnects happen. case reconnects(Bool) /// The number of times to try and reconnect before giving up. Pass `-1` to [never give up](https://www.youtube.com/watch?v=dQw4w9WgXcQ). case reconnectAttempts(Int) /// The number of seconds to wait before reconnect attempts. case reconnectWait(Int) /// Set `true` if your server is using secure transports. case secure(Bool) /// Allows you to set which certs are valid. Useful for SSL pinning. case security(SSLSecurity) /// If you're using a self-signed set. Only use for development. case selfSigned(Bool) /// Sets an NSURLSessionDelegate for the underlying engine. Useful if you need to handle self-signed certs. case sessionDelegate(URLSessionDelegate) /// If passed `true`, the WebSocket transport will try and use voip logic to keep network connections open in /// the background. **This option is experimental as socket.io shouldn't be used for background communication.** case voipEnabled(Bool) // MARK: Properties /// The description of this option. public var description: String { let description: String switch self { case .connectParams: description = "connectParams" case .cookies: description = "cookies" case .doubleEncodeUTF8: description = "doubleEncodeUTF8" case .extraHeaders: description = "extraHeaders" case .forceNew: description = "forceNew" case .forcePolling: description = "forcePolling" case .forceWebsockets: description = "forceWebsockets" case .handleQueue: description = "handleQueue" case .log: description = "log" case .logger: description = "logger" case .nsp: description = "nsp" case .path: description = "path" case .reconnects: description = "reconnects" case .reconnectAttempts: description = "reconnectAttempts" case .reconnectWait: description = "reconnectWait" case .secure: description = "secure" case .selfSigned: description = "selfSigned" case .security: description = "security" case .sessionDelegate: description = "sessionDelegate" case .voipEnabled: description = "voipEnabled" } return description } func getSocketIOOptionValue() -> Any { let value: Any switch self { case let .connectParams(params): value = params case let .cookies(cookies): value = cookies case let .doubleEncodeUTF8(encode): value = encode case let .extraHeaders(headers): value = headers case let .forceNew(force): value = force case let .forcePolling(force): value = force case let .forceWebsockets(force): value = force case let .handleQueue(queue): value = queue case let .log(log): value = log case let .logger(logger): value = logger case let .nsp(nsp): value = nsp case let .path(path): value = path case let .reconnects(reconnects): value = reconnects case let .reconnectAttempts(attempts): value = attempts case let .reconnectWait(wait): value = wait case let .secure(secure): value = secure case let .security(security): value = security case let .selfSigned(signed): value = signed case let .sessionDelegate(delegate): value = delegate case let .voipEnabled(enabled): value = enabled } return value } // MARK: Operators /// Compares whether two options are the same. /// /// - parameter lhs: Left operand to compare. /// - parameter rhs: Right operand to compare. /// - returns: `true` if the two are the same option. public static func ==(lhs: SocketIOClientOption, rhs: SocketIOClientOption) -> Bool { return lhs.description == rhs.description } } ================================================ FILE: ArcBit/External/socket.io-client-swift-10.0.0/Source/SocketIOClientSpec.swift ================================================ // // SocketIOClientSpec.swift // Socket.IO-Client-Swift // // Created by Erik Little on 1/3/16. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Dispatch protocol SocketIOClientSpec : class { var handleQueue: DispatchQueue { get set } var nsp: String { get set } var waitingPackets: [SocketPacket] { get set } func didConnect() func didDisconnect(reason: String) func didError(reason: String) func handleAck(_ ack: Int, data: [Any]) func handleEvent(_ event: String, data: [Any], isInternalMessage: Bool, withAck ack: Int) func handleClientEvent(_ event: SocketClientEvent, data: [Any]) func joinNamespace(_ namespace: String) } extension SocketIOClientSpec { func didError(reason: String) { DefaultSocketLogger.Logger.error("%@", type: "SocketIOClient", args: reason) handleClientEvent(.error, data: [reason]) } } /// The set of events that are generated by the client. public enum SocketClientEvent : String { /// Emitted when the client connects. This is also called on a successful reconnection. case connect /// Called when the socket has disconnected and will not attempt to try to reconnect. case disconnect /// Called when an error occurs. case error /// Called when the client begins the reconnection process. case reconnect /// Called each time the client tries to reconnect to the server. case reconnectAttempt /// Called every time there is a change in the client's status. case statusChange } ================================================ FILE: ArcBit/External/socket.io-client-swift-10.0.0/Source/SocketIOClientStatus.swift ================================================ // // SocketIOClientStatus.swift // Socket.IO-Client-Swift // // Created by Erik Little on 8/14/15. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation /// Represents the state of the client. @objc public enum SocketIOClientStatus : Int { /// The client has never been connected. Or the client has been reset. case notConnected /// The client was once connected, but not anymore. case disconnected /// The client is in the process of connecting. case connecting /// The client is currently connected. case connected } ================================================ FILE: ArcBit/External/socket.io-client-swift-10.0.0/Source/SocketLogger.swift ================================================ // // SocketLogger.swift // Socket.IO-Client-Swift // // Created by Erik Little on 4/11/15. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation /// Represents a class will log client events. public protocol SocketLogger : class { // MARK: Properties /// Whether to log or not var log: Bool { get set } // MARK: Methods /// Normal log messages /// /// - parameter message: The message being logged. Can include `%@` that will be replaced with `args` /// - parameter type: The type of entity that called for logging. /// - parameter args: Any args that should be inserted into the message. May be left out. func log(_ message: String, type: String, args: Any...) /// Error Messages /// /// - parameter message: The message being logged. Can include `%@` that will be replaced with `args` /// - parameter type: The type of entity that called for logging. /// - parameter args: Any args that should be inserted into the message. May be left out. func error(_ message: String, type: String, args: Any...) } public extension SocketLogger { /// Default implementation. func log(_ message: String, type: String, args: Any...) { abstractLog("LOG", message: message, type: type, args: args) } /// Default implementation. func error(_ message: String, type: String, args: Any...) { abstractLog("ERROR", message: message, type: type, args: args) } private func abstractLog(_ logType: String, message: String, type: String, args: [Any]) { guard log else { return } let newArgs = args.map({arg -> CVarArg in String(describing: arg)}) let messageFormat = String(format: message, arguments: newArgs) NSLog("\(logType) \(type): %@", messageFormat) } } class DefaultSocketLogger : SocketLogger { static var Logger: SocketLogger = DefaultSocketLogger() var log = false } ================================================ FILE: ArcBit/External/socket.io-client-swift-10.0.0/Source/SocketPacket.swift ================================================ // // SocketPacket.swift // Socket.IO-Client-Swift // // Created by Erik Little on 1/18/15. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation struct SocketPacket { enum PacketType: Int { case connect, disconnect, event, ack, error, binaryEvent, binaryAck } private let placeholders: Int private static let logType = "SocketPacket" let nsp: String let id: Int let type: PacketType var binary: [Data] var data: [Any] var args: [Any] { if type == .event || type == .binaryEvent && data.count != 0 { return Array(data.dropFirst()) } else { return data } } var description: String { return "SocketPacket {type: \(String(type.rawValue)); data: " + "\(String(describing: data)); id: \(id); placeholders: \(placeholders); nsp: \(nsp)}" } var event: String { return String(describing: data[0]) } var packetString: String { return createPacketString() } init(type: PacketType, data: [Any] = [Any](), id: Int = -1, nsp: String, placeholders: Int = 0, binary: [Data] = [Data]()) { self.data = data self.id = id self.nsp = nsp self.type = type self.placeholders = placeholders self.binary = binary } mutating func addData(_ data: Data) -> Bool { if placeholders == binary.count { return true } binary.append(data) if placeholders == binary.count { fillInPlaceholders() return true } else { return false } } private func completeMessage(_ message: String) -> String { if data.count == 0 { return message + "[]" } guard let jsonSend = try? data.toJSON(), let jsonString = String(data: jsonSend, encoding: .utf8) else { DefaultSocketLogger.Logger.error("Error creating JSON object in SocketPacket.completeMessage", type: SocketPacket.logType) return message + "[]" } return message + jsonString } private func createPacketString() -> String { let typeString = String(type.rawValue) // Binary count? let binaryCountString = typeString + (type == .binaryEvent || type == .binaryAck ? "\(String(binary.count))-" : "") // Namespace? let nspString = binaryCountString + (nsp != "/" ? "\(nsp)," : "") // Ack number? let idString = nspString + (id != -1 ? String(id) : "") return completeMessage(idString) } // Called when we have all the binary data for a packet // calls _fillInPlaceholders, which replaces placeholders with the // corresponding binary private mutating func fillInPlaceholders() { data = data.map(_fillInPlaceholders) } // Helper method that looks for placeholders // If object is a collection it will recurse // Returns the object if it is not a placeholder or the corresponding // binary data private func _fillInPlaceholders(_ object: Any) -> Any { switch object { case let dict as JSON: if dict["_placeholder"] as? Bool ?? false { return binary[dict["num"] as! Int] } else { return dict.reduce(JSON(), {cur, keyValue in var cur = cur cur[keyValue.0] = _fillInPlaceholders(keyValue.1) return cur }) } case let arr as [Any]: return arr.map(_fillInPlaceholders) default: return object } } } extension SocketPacket { private static func findType(_ binCount: Int, ack: Bool) -> PacketType { switch binCount { case 0 where !ack: return .event case 0 where ack: return .ack case _ where !ack: return .binaryEvent case _ where ack: return .binaryAck default: return .error } } static func packetFromEmit(_ items: [Any], id: Int, nsp: String, ack: Bool) -> SocketPacket { let (parsedData, binary) = deconstructData(items) let packet = SocketPacket(type: findType(binary.count, ack: ack), data: parsedData, id: id, nsp: nsp, binary: binary) return packet } } private extension SocketPacket { // Recursive function that looks for NSData in collections static func shred(_ data: Any, binary: inout [Data]) -> Any { let placeholder = ["_placeholder": true, "num": binary.count] as JSON switch data { case let bin as Data: binary.append(bin) return placeholder case let arr as [Any]: return arr.map({shred($0, binary: &binary)}) case let dict as JSON: return dict.reduce(JSON(), {cur, keyValue in var mutCur = cur mutCur[keyValue.0] = shred(keyValue.1, binary: &binary) return mutCur }) default: return data } } // Removes binary data from emit data // Returns a type containing the de-binaryed data and the binary static func deconstructData(_ data: [Any]) -> ([Any], [Data]) { var binary = [Data]() return (data.map({shred($0, binary: &binary)}), binary) } } ================================================ FILE: ArcBit/External/socket.io-client-swift-10.0.0/Source/SocketParsable.swift ================================================ // // SocketParsable.swift // Socket.IO-Client-Swift // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation protocol SocketParsable { func parseBinaryData(_ data: Data) func parseSocketMessage(_ message: String) } extension SocketParsable where Self: SocketIOClientSpec { private func isCorrectNamespace(_ nsp: String) -> Bool { return nsp == self.nsp } private func handleConnect(_ packetNamespace: String) { if packetNamespace == "/" && nsp != "/" { joinNamespace(nsp) } else { didConnect() } } private func handlePacket(_ pack: SocketPacket) { switch pack.type { case .event where isCorrectNamespace(pack.nsp): handleEvent(pack.event, data: pack.args, isInternalMessage: false, withAck: pack.id) case .ack where isCorrectNamespace(pack.nsp): handleAck(pack.id, data: pack.data) case .binaryEvent where isCorrectNamespace(pack.nsp): waitingPackets.append(pack) case .binaryAck where isCorrectNamespace(pack.nsp): waitingPackets.append(pack) case .connect: handleConnect(pack.nsp) case .disconnect: didDisconnect(reason: "Got Disconnect") case .error: handleEvent("error", data: pack.data, isInternalMessage: true, withAck: pack.id) default: DefaultSocketLogger.Logger.log("Got invalid packet: %@", type: "SocketParser", args: pack.description) } } /// Parses a messsage from the engine. Returning either a string error or a complete SocketPacket func parseString(_ message: String) -> Either { var reader = SocketStringReader(message: message) guard let type = Int(reader.read(count: 1)).flatMap({ SocketPacket.PacketType(rawValue: $0) }) else { return .left("Invalid packet type") } if !reader.hasNext { return .right(SocketPacket(type: type, nsp: "/")) } var namespace = "/" var placeholders = -1 if type == .binaryEvent || type == .binaryAck { if let holders = Int(reader.readUntilOccurence(of: "-")) { placeholders = holders } else { return .left("Invalid packet") } } if reader.currentCharacter == "/" { namespace = reader.readUntilOccurence(of: ",") } if !reader.hasNext { return .right(SocketPacket(type: type, nsp: namespace, placeholders: placeholders)) } var idString = "" if type == .error { reader.advance(by: -1) } else { while reader.hasNext { if let int = Int(reader.read(count: 1)) { idString += String(int) } else { reader.advance(by: -2) break } } } var dataArray = String(message.utf16[message.utf16.index(reader.currentIndex, offsetBy: 1).. Either { do { return .right(try data.toArray()) } catch { return .left("Error parsing data for packet") } } // Parses messages recieved func parseSocketMessage(_ message: String) { guard !message.isEmpty else { return } DefaultSocketLogger.Logger.log("Parsing %@", type: "SocketParser", args: message) switch parseString(message) { case let .left(err): DefaultSocketLogger.Logger.error("\(err): %@", type: "SocketParser", args: message) case let .right(pack): DefaultSocketLogger.Logger.log("Decoded packet as: %@", type: "SocketParser", args: pack.description) handlePacket(pack) } } func parseBinaryData(_ data: Data) { guard !waitingPackets.isEmpty else { DefaultSocketLogger.Logger.error("Got data when not remaking packet", type: "SocketParser") return } // Should execute event? guard waitingPackets[waitingPackets.count - 1].addData(data) else { return } let packet = waitingPackets.removeLast() if packet.type != .binaryAck { handleEvent(packet.event, data: packet.args, isInternalMessage: false, withAck: packet.id) } else { handleAck(packet.id, data: packet.args) } } } ================================================ FILE: ArcBit/External/socket.io-client-swift-10.0.0/Source/SocketStringReader.swift ================================================ // // SocketStringReader.swift // Socket.IO-Client-Swift // // Created by Lukas Schmidt on 07.09.15. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. struct SocketStringReader { let message: String var currentIndex: String.UTF16View.Index var hasNext: Bool { return currentIndex != message.utf16.endIndex } var currentCharacter: String { return String(UnicodeScalar(message.utf16[currentIndex])!) } init(message: String) { self.message = message currentIndex = message.utf16.startIndex } @discardableResult mutating func advance(by: Int) -> String.UTF16View.Index { currentIndex = message.utf16.index(currentIndex, offsetBy: by) return currentIndex } mutating func read(count: Int) -> String { let readString = String(message.utf16[currentIndex.. String { let substring = message.utf16[currentIndex.. String { return read(count: message.utf16.distance(from: currentIndex, to: message.utf16.endIndex)) } } ================================================ FILE: ArcBit/External/socket.io-client-swift-10.0.0/Source/SocketTypes.swift ================================================ // // SocketTypes.swift // Socket.IO-Client-Swift // // Created by Erik Little on 4/8/15. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation /// A marking protocol that says a type can be represented in a socket.io packet. /// /// Example: /// /// ```swift /// struct CustomData : SocketData { /// let name: String /// let age: Int /// /// func socketRepresentation() -> SocketData { /// return ["name": name, "age": age] /// } /// } /// /// socket.emit("myEvent", CustomData(name: "Erik", age: 24)) /// ``` public protocol SocketData { // MARK: Methods /// A representation of self that can sent over socket.io. func socketRepresentation() throws -> SocketData } public extension SocketData { /// Default implementation. Only works for native Swift types and a few Foundation types. func socketRepresentation() -> SocketData { return self } } extension Array : SocketData { } extension Bool : SocketData { } extension Dictionary : SocketData { } extension Double : SocketData { } extension Int : SocketData { } extension NSArray : SocketData { } extension Data : SocketData { } extension NSData : SocketData { } extension NSDictionary : SocketData { } extension NSString : SocketData { } extension NSNull : SocketData { } extension String : SocketData { } /// A typealias for an ack callback. public typealias AckCallback = ([Any]) -> Void /// A typealias for a normal callback. public typealias NormalCallback = ([Any], SocketAckEmitter) -> Void typealias JSON = [String: Any] typealias Probe = (msg: String, type: SocketEnginePacketType, data: [Data]) typealias ProbeWaitQueue = [Probe] enum Either { case left(E) case right(V) } ================================================ FILE: ArcBit/External/socket.io-client-swift-10.0.0/Source/WebSocket.swift ================================================ ////////////////////////////////////////////////////////////////////////////////////////////////// // // Websocket.swift // // Created by Dalton Cherry on 7/16/14. // Copyright (c) 2014-2016 Dalton Cherry. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ////////////////////////////////////////////////////////////////////////////////////////////////// import Foundation import CoreFoundation import Security public let WebsocketDidConnectNotification = "WebsocketDidConnectNotification" public let WebsocketDidDisconnectNotification = "WebsocketDidDisconnectNotification" public let WebsocketDisconnectionErrorKeyName = "WebsocketDisconnectionErrorKeyName" public protocol WebSocketDelegate: class { func websocketDidConnect(socket: WebSocket) func websocketDidDisconnect(socket: WebSocket, error: NSError?) func websocketDidReceiveMessage(socket: WebSocket, text: String) func websocketDidReceiveData(socket: WebSocket, data: Data) } public protocol WebSocketPongDelegate: class { func websocketDidReceivePong(socket: WebSocket, data: Data?) } open class WebSocket : NSObject, StreamDelegate { enum OpCode : UInt8 { case continueFrame = 0x0 case textFrame = 0x1 case binaryFrame = 0x2 // 3-7 are reserved. case connectionClose = 0x8 case ping = 0x9 case pong = 0xA // B-F reserved. } public enum CloseCode : UInt16 { case normal = 1000 case goingAway = 1001 case protocolError = 1002 case protocolUnhandledType = 1003 // 1004 reserved. case noStatusReceived = 1005 //1006 reserved. case encoding = 1007 case policyViolated = 1008 case messageTooBig = 1009 } public static let ErrorDomain = "WebSocket" enum InternalErrorCode: UInt16 { // 0-999 WebSocket status codes not used case outputStreamWriteError = 1 } // Where the callback is executed. It defaults to the main UI thread queue. public var callbackQueue = DispatchQueue.main var optionalProtocols: [String]? // MARK: - Constants let headerWSUpgradeName = "Upgrade" let headerWSUpgradeValue = "websocket" let headerWSHostName = "Host" let headerWSConnectionName = "Connection" let headerWSConnectionValue = "Upgrade" let headerWSProtocolName = "Sec-WebSocket-Protocol" let headerWSVersionName = "Sec-WebSocket-Version" let headerWSVersionValue = "13" let headerWSKeyName = "Sec-WebSocket-Key" let headerOriginName = "Origin" let headerWSAcceptName = "Sec-WebSocket-Accept" let BUFFER_MAX = 4096 let FinMask: UInt8 = 0x80 let OpCodeMask: UInt8 = 0x0F let RSVMask: UInt8 = 0x70 let MaskMask: UInt8 = 0x80 let PayloadLenMask: UInt8 = 0x7F let MaxFrameSize: Int = 32 let httpSwitchProtocolCode = 101 let supportedSSLSchemes = ["wss", "https"] class WSResponse { var isFin = false var code: OpCode = .continueFrame var bytesLeft = 0 var frameCount = 0 var buffer: NSMutableData? } // MARK: - Delegates /// Responds to callback about new messages coming in over the WebSocket /// and also connection/disconnect messages. public weak var delegate: WebSocketDelegate? /// Receives a callback for each pong message recived. public weak var pongDelegate: WebSocketPongDelegate? // MARK: - Block based API. public var onConnect: ((Void) -> Void)? public var onDisconnect: ((NSError?) -> Void)? public var onText: ((String) -> Void)? public var onData: ((Data) -> Void)? public var onPong: ((Data?) -> Void)? public var headers = [String: String]() public var voipEnabled = false public var disableSSLCertValidation = false public var security: SSLTrustValidator? public var enabledSSLCipherSuites: [SSLCipherSuite]? public var origin: String? public var timeout = 5 public var isConnected: Bool { return connected } public var currentURL: URL { return url } // MARK: - Private private var url: URL private var inputStream: InputStream? private var outputStream: OutputStream? private var connected = false private var isConnecting = false private var writeQueue = OperationQueue() private var readStack = [WSResponse]() private var inputQueue = [Data]() private var fragBuffer: Data? private var certValidated = false private var didDisconnect = false private var readyToWrite = false private let mutex = NSLock() private let notificationCenter = NotificationCenter.default private var canDispatch: Bool { mutex.lock() let canWork = readyToWrite mutex.unlock() return canWork } /// The shared processing queue used for all WebSocket. private static let sharedWorkQueue = DispatchQueue(label: "com.vluxe.starscream.websocket", attributes: []) /// Used for setting protocols. public init(url: URL, protocols: [String]? = nil) { self.url = url self.origin = url.absoluteString if let hostUrl = URL (string: "/", relativeTo: url) { var origin = hostUrl.absoluteString origin.remove(at: origin.index(before: origin.endIndex)) self.origin = origin } writeQueue.maxConcurrentOperationCount = 1 optionalProtocols = protocols } // Used for specifically setting the QOS for the write queue. public convenience init(url: URL, writeQueueQOS: QualityOfService, protocols: [String]? = nil) { self.init(url: url, protocols: protocols) writeQueue.qualityOfService = writeQueueQOS } /** Connect to the WebSocket server on a background thread. */ open func connect() { guard !isConnecting else { return } didDisconnect = false isConnecting = true createHTTPRequest() } /** Disconnect from the server. I send a Close control frame to the server, then expect the server to respond with a Close control frame and close the socket from its end. I notify my delegate once the socket has been closed. If you supply a non-nil `forceTimeout`, I wait at most that long (in seconds) for the server to close the socket. After the timeout expires, I close the socket and notify my delegate. If you supply a zero (or negative) `forceTimeout`, I immediately close the socket (without sending a Close control frame) and notify my delegate. - Parameter forceTimeout: Maximum time to wait for the server to close the socket. - Parameter closeCode: The code to send on disconnect. The default is the normal close code for cleanly disconnecting a webSocket. */ open func disconnect(forceTimeout: TimeInterval? = nil, closeCode: UInt16 = CloseCode.normal.rawValue) { guard isConnected else { return } switch forceTimeout { case .some(let seconds) where seconds > 0: let milliseconds = Int(seconds * 1_000) callbackQueue.asyncAfter(deadline: .now() + .milliseconds(milliseconds)) { [weak self] in self?.disconnectStream(nil) } fallthrough case .none: writeError(closeCode) default: disconnectStream(nil) break } } /** Write a string to the websocket. This sends it as a text frame. If you supply a non-nil completion block, I will perform it when the write completes. - parameter string: The string to write. - parameter completion: The (optional) completion handler. */ open func write(string: String, completion: (() -> ())? = nil) { guard isConnected else { return } dequeueWrite(string.data(using: String.Encoding.utf8)!, code: .textFrame, writeCompletion: completion) } /** Write binary data to the websocket. This sends it as a binary frame. If you supply a non-nil completion block, I will perform it when the write completes. - parameter data: The data to write. - parameter completion: The (optional) completion handler. */ open func write(data: Data, completion: (() -> ())? = nil) { guard isConnected else { return } dequeueWrite(data, code: .binaryFrame, writeCompletion: completion) } /** Write a ping to the websocket. This sends it as a control frame. Yodel a sound to the planet. This sends it as an astroid. http://youtu.be/Eu5ZJELRiJ8?t=42s */ open func write(ping: Data, completion: (() -> ())? = nil) { guard isConnected else { return } dequeueWrite(ping, code: .ping, writeCompletion: completion) } /** Private method that starts the connection. */ private func createHTTPRequest() { let urlRequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, "GET" as CFString, url as CFURL, kCFHTTPVersion1_1).takeRetainedValue() var port = url.port if port == nil { if supportedSSLSchemes.contains(url.scheme!) { port = 443 } else { port = 80 } } addHeader(urlRequest, key: headerWSUpgradeName, val: headerWSUpgradeValue) addHeader(urlRequest, key: headerWSConnectionName, val: headerWSConnectionValue) if let protocols = optionalProtocols { addHeader(urlRequest, key: headerWSProtocolName, val: protocols.joined(separator: ",")) } addHeader(urlRequest, key: headerWSVersionName, val: headerWSVersionValue) addHeader(urlRequest, key: headerWSKeyName, val: generateWebSocketKey()) if let origin = origin { addHeader(urlRequest, key: headerOriginName, val: origin) } addHeader(urlRequest, key: headerWSHostName, val: "\(url.host!):\(port!)") for (key, value) in headers { addHeader(urlRequest, key: key, val: value) } if let cfHTTPMessage = CFHTTPMessageCopySerializedMessage(urlRequest) { let serializedRequest = cfHTTPMessage.takeRetainedValue() initStreamsWithData(serializedRequest as Data, Int(port!)) } } /** Add a header to the CFHTTPMessage by using the NSString bridges to CFString */ private func addHeader(_ urlRequest: CFHTTPMessage, key: String, val: String) { CFHTTPMessageSetHeaderFieldValue(urlRequest, key as CFString, val as CFString) } /** Generate a WebSocket key as needed in RFC. */ private func generateWebSocketKey() -> String { var key = "" let seed = 16 for _ in 0..? var writeStream: Unmanaged? let h = url.host! as NSString CFStreamCreatePairWithSocketToHost(nil, h, UInt32(port), &readStream, &writeStream) inputStream = readStream!.takeRetainedValue() outputStream = writeStream!.takeRetainedValue() guard let inStream = inputStream, let outStream = outputStream else { return } inStream.delegate = self outStream.delegate = self if supportedSSLSchemes.contains(url.scheme!) { certValidated = false inStream.setProperty(StreamSocketSecurityLevel.negotiatedSSL as AnyObject, forKey: Stream.PropertyKey.socketSecurityLevelKey) outStream.setProperty(StreamSocketSecurityLevel.negotiatedSSL as AnyObject, forKey: Stream.PropertyKey.socketSecurityLevelKey) if disableSSLCertValidation { let settings: [NSObject: NSObject] = [kCFStreamSSLValidatesCertificateChain: NSNumber(value: false), kCFStreamSSLPeerName: kCFNull] inStream.setProperty(settings, forKey: kCFStreamPropertySSLSettings as Stream.PropertyKey) outStream.setProperty(settings, forKey: kCFStreamPropertySSLSettings as Stream.PropertyKey) } if let cipherSuites = self.enabledSSLCipherSuites { if let sslContextIn = CFReadStreamCopyProperty(inputStream, CFStreamPropertyKey(rawValue: kCFStreamPropertySSLContext)) as! SSLContext?, let sslContextOut = CFWriteStreamCopyProperty(outputStream, CFStreamPropertyKey(rawValue: kCFStreamPropertySSLContext)) as! SSLContext? { let resIn = SSLSetEnabledCiphers(sslContextIn, cipherSuites, cipherSuites.count) let resOut = SSLSetEnabledCiphers(sslContextOut, cipherSuites, cipherSuites.count) if resIn != errSecSuccess { let error = self.errorWithDetail("Error setting ingoing cypher suites", code: UInt16(resIn)) disconnectStream(error) return } if resOut != errSecSuccess { let error = self.errorWithDetail("Error setting outgoing cypher suites", code: UInt16(resOut)) disconnectStream(error) return } } } } else { certValidated = true //not a https session, so no need to check SSL pinning } if voipEnabled { inStream.setProperty(StreamNetworkServiceTypeValue.voIP as AnyObject, forKey: Stream.PropertyKey.networkServiceType) outStream.setProperty(StreamNetworkServiceTypeValue.voIP as AnyObject, forKey: Stream.PropertyKey.networkServiceType) } CFReadStreamSetDispatchQueue(inStream, WebSocket.sharedWorkQueue) CFWriteStreamSetDispatchQueue(outStream, WebSocket.sharedWorkQueue) inStream.open() outStream.open() self.mutex.lock() self.readyToWrite = true self.mutex.unlock() let bytes = UnsafeRawPointer((data as NSData).bytes).assumingMemoryBound(to: UInt8.self) var out = timeout * 1_000_000 // wait 5 seconds before giving up let operation = BlockOperation() operation.addExecutionBlock { [weak self, weak operation] in guard let sOperation = operation else { return } while !outStream.hasSpaceAvailable && !sOperation.isCancelled { usleep(100) // wait until the socket is ready guard !sOperation.isCancelled else { return } out -= 100 if out < 0 { WebSocket.sharedWorkQueue.async { self?.cleanupStream() } self?.doDisconnect(self?.errorWithDetail("write wait timed out", code: 2)) return } else if outStream.streamError != nil { return // disconnectStream will be called. } } guard !sOperation.isCancelled, let s = self else { return } // Do the pinning now if needed if let sec = s.security, !s.certValidated { let trust = outStream.property(forKey: kCFStreamPropertySSLPeerTrust as Stream.PropertyKey) as! SecTrust let domain = outStream.property(forKey: kCFStreamSSLPeerName as Stream.PropertyKey) as? String s.certValidated = sec.isValid(trust, domain: domain) if !s.certValidated { WebSocket.sharedWorkQueue.async { let error = s.errorWithDetail("Invalid SSL certificate", code: 1) s.disconnectStream(error) } return } } outStream.write(bytes, maxLength: data.count) } writeQueue.addOperation(operation) } /** Delegate for the stream methods. Processes incoming bytes */ open func stream(_ aStream: Stream, handle eventCode: Stream.Event) { if eventCode == .hasBytesAvailable { if aStream == inputStream { processInputStream() } } else if eventCode == .errorOccurred { disconnectStream(aStream.streamError as NSError?) } else if eventCode == .endEncountered { disconnectStream(nil) } } /** Disconnect the stream object and notifies the delegate. */ private func disconnectStream(_ error: NSError?, runDelegate: Bool = true) { if error == nil { writeQueue.waitUntilAllOperationsAreFinished() } else { writeQueue.cancelAllOperations() } cleanupStream() connected = false if runDelegate { doDisconnect(error) } } /** cleanup the streams. */ private func cleanupStream() { outputStream?.delegate = nil inputStream?.delegate = nil if let stream = inputStream { CFReadStreamSetDispatchQueue(stream, nil) stream.close() } if let stream = outputStream { CFWriteStreamSetDispatchQueue(stream, nil) stream.close() } outputStream = nil inputStream = nil fragBuffer = nil } /** Handles the incoming bytes and sending them to the proper processing method. */ private func processInputStream() { let buf = NSMutableData(capacity: BUFFER_MAX) let buffer = UnsafeMutableRawPointer(mutating: buf!.bytes).assumingMemoryBound(to: UInt8.self) let length = inputStream!.read(buffer, maxLength: BUFFER_MAX) guard length > 0 else { return } var process = false if inputQueue.count == 0 { process = true } inputQueue.append(Data(bytes: buffer, count: length)) if process { dequeueInput() } } /** Dequeue the incoming input so it is processed in order. */ private func dequeueInput() { while !inputQueue.isEmpty { autoreleasepool { let data = inputQueue[0] var work = data if let buffer = fragBuffer { var combine = NSData(data: buffer) as Data combine.append(data) work = combine fragBuffer = nil } let buffer = UnsafeRawPointer((work as NSData).bytes).assumingMemoryBound(to: UInt8.self) let length = work.count if !connected { processTCPHandshake(buffer, bufferLen: length) } else { processRawMessagesInBuffer(buffer, bufferLen: length) } inputQueue = inputQueue.filter{ $0 != data } } } } /** Handle checking the inital connection status */ private func processTCPHandshake(_ buffer: UnsafePointer, bufferLen: Int) { let code = processHTTP(buffer, bufferLen: bufferLen) switch code { case 0: break case -1: fragBuffer = Data(bytes: buffer, count: bufferLen) break // do nothing, we are going to collect more data default: doDisconnect(errorWithDetail("Invalid HTTP upgrade", code: UInt16(code))) } } /** Finds the HTTP Packet in the TCP stream, by looking for the CRLF. */ private func processHTTP(_ buffer: UnsafePointer, bufferLen: Int) -> Int { let CRLFBytes = [UInt8(ascii: "\r"), UInt8(ascii: "\n"), UInt8(ascii: "\r"), UInt8(ascii: "\n")] var k = 0 var totalSize = 0 for i in 0.. 0 { let code = validateResponse(buffer, bufferLen: totalSize) if code != 0 { return code } isConnecting = false connected = true didDisconnect = false if canDispatch { callbackQueue.async { [weak self] in guard let s = self else { return } s.onConnect?() s.delegate?.websocketDidConnect(socket: s) s.notificationCenter.post(name: NSNotification.Name(WebsocketDidConnectNotification), object: self) } } totalSize += 1 //skip the last \n let restSize = bufferLen - totalSize if restSize > 0 { processRawMessagesInBuffer(buffer + totalSize, bufferLen: restSize) } return 0 //success } return -1 // Was unable to find the full TCP header. } /** Validates the HTTP is a 101 as per the RFC spec. */ private func validateResponse(_ buffer: UnsafePointer, bufferLen: Int) -> Int { let response = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, false).takeRetainedValue() CFHTTPMessageAppendBytes(response, buffer, bufferLen) let code = CFHTTPMessageGetResponseStatusCode(response) if code != httpSwitchProtocolCode { return code } if let cfHeaders = CFHTTPMessageCopyAllHeaderFields(response) { let headers = cfHeaders.takeRetainedValue() as NSDictionary if let acceptKey = headers[headerWSAcceptName as NSString] as? NSString { if acceptKey.length > 0 { return 0 } } } return -1 } /** Read a 16 bit big endian value from a buffer */ private static func readUint16(_ buffer: UnsafePointer, offset: Int) -> UInt16 { return (UInt16(buffer[offset + 0]) << 8) | UInt16(buffer[offset + 1]) } /** Read a 64 bit big endian value from a buffer */ private static func readUint64(_ buffer: UnsafePointer, offset: Int) -> UInt64 { var value = UInt64(0) for i in 0...7 { value = (value << 8) | UInt64(buffer[offset + i]) } return value } /** Write a 16-bit big endian value to a buffer. */ private static func writeUint16(_ buffer: UnsafeMutablePointer, offset: Int, value: UInt16) { buffer[offset + 0] = UInt8(value >> 8) buffer[offset + 1] = UInt8(value & 0xff) } /** Write a 64-bit big endian value to a buffer. */ private static func writeUint64(_ buffer: UnsafeMutablePointer, offset: Int, value: UInt64) { for i in 0...7 { buffer[offset + i] = UInt8((value >> (8*UInt64(7 - i))) & 0xff) } } /** Process one message at the start of `buffer`. Return another buffer (sharing storage) that contains the leftover contents of `buffer` that I didn't process. */ private func processOneRawMessage(inBuffer buffer: UnsafeBufferPointer) -> UnsafeBufferPointer { let response = readStack.last guard let baseAddress = buffer.baseAddress else {return emptyBuffer} let bufferLen = buffer.count if response != nil && bufferLen < 2 { fragBuffer = Data(buffer: buffer) return emptyBuffer } if let response = response, response.bytesLeft > 0 { var len = response.bytesLeft var extra = bufferLen - response.bytesLeft if response.bytesLeft > bufferLen { len = bufferLen extra = 0 } response.bytesLeft -= len response.buffer?.append(Data(bytes: baseAddress, count: len)) _ = processResponse(response) return buffer.fromOffset(bufferLen - extra) } else { let isFin = (FinMask & baseAddress[0]) let receivedOpcodeRawValue = (OpCodeMask & baseAddress[0]) let receivedOpcode = OpCode(rawValue: receivedOpcodeRawValue) let isMasked = (MaskMask & baseAddress[1]) let payloadLen = (PayloadLenMask & baseAddress[1]) var offset = 2 if (isMasked > 0 || (RSVMask & baseAddress[0]) > 0) && receivedOpcode != .pong { let errCode = CloseCode.protocolError.rawValue doDisconnect(errorWithDetail("masked and rsv data is not currently supported", code: errCode)) writeError(errCode) return emptyBuffer } let isControlFrame = (receivedOpcode == .connectionClose || receivedOpcode == .ping) if !isControlFrame && (receivedOpcode != .binaryFrame && receivedOpcode != .continueFrame && receivedOpcode != .textFrame && receivedOpcode != .pong) { let errCode = CloseCode.protocolError.rawValue doDisconnect(errorWithDetail("unknown opcode: \(receivedOpcodeRawValue)", code: errCode)) writeError(errCode) return emptyBuffer } if isControlFrame && isFin == 0 { let errCode = CloseCode.protocolError.rawValue doDisconnect(errorWithDetail("control frames can't be fragmented", code: errCode)) writeError(errCode) return emptyBuffer } var closeCode = CloseCode.normal.rawValue if receivedOpcode == .connectionClose { if payloadLen == 1 { closeCode = CloseCode.protocolError.rawValue } else if payloadLen > 1 { closeCode = WebSocket.readUint16(baseAddress, offset: offset) if closeCode < 1000 || (closeCode > 1003 && closeCode < 1007) || (closeCode > 1011 && closeCode < 3000) { closeCode = CloseCode.protocolError.rawValue } } if payloadLen < 2 { doDisconnect(errorWithDetail("connection closed by server", code: closeCode)) writeError(closeCode) return emptyBuffer } } else if isControlFrame && payloadLen > 125 { writeError(CloseCode.protocolError.rawValue) return emptyBuffer } var dataLength = UInt64(payloadLen) if dataLength == 127 { dataLength = WebSocket.readUint64(baseAddress, offset: offset) offset += MemoryLayout.size } else if dataLength == 126 { dataLength = UInt64(WebSocket.readUint16(baseAddress, offset: offset)) offset += MemoryLayout.size } if bufferLen < offset || UInt64(bufferLen - offset) < dataLength { fragBuffer = Data(bytes: baseAddress, count: bufferLen) return emptyBuffer } var len = dataLength if dataLength > UInt64(bufferLen) { len = UInt64(bufferLen-offset) } if receivedOpcode == .connectionClose && len > 0 { let size = MemoryLayout.size offset += size len -= UInt64(size) } let data = Data(bytes: baseAddress+offset, count: Int(len)) if receivedOpcode == .connectionClose { var closeReason = "connection closed by server" if let customCloseReason = String(data: data, encoding: .utf8) { closeReason = customCloseReason } else { closeCode = CloseCode.protocolError.rawValue } doDisconnect(errorWithDetail(closeReason, code: closeCode)) writeError(closeCode) return emptyBuffer } if receivedOpcode == .pong { if canDispatch { callbackQueue.async { [weak self] in guard let s = self else { return } let pongData: Data? = data.count > 0 ? data : nil s.onPong?(pongData) s.pongDelegate?.websocketDidReceivePong(socket: s, data: pongData) } } return buffer.fromOffset(offset + Int(len)) } var response = readStack.last if isControlFrame { response = nil // Don't append pings. } if isFin == 0 && receivedOpcode == .continueFrame && response == nil { let errCode = CloseCode.protocolError.rawValue doDisconnect(errorWithDetail("continue frame before a binary or text frame", code: errCode)) writeError(errCode) return emptyBuffer } var isNew = false if response == nil { if receivedOpcode == .continueFrame { let errCode = CloseCode.protocolError.rawValue doDisconnect(errorWithDetail("first frame can't be a continue frame", code: errCode)) writeError(errCode) return emptyBuffer } isNew = true response = WSResponse() response!.code = receivedOpcode! response!.bytesLeft = Int(dataLength) response!.buffer = NSMutableData(data: data) } else { if receivedOpcode == .continueFrame { response!.bytesLeft = Int(dataLength) } else { let errCode = CloseCode.protocolError.rawValue doDisconnect(errorWithDetail("second and beyond of fragment message must be a continue frame", code: errCode)) writeError(errCode) return emptyBuffer } response!.buffer!.append(data) } if let response = response { response.bytesLeft -= Int(len) response.frameCount += 1 response.isFin = isFin > 0 ? true : false if isNew { readStack.append(response) } _ = processResponse(response) } let step = Int(offset + numericCast(len)) return buffer.fromOffset(step) } } /** Process all messages in the buffer if possible. */ private func processRawMessagesInBuffer(_ pointer: UnsafePointer, bufferLen: Int) { var buffer = UnsafeBufferPointer(start: pointer, count: bufferLen) repeat { buffer = processOneRawMessage(inBuffer: buffer) } while buffer.count >= 2 if buffer.count > 0 { fragBuffer = Data(buffer: buffer) } } /** Process the finished response of a buffer. */ private func processResponse(_ response: WSResponse) -> Bool { if response.isFin && response.bytesLeft <= 0 { if response.code == .ping { let data = response.buffer! // local copy so it is perverse for writing dequeueWrite(data as Data, code: .pong) } else if response.code == .textFrame { let str: NSString? = NSString(data: response.buffer! as Data, encoding: String.Encoding.utf8.rawValue) if str == nil { writeError(CloseCode.encoding.rawValue) return false } if canDispatch { callbackQueue.async { [weak self] in guard let s = self else { return } s.onText?(str! as String) s.delegate?.websocketDidReceiveMessage(socket: s, text: str! as String) } } } else if response.code == .binaryFrame { if canDispatch { let data = response.buffer! // local copy so it is perverse for writing callbackQueue.async { [weak self] in guard let s = self else { return } s.onData?(data as Data) s.delegate?.websocketDidReceiveData(socket: s, data: data as Data) } } } readStack.removeLast() return true } return false } /** Create an error */ private func errorWithDetail(_ detail: String, code: UInt16) -> NSError { var details = [String: String]() details[NSLocalizedDescriptionKey] = detail return NSError(domain: WebSocket.ErrorDomain, code: Int(code), userInfo: details) } /** Write an error to the socket */ private func writeError(_ code: UInt16) { let buf = NSMutableData(capacity: MemoryLayout.size) let buffer = UnsafeMutableRawPointer(mutating: buf!.bytes).assumingMemoryBound(to: UInt8.self) WebSocket.writeUint16(buffer, offset: 0, value: code) dequeueWrite(Data(bytes: buffer, count: MemoryLayout.size), code: .connectionClose) } /** Used to write things to the stream */ private func dequeueWrite(_ data: Data, code: OpCode, writeCompletion: (() -> ())? = nil) { let operation = BlockOperation() operation.addExecutionBlock { [weak self, weak operation] in //stream isn't ready, let's wait guard let s = self else { return } guard let sOperation = operation else { return } var offset = 2 let dataLength = data.count let frame = NSMutableData(capacity: dataLength + s.MaxFrameSize) let buffer = UnsafeMutableRawPointer(frame!.mutableBytes).assumingMemoryBound(to: UInt8.self) buffer[0] = s.FinMask | code.rawValue if dataLength < 126 { buffer[1] = CUnsignedChar(dataLength) } else if dataLength <= Int(UInt16.max) { buffer[1] = 126 WebSocket.writeUint16(buffer, offset: offset, value: UInt16(dataLength)) offset += MemoryLayout.size } else { buffer[1] = 127 WebSocket.writeUint64(buffer, offset: offset, value: UInt64(dataLength)) offset += MemoryLayout.size } buffer[1] |= s.MaskMask let maskKey = UnsafeMutablePointer(buffer + offset) _ = SecRandomCopyBytes(kSecRandomDefault, Int(MemoryLayout.size), maskKey) offset += MemoryLayout.size for i in 0...size] offset += 1 } var total = 0 while !sOperation.isCancelled { guard let outStream = s.outputStream else { break } let writeBuffer = UnsafeRawPointer(frame!.bytes+total).assumingMemoryBound(to: UInt8.self) let len = outStream.write(writeBuffer, maxLength: offset-total) if len < 0 { var error: Error? if let streamError = outStream.streamError { error = streamError } else { let errCode = InternalErrorCode.outputStreamWriteError.rawValue error = s.errorWithDetail("output stream error during write", code: errCode) } s.doDisconnect(error as NSError?) break } else { total += len } if total >= offset { if let queue = self?.callbackQueue, let callback = writeCompletion { queue.async { callback() } } break } } } writeQueue.addOperation(operation) } /** Used to preform the disconnect delegate */ private func doDisconnect(_ error: NSError?) { guard !didDisconnect else { return } didDisconnect = true isConnecting = false connected = false guard canDispatch else {return} callbackQueue.async { [weak self] in guard let s = self else { return } s.onDisconnect?(error) s.delegate?.websocketDidDisconnect(socket: s, error: error) let userInfo = error.map{ [WebsocketDisconnectionErrorKeyName: $0] } s.notificationCenter.post(name: NSNotification.Name(WebsocketDidDisconnectNotification), object: self, userInfo: userInfo) } } // MARK: - Deinit deinit { mutex.lock() readyToWrite = false mutex.unlock() cleanupStream() writeQueue.cancelAllOperations() } } private extension Data { init(buffer: UnsafeBufferPointer) { self.init(bytes: buffer.baseAddress!, count: buffer.count) } } private extension UnsafeBufferPointer { func fromOffset(_ offset: Int) -> UnsafeBufferPointer { return UnsafeBufferPointer(start: baseAddress?.advanced(by: offset), count: count - offset) } } private let emptyBuffer = UnsafeBufferPointer(start: nil, count: 0) ================================================ FILE: ArcBit/Images.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "size" : "20x20", "scale" : "2x" }, { "idiom" : "iphone", "size" : "20x20", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "58X58.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "87x87me.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "80X80.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "120X120.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "120X120-1.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "180X180.png", "scale" : "3x" }, { "size" : "1024x1024", "idiom" : "ios-marketing", "filename" : "1024X1024.png", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: ArcBit/Images.xcassets/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: ArcBit/Images.xcassets/home3.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "home3@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "home3@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: ArcBit/Images.xcassets/lifebuoy.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "lifebuoy@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "lifebuoy@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: ArcBit/Images.xcassets/link.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "link@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "link@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: ArcBit/Images.xcassets/twitter.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "twitter@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "twitter@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: ArcBit/Images.xcassets/vault.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "vault.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "vault@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "vault@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: ArcBit/InAppSettings.bundle/Advanced.plist ================================================ Title ADVANCED_TITLE StringsTable Root PreferenceSpecifiers Title EMPTY Type PSGroupSpecifier FooterText ENABLE_COLD_WALLET_FOOTER DefaultValue NO Key enablecoldwallet Title ENABLE_COLD_WALLET Type PSToggleSwitchSpecifier Title EMPTY Type PSGroupSpecifier FooterText ENABLE_ADVANCE_MODE_FOOTER DefaultValue NO Key enableadvancemode Title ENABLE_ADVANCE_MODE Type PSToggleSwitchSpecifier Title EMPTY Type PSGroupSpecifier DefaultValue 0 Key blockexplorerapi Title BLOCKCHAIN_API_TYPE Titles BLOCKCHAIN_API_TYPE_1 BLOCKCHAIN_API_TYPE_2 Type PSMultiValueSpecifier Values 0 1 Key blockexplorerurl Type PSTitleValueSpecifier IASKTextAlignment IASKUITextAlignmentCenter Title CHANGE_BLOCKEXPLORER_URL Key setblockexplorerurl Type IASKButtonSpecifier Title EMPTY Type PSGroupSpecifier FooterText ENABLE_STEALTH_ADDRESS_DEFAULT_FOOTER Key stealthaddressfooter DefaultValue NO Key stealthaddressdefault Title ENABLE_STEALTH_ADDRESS_DEFAULT Type PSToggleSwitchSpecifier Title EMPTY Type PSGroupSpecifier FooterText ENABLE_CAN_RESTORE_DELETED_APP_FOOTER DefaultValue NO Key canrestoredeletedapp Title ENABLE_CAN_RESTORE_DELETED_APP Type PSToggleSwitchSpecifier ================================================ FILE: ArcBit/InAppSettings.bundle/Root.plist ================================================ PreferenceSpecifiers Title EMPTY Type PSGroupSpecifier DefaultValue NO Key enablepincode Title ENABLE_PIN_CODE Type PSToggleSwitchSpecifier Title CHANGE_PIN_CODE Key changepincode Type IASKButtonSpecifier Title EMPTY Type PSGroupSpecifier DefaultValue NO Key displaylocalcurrency Title DISPLAY_LOCAL_CURRENCY Type PSToggleSwitchSpecifier DefaultValue 0 Key currency Title DEFAULT_CURRENCY Titles DEFAULT_CURRENCY_1 DEFAULT_CURRENCY_2 DEFAULT_CURRENCY_3 DEFAULT_CURRENCY_4 DEFAULT_CURRENCY_5 DEFAULT_CURRENCY_6 DEFAULT_CURRENCY_7 DEFAULT_CURRENCY_8 DEFAULT_CURRENCY_9 DEFAULT_CURRENCY_10 DEFAULT_CURRENCY_11 DEFAULT_CURRENCY_12 DEFAULT_CURRENCY_13 DEFAULT_CURRENCY_14 DEFAULT_CURRENCY_15 DEFAULT_CURRENCY_16 DEFAULT_CURRENCY_17 DEFAULT_CURRENCY_18 DEFAULT_CURRENCY_19 DEFAULT_CURRENCY_20 DEFAULT_CURRENCY_21 DEFAULT_CURRENCY_22 DEFAULT_CURRENCY_23 DEFAULT_CURRENCY_24 DEFAULT_CURRENCY_25 DEFAULT_CURRENCY_26 DEFAULT_CURRENCY_27 DEFAULT_CURRENCY_28 DEFAULT_CURRENCY_29 DEFAULT_CURRENCY_30 DEFAULT_CURRENCY_31 DEFAULT_CURRENCY_32 DEFAULT_CURRENCY_33 DEFAULT_CURRENCY_34 DEFAULT_CURRENCY_35 DEFAULT_CURRENCY_36 DEFAULT_CURRENCY_37 DEFAULT_CURRENCY_38 DEFAULT_CURRENCY_39 DEFAULT_CURRENCY_40 DEFAULT_CURRENCY_41 DEFAULT_CURRENCY_42 DEFAULT_CURRENCY_43 DEFAULT_CURRENCY_44 DEFAULT_CURRENCY_45 DEFAULT_CURRENCY_46 DEFAULT_CURRENCY_47 DEFAULT_CURRENCY_48 DEFAULT_CURRENCY_49 DEFAULT_CURRENCY_50 DEFAULT_CURRENCY_51 DEFAULT_CURRENCY_52 DEFAULT_CURRENCY_53 DEFAULT_CURRENCY_54 DEFAULT_CURRENCY_55 DEFAULT_CURRENCY_56 DEFAULT_CURRENCY_57 DEFAULT_CURRENCY_58 DEFAULT_CURRENCY_59 DEFAULT_CURRENCY_60 DEFAULT_CURRENCY_61 DEFAULT_CURRENCY_62 DEFAULT_CURRENCY_63 DEFAULT_CURRENCY_64 DEFAULT_CURRENCY_65 DEFAULT_CURRENCY_66 DEFAULT_CURRENCY_67 DEFAULT_CURRENCY_68 DEFAULT_CURRENCY_69 DEFAULT_CURRENCY_70 DEFAULT_CURRENCY_71 DEFAULT_CURRENCY_72 DEFAULT_CURRENCY_73 DEFAULT_CURRENCY_74 DEFAULT_CURRENCY_75 DEFAULT_CURRENCY_76 DEFAULT_CURRENCY_77 DEFAULT_CURRENCY_78 DEFAULT_CURRENCY_79 DEFAULT_CURRENCY_80 DEFAULT_CURRENCY_81 DEFAULT_CURRENCY_82 DEFAULT_CURRENCY_83 DEFAULT_CURRENCY_84 DEFAULT_CURRENCY_85 DEFAULT_CURRENCY_86 DEFAULT_CURRENCY_87 DEFAULT_CURRENCY_88 DEFAULT_CURRENCY_89 DEFAULT_CURRENCY_90 DEFAULT_CURRENCY_91 DEFAULT_CURRENCY_92 DEFAULT_CURRENCY_93 DEFAULT_CURRENCY_94 DEFAULT_CURRENCY_95 DEFAULT_CURRENCY_96 DEFAULT_CURRENCY_97 DEFAULT_CURRENCY_98 DEFAULT_CURRENCY_99 DEFAULT_CURRENCY_100 DEFAULT_CURRENCY_101 DEFAULT_CURRENCY_102 DEFAULT_CURRENCY_103 DEFAULT_CURRENCY_104 DEFAULT_CURRENCY_105 DEFAULT_CURRENCY_106 DEFAULT_CURRENCY_107 DEFAULT_CURRENCY_108 DEFAULT_CURRENCY_109 DEFAULT_CURRENCY_110 DEFAULT_CURRENCY_111 DEFAULT_CURRENCY_112 DEFAULT_CURRENCY_113 DEFAULT_CURRENCY_114 DEFAULT_CURRENCY_115 DEFAULT_CURRENCY_116 DEFAULT_CURRENCY_117 DEFAULT_CURRENCY_118 DEFAULT_CURRENCY_119 DEFAULT_CURRENCY_120 DEFAULT_CURRENCY_121 DEFAULT_CURRENCY_122 DEFAULT_CURRENCY_123 DEFAULT_CURRENCY_124 DEFAULT_CURRENCY_125 DEFAULT_CURRENCY_126 DEFAULT_CURRENCY_127 DEFAULT_CURRENCY_128 DEFAULT_CURRENCY_129 DEFAULT_CURRENCY_130 DEFAULT_CURRENCY_131 DEFAULT_CURRENCY_132 DEFAULT_CURRENCY_133 DEFAULT_CURRENCY_134 DEFAULT_CURRENCY_135 DEFAULT_CURRENCY_136 DEFAULT_CURRENCY_137 DEFAULT_CURRENCY_138 DEFAULT_CURRENCY_139 DEFAULT_CURRENCY_140 DEFAULT_CURRENCY_141 DEFAULT_CURRENCY_142 DEFAULT_CURRENCY_143 DEFAULT_CURRENCY_144 DEFAULT_CURRENCY_145 DEFAULT_CURRENCY_146 DEFAULT_CURRENCY_147 DEFAULT_CURRENCY_148 DEFAULT_CURRENCY_149 DEFAULT_CURRENCY_150 DEFAULT_CURRENCY_151 DEFAULT_CURRENCY_152 DEFAULT_CURRENCY_153 DEFAULT_CURRENCY_154 DEFAULT_CURRENCY_155 DEFAULT_CURRENCY_156 DEFAULT_CURRENCY_157 DEFAULT_CURRENCY_158 Type PSMultiValueSpecifier Values 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 DefaultValue 0 Key bitcoindisplay Title BITCOIN_DISPLAY Titles BITCOIN_DISPLAY_1 BITCOIN_DISPLAY_2 BITCOIN_DISPLAY_3 Type PSMultiValueSpecifier Values 0 1 2 Title EMPTY Type PSGroupSpecifier DefaultValue NO Key enabledynamicfee Title ENABLE_DYNAMIC_FEE Type PSToggleSwitchSpecifier Key transactionfee Type PSTitleValueSpecifier IASKTextAlignment IASKUITextAlignmentCenter Title SET_FIXED_TRANSACTION_FEE Key settransactionfee Type IASKButtonSpecifier DefaultValue 0 Key dynamicfeeoption Title CONFIRMATION_TIME Titles DYNAMIC_FEE_OPTION_1 DYNAMIC_FEE_OPTION_2 DYNAMIC_FEE_OPTION_3 Type PSMultiValueSpecifier Values 0 1 2 Title EMPTY Type PSGroupSpecifier Title SHOW_PASSPHRASE Key showpassphrase Type IASKButtonSpecifier Title EMPTY Type PSGroupSpecifier Title RESTORE_WALLET Key restorewallet Type IASKButtonSpecifier Title EMPTY Type PSGroupSpecifier Type PSChildPaneSpecifier Title ADVANCED_TITLE File Advanced StringsTable Root Title WhereTo ================================================ FILE: ArcBit/InAppSettings.bundle/de.lproj/Root.strings ================================================ "DEFAULT_CURRENCY" = "Währung"; "DEFAULT_CURRENCY_1" = "$ - AUD"; "DEFAULT_CURRENCY_2" = "R$ - BRL"; "DEFAULT_CURRENCY_3" = "$ - CAD"; "DEFAULT_CURRENCY_4" = "CHF - CHF"; "DEFAULT_CURRENCY_5" = "$ - CLP"; "DEFAULT_CURRENCY_6" = "¥ - CNY"; "DEFAULT_CURRENCY_7" = "kr - DKK"; "DEFAULT_CURRENCY_8" = "€ - EUR"; "DEFAULT_CURRENCY_9" = "£ - GBP"; "DEFAULT_CURRENCY_10" = "$ - HKD"; "DEFAULT_CURRENCY_11" = "kr - ISK"; "DEFAULT_CURRENCY_12" = "¥ - JPY"; "DEFAULT_CURRENCY_13" = "₩ - KRW"; "DEFAULT_CURRENCY_14" = "$ - NZD"; "DEFAULT_CURRENCY_15" = "zł - PLN"; "DEFAULT_CURRENCY_16" = "RUB - RUB"; "DEFAULT_CURRENCY_17" = "kr - SEK"; "DEFAULT_CURRENCY_18" = "$ - SGD"; "DEFAULT_CURRENCY_19" = "฿ - THB"; "DEFAULT_CURRENCY_20" = "$ - TWD"; "DEFAULT_CURRENCY_21" = "$ - USD"; "DEFAULT_CURRENCY_22" = "UAE Dirham - AED"; "DEFAULT_CURRENCY_23" = "Afghan Afghani - AFN"; "DEFAULT_CURRENCY_24" = "Albanian Lek - ALL"; "DEFAULT_CURRENCY_25" = "Armenian Dram - AMD"; "DEFAULT_CURRENCY_26" = "Netherlands Antillean Guilder - ANG"; "DEFAULT_CURRENCY_27" = "Angolan Kwanza - AOA"; "DEFAULT_CURRENCY_28" = "Argentine Peso - ARS"; "DEFAULT_CURRENCY_29" = "Aruban Florin - AWG"; "DEFAULT_CURRENCY_30" = "Azerbaijani Manat - AZN"; "DEFAULT_CURRENCY_31" = "Bosnia-Herzegovina Convertible Mark - BAM"; "DEFAULT_CURRENCY_32" = "Barbadian Dollar - BBD"; "DEFAULT_CURRENCY_33" = "Bangladeshi Taka - BDT"; "DEFAULT_CURRENCY_34" = "Bulgarian Lev - BGN"; "DEFAULT_CURRENCY_35" = "Bahraini Dinar - BHD"; "DEFAULT_CURRENCY_36" = "Burundian Franc - BIF"; "DEFAULT_CURRENCY_37" = "Bermudan Dollar - BMD"; "DEFAULT_CURRENCY_38" = "Brunei Dollar - BND"; "DEFAULT_CURRENCY_39" = "Bolivian Boliviano - BOB"; "DEFAULT_CURRENCY_40" = "Bahamian Dollar - BSD"; "DEFAULT_CURRENCY_41" = "Bhutanese Ngultrum - BTN"; "DEFAULT_CURRENCY_42" = "Botswanan Pula - BWP"; "DEFAULT_CURRENCY_43" = "Belarusian Ruble - BYR"; "DEFAULT_CURRENCY_44" = "Belize Dollar - BZD"; "DEFAULT_CURRENCY_45" = "Congolese Franc - CDF"; "DEFAULT_CURRENCY_46" = "Chilean Unit of Account (UF) - CLF"; "DEFAULT_CURRENCY_47" = "Colombian Peso - COP"; "DEFAULT_CURRENCY_48" = "Costa Rican Colón - CRC"; "DEFAULT_CURRENCY_49" = "Cape Verdean Escudo - CVE"; "DEFAULT_CURRENCY_50" = "Czech Koruna - CZK"; "DEFAULT_CURRENCY_51" = "Djiboutian Franc - DJF"; "DEFAULT_CURRENCY_52" = "Dominican Peso - DOP"; "DEFAULT_CURRENCY_53" = "Algerian Dinar - DZD"; "DEFAULT_CURRENCY_54" = "Estonian Kroon - EEK"; "DEFAULT_CURRENCY_55" = "Egyptian Pound - EGP"; "DEFAULT_CURRENCY_56" = "Ethiopian Birr - ETB"; "DEFAULT_CURRENCY_57" = "Fijian Dollar - FJD"; "DEFAULT_CURRENCY_58" = "Falkland Islands Pound - FKP"; "DEFAULT_CURRENCY_59" = "Georgian Lari - GEL"; "DEFAULT_CURRENCY_60" = "Ghanaian Cedi - GHS"; "DEFAULT_CURRENCY_61" = "Gibraltar Pound - GIP"; "DEFAULT_CURRENCY_62" = "Gambian Dalasi - GMD"; "DEFAULT_CURRENCY_63" = "Guinean Franc - GNF"; "DEFAULT_CURRENCY_64" = "Guatemalan Quetzal - GTQ"; "DEFAULT_CURRENCY_65" = "Guyanaese Dollar - GYD"; "DEFAULT_CURRENCY_66" = "Honduran Lempira - HNL"; "DEFAULT_CURRENCY_67" = "Croatian Kuna - HRK"; "DEFAULT_CURRENCY_68" = "Haitian Gourde - HTG"; "DEFAULT_CURRENCY_69" = "Hungarian Forint - HUF"; "DEFAULT_CURRENCY_70" = "Indonesian Rupiah - IDR"; "DEFAULT_CURRENCY_71" = "Israeli Shekel - ILS"; "DEFAULT_CURRENCY_72" = "Indian Rupee - INR"; "DEFAULT_CURRENCY_73" = "Iraqi Dinar - IQD"; "DEFAULT_CURRENCY_74" = "Jersey Pound - JEP"; "DEFAULT_CURRENCY_75" = "Jamaican Dollar - JMD"; "DEFAULT_CURRENCY_76" = "Jordanian Dinar - JOD"; "DEFAULT_CURRENCY_77" = "Kenyan Shilling - KES"; "DEFAULT_CURRENCY_78" = "Kyrgystani Som - KGS"; "DEFAULT_CURRENCY_79" = "Cambodian Riel - KHR"; "DEFAULT_CURRENCY_80" = "Comorian Franc - KMF"; "DEFAULT_CURRENCY_81" = "Kuwaiti Dinar - KWD"; "DEFAULT_CURRENCY_82" = "Cayman Islands Dollar - KYD"; "DEFAULT_CURRENCY_83" = "Kazakhstani Tenge - KZT"; "DEFAULT_CURRENCY_84" = "Laotian Kip - LAK"; "DEFAULT_CURRENCY_85" = "Lebanese Pound - LBP"; "DEFAULT_CURRENCY_86" = "Sri Lankan Rupee - LKR"; "DEFAULT_CURRENCY_87" = "Liberian Dollar - LRD"; "DEFAULT_CURRENCY_88" = "Lesotho Loti - LSL"; "DEFAULT_CURRENCY_89" = "Lithuanian Litas - LTL"; "DEFAULT_CURRENCY_90" = "Latvian Lats - LVL"; "DEFAULT_CURRENCY_91" = "Libyan Dinar - LYD"; "DEFAULT_CURRENCY_92" = "Moroccan Dirham - MAD"; "DEFAULT_CURRENCY_93" = "Moldovan Leu - MDL"; "DEFAULT_CURRENCY_94" = "Malagasy Ariary - MGA"; "DEFAULT_CURRENCY_95" = "Macedonian Denar - MKD"; "DEFAULT_CURRENCY_96" = "Myanma Kyat - MMK"; "DEFAULT_CURRENCY_97" = "Mongolian Tugrik - MNT"; "DEFAULT_CURRENCY_98" = "Macanese Pataca - MOP"; "DEFAULT_CURRENCY_99" = "Mauritanian Ouguiya - MRO"; "DEFAULT_CURRENCY_100" = "Mauritian Rupee - MUR"; "DEFAULT_CURRENCY_101" = "Maldivian Rufiyaa - MVR"; "DEFAULT_CURRENCY_102" = "Malawian Kwacha - MWK"; "DEFAULT_CURRENCY_103" = "Mexican Peso - MXN"; "DEFAULT_CURRENCY_104" = "Malaysian Ringgit - MYR"; "DEFAULT_CURRENCY_105" = "Mozambican Metical - MZN"; "DEFAULT_CURRENCY_106" = "Namibian Dollar - NAD"; "DEFAULT_CURRENCY_107" = "Nigerian Naira - NGN"; "DEFAULT_CURRENCY_108" = "Nicaraguan Córdoba - NIO"; "DEFAULT_CURRENCY_109" = "Norwegian Krone - NOK"; "DEFAULT_CURRENCY_110" = "Nepalese Rupee - NPR"; "DEFAULT_CURRENCY_111" = "Omani Rial - OMR"; "DEFAULT_CURRENCY_112" = "Panamanian Balboa - PAB"; "DEFAULT_CURRENCY_113" = "Peruvian Nuevo Sol - PEN"; "DEFAULT_CURRENCY_114" = "Papua New Guinean Kina - PGK"; "DEFAULT_CURRENCY_115" = "Philippine Peso - PHP"; "DEFAULT_CURRENCY_116" = "Pakistani Rupee - PKR"; "DEFAULT_CURRENCY_117" = "Paraguayan Guarani - PYG"; "DEFAULT_CURRENCY_118" = "Qatari Rial - QAR"; "DEFAULT_CURRENCY_119" = "Romanian Leu - RON"; "DEFAULT_CURRENCY_120" = "Serbian Dinar - RSD"; "DEFAULT_CURRENCY_121" = "Rwandan Franc - RWF"; "DEFAULT_CURRENCY_122" = "Saudi Riyal - SAR"; "DEFAULT_CURRENCY_123" = "Solomon Islands Dollar - SBD"; "DEFAULT_CURRENCY_124" = "Seychellois Rupee - SCR"; "DEFAULT_CURRENCY_125" = "Sudanese Pound - SDG"; "DEFAULT_CURRENCY_126" = "Saint Helena Pound - SHP"; "DEFAULT_CURRENCY_127" = "Sierra Leonean Leone - SLL"; "DEFAULT_CURRENCY_128" = "Somali Shilling - SOS"; "DEFAULT_CURRENCY_129" = "Surinamese Dollar - SRD"; "DEFAULT_CURRENCY_130" = "São Tomé and Príncipe Dobra - STD"; "DEFAULT_CURRENCY_131" = "Salvadoran Colón - SVC"; "DEFAULT_CURRENCY_132" = "Syrian Pound - SYP"; "DEFAULT_CURRENCY_133" = "Swazi Lilangeni - SZL"; "DEFAULT_CURRENCY_134" = "Tajikistani Somoni - TJS"; "DEFAULT_CURRENCY_135" = "Turkmenistani Manat - TMT"; "DEFAULT_CURRENCY_136" = "Tunisian Dinar - TND"; "DEFAULT_CURRENCY_137" = "Tongan Paʻanga - TOP"; "DEFAULT_CURRENCY_138" = "Turkish Lira - TRY"; "DEFAULT_CURRENCY_139" = "Trinidad and Tobago Dollar - TTD"; "DEFAULT_CURRENCY_140" = "Tanzanian Shilling - TZS"; "DEFAULT_CURRENCY_141" = "Ukrainian Hryvnia - UAH"; "DEFAULT_CURRENCY_142" = "Ugandan Shilling - UGX"; "DEFAULT_CURRENCY_143" = "Uruguayan Peso - UYU"; "DEFAULT_CURRENCY_144" = "Uzbekistan Som - UZS"; "DEFAULT_CURRENCY_145" = "Venezuelan Bolívar Fuerte - VEF"; "DEFAULT_CURRENCY_146" = "Vietnamese Dong - VND"; "DEFAULT_CURRENCY_147" = "Vanuatu Vatu - VUV"; "DEFAULT_CURRENCY_148" = "Samoan Tala - WST"; "DEFAULT_CURRENCY_149" = "CFA Franc BEAC - XAF"; "DEFAULT_CURRENCY_150" = "Silver (troy ounce) - XAG"; "DEFAULT_CURRENCY_151" = "Gold (troy ounce) - XAU"; "DEFAULT_CURRENCY_152" = "East Caribbean Dollar - XCD"; "DEFAULT_CURRENCY_153" = "CFA Franc BCEAO - XOF"; "DEFAULT_CURRENCY_154" = "CFP Franc - XPF"; "DEFAULT_CURRENCY_155" = "Yemeni Rial - YER"; "DEFAULT_CURRENCY_156" = "South African Rand - ZAR"; "DEFAULT_CURRENCY_157" = "Zambian Kwacha - ZMW"; "DEFAULT_CURRENCY_158" = "Zimbabwean Dollar - ZWL"; "BITCOIN_DISPLAY" = "Bitcoin Einheit"; "BLOCKCHAIN_API_TYPE" = "Block Explorer API Typ"; "CHANGE_BLOCKEXPLORER_URL" = "Wechsel Blockexplorer URL"; "BLOCKCHAIN_API_TYPE_1" = "blockchain.info"; "BLOCKCHAIN_API_TYPE_2" = "Bitpay's Insight"; "ADVANCED_TITLE" = "Erweiterte Einstellunge"; "BITCOIN_DISPLAY_1" = "Bitcoin - BTC"; "BITCOIN_DISPLAY_2" = "MilliBit - mBTC"; "BITCOIN_DISPLAY_3" = "Bits - uBTC"; "ENABLE_PIN_CODE" = "Aktiviere PIN"; "SHOW_PASSPHRASE" = "Zeige Backup Wortfolge"; "RESTORE_WALLET" = "Stelle das Wallet wieder her"; "DISPLAY_LOCAL_CURRENCY" = "Zeige locale Währung"; "FEE_AMOUNT_IN_BITCOINS" = "Gebührenbetrag in Bitcoins"; "ENABLE_DYNAMIC_FEE" = "Aktiviere dynamische Gebühr"; "CONFIRMATION_TIME" = "Bestätigungszeit"; "DYNAMIC_FEE_OPTION_1" = "So schnell wie möglich"; "DYNAMIC_FEE_OPTION_2" = "Durchschnittlich 30 Minuten"; "DYNAMIC_FEE_OPTION_3" = "Durchschnittlich 60 Minuten"; "ENABLE_COLD_WALLET_FOOTER" = "Das aktivieren der offline Brieftasche erhöht die Sicherheit, erfordert jedoch auch zwei Geräte. Eines, dass täglich genutzte, hat eine Verbindung zum Internet. Das andere nicht, wodurch es vor Einflüssen von außen geschützt ist."; "ENABLE_COLD_WALLET" = "Aktiviere das Offline Brieftasche"; "ENABLE_ADVANCE_MODE_FOOTER" = "Der fortgeschrittenen Modus ermögliche den Import von privaten Schlüsseln und Adressen. Bitte beachte, dass die Wortfolge nicht den privaten Schlüssel wiederherstellen kann. Diesen solltest du getrennt sichern. Alle Features findest du wenn du den Modus aktivierst und dann zu Hilfe wechselst."; "ENABLE_ADVANCE_MODE" = "Aktiviere den fortgeschrittenen Modus"; "SET_FIXED_TRANSACTION_FEE" = "Bestimme eine feste Gebühr"; "CHANGE_PIN_CODE" = "PIN ändern"; "EMPTY" = ""; "VERSION" = "Version"; "ABOUT_TITLE" = "Über"; "ENABLE_SOUND_NOTIFICATION" = "Soundbenachrichtigung"; "EMAIL_SUPPORT" = "Email Unterstützung"; "ENABLE_STEALTH_ADDRESS_DEFAULT_FOOTER" = "Das aktivieren der wiederverwendbaren Adressen zeigt alle wiederverwendbaren Adressen auf dem zugehörigen Tap ganz vorne an."; "ENABLE_STEALTH_ADDRESS_DEFAULT" = "Standart Adresse"; "ENABLE_CAN_RESTORE_DELETED_APP_FOOTER" = "Mit dieser Funktion können Sie Ihre native ArcBit-Wallet wiederherstellen, wenn Sie diese App löschen. Ihre Backup-Passphrase wird in Ihrem Schlüsselbund gespeichert. Wenn Sie diese Funktion aktivieren, kann eine neu installierte App sie lesen. Diese Funktion ermöglicht keine Wiederherstellung von Brieftaschen oder Adressen, die Sie in andere Konten importiert haben."; "ENABLE_CAN_RESTORE_DELETED_APP" = "Wiederherstellbar"; "ARCBIT_CONTRIBUTORS_LIST" = "ArcBit Beitragsliste"; "CONTRIBUTORS_LIST" = "Liste der Mitwirkenden"; "CONTRIBUTOR_1" = "Mikhail Barinov"; "CONTRIBUTOR_2" = "Tomáš Horváth"; "CONTRIBUTOR_3" = "David Johnson"; "CONTRIBUTOR_4" = "汉服骑射"; ================================================ FILE: ArcBit/InAppSettings.bundle/es.lproj/Root.strings ================================================ "DEFAULT_CURRENCY" = "Moneda"; "DEFAULT_CURRENCY_1" = "$ - AUD"; "DEFAULT_CURRENCY_2" = "R$ - BRL"; "DEFAULT_CURRENCY_3" = "$ - CAD"; "DEFAULT_CURRENCY_4" = "CHF - CHF"; "DEFAULT_CURRENCY_5" = "$ - CLP"; "DEFAULT_CURRENCY_6" = "¥ - CNY"; "DEFAULT_CURRENCY_7" = "kr - DKK"; "DEFAULT_CURRENCY_8" = "€ - EUR"; "DEFAULT_CURRENCY_9" = "£ - GBP"; "DEFAULT_CURRENCY_10" = "$ - HKD"; "DEFAULT_CURRENCY_11" = "kr - ISK"; "DEFAULT_CURRENCY_12" = "¥ - JPY"; "DEFAULT_CURRENCY_13" = "₩ - KRW"; "DEFAULT_CURRENCY_14" = "$ - NZD"; "DEFAULT_CURRENCY_15" = "zł - PLN"; "DEFAULT_CURRENCY_16" = "RUB - RUB"; "DEFAULT_CURRENCY_17" = "kr - SEK"; "DEFAULT_CURRENCY_18" = "$ - SGD"; "DEFAULT_CURRENCY_19" = "฿ - THB"; "DEFAULT_CURRENCY_20" = "$ - TWD"; "DEFAULT_CURRENCY_21" = "$ - USD"; "DEFAULT_CURRENCY_22" = "UAE Dirham - AED"; "DEFAULT_CURRENCY_23" = "Afghan Afghani - AFN"; "DEFAULT_CURRENCY_24" = "Albanian Lek - ALL"; "DEFAULT_CURRENCY_25" = "Armenian Dram - AMD"; "DEFAULT_CURRENCY_26" = "Netherlands Antillean Guilder - ANG"; "DEFAULT_CURRENCY_27" = "Angolan Kwanza - AOA"; "DEFAULT_CURRENCY_28" = "Argentine Peso - ARS"; "DEFAULT_CURRENCY_29" = "Aruban Florin - AWG"; "DEFAULT_CURRENCY_30" = "Azerbaijani Manat - AZN"; "DEFAULT_CURRENCY_31" = "Bosnia-Herzegovina Convertible Mark - BAM"; "DEFAULT_CURRENCY_32" = "Barbadian Dollar - BBD"; "DEFAULT_CURRENCY_33" = "Bangladeshi Taka - BDT"; "DEFAULT_CURRENCY_34" = "Bulgarian Lev - BGN"; "DEFAULT_CURRENCY_35" = "Bahraini Dinar - BHD"; "DEFAULT_CURRENCY_36" = "Burundian Franc - BIF"; "DEFAULT_CURRENCY_37" = "Bermudan Dollar - BMD"; "DEFAULT_CURRENCY_38" = "Brunei Dollar - BND"; "DEFAULT_CURRENCY_39" = "Bolivian Boliviano - BOB"; "DEFAULT_CURRENCY_40" = "Bahamian Dollar - BSD"; "DEFAULT_CURRENCY_41" = "Bhutanese Ngultrum - BTN"; "DEFAULT_CURRENCY_42" = "Botswanan Pula - BWP"; "DEFAULT_CURRENCY_43" = "Belarusian Ruble - BYR"; "DEFAULT_CURRENCY_44" = "Belize Dollar - BZD"; "DEFAULT_CURRENCY_45" = "Congolese Franc - CDF"; "DEFAULT_CURRENCY_46" = "Chilean Unit of Account (UF) - CLF"; "DEFAULT_CURRENCY_47" = "Colombian Peso - COP"; "DEFAULT_CURRENCY_48" = "Costa Rican Colón - CRC"; "DEFAULT_CURRENCY_49" = "Cape Verdean Escudo - CVE"; "DEFAULT_CURRENCY_50" = "Czech Koruna - CZK"; "DEFAULT_CURRENCY_51" = "Djiboutian Franc - DJF"; "DEFAULT_CURRENCY_52" = "Dominican Peso - DOP"; "DEFAULT_CURRENCY_53" = "Algerian Dinar - DZD"; "DEFAULT_CURRENCY_54" = "Estonian Kroon - EEK"; "DEFAULT_CURRENCY_55" = "Egyptian Pound - EGP"; "DEFAULT_CURRENCY_56" = "Ethiopian Birr - ETB"; "DEFAULT_CURRENCY_57" = "Fijian Dollar - FJD"; "DEFAULT_CURRENCY_58" = "Falkland Islands Pound - FKP"; "DEFAULT_CURRENCY_59" = "Georgian Lari - GEL"; "DEFAULT_CURRENCY_60" = "Ghanaian Cedi - GHS"; "DEFAULT_CURRENCY_61" = "Gibraltar Pound - GIP"; "DEFAULT_CURRENCY_62" = "Gambian Dalasi - GMD"; "DEFAULT_CURRENCY_63" = "Guinean Franc - GNF"; "DEFAULT_CURRENCY_64" = "Guatemalan Quetzal - GTQ"; "DEFAULT_CURRENCY_65" = "Guyanaese Dollar - GYD"; "DEFAULT_CURRENCY_66" = "Honduran Lempira - HNL"; "DEFAULT_CURRENCY_67" = "Croatian Kuna - HRK"; "DEFAULT_CURRENCY_68" = "Haitian Gourde - HTG"; "DEFAULT_CURRENCY_69" = "Hungarian Forint - HUF"; "DEFAULT_CURRENCY_70" = "Indonesian Rupiah - IDR"; "DEFAULT_CURRENCY_71" = "Israeli Shekel - ILS"; "DEFAULT_CURRENCY_72" = "Indian Rupee - INR"; "DEFAULT_CURRENCY_73" = "Iraqi Dinar - IQD"; "DEFAULT_CURRENCY_74" = "Jersey Pound - JEP"; "DEFAULT_CURRENCY_75" = "Jamaican Dollar - JMD"; "DEFAULT_CURRENCY_76" = "Jordanian Dinar - JOD"; "DEFAULT_CURRENCY_77" = "Kenyan Shilling - KES"; "DEFAULT_CURRENCY_78" = "Kyrgystani Som - KGS"; "DEFAULT_CURRENCY_79" = "Cambodian Riel - KHR"; "DEFAULT_CURRENCY_80" = "Comorian Franc - KMF"; "DEFAULT_CURRENCY_81" = "Kuwaiti Dinar - KWD"; "DEFAULT_CURRENCY_82" = "Cayman Islands Dollar - KYD"; "DEFAULT_CURRENCY_83" = "Kazakhstani Tenge - KZT"; "DEFAULT_CURRENCY_84" = "Laotian Kip - LAK"; "DEFAULT_CURRENCY_85" = "Lebanese Pound - LBP"; "DEFAULT_CURRENCY_86" = "Sri Lankan Rupee - LKR"; "DEFAULT_CURRENCY_87" = "Liberian Dollar - LRD"; "DEFAULT_CURRENCY_88" = "Lesotho Loti - LSL"; "DEFAULT_CURRENCY_89" = "Lithuanian Litas - LTL"; "DEFAULT_CURRENCY_90" = "Latvian Lats - LVL"; "DEFAULT_CURRENCY_91" = "Libyan Dinar - LYD"; "DEFAULT_CURRENCY_92" = "Moroccan Dirham - MAD"; "DEFAULT_CURRENCY_93" = "Moldovan Leu - MDL"; "DEFAULT_CURRENCY_94" = "Malagasy Ariary - MGA"; "DEFAULT_CURRENCY_95" = "Macedonian Denar - MKD"; "DEFAULT_CURRENCY_96" = "Myanma Kyat - MMK"; "DEFAULT_CURRENCY_97" = "Mongolian Tugrik - MNT"; "DEFAULT_CURRENCY_98" = "Macanese Pataca - MOP"; "DEFAULT_CURRENCY_99" = "Mauritanian Ouguiya - MRO"; "DEFAULT_CURRENCY_100" = "Mauritian Rupee - MUR"; "DEFAULT_CURRENCY_101" = "Maldivian Rufiyaa - MVR"; "DEFAULT_CURRENCY_102" = "Malawian Kwacha - MWK"; "DEFAULT_CURRENCY_103" = "Mexican Peso - MXN"; "DEFAULT_CURRENCY_104" = "Malaysian Ringgit - MYR"; "DEFAULT_CURRENCY_105" = "Mozambican Metical - MZN"; "DEFAULT_CURRENCY_106" = "Namibian Dollar - NAD"; "DEFAULT_CURRENCY_107" = "Nigerian Naira - NGN"; "DEFAULT_CURRENCY_108" = "Nicaraguan Córdoba - NIO"; "DEFAULT_CURRENCY_109" = "Norwegian Krone - NOK"; "DEFAULT_CURRENCY_110" = "Nepalese Rupee - NPR"; "DEFAULT_CURRENCY_111" = "Omani Rial - OMR"; "DEFAULT_CURRENCY_112" = "Panamanian Balboa - PAB"; "DEFAULT_CURRENCY_113" = "Peruvian Nuevo Sol - PEN"; "DEFAULT_CURRENCY_114" = "Papua New Guinean Kina - PGK"; "DEFAULT_CURRENCY_115" = "Philippine Peso - PHP"; "DEFAULT_CURRENCY_116" = "Pakistani Rupee - PKR"; "DEFAULT_CURRENCY_117" = "Paraguayan Guarani - PYG"; "DEFAULT_CURRENCY_118" = "Qatari Rial - QAR"; "DEFAULT_CURRENCY_119" = "Romanian Leu - RON"; "DEFAULT_CURRENCY_120" = "Serbian Dinar - RSD"; "DEFAULT_CURRENCY_121" = "Rwandan Franc - RWF"; "DEFAULT_CURRENCY_122" = "Saudi Riyal - SAR"; "DEFAULT_CURRENCY_123" = "Solomon Islands Dollar - SBD"; "DEFAULT_CURRENCY_124" = "Seychellois Rupee - SCR"; "DEFAULT_CURRENCY_125" = "Sudanese Pound - SDG"; "DEFAULT_CURRENCY_126" = "Saint Helena Pound - SHP"; "DEFAULT_CURRENCY_127" = "Sierra Leonean Leone - SLL"; "DEFAULT_CURRENCY_128" = "Somali Shilling - SOS"; "DEFAULT_CURRENCY_129" = "Surinamese Dollar - SRD"; "DEFAULT_CURRENCY_130" = "São Tomé and Príncipe Dobra - STD"; "DEFAULT_CURRENCY_131" = "Salvadoran Colón - SVC"; "DEFAULT_CURRENCY_132" = "Syrian Pound - SYP"; "DEFAULT_CURRENCY_133" = "Swazi Lilangeni - SZL"; "DEFAULT_CURRENCY_134" = "Tajikistani Somoni - TJS"; "DEFAULT_CURRENCY_135" = "Turkmenistani Manat - TMT"; "DEFAULT_CURRENCY_136" = "Tunisian Dinar - TND"; "DEFAULT_CURRENCY_137" = "Tongan Paʻanga - TOP"; "DEFAULT_CURRENCY_138" = "Turkish Lira - TRY"; "DEFAULT_CURRENCY_139" = "Trinidad and Tobago Dollar - TTD"; "DEFAULT_CURRENCY_140" = "Tanzanian Shilling - TZS"; "DEFAULT_CURRENCY_141" = "Ukrainian Hryvnia - UAH"; "DEFAULT_CURRENCY_142" = "Ugandan Shilling - UGX"; "DEFAULT_CURRENCY_143" = "Uruguayan Peso - UYU"; "DEFAULT_CURRENCY_144" = "Uzbekistan Som - UZS"; "DEFAULT_CURRENCY_145" = "Venezuelan Bolívar Fuerte - VEF"; "DEFAULT_CURRENCY_146" = "Vietnamese Dong - VND"; "DEFAULT_CURRENCY_147" = "Vanuatu Vatu - VUV"; "DEFAULT_CURRENCY_148" = "Samoan Tala - WST"; "DEFAULT_CURRENCY_149" = "CFA Franc BEAC - XAF"; "DEFAULT_CURRENCY_150" = "Silver (troy ounce) - XAG"; "DEFAULT_CURRENCY_151" = "Gold (troy ounce) - XAU"; "DEFAULT_CURRENCY_152" = "East Caribbean Dollar - XCD"; "DEFAULT_CURRENCY_153" = "CFA Franc BCEAO - XOF"; "DEFAULT_CURRENCY_154" = "CFP Franc - XPF"; "DEFAULT_CURRENCY_155" = "Yemeni Rial - YER"; "DEFAULT_CURRENCY_156" = "South African Rand - ZAR"; "DEFAULT_CURRENCY_157" = "Zambian Kwacha - ZMW"; "DEFAULT_CURRENCY_158" = "Zimbabwean Dollar - ZWL"; "BITCOIN_DISPLAY" = "Unidad de Bitcoin"; "BLOCKCHAIN_API_TYPE" = "Bloquear el Explorador de tipo API"; "CHANGE_BLOCKEXPLORER_URL" = "Cambiar la URL blockexplorer"; "BLOCKCHAIN_API_TYPE_1" = "blockchain.info"; "BLOCKCHAIN_API_TYPE_2" = "Bitpay's Insight"; "ADVANCED_TITLE" = "Ajustes avanzados"; "BITCOIN_DISPLAY_1" = "Bitcoin - BTC"; "BITCOIN_DISPLAY_2" = "MilliBit - mBTC"; "BITCOIN_DISPLAY_3" = "Bits - uBTC"; "ENABLE_PIN_CODE" = "Habilitar código PIN"; "SHOW_PASSPHRASE" = "Mostrar contraseña de copia de seguridad"; "RESTORE_WALLET" = "Restaurar cartera"; "DISPLAY_LOCAL_CURRENCY" = "Mostrar la moneda local"; "FEE_AMOUNT_IN_BITCOINS" = "Cuota de la tasa en bitcoins"; "ENABLE_DYNAMIC_FEE" = "Habilitar comisión dinámico"; "CONFIRMATION_TIME" = "Hora de confirmación"; "DYNAMIC_FEE_OPTION_1" = "Tan rápido como sea posible"; "DYNAMIC_FEE_OPTION_2" = "Promedio 30 minutos"; "DYNAMIC_FEE_OPTION_3" = "Promedio 60 minutos"; "ENABLE_COLD_WALLET_FOOTER" = "Activación de la función Cartera fría le permitirá crear cuentas que ofrecen una mayor seguridad en línea a continuación, carteras normales. Se necesitan 2 dispositivos para utilizar esta función. Su día normal a día dispositivo que está conectado a internet y otro dispositivo que no está conectado a la internet. Puede encontrar la función Cartera fría en el menú lateral."; "ENABLE_COLD_WALLET" = "Habilitar cartera fría"; "ENABLE_ADVANCE_MODE_FOOTER" = "La activación del modo avanzado expondrá las características ocultas, incluyendo la capacidad de importar bitcoin claves privadas y direcciones. Tenga en cuenta que la frase de contraseña de copia de seguridad no puede recuperar sus claves privadas bitcoin y direcciones, por lo que debe ser respaldado por separado. Para ver una lista de todas las características avanzadas, active el modo avanzado y vaya a la sección de Ayuda."; "ENABLE_ADVANCE_MODE" = "Activar el modo avanzado"; "SET_FIXED_TRANSACTION_FEE" = "Conjunto fijo comisión"; "CHANGE_PIN_CODE" = "Cambiar PIN"; "EMPTY" = ""; "VERSION" = "Versión"; "ABOUT_TITLE" = "Acerca de"; "ENABLE_SOUND_NOTIFICATION" = "Notificación de sonido"; "EMAIL_SUPPORT" = "Soporte de correo electrónico"; "ENABLE_STEALTH_ADDRESS_DEFAULT_FOOTER" = "Habilitación de direcciones reutilizables como predeterminado pondrá toda cuenta de direcciones reutilizables en el frente de la lista de direcciones de recepción en la pantalla de recepción."; "ENABLE_STEALTH_ADDRESS_DEFAULT" = "Por defecto direcciones reutilizables"; "ENABLE_CAN_RESTORE_DELETED_APP_FOOTER" = "Esta característica le permite recuperar su billetera ArcBit nativa si se elimina esta aplicación. La frase de contraseña de copia de seguridad se almacena en su llavero y si se habilita esta función una aplicación de re-instalada será capaz de leerlo. Esta característica no permite la recuperación de carteras o direcciones que se importan desde otras cuentas."; "ENABLE_CAN_RESTORE_DELETED_APP" = "Recuperable"; "ARCBIT_CONTRIBUTORS_LIST" = "Lista de colaboradores de ArcBit"; "CONTRIBUTORS_LIST" = "Lista de contribuyentes"; "CONTRIBUTOR_1" = "Mikhail Barinov"; "CONTRIBUTOR_2" = "Tomáš Horváth"; "CONTRIBUTOR_3" = "David Johnson"; "CONTRIBUTOR_4" = "汉服骑射"; ================================================ FILE: ArcBit/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString 1.4.10 CFBundleSignature ???? CFBundleURLTypes CFBundleTypeRole Editor CFBundleURLName com.ArcBit.app CFBundleURLSchemes bitcoin CFBundleVersion 1 Fabric APIKey 24ecfd2fbad3162671908399854ab3e9ddba0710 Kits KitInfo KitName Crashlytics LSRequiresIPhoneOS NSCameraUsageDescription Camera permission is require to scan QR Codes UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown ================================================ FILE: ArcBit/de.lproj/Localizable.strings ================================================ "" = ""; "%@ is not allowed to access the camera" = "%@ darf nicht auf die Kamera zugreifen"; "%@ servers not reachable." = "%@ server nicht erreichbar.."; "%d/%d parts scanned." = "%d/%d Teile gescannt."; "%llu confirmations" = "%llu Bestätigungen"; "1 Confirmation" = "1 Bestätigung"; "A bitcoin address typically begins with a '1' or '3'. You can see the transactions and track the balance of an address, but you cannot spend from an imported address.\nYou can temporarily import this watch addresses private key to spend its bitcoins. Simply go the Send screen and select a watch address to spend from and you will be prompted to temporarily import your addresses' private key when you click 'Review Payment' in the Send screen. The private key will stay in memory until the app exits or until you remove it manually in the Accounts screen." = "A bitcoin address typically begins with a '1' or '3'. You can see the transactions and track the balance of an address, but you cannot spend from an imported address.\nYou can temporarily import this watch addresses private key to spend its bitcoins. Simply go the Send screen and select a watch address to spend from and you will be prompted to temporarily import your addresses' private key when you click 'Review Payment' in the Send screen. The private key will stay in memory until the app exits or until you remove it manually in the Accounts screen."; "A bitcoin wallet is a software application that allows people to send, receive and manage their bitcoins.\nBe aware of how other bitcoin applications store your bitcoins' private keys, which are needed to spend your bitcoins.\nThere are generally three different ways applications can your store your bitcoins.\n1.\nThe banking model, where your bitcoin private keys are held for you by someone else.\n2.\nThe security box model, where your bitcoin private keys are stored encrypted on someone else’s servers.\n3.\nThe wallet model, where your bitcoin private keys are stored only on your device." = "A bitcoin wallet is a software application that allows people to send, receive and manage their bitcoins.\nBe aware of how other bitcoin applications store your bitcoins' private keys, which are needed to spend your bitcoins.\nThere are generally three different ways applications can your store your bitcoins.\n1.\nThe banking model, where your bitcoin private keys are held for you by someone else.\n2.\nThe security box model, where your bitcoin private keys are stored encrypted on someone else’s servers.\n3.\nThe wallet model, where your bitcoin private keys are stored only on your device."; "A private key begins with an 'L', 'K', or '5'.\nBIP 38 encrypted private keys can also be imported. They can either be imported encrypted or unencrypted. If you choose to import it encrypted, you will need to input the password each time you spend from your encrypted private key." = "A private key begins with an 'L', 'K', or '5'.\nBIP 38 encrypted private keys can also be imported. They can either be imported encrypted or unencrypted. If you choose to import it encrypted, you will need to input the password each time you spend from your encrypted private key."; "Account %@ imported" = "Account %@ importiert"; "Account %lu" = "Account %lu"; "Account 1" = "Account 1"; "Account ID" = "Account ID"; "Account ID: %u" = "Account ID: %u"; "Account Private Key" = "Account privater Schlüssel"; "Account Public Key" = "Account öffentlicher Schlüssel"; "Account private key does not match imported account public key" = "Der private Schlüssel passt nicht zum öffentlichen Schlüssel"; "Account private key missing" = "Account privater Schlüssel fehlt"; "Accounts" = "Accounts"; "Achievement List" = "Achievement List"; "Achievements" = "Achievements"; "Actions" = "Aktionen"; "Active Change Addresses" = "Aktive gewechselte Adressen"; "Active Main Addresses" = "Aktive Hauptadressen"; "Add Contacts Entry" = "Add Contacts Entry"; "Address" = "Adresse"; "Address ID " = "Adresse ID "; "Address ID: %lu" = "Adresse ID: %lu"; "Addresses" = "Adressen"; "Advanced Achievement List" = "Advanced Achievement List"; "Advanced FAQ" = "Advanced FAQ"; "Advanced how To:" = "Advanced how To:"; "After a transaction is broadcast to the Bitcoin network, it may be included in a block that is published to the network. When that happens, it is said that the transaction has been mined at a depth of 1 block. With each subsequent block that is found, the number of blocks deep is increased by one. To be secure against double spending, a transaction should not be considered as confirmed until it is a certain number of blocks deep.\nA good rule of thumb is that 1 confirmation is good for small value amounts of bitcoins and a user should wait for more confirmations for larger value amounts.\nArcBit will display the confirmation number up until the 6th confirmation." = "After a transaction is broadcast to the Bitcoin network, it may be included in a block that is published to the network. When that happens, it is said that the transaction has been mined at a depth of 1 block. With each subsequent block that is found, the number of blocks deep is increased by one. To be secure against double spending, a transaction should not be considered as confirmed until it is a certain number of blocks deep.\nA good rule of thumb is that 1 confirmation is good for small value amounts of bitcoins and a user should wait for more confirmations for larger value amounts.\nArcBit will display the confirmation number up until the 6th confirmation."; "Amount:" = "Betrag:"; "An account is a collection of bitcoin addresses. With accounts, you will no longer have to manage bitcoin addresses directly anymore. Since address reuse results in a loss of privacy for people using Bitcoin, ArcBit’s HD wallet account system will automatically handle the cycling of bitcoin addresses for you. This ensures you don’t use the same bitcoin address more then once.\nEach account also has a reusable address. You can find it in your Receive screen. Swipe all the way to right on the QRCode in your Receive screen and you will find a reusable address.\nYou can create an unlimited amount of accounts with ArcBit. See the help section on how to create a new account in ArcBit." = "An account is a collection of bitcoin addresses. With accounts, you will no longer have to manage bitcoin addresses directly anymore. Since address reuse results in a loss of privacy for people using Bitcoin, ArcBit’s HD wallet account system will automatically handle the cycling of bitcoin addresses for you. This ensures you don’t use the same bitcoin address more then once.\nEach account also has a reusable address. You can find it in your Receive screen. Swipe all the way to right on the QRCode in your Receive screen and you will find a reusable address.\nYou can create an unlimited amount of accounts with ArcBit. See the help section on how to create a new account in ArcBit."; "An account private key begins with the letters 'xprv'. You can see, spend and recover the transactions and bitcoins of an entire account from an account private key." = "An account private key begins with the letters 'xprv'. You can see, spend and recover the transactions and bitcoins of an entire account from an account private key."; "An account public key begins with the letters 'xpub'. You can see the transactions and bitcoins of an entire account from an account private key, with the exception of reusable address payments. Future releases will address this issue.\nYou can temporarily import the corresponding account private key for this accounts' account public key to spend your watch accounts' bitcoins. Simply go to the Send screen and select a watch account to spend from and you will be prompted to temporarily import your account's private key when you click 'Review Payment' on the Send screen. The private key will stay in memory until the app exits or until you remove it manually on the Accounts screen." = "An account public key begins with the letters 'xpub'. You can see the transactions and bitcoins of an entire account from an account private key, with the exception of reusable address payments. Future releases will address this issue.\nYou can temporarily import the corresponding account private key for this accounts' account public key to spend your watch accounts' bitcoins. Simply go to the Send screen and select a watch account to spend from and you will be prompted to temporarily import your account's private key when you click 'Review Payment' on the Send screen. The private key will stay in memory until the app exits or until you remove it manually on the Accounts screen."; "ArcBit Brain Wallet" = "Arcbit Gedächtnisbrieftasche"; "ArcBit Web Wallet" = "Arcbit Web Wallet"; "ArcBit uses the the bitcoin wallet model (See the section ’What is a bitcoin wallet?’ to understand the 3 different security models of bitcoin software). However, if you use iCloud to backup your wallet, you will be using the security box model. It is recommended that you do not use iCloud and be responsible for your bitcoins yourself. For those who don’t want to remember a simple backup passphrase, iCloud backup is a good alternative." = "ArcBit uses the the bitcoin wallet model (See the section ’What is a bitcoin wallet?’ to understand the 3 different security models of bitcoin software). However, if you use iCloud to backup your wallet, you will be using the security box model. It is recommended that you do not use iCloud and be responsible for your bitcoins yourself. For those who don’t want to remember a simple backup passphrase, iCloud backup is a good alternative."; "Archive Account" = "Archiviere Account"; "Archive address" = "Archiviere Adresse"; "Archived Accounts" = "Archivierte Accounts"; "Archived Change Addresses" = "Wechsel archivierte Adresse"; "Archived Cold Wallet Accounts" = "Archivierte Offline Brieftasche"; "Archived Imported Accounts" = "Archivierte importierte Accounts"; "Archived Imported Addresses" = "Archivierte importierte Adressen"; "Archived Imported Watch Accounts" = "Archivierte importierte watch Accounts"; "Archived Imported Watch Addresses" = "Archivierte importierte watch Adressen"; "Archived Main Addresses" = "Archivierte Hauptadressen"; "Are you sure you want to archive account %@?" = "Bist du sicher, dass du diesen Account archivieren möchtest %@?"; "Are you sure you want to archive address %@?" = "Bist du sicher, dass du diese Adresse archivieren möchtest %@?"; "Are you sure you want to delete this account?" = "Bist du sicher, dass du diesen Account löschen möchtest"; "Are you sure you want to unarchive account %@" = "Bist du sicher, dass du diesen Account entarchivieren möchtest %@?"; "Are you sure you want to unarchive address %@?" = "Bist du sicher, dass du diese Adresse entarchivieren möchtest %@?"; "Authorize Cold Wallet Account Payment" = "Autorisiere eine Zahlung des Offline Brieftasches"; "Authorize Payment" = "Autorisiere Zahlung"; "Backup Passphrase" = "Backup Passphrase"; "Backup wallet" = "Backup Brieftasche"; "Backup local wallet" = "Lokale Brieftasche sichern"; "Backup passphrase found in keychain" = "Backup-Passphrase im Schlüsselbund gefunden"; "Bitcoin cuts out the middleman and allows you to send money anywhere in the world with an internet connection with minimum to zero fees." = "Bitcoin cuts out the middleman and allows you to send money anywhere in the world with an internet connection with minimum to zero fees."; "Bitcoin, uppercase 'B', is an online payment system invented in 2008 and released as open-source software in 2009 by a programmer named Satoshi Nakamoto. The system is decentralized and peer-to-peer allowing users to transact directly without needing an intermediary.\nBitcoin is also a platform of which other decentralized applications can be built upon. Bitcoin, lowercase 'b' is the currency unit that Bitcoin uses." = "Bitcoin, uppercase 'B', is an online payment system invented in 2008 and released as open-source software in 2009 by a programmer named Satoshi Nakamoto. The system is decentralized and peer-to-peer allowing users to transact directly without needing an intermediary.\nBitcoin is also a platform of which other decentralized applications can be built upon. Bitcoin, lowercase 'b' is the currency unit that Bitcoin uses."; "Bitcoins can be purchased from various bitcoin exchanges. ArcBit is not a bitcoin exchange. ArcBit is a bitcoin wallet. After you purchase some bitcoins from an exchange, you can move it to a bitcoin wallet." = "Bitcoins can be purchased from various bitcoin exchanges. ArcBit is not a bitcoin exchange. ArcBit is a bitcoin wallet. After you purchase some bitcoins from an exchange, you can move it to a bitcoin wallet."; "Cancel" = "Abbrechen"; "Cannot archive your default account" = "Der standard Account kann nicht archiviert werden."; "Cannot archive your one and only account" = "Der einzige Account kann nicht archiviert werden."; "Cannot create transactions with outputs less then %@" = "Transaktionen können nicht erstellt werden mit Outputs weniger als %@"; "Cannot decrypt iCloud backup wallet." = "Kann iCloud-Backup-Wallet nicht entschlüsseln."; "Cannot import reusable address" = "Wiederverwendete Adressen können nicht importiert werden"; "Change Address ID " = "Wechsel Adressen ID "; "Change Automatic Transaction Fee" = "Change Automatic Transaction Fee"; "Change Block Explorer URL" = "Wechsel Blockexplorer URL"; "Change Blockexplorer Type" = "Change Blockexplorer Type"; "Check out the ArcBit Brain Wallet" = "Schau dir das ArcBit Gedächtnisbrieftasche an"; "Check out the ArcBit Web Wallet" = "Schau dir das ArcBit Web-Brieftasche an"; "Check out the ArcBit Web Wallet!" = "Schau dir das ArcBit Web-Brieftasche an!"; "Checking Transaction" = "Prüfe Trnsaktionen"; "Clear account private key from memory" = "Lösche Account Schlüssel aus dem Speicher"; "Clear private key from memory" = "Lösche privaten Schlüssel aus dem Speicher"; "Cleared from memory" = "Speicher gesäubert"; "Click an address" = "Click an address"; "Click the button with the arrow" = "Click the button with the arrow"; "Click the plus button at the top right" = "Click the plus button at the top right"; "Click the ‘Contacts’ button" = "Click the ‘Contacts’ button"; "Click ‘Accounts’" = "Click ‘Accounts’"; "Click ‘Advanced settings’" = "Click ‘Advanced settings’"; "Click ‘Archive Account’" = "Click ‘Archive Account’"; "Click ‘Create New Account’" = "Click ‘Create New Account’"; "Click ‘Delete’" = "Click ‘Delete’"; "Click ‘Done’" = "Click ‘Done’"; "Click ‘Edit Account Name’" = "Click ‘Edit Account Name’"; "Click ‘Edit’" = "Click ‘Edit’"; "Click ‘Enable PIN Code’" = "Click ‘Enable PIN Code’"; "Click ‘History’" = "Click ‘History’"; "Click ‘Import Account’" = "Click ‘Import Account’"; "Click ‘Import Private Key’" = "Click ‘Import Private Key’"; "Click ‘Import Watch Only Account’" = "Click ‘Import Watch Only Account’"; "Click ‘Import Watch Only Address’" = "Click ‘Import Watch Only Address’"; "Click ‘Label transaction’" = "Click ‘Label transaction’"; "Click ‘Restore Wallet’" = "Click ‘Restore Wallet’"; "Click ‘Restore’" = "Click ‘Restore’"; "Click ‘Review Payment’" = "Click ‘Review Payment’"; "Click ‘Send’" = "Click ‘Send’"; "Click ‘Set Transaction Fee’" = "Click ‘Set Transaction Fee’"; "Click ‘Settings’" = "Click ‘Settings’"; "Click ‘Show Backup Passphrase’" = "Click ‘Show Backup Passphrase’"; "Click ‘View Addresses’" = "Click ‘View Addresses’"; "Click ‘View account private key QR code’" = "Click ‘View account private key QR code’"; "Click ‘View account public key QR code’" = "Click ‘View account public key QR code’"; "Click ‘View address QR code’" = "Click ‘View address QR code’"; "Click ‘View in web’" = "Click ‘View in web’"; "Click ‘View private key QR code’" = "Click ‘View private key QR code’"; "Click ‘blockexplorer API type’" = "Click ‘blockexplorer API type’"; "Click ’Receive’" = "Click ’Receive’"; "Close" = "Schließen"; "Cold Wallet" = "Offline Brieftasche"; "Cold Wallet Accounts" = "Offline Brieftasche Accounts"; "Cold Wallet Accounts can't see reusable address payments, thus this accounts' reusable address is not available." = "Die Offline Brieftasche kann keine wiederverwendbaren Adressen nutzen."; "Cold Wallet Overview" = "Offline Brieftasche Übersicht"; "Cold wallet private keys are not stored here and cannot be viewed" = "Die privaten Schlüssel des offline Brieftasche sind nicht gespeichert und können nicht angezeigt werden."; "Complete" = "Vollständig"; "Complete step 1" = "Schritt 1 vollständig"; "Confirm Payment" = "Bestätige Zahlung"; "Confirm Pin Code" = "Bestätige PIN"; "Contacts" = "Kontakte"; "Continue" = "Weiter"; "Copied To clipboard" = "In die Zwischenablage kopiert."; "Copy" = "Kopieren"; "Copy Transaction ID to Clipboard" = "Transaktions ID in die Zwischenablage kopieren"; "Create Cold Wallet" = "Erstelle Offline Brieftasche"; "Create New Account" = "Erstelle neuen Account"; "Create new contact" = "Erstelle neuen Kontakt"; "Customize Fee" = "Gebühr anpassen"; "Decrypting" = "Entschlüsseln"; "Delete" = "Löschen"; "Delete %@" = "Lösche %@"; "Delete Account" = "Lösche Account"; "Delete Contact" = "Kontakt löschen?"; "Delete address" = "Lösche Adresse"; "Do not use the QR code from here to receive bitcoins. Go to the Receive screen to get a QR code to receive bitcoins." = "Benutze nicht diesen QR-Code um BTC zu erhalten sondern wechsel in den 'Erhalten' Tap"; "Do you like using ArcBit?" = "Magst du ArcBit?"; "Do you want to load and backup your current local wallet file?" = "Möchten Sie Ihre aktuelle lokale Wallet-Datei laden und sichern?"; "Do you want to load local wallet file?" = "Möchten Sie eine lokale Wallet-Datei laden?"; "Do you want to restore from your backup passphrase or start a new wallet?" = "Möchtest du eine neue Brieftasche erstellen oder mit der Wortfolge wiederherstellen?"; "Do you want to temporary import your account private key?" = "Möchtest du den private Schlüssel temporär importieren?"; "Do you want to temporary import your private key?" = "Möchtest du den privaten Schlüssel temporär importieren?"; "Don't remind me" = "Erinnere mich nicht"; "Done" = "Fertig"; "Each account has a public and private account key. Account keys should be kept secret as they are used to view the account's transactions and spend the account's bitcoins." = "Each account has a public and private account key. Account keys should be kept secret as they are used to view the account's transactions and spend the account's bitcoins."; "Edit" = "Bearbeiten"; "Edit Account Name" = "Account Namen ändern"; "Edit Contact Name" = "Kontakt Namen ändern"; "Edit Label" = "Bezeichnung ändern"; "Edit Transaction label" = "Transaktionsbezeichnung ändern"; "Email Support" = "Email Unterstützung"; "Enable PIN code in settings to better secure your wallet." = "Aktiviere die PIN in den Einstellungen um das Wallet besser zu schützen."; "Enable Pin Code" = "Aktiviere PIN"; "Enable Transaction Fee" = "Enable Transaction Fee"; "Enable advanced mode" = "Aktiviere den fortgeschrittenen Modus"; "Encountered error creating transaction. Please try again." = "Fehler beim Erstellen einer Transaktion Bitte versuche es erneut."; "Encrypted" = "Verschlüsselt"; "Enter Label" = "Bezeichnung eingeben"; "Enter PIN" = "PIN eingeben"; "Enter a wallet backup passphrase to wipe the current wallet and start/restore another." = "Gib die Wortfolge ein, um ein neues Wallet zu erstellen oder eines wiederherzustellen."; "Enter account private key" = "Gib den privaten Schlüssel ei"; "Enter account public key" = "Gib den öffentlichen Schlüssel ein"; "Enter address" = "Gib die Adresse ein"; "Enter an account ID and click 'QR Code'. Then on your primary online device, enable Cold Wallet in settings. Then go to the Accounts screen and click 'Import Cold Wallet Account' and scan the Account Public Key QR Code. Afterwards use this cold wallet account as you would a normal account and deposit bitcoins into it. When you want to make a payment from a cold wallet account, go to the next section on the previous screen and follow the step by step instructions there." = "Gibt eine Account ID ein und klicke 'QR-Code'. Danach auf deinem primären Gerät, aktiviereOffline Brieftasche. Gehe auf den Account und klicke auf 'Account importieren' und scanne den öffentlichen Schlüssel. Danach kannst du diesen Account benutzen um BTC zu erhalten. Um von dieser Brieftasche eine Transaktion durchzuführen gehe bitte in die vorherige Ansicht und folge dort der Anleitung."; "Enter backup passphrase" = "Enter backup passphrase"; "Enter passphrase for your iCloud backup wallet." = "Geben Sie die Passphrase für Ihre iCloud-Backup-Wallet ein."; "Enter password for encrypted private key" = "Passwort für die Verschlüsselung der privaten Schlüssel"; "Enter the 12 word passphrase that belongs to the cold wallet account that you want to make a payment from. This is the passphrase that was used to generate your account public key that was generated on the 'Create Cold Wallet' section found on the previous screen." = "Gib deine 12 Worte lange Wortfolge ein, die zu der offlinen Brieftasche gehört, die importiert werden soll. Diese Wortfolge wurde erstellt, als die offline Brieftasche erstellt worden ist."; "Error" = "Fehler"; "Error fetching Transaction." = "Fehler beim Holen der Transaktionen"; "Error fetching unspent outputs. Try again later." = "Fehler beim Holen der unverbrauchten Ausgänge, versuche es später."; "Error fetching unspent outputs. Try again." = "Fehler beim Holen der unverbrauchten Ausgänge, versuche es später."; "Error getting block height." = "Fehler beim Abrufen der Blockhöhe."; "Error importing account" = "Fehler beim Importieren des Accounts"; "Error loading wallet JSON file" = "Fehler beim Laden der Wallet-JSON-Datei"; "Explanation" = "Explanation"; "FAQ" = "FAQ"; "Fee:" = "Gebühr:"; "Fill address field" = "Fill address field"; "Finished Passing Transaction Data" = "Transaktionsdaten lesen fertiggestellt"; "First make sure you are using your secondary offline device for this screen (as mentioned in the overview on the previous screen). Click 'New Wallet' and write down or memorize the generated 12 word passphrase. This passphrase can recover and generate all your accounts and the bitcoins associated with it, so keep it safe and to yourself. Also instead of creating a new wallet, you can also input an existing 12 word passphrase that was generated here to create additional accounts." = "Stelle sicher, das du ein Zweitgerät nutzt, welches nicht mit dem Internet verbunden ist. Klicke 'neues Wallet' und schreibe die Wortreihenfolge auf oder merke sie dir. Diese Wortreihenfolge kann alle deine Accounts wiederherstellen. Falls du bereits eine Wortreihenfolge hast, kannst du diese verwenden um weitere Accounts hinzuzufügen."; "Follow us on Twitter" = "Folge uns auf Twitter"; "From:" = "Von:"; "Funds have been claimed already." = "Guthaben wurde bereits verwendet."; "Funds imported" = "Guthaben importiert"; "Go" = "Gehen"; "Go to the side menu" = "Go to the side menu"; "Have sender scan QR code" = "Have sender scan QR code"; "Have sender send you payment" = "Have sender send you payment"; "Help" = "Hilfe"; "Here are some features that no other mobile bitcoin wallet supports.\n- Reusable address support\n- Ability to import individual account (extended) keys\n- iCloud backup support\n- Over 150 local currencies supported" = "Here are some features that no other mobile bitcoin wallet supports.\n- Reusable address support\n- Ability to import individual account (extended) keys\n- iCloud backup support\n- Over 150 local currencies supported"; "Hierarchical Deterministic Wallet" = "Hierarchical Deterministic Wallet"; "History" = "Geschichte"; "How To:" = "How To:"; "How do I get bitcoins?" = "How do I get bitcoins?"; "How does ArcBit Wallet work?" = "How does ArcBit Wallet work?"; "Import Account" = "Account importieren"; "Import Cold Wallet Account" = "Importiere Offline Brieftasche Account"; "Import Feature" = "Import Feature"; "Import Private Key" = "Importiere privaten Schlüssel"; "Import Private/Encrypted Key" = "Import Private/Encrypted Key"; "Import Watch Account" = "Importiere Watch Account"; "Import Watch Address" = "Importiere Watch Adresse"; "Import private key encrypted or unencrypted?" = "Privaten Schlüssel ent- oder verschlüsselt importieren?"; "Import with QR code" = "Importiere mit QR-Code"; "Import with text input" = "Importiere mit Texteingabe"; "Imported Account %@" = "Importierter Account %@"; "Imported Accounts" = "Importiere Accounts"; "Imported Address" = "Importierte Adresse"; "Imported Addresses" = "Importierte Adressen"; "Imported Cold Wallet Account %@" = "Importierter Offline Brieftasche Account %@"; "Imported Watch Account %@" = "Importierter Watch Account %@"; "Imported Watch Accounts" = "Importierte Watch Accounts"; "Imported Watch Addresses" = "Importierte Watch Adressen"; "Imported Watch Only Accounts can't see reusable address payments, thus this accounts' reusable address is not available. If you want see the reusable address for this account, import the account private key that corresponds to this accounts public key." = "Importierte Watch Accounts können keine Zahlungen der wiederverwendeten Adressen sehen. Wenn du die Zahlungen sehen willst, muss der passende private Schlüssel importiert werden."; "Importing Account" = "Importiere Account"; "Importing Cold Wallet Account" = "Importiere Offline Brieftasche Accounts"; "Importing a Private Key" = "Importing a Private Key"; "Importing a Watch Only Account" = "Importing a Watch Only Account"; "Importing a Watch Only Address" = "Importing a Watch Only Address"; "Importing an Account" = "Importing an Account"; "Importing an encrypted key will require you to input the password every time you want to send bitcoins from it." = "Wenn verschlüsselt imortiert, muss vor jeder Zahlung der Schlüssel entschlüsselt werden mittels des Passwortes."; "In advanced mode, you can import bitcoin keys and addresses from other sources. You can import account private keys, account public keys, private keys, and addresses.\nPlease note that your 12 word passphrase cannot recover your bitcoins, so it is recommended that you backup imported keys and addresses separately." = "In advanced mode, you can import bitcoin keys and addresses from other sources. You can import account private keys, account public keys, private keys, and addresses.\nPlease note that your 12 word passphrase cannot recover your bitcoins, so it is recommended that you backup imported keys and addresses separately."; "Incomplete" = "unvollständig"; "Incorrect passphrase, could not decrypt iCloud wallet backup." = "Falsche Passphrase, konnte iCloud Wallet-Backup nicht entschlüsseln."; "Input a bitcoin address" = "Input a bitcoin address"; "Input a label" = "Input a label"; "Input a new label" = "Input a new label"; "Input a recommended amount. Somewhere between %@ and %@ BTC" = "Input a recommended amount. Somewhere between %@ and %@ BTC"; "Input amount" = "Input amount"; "Input label" = "Input label"; "Input new account name" = "Input new account name"; "Input transaction fee in bitcoins" = "Input transaction fee in bitcoins"; "Instructions" = "Instructions"; "Insufficient Funds" = "Nicht genug Guthaben"; "Insufficient Funds. Account balance is %@ when %@ is required." = "Nicht genug Guthaben. Account Guthaben beträgt %@ aber %@ wird benötigt."; "Insufficient Funds. Account contains bitcoin dust. You can only send up to %@ for now." = "Nicht genug Guthaben. Der Account erhält nur sehr geringe BTC-Mengen, es werden mindestens %@ benötigt zum senden."; "Internal Wallet Data" = "Wallet Daten"; "Internal account transfer" = "Account intern verschieben"; "Invalid Address" = "Ungültige Adresse"; "Invalid Passphrase" = "Ungültige Wortfolge"; "Invalid URL" = "Ungültige URL"; "Invalid account private key" = "Ungültiger privater Schlüssel"; "Invalid account public Key" = "Ungültiger öffentlicher Schlüssel"; "Invalid amount" = "Ungültiger Betrag"; "Invalid backup passphrase" = "Ungültige Backup Wortfolge"; "Invalid private key" = "Ungültiger privater Schlüssel"; "Invalid scanned data" = "Ungültiger Scann"; "Invalid transaction ID" = "Ungültige Transactions ID"; "It is not recommended that you manually manage private keys yourself. A leak of a private key can lead to the compromise of your accounts." = "Es wird nicht geraten die privaten Schlüssel selber zu verwalten. Ein Leck kann zum Verlust des gesamten Wallet-Guthabens führen."; "It is not recommended that you use a regular bitcoin address for multiple payments, but instead you should import a reusable address. Add address anyways?" = "Es wird nicht geraten Adressen mehrfach zu nutzen, lieber sollte eine wiederverwendbare Adresse importiert werden. Trotzdem hinzufügen?"; "Label" = "Beschriftung"; "Label Transaction" = "Transaktions Beschriftung"; "Local backup to wallet failed!" = "Die lokale Sicherung in der Brieftasche ist fehlgeschlagen!"; "Local wallet will be lost. Are you sure you want to restore wallet from iCloud?" = "Die lokale Brieftasche geht verloren. Sind Sie sicher, dass Sie die Brieftasche von iCloud wiederherstellen möchten?"; "Maximum accounts reached" = "Maximale Anzahl an Accounts erreicht"; "More" = "Mehr"; "Name" = "Name"; "Network Error" = "Netzwerkfehler"; "New Wallet" = "Neue Brieftasche"; "New addresses will be automatically generated and cycled for you as you use your current available addresses." = "Neue Adressen werden automatisch erzeugt und durchgewechselt."; "Next" = "Nächste"; "No" = "Nein"; "None currently" = "Gegenwärtig keine"; "Not now" = "Nicht jetzt"; "Now authorize the transaction on your air gap device. When you have done so, click continue on this device to scan the authorized transaction data and make your payment." = "Autorisiere nun die Transaktion mit dem zweiten Gerät. Scanne dazu die Transaktionsdaten und führe die Zahlung durch."; "OK" = "OK"; "On your primary online device, when you want to make a payment from a cold wallet account, simply do it as you normally would on a normal account. When you click 'Send' on the Review Payment screen, instead of the payment going out immediately, you will be prompted to pass the unauthorized transaction data. Then on your secondary offline device, within this screen click 'Scan' to import the transaction so it can be authorized." = "Auf dem primären Gerät wird die Zahlung auch aus dem Offline Brieftasche 'ganz normal' durchgeführt. Nachdem die 'normale' Zahlung abgeschlossen ist und auf 'senden' geklickt wurde, muss die umautorisierte Zahlung noch autorisiert werden. Auf dem Zweitgerät müssen diese Daten dann eingescannt werden und autorisiert werden."; "Once the transaction has been authorized by completing the above two steps, pass the authorized transaction back to your primary online device to finalize your payment." = "Nachdem die Transaktion so autorisiert wurde, muss sie zurück auf das online Gerät übertragen werden um an das Netzwerk übertragen zu werden."; "Other Links" = "andere Links"; "Pass" = "Pass"; "Passphrase" = "Wortfolge"; "Passphrase does not match the transaction" = "Die Wortfolge passt nicht zur Transaktion"; "Password" = "Passwort"; "Payment Index: %lu" = "Zahlungs Index:%lu"; "Private key does not match address" = "Der private Schlüssel passt nicht zur Adresse"; "Private key missing" = "Privater Schlüssel fehlt"; "QR code" = "QR-Code"; "Quit and re-enter app" = "Quit and re-enter app"; "Rate" = "Bewertung"; "Rate us in the App Store!" = "Bewerte uns im App Store!"; "Receive" = "Erhalten"; "Receive Payment" = "Receive Payment"; "Receive Payment From Reusable Address" = "Receive Payment From Reusable Address"; "Remind me Later" = "Erinnere mich später"; "Restore" = "Wiederherstellen"; "Restore Wallet" = "Stelle das Wallet wieder her"; "Restore from iCloud" = "Wiederherstellen von iCloud"; "Restoring Wallet" = "Stelle Wallet wieder her"; "Retry" = "Wiederholen"; "Reusable Address Payment Addresses" = "Wiederverwendbare Zahlungsadressen"; "Reusable Address:" = "Wiederverwendbare Adresse:"; "Reusable Addresses" = "Reusable Addresses"; "Review Payment" = "Überprüfe Zahlung"; "Save" = "sichern"; "Scan" = "Scannen"; "Scan For Reusable Address Payment" = "Scanne auf wiederverwendbare Adressen"; "Scan QR Code" = "Scanne QR Code"; "Scan for reusable address transaction" = "Scanne auf Transaktionen"; "Scan next part" = "扫描下一部分"; "Scroll down to the section ‘Account Actions’" = "Scroll down to the section ‘Account Actions’"; "Select Account" = "Scanne nächstes Stück"; "Select and click a blockexplorer API" = "Select and click a blockexplorer API"; "Select and click a transaction" = "Select and click a transaction"; "Select and click an account" = "Select and click an account"; "Select and click an account to receive from" = "Select and click an account to receive from"; "Select and click an account to view it’s transaction history" = "Select and click an account to view it’s transaction history"; "Select and click an address" = "Select and click an address"; "Send" = "Gesendet"; "Send Payment" = "Zahlung gesendet"; "Send To Address In Contacts" = "Send To Address In Contacts"; "Send authorized payment?" = "Sende autorisierte Zahlung?"; "Sending" = "Sende"; "Sending payment to a reusable address might take longer to show up then a normal transaction with the blockchain.info API. You might have to wait until at least 1 confirmation for the transaction to show up. This is due to the limitations of the blockchain.info API. For reusable address payments to show up faster, configure your app to use the Insight API in advance settings." = "Gesendete Transaktionen mit wiederverwendbaren Adressen brauchen vielleicht länger um angezeigt zu werden. Eventuell muss auf +1 Bestätigung gewartet werden. Durch einen Wechsel der API in den erweiterten Einstellungen kann das behoben werden."; "Sent %@ to %@" = "Gesendet %@ an %@"; "Set Transaction Fee in %@" = "Setzt Transaktionsgebühr auf %@"; "Settings" = "Einstellungen"; "Some funds may be pending confirmation and cannot be spent yet. (Check your account history) Account only has a spendable balance of %@" = "Einige eingehende Transaktionen sind noch nicht bestätigt und können nicht ausgegeben werden. Der Account hat eine senfbare Palace von %@"; "Some people have compared bitcoin addresses to a bank routing number. It is a good analogy, however bitcoin addresses are public. So if you reuse the same bitcoin address for multiple payments like you would a routing number, people will be able to figure out how much bitcoin you have. Thus it is recommended that you only use one address per payment.\nThis causes usability issues making the user use a new address whenever receiving a payment is cumbersome.\nStealth/reusable addresses provides a better solution. When you give a sender a reusable address, the sender will derive a one time regular bitcoin address from the reusable address. Then the sender will send a payment to that regular bitcoin address. Now you can give many people just one reusable address and have them all send you payments without letting other people know how much bitcoin you have.\nA reusable address looks like this vJmxthatTBXibYe9aZavx18iAT9gyiJETGkhwPX2WbHQGuzX83YvQXynD2t8yHU4Xjfonu5x9m6B4yxquytFP1c2CRbVR9mecxesvE. A reusable address is a lot longer then a regular bitcoin address, it is 102 characters in length.\nReusable addresses are great, however there are no other mobile bitcoin wallets but ArcBit that supports reusable addresses for now. Which is why ArcBit supports receiving payments from both regular bitcoin addresses and reusable addresses.\nFor each account, you have one reusable address. You can find it on your Receive screen. Swipe all the way to right on the QRCode on your Receive screen and you will find a reusable address." = "Some people have compared bitcoin addresses to a bank routing number. It is a good analogy, however bitcoin addresses are public. So if you reuse the same bitcoin address for multiple payments like you would a routing number, people will be able to figure out how much bitcoin you have. Thus it is recommended that you only use one address per payment.\nThis causes usability issues making the user use a new address whenever receiving a payment is cumbersome.\nStealth/reusable addresses provides a better solution. When you give a sender a reusable address, the sender will derive a one time regular bitcoin address from the reusable address. Then the sender will send a payment to that regular bitcoin address. Now you can give many people just one reusable address and have them all send you payments without letting other people know how much bitcoin you have.\nA reusable address looks like this vJmxthatTBXibYe9aZavx18iAT9gyiJETGkhwPX2WbHQGuzX83YvQXynD2t8yHU4Xjfonu5x9m6B4yxquytFP1c2CRbVR9mecxesvE. A reusable address is a lot longer then a regular bitcoin address, it is 102 characters in length.\nReusable addresses are great, however there are no other mobile bitcoin wallets but ArcBit that supports reusable addresses for now. Which is why ArcBit supports receiving payments from both regular bitcoin addresses and reusable addresses.\nFor each account, you have one reusable address. You can find it on your Receive screen. Swipe all the way to right on the QRCode on your Receive screen and you will find a reusable address."; "Spending from a cold wallet account" = "Sende vom Offline Brieftasche Account"; "Start fresh" = "Frisch starten"; "Start/Restore Another Wallet" = "Start/Restore Another Wallet"; "Starting Change address ID:" = "Starte Adressen ID:"; "Starting Receiving Address ID:" = "Empfangen der Adressen ID:"; "Step 1: Scan transaction to authorize" = "Schritt 1: Scanne Transaktion zum Autorisieren"; "Step 2: Input 12 word backup passphrase" = "Schritt 2: Gib die Wortfolge ein"; "Step 3: Pass authorized transaction data" = "Schritt 3: Übertrage die autorisierte Transaktion"; "Steps" = "Steps"; "Success" = "Erfolgreich"; "Swipe right on an address" = "Swipe right on an address"; "Swipe to the right on the QR Code Image until you see the reusable address" = "Swipe to the right on the QR Code Image until you see the reusable address"; "Temporarily import account private key" = "Importiere privaten Account Schlüssel temporär"; "Temporarily import private key" = "Importiere privaten Schlüssel temporär"; "The cold wallet feature will allow you to create accounts which offer better security compared to normal online wallets. You will need 2 devices to use this feature. Your normal day to day device that is connected to the internet and a secondary device that is not connected to the internet (Your secondary device would need to be online once to download the ArcBit app. Afterwards keep the secondary device offline for maximum security). This feature allows you to authorize bitcoin payments from an offline device so that the keys to your bitcoins will never need to be stored on your online device. Follow the step by step instructions by clicking the info buttons within the below sections." = "Das Offline Brieftasche gibt dir die Möglichkeit die Sicherheit zu erhöhen. Ein Gerät ist mit dem Internet verbunden und ein zweites ist am Besten immer offline nachdem die ArcBit App heruntergeladen ist. So können Transaktionen autorisiert werden und das Guthaben muss trotzdem nicht auf einem Online Wallet liegen. Informationen dazu findest du in der Schritt für Schritt Anleitung weiter unten."; "The cold wallet feature will allow you to create accounts which offer better security compared to normal online wallets. You will need 2 devices to use this feature. Your normal day to day device that is connected to the internet and a secondary device that is not connected to the internet (Your secondary device would need to be online once to download the ArcBit app. Afterwards keep the secondary device offline for maximum security). This feature allows you to authorize bitcoin payments from an offline device so that the keys to your bitcoins will never need to be stored on your online device. You can enable the cold wallet feature by going into advanced settings." = "The cold wallet feature will allow you to create accounts which offer better security compared to normal online wallets. You will need 2 devices to use this feature. Your normal day to day device that is connected to the internet and a secondary device that is not connected to the internet (Your secondary device would need to be online once to download the ArcBit app. Afterwards keep the secondary device offline for maximum security). This feature allows you to authorize bitcoin payments from an offline device so that the keys to your bitcoins will never need to be stored on your online device. You can enable the cold wallet feature by going into advanced settings."; "This account type can't see reusable address payments" = "Dieser Account Typ kann keine wiederverwendbaren Adressen sehen."; "This feature allows you to manually input a transaction ID and see if the corresponding transaction contains a reusable address payment to your reusable address. If so, then the funds will be added to your wallet. Normally the app will discover reusable address payments automatically for you, but if you believe a payment is missing you can use this feature." = "Dies Funktion erlaubt es manuell eine Transaktion ID einzugeben um zu prüfen ob noch Transaktionen auf wiederverwendbaren Adressen liegen. Wenn ja werden die BTC dem Account hinzugefügt. Normalerweise geschieht das automatisch."; "To:" = "An:"; "Today" = "Heute"; "Toggle Automatic Transaction Fee" = "Toggle Automatic Transaction Fee"; "Toggle ‘Enable Transaction Fee’" = "Toggle ‘Enable Transaction Fee’"; "Toggle ’Enable advanced mode’" = "Toggle ’Enable advanced mode’"; "Total:" = "Gesamt:"; "Transaction %@ already accounted for." = "Transaktion %@ wurde bereits gezählt."; "Transaction %@ does not belong to this account." = "Die Transaktion gehört nicht zu diesem Account."; "Transaction Fee" = "Transaktionsgebühr"; "Transaction ID" = "Transaktions ID"; "Transaction ID: %@" = "Transaktions ID: %@"; "Transaction authorized" = "Transaktion autorisiert"; "Transaction confirmations" = "Transaction confirmations"; "Transaction fees impact how quickly the Bitcoin network will confirm your transactions. Higher fees means faster confirmation times. Default fee behavior can be configured in settings." = "Transaktionsgebühren beeinflussen wie schnell das Bitcoin Netzwerk die Transaktion bestätigt. Höhere Gebühren bedeuten eine schnellere Bestätigung. Die Gebühren Berechnung kann in den Einstellungen eingestellt werden."; "Transaction is not a reusable address transaction." = "Diese Transaktion ist keine Transaktion einer wiederverwendbaren Adresse."; "Transaction needs to be authorized by an offline and air gap device. Send transaction to an offline device for authorization?" = "Transaktion muss durch ein zweites Gerät autorisiert werden. Zum autorisieren übertragen?"; "Transaction needs to be passed back to your online device in order for the payment to be sent" = ">Transaktion muss wieder zum online Gerät übertragen werden um gesendet zu werden."; "Try Again" = "Versuche es erneut"; "Try our new cold wallet feature!" = "Probiere unser Offline Brieftasche feature aus!"; "URL does not contain an address." = "URL enthält keine Adresse."; "Unable to get dynamic fees. Falling back on fixed transaction fee. (fee can be configured on review payment)" = "Fehler beim lesen der dynamischen Gebührendaten. Es wird die festgelegte Gebühr genutzt."; "Unarchive Account" = "Entarchiviere Account"; "Unarchive address" = "Entarchiviere Adresse"; "Unarchived address" = "Entarchivierte Adresse"; "Unconfirmed" = "Unbestätigt"; "Unencrypted" = "Unverschlüsselt"; "Use ArcBit on your browser to complement the mobile app. The web wallet has all the features that the mobile wallet has plus more!" = "Nutzer ArcBit im Browser um die App zu vervollständigen. Das Web Wallet hat alle Features und mehr!"; "Use all funds" = "Alle Mittel verwenden"; "View Account Address" = "View Account Address"; "View Account Address In Web" = "View Account Address In Web"; "View Account Addresses" = "View Account Addresses"; "View Account Private Key" = "View Account Private Key"; "View Account Public Key" = "View Account Public Key"; "View Achievements" = "View Achievements"; "View Addresses" = "Adressen ansehen"; "View ArcBit Brain Wallet Details" = "ArcBit Gedächtnisbrieftasche Details"; "View ArcBit Web Wallet Details" = "ArcBit Web-Brieftasche Details"; "View History" = "View History"; "View Private Key" = "View Private Key"; "View Transaction In Web" = "View Transaction In Web"; "View account private key QR code" = "QR-Code Account privater Schlüssel"; "View account public key QR code" = "QR-Code Account öffentlicher Schlüssel"; "View address QR code" = "QR-Code Adresse"; "View address in web" = "Adresse im Web ansehen"; "View in web" = "Im Web ansehen"; "View private key QR code" = "QR-Code privater Schlüssel"; "Visit our home page" = "Besuchen Sie unsere Homepage"; "Wallet backup passphrase" = "Wallet backup Wortfolge"; "Warning" = "Warnung"; "Welcome to ArcBit, a user only controlled Bitcoin wallet. Start using the app now by depositing your Bitcoins here." = "Willkommen bei ArcBit. Einem Nutzer kontrolliertem Bitcoin Wallet. Fange an und sende Bitcoin an dieses Wallet."; "Welcome!" = "Willkommen!"; "What are Account/Extended Keys?" = "What are Account/Extended Keys?"; "What are accounts?" = "What are accounts?"; "What are reusable addresses?" = "What are reusable addresses?"; "What are the benefits and advantages of Bitcoin?" = "What are the benefits and advantages of Bitcoin?"; "What are transaction confirmations?" = "What are transaction confirmations?"; "What is ArcBit's cold wallet feature?" = "What is ArcBit's cold wallet feature?"; "What is Bitcoin?" = "What is Bitcoin?"; "What is a bitcoin wallet?" = "What is a bitcoin wallet?"; "What makes ArcBit different from other bitcoin wallets?" = "What makes ArcBit different from other bitcoin wallets?"; "With an ArcBit cold wallet feature, you can create wallets and make payments offline without exposing your private keys to an internet connected device. This feature is great for storing large amounts of bitcoin or for the security conscious minded. Check out this feature in the cold wallet section in the side menu." = "Mit dem ArcBit Offline Brieftasche können größere BTC Beträge sicher gelagert werden. Es werden keine privaten Schlüssel auf einem Online Gerät verwaltet sondern getrennt offline. Es erhöht die Sicherheit auch bei kleineren Beträgen. Mehr dazu im Offline Brieftasche Menu an der Seite."; "Write down backup passphrase" = "Write down backup passphrase"; "Write down or memorize your 12 word wallet backup passphrase. You can view it by clicking \"Show backup passphrase\" in Settings. Your wallet backup passphrase is needed to recover your bitcoins." = "Schriebe diese Wortfolge auf oder merke sie dir gut! Diese Wortfolge wird benötigt um deine BTC wiederherzustellen."; "Write down the 12 word passphrase below and keep it safe. This passphrase alone can restore your entire wallets\' bitcoins (excluding imports)." = "Schreib diese Wortfolge auf und bewahre sie gut auf. Diese Wortfolge kann das gesamte Wallet wiederherstellen (ausgenommen sind Imports)"; "Write down the 12 word passphrase below and keep it safe. This passphrase alone can restore your entire wallets\' bitcoins." = "Schreib diese Wortfolge auf und bewahre sie gut auf. Diese Wortfolge kann das gesamte Wallet wiederherstellen."; "Yes" = "Ja"; "You are making a payment to a reusable address. Make sure that the receiver can see the payment made to them. (All ArcBit reusable addresses are compatible with other ArcBit wallets)" = "Du führst eine Zahlung an eine Wiederverwendbare Adresse aus. Stelle sicher, dass der Empfänger die Zahlung sehen kann. Wiederverwendbare Adressen sind mit anderen ArcBit Wallets kompatibel."; "You have %@, but %@ is needed. (This includes the transactions fee)" = "Du hast %@, aber %@ Wird benötigt. Darin enthalten ist die Transaktionsgebühr."; "You must exit and kill this app in order for this to take effect." = "Du musst die App komplett beenden damit die Änderung wirksam wird."; "Your current wallet will be deleted. Your can restore your current wallet later with the wallet passphrase, but any imported accounts or addresses created in advanced mode cannot be recovered. Do you wish to continue?" = "Das aktuelle Wallet wird gelöscht. Du kannst es wiederherstellen mit der Wortfolge aber alle importierten importierten Accounts und im fortgeschrittenen Modus erstellten Adressen können nicht wieder hergestellt werden. Trotzdem fortfahren?"; "Your iCloud backup was last saved on %@. Do you want to restore your wallet from iCloud or backup your local wallet to iCloud?" = "Ihre iCloud-Sicherung wurde zuletzt auf %@ gespeichert. Möchten Sie Ihre Brieftasche von iCloud wiederherstellen oder Ihre lokale Brieftasche auf iCloud sichern?"; "Your new transaction fee is too high" = "Die neue Transaktionsgebühr ist zu hoch."; "Your wallet is now restored" = "Dein Wallet ist nun wiederhergetellt"; "\nAllow camera access in\n Settings->Privacy->Camera->%@" = "\nKamerazugriff in\n Einstellungen->Privatsphäre->Kamera->%@"; "\tArcBit Web Wallet is a Chrome extension. It has all the features of the mobile wallet plus more. Highlights include the ability to create multiple wallets instead of just one, and a new non-cumbersome way to generate wallets, store and spend bitcoins all from cold storage! ArcBit's new way to manage your cold storage bitcoins also offers a more compelling reason to use ArcBit's watch account feature. Now you can safely watch the balance of your cold storage bitcoins by enabling advance mode in ArcBit and importing your cold storage account public keys.\n\tUse ArcBit Web Wallet in whatever way you wish. You can create a new wallet, or you can input your current 12 word backup passphrase to manage the same bitcoins across different devices. Check out the ArcBit Web Wallet in the Chrome Web Store for more details!\n" = "\tArcBits Web Wallet ist eine Chrome Erweiterung. Es hat alle Features der mobilen Wallets und mehr. Es können mehr als ein Wallet gleichzeitig erstellt werden. Außerdem kann so das Offline Brieftasche Feature noch besser genutzt werden. Von überall kann mit dem öffentlichen Schlüssel die Balance angesehen werden und trotzdem sind die BTC sicher nur auf dem Offline Brieftasche und können nicht gesendet werden ohne extra Autorisierung.\n\tWenn du im Web Wallet die selbe Wortfolge verwendest, kannst du das selbe Wallet von überall aus verwalten. Schau dir mehr Detail an m Chrome Web Store.\n"; "\tWith the Arcbit Brain Wallet you can safely spend your bitcoins without ever having your private keys be exposed to the internet. It can be use in conjunction with your Arcbit Wallet or as a stand alone wallet.\n" = "\tDas ArcBit Gedächtnisbrieftasche kann genutzt werden um deine Bitcoin zu verwalten ohne jemals die privaten Schlüssel öffentlich werden zu lassen. Du kannst es zusammen mit der ArcBit App verwenden oder allein. Nur du hast Zugang.\n"; "iCloud Error: %@" = "iCloud-Fehler: %@"; "iCloud backup found" = "iCloud-Sicherung gefunden"; "iCloud backup not found" = "iCloud-Sicherung nicht gefunden"; "iCloud backup will be lost. Are you sure you want to backup your local wallet to iCloud?" = "Die iCloud-Sicherung geht verloren. Sind Sie sicher, dass Sie Ihre lokale Brieftasche auf iCloud sichern möchten?"; "Wallet backup passphrase will be shown" = "Wallet backup Wortfolge wird angezeigt werden"; "Write down or memorize your wallet backup passphrase. If you lose your backup passphrase, your wallet cannot be recovered." = "Notieren oder merken Sie sich Ihre Brieftaschen-Backup-Passphrase. Wenn Sie Ihre Backup-Passphrase verlieren, kann Ihre Brieftasche nicht wiederhergestellt werden."; "I understand" = "ich verstehe"; "iCloud support for ArcBit discontinued" = "Die iCloud-Unterstützung für ArcBit wird eingestellt"; "iCloud support for ArcBit is being discontinued. If your backup passphrase has not been backed up already, please do so." = "Die iCloud-Unterstützung für ArcBit wird eingestellt. Wenn Ihre Backup-Passphrase noch nicht gesichert wurde, tun Sie dies bitte."; "Reusable address payments are disabled until further notice." = "Wiederverwendbare Adresszahlungen sind bis auf weiteres deaktiviert."; ================================================ FILE: ArcBit/es.lproj/Localizable.strings ================================================ "" = ""; "%@ is not allowed to access the camera" = "%@ no está permitido acceder a la cámara"; "%@ servers not reachable." = "%@ servidores no accesibles."; "%d/%d parts scanned." = "%d/%d partes escaneadas."; "%llu confirmations" = "%llu confirmaciones"; "1 Confirmation" = "1 confirmación"; "A bitcoin address typically begins with a '1' or '3'. You can see the transactions and track the balance of an address, but you cannot spend from an imported address.\nYou can temporarily import this watch addresses private key to spend its bitcoins. Simply go the Send screen and select a watch address to spend from and you will be prompted to temporarily import your addresses' private key when you click 'Review Payment' in the Send screen. The private key will stay in memory until the app exits or until you remove it manually in the Accounts screen." = "A bitcoin address typically begins with a '1' or '3'. You can see the transactions and track the balance of an address, but you cannot spend from an imported address.\nYou can temporarily import this watch addresses private key to spend its bitcoins. Simply go the Send screen and select a watch address to spend from and you will be prompted to temporarily import your addresses' private key when you click 'Review Payment' in the Send screen. The private key will stay in memory until the app exits or until you remove it manually in the Accounts screen."; "A bitcoin wallet is a software application that allows people to send, receive and manage their bitcoins.\nBe aware of how other bitcoin applications store your bitcoins' private keys, which are needed to spend your bitcoins.\nThere are generally three different ways applications can your store your bitcoins.\n1.\nThe banking model, where your bitcoin private keys are held for you by someone else.\n2.\nThe security box model, where your bitcoin private keys are stored encrypted on someone else’s servers.\n3.\nThe wallet model, where your bitcoin private keys are stored only on your device." = "A bitcoin wallet is a software application that allows people to send, receive and manage their bitcoins.\nBe aware of how other bitcoin applications store your bitcoins' private keys, which are needed to spend your bitcoins.\nThere are generally three different ways applications can your store your bitcoins.\n1.\nThe banking model, where your bitcoin private keys are held for you by someone else.\n2.\nThe security box model, where your bitcoin private keys are stored encrypted on someone else’s servers.\n3.\nThe wallet model, where your bitcoin private keys are stored only on your device."; "A private key begins with an 'L', 'K', or '5'.\nBIP 38 encrypted private keys can also be imported. They can either be imported encrypted or unencrypted. If you choose to import it encrypted, you will need to input the password each time you spend from your encrypted private key." = "A private key begins with an 'L', 'K', or '5'.\nBIP 38 encrypted private keys can also be imported. They can either be imported encrypted or unencrypted. If you choose to import it encrypted, you will need to input the password each time you spend from your encrypted private key."; "Account %@ imported" = "Cuenta %@ importada"; "Account %lu" = "Cuenta %lu"; "Account 1" = "Cuenta 1"; "Account ID" = "ID de cuenta:"; "ID de cuenta: %u" = "ID de cuenta %u"; "Account Private Key" = "Clave privada de cuenta"; "Account Public Key" = "Clave pública de cuenta"; "Account private key does not match imported account public key" = "La clave privada de la cuenta no coincide con la clave pública de la cuenta importad"; "Account private key missing" = "Falta la clave privada de la cuenta"; "Accounts" = "Cuentas"; "Achievement List" = "Achievement List"; "Achievements" = "Achievements"; "Actions" = "Acciones"; "Active Change Addresses" = "Direcciones de cambios activo"; "Active Main Addresses" = "Direcciones principales activas"; "Add Contacts Entry" = "Add Contacts Entry"; "Address" = "Dirección"; "Address ID " = "ID dirección "; "Address ID: %lu" = "ID dirección: %lu"; "Addresses" = "Direcciones"; "Advanced Achievement List" = "Advanced Achievement List"; "Advanced FAQ" = "Advanced FAQ"; "Advanced how To:" = "Advanced how To:"; "After a transaction is broadcast to the Bitcoin network, it may be included in a block that is published to the network. When that happens, it is said that the transaction has been mined at a depth of 1 block. With each subsequent block that is found, the number of blocks deep is increased by one. To be secure against double spending, a transaction should not be considered as confirmed until it is a certain number of blocks deep.\nA good rule of thumb is that 1 confirmation is good for small value amounts of bitcoins and a user should wait for more confirmations for larger value amounts.\nArcBit will display the confirmation number up until the 6th confirmation." = "After a transaction is broadcast to the Bitcoin network, it may be included in a block that is published to the network. When that happens, it is said that the transaction has been mined at a depth of 1 block. With each subsequent block that is found, the number of blocks deep is increased by one. To be secure against double spending, a transaction should not be considered as confirmed until it is a certain number of blocks deep.\nA good rule of thumb is that 1 confirmation is good for small value amounts of bitcoins and a user should wait for more confirmations for larger value amounts.\nArcBit will display the confirmation number up until the 6th confirmation."; "Amount:" = "Cantidad:"; "An account is a collection of bitcoin addresses. With accounts, you will no longer have to manage bitcoin addresses directly anymore. Since address reuse results in a loss of privacy for people using Bitcoin, ArcBit’s HD wallet account system will automatically handle the cycling of bitcoin addresses for you. This ensures you don’t use the same bitcoin address more then once.\nEach account also has a reusable address. You can find it in your Receive screen. Swipe all the way to right on the QRCode in your Receive screen and you will find a reusable address.\nYou can create an unlimited amount of accounts with ArcBit. See the help section on how to create a new account in ArcBit." = "An account is a collection of bitcoin addresses. With accounts, you will no longer have to manage bitcoin addresses directly anymore. Since address reuse results in a loss of privacy for people using Bitcoin, ArcBit’s HD wallet account system will automatically handle the cycling of bitcoin addresses for you. This ensures you don’t use the same bitcoin address more then once.\nEach account also has a reusable address. You can find it in your Receive screen. Swipe all the way to right on the QRCode in your Receive screen and you will find a reusable address.\nYou can create an unlimited amount of accounts with ArcBit. See the help section on how to create a new account in ArcBit."; "An account private key begins with the letters 'xprv'. You can see, spend and recover the transactions and bitcoins of an entire account from an account private key." = "An account private key begins with the letters 'xprv'. You can see, spend and recover the transactions and bitcoins of an entire account from an account private key."; "An account public key begins with the letters 'xpub'. You can see the transactions and bitcoins of an entire account from an account private key, with the exception of reusable address payments. Future releases will address this issue.\nYou can temporarily import the corresponding account private key for this accounts' account public key to spend your watch accounts' bitcoins. Simply go to the Send screen and select a watch account to spend from and you will be prompted to temporarily import your account's private key when you click 'Review Payment' on the Send screen. The private key will stay in memory until the app exits or until you remove it manually on the Accounts screen." = "An account public key begins with the letters 'xpub'. You can see the transactions and bitcoins of an entire account from an account private key, with the exception of reusable address payments. Future releases will address this issue.\nYou can temporarily import the corresponding account private key for this accounts' account public key to spend your watch accounts' bitcoins. Simply go to the Send screen and select a watch account to spend from and you will be prompted to temporarily import your account's private key when you click 'Review Payment' on the Send screen. The private key will stay in memory until the app exits or until you remove it manually on the Accounts screen."; "ArcBit Brain Wallet" = "Cartera Arcbit cerebro"; "ArcBit Web Wallet" = "Cartera Arcbit Web"; "ArcBit uses the the bitcoin wallet model (See the section ’What is a bitcoin wallet?’ to understand the 3 different security models of bitcoin software). However, if you use iCloud to backup your wallet, you will be using the security box model. It is recommended that you do not use iCloud and be responsible for your bitcoins yourself. For those who don’t want to remember a simple backup passphrase, iCloud backup is a good alternative." = "ArcBit uses the the bitcoin wallet model (See the section ’What is a bitcoin wallet?’ to understand the 3 different security models of bitcoin software). However, if you use iCloud to backup your wallet, you will be using the security box model. It is recommended that you do not use iCloud and be responsible for your bitcoins yourself. For those who don’t want to remember a simple backup passphrase, iCloud backup is a good alternative."; "Archive Account" = "Archivo cuenta"; "Archive address" = "Archivo dirección"; "Archived Accounts" = "Cuentas archivados"; "Archived Change Addresses" = "Direcciónes de cambios archivadas"; "Archived Cold Wallet Accounts" = "Cuentas archivadas de la cartera fría"; "Archived Imported Accounts" = "Cuentas importadas archivadas"; "Archived Imported Addresses" = "Direcciones importadas archivadas"; "Archived Imported Watch Accounts" = "Cuentas de observar importadas archivadas"; "Archived Imported Watch Addresses" = "Las direcciones de observar importadas archivadas"; "Archived Main Addresses" = "Direcciones principales archivadas"; "Are you sure you want to archive account %@?" = "¿Está seguro de que desea archivar cuenta %@?"; "Are you sure you want to archive address %@?" = "¿Está seguro de que desea archivar la dirección %@?"; "Are you sure you want to delete this account?" = "¿Está seguro de que quiere eliminar esta cuenta?"; "Are you sure you want to unarchive account %@" = "¿Está seguro de que desea anular el archivo cuenta %1$s?"; "Are you sure you want to unarchive address %@?" = "Está seguro de que desea anular el archivo de dirección %1$s?"; "Authorize Cold Wallet Account Payment" = "Autorizar cartera fría pago de la cuenta"; "Authorize Payment" = "autorizar el pago"; "Backup Passphrase" = "Frase de copia de seguridad"; "Backup wallet" = "Cartera de copia de seguridad"; "Backup local wallet" = "Carpeta local de respaldo"; "Backup passphrase found in keychain" = "Frase de paso de seguridad encontrada en llavero"; "Bitcoin cuts out the middleman and allows you to send money anywhere in the world with an internet connection with minimum to zero fees." = "Bitcoin cuts out the middleman and allows you to send money anywhere in the world with an internet connection with minimum to zero fees."; "Bitcoin, uppercase 'B', is an online payment system invented in 2008 and released as open-source software in 2009 by a programmer named Satoshi Nakamoto. The system is decentralized and peer-to-peer allowing users to transact directly without needing an intermediary.\nBitcoin is also a platform of which other decentralized applications can be built upon. Bitcoin, lowercase 'b' is the currency unit that Bitcoin uses." = "Bitcoin, uppercase 'B', is an online payment system invented in 2008 and released as open-source software in 2009 by a programmer named Satoshi Nakamoto. The system is decentralized and peer-to-peer allowing users to transact directly without needing an intermediary.\nBitcoin is also a platform of which other decentralized applications can be built upon. Bitcoin, lowercase 'b' is the currency unit that Bitcoin uses."; "Bitcoins can be purchased from various bitcoin exchanges. ArcBit is not a bitcoin exchange. ArcBit is a bitcoin wallet. After you purchase some bitcoins from an exchange, you can move it to a bitcoin wallet." = "Bitcoins can be purchased from various bitcoin exchanges. ArcBit is not a bitcoin exchange. ArcBit is a bitcoin wallet. After you purchase some bitcoins from an exchange, you can move it to a bitcoin wallet."; "Cancel" = "Cancelar"; "Cannot archive your default account" = "No se puede archivar su cuenta predeterminad"; "Cannot archive your one and only account" = "No se puede archivar su primera y única cuenta"; "Cannot create transactions with outputs less then %@" = "No se puede crear transacciones con salidas de menos de %@"; "Cannot decrypt iCloud backup wallet." = "No se puede descifrar la billetera de copia de seguridad de iCloud."; "Cannot import reusable address" = "No se puede importar la dirección reutilizable"; "Change Address ID " = "Dirección de cambio ID "; "Change Automatic Transaction Fee" = "Change Automatic Transaction Fee"; "Change Block Explorer URL" = "Cambiar URL bloque explorador"; "Change Blockexplorer Type" = "Change Blockexplorer Type"; "Check out the ArcBit Brain Wallet" = "Echa un vistazo a la cartera Arcbit cerebro"; "Check out the ArcBit Web Wallet" = "Echa un vistazo a la cartera Arcbit Web"; "Check out the ArcBit Web Wallet!" = "Echa un vistazo a la cartera Arcbit Web!"; "Checking Transaction" = "Comprobación de Transacció"; "Clear account private key from memory" = "Borrar clave privada de la memoria"; "Clear private key from memory" = "Borrar clave privada de la memoria"; "Cleared from memory" = "Borradas de la memoria"; "Click an address" = "Click an address"; "Click the button with the arrow" = "Click the button with the arrow"; "Click the plus button at the top right" = "Click the plus button at the top right"; "Click the ‘Contacts’ button" = "Click the ‘Contacts’ button"; "Click ‘Accounts’" = "Click ‘Accounts’"; "Click ‘Advanced settings’" = "Click ‘Advanced settings’"; "Click ‘Archive Account’" = "Click ‘Archive Account’"; "Click ‘Create New Account’" = "Click ‘Create New Account’"; "Click ‘Delete’" = "Click ‘Delete’"; "Click ‘Done’" = "Click ‘Done’"; "Click ‘Edit Account Name’" = "Click ‘Edit Account Name’"; "Click ‘Edit’" = "Click ‘Edit’"; "Click ‘Enable PIN Code’" = "Click ‘Enable PIN Code’"; "Click ‘History’" = "Click ‘History’"; "Click ‘Import Account’" = "Click ‘Import Account’"; "Click ‘Import Private Key’" = "Click ‘Import Private Key’"; "Click ‘Import Watch Only Account’" = "Click ‘Import Watch Only Account’"; "Click ‘Import Watch Only Address’" = "Click ‘Import Watch Only Address’"; "Click ‘Label transaction’" = "Click ‘Label transaction’"; "Click ‘Restore Wallet’" = "Click ‘Restore Wallet’"; "Click ‘Restore’" = "Click ‘Restore’"; "Click ‘Review Payment’" = "Click ‘Review Payment’"; "Click ‘Send’" = "Click ‘Send’"; "Click ‘Set Transaction Fee’" = "Click ‘Set Transaction Fee’"; "Click ‘Settings’" = "Click ‘Settings’"; "Click ‘Show Backup Passphrase’" = "Click ‘Show Backup Passphrase’"; "Click ‘View Addresses’" = "Click ‘View Addresses’"; "Click ‘View account private key QR code’" = "Click ‘View account private key QR code’"; "Click ‘View account public key QR code’" = "Click ‘View account public key QR code’"; "Click ‘View address QR code’" = "Click ‘View address QR code’"; "Click ‘View in web’" = "Click ‘View in web’"; "Click ‘View private key QR code’" = "Click ‘View private key QR code’"; "Click ‘blockexplorer API type’" = "Click ‘blockexplorer API type’"; "Click ’Receive’" = "Click ’Receive’"; "Close" = "Cerca"; "Cold Wallet" = "Cartera fría"; "Cold Wallet Accounts" = "Cuentas de cartera fría"; "Cold Wallet Accounts can't see reusable address payments, thus this accounts' reusable address is not available." = "Las cuentas de cartera fría no pueden ver los pagos de direcciones reutilizables, por lo que la dirección reutilizable de estas cuentas no está disponible."; "Cold Wallet Overview" = "Descripción general de la cartera frío"; "Cold wallet private keys are not stored here and cannot be viewed" = "Las claves privadas de cartera frío no se almacenan aquí y no se puede ver."; "Complete" = "Completar"; "Complete step 1" = "Complete el paso 1"; "Confirm Payment" = "Confirmar pago"; "Confirm Pin Code" = "Confirmar PIN"; "Contacts" = "Contactos"; "Continue" = "Continuar"; "Copied To clipboard" = "Copiado al portapapeles"; "Copy" = "Dupdo"; "Copy Transaction ID to Clipboard" = "Copia el ID de transacción en el portapapeles"; "Create Cold Wallet" = "Crear Cartera Fría"; "Create New Account" = "Crear una nueva cuenta"; "Create new contact" = "Crear nuevo contacto"; "Customize Fee" = "Personalizar tarifa"; "Decrypting" = "Descifrado"; "Delete" = "Eliminar"; "Delete %@" = "Eliminar %@"; "Delete Account" = "Borrar cuenta"; "Delete Contact" = "Borrar contacto?"; "Delete address" = "Borrar dirección"; "Do not use the QR code from here to receive bitcoins. Go to the Receive screen to get a QR code to receive bitcoins." = "No utilice el código QR desde aquí para recibir bitcoins. Ir a la pantalla de recepción para obtener un código QR para recibir bitcoins."; "Do you like using ArcBit?" = "¿Te gusta usar Arcbit?"; "Do you want to load and backup your current local wallet file?" = "¿Desea cargar y hacer una copia de seguridad de su archivo de billetera local actual?"; "Do you want to load local wallet file?" = "¿Desea cargar el archivo de monedero local?"; "Do you want to restore from your backup passphrase or start a new wallet?" = "¿Quieres restaurar a partir de copia de seguridad de su contraseña o iniciar una nueva cartera?"; "Do you want to temporary import your account private key?" = "¿Quieres importación temporal clave privada de su cuenta?"; "Do you want to temporary import your private key?" = "¿Quieres importación temporal de la clave privada?"; "Don't remind me" = "No me lo recuerdes"; "Done" = "Hecho"; "Each account has a public and private account key. Account keys should be kept secret as they are used to view the account's transactions and spend the account's bitcoins." = "Each account has a public and private account key. Account keys should be kept secret as they are used to view the account's transactions and spend the account's bitcoins."; "Edit" = "Editar"; "Edit Account Name" = "Editar Nombre de Cuenta"; "Edit Contact Name" = "Editar Nombre de Contacto"; "Edit Label" = "Editar etiqueta"; "Edit Transaction label" = "Editar etiqueta de Transacción"; "Email Support" = "Soporte de correo electrónico"; "Enable PIN code in settings to better secure your wallet." = "Habilitar que el código PIN en Ajustes para asegurar mejor su cartera."; "Enable Pin Code" = "Habilitar código PIN"; "Enable Transaction Fee" = "Enable Transaction Fee"; "Enable advanced mode" = "Activar el modo avanzado"; "Encountered error creating transaction. Please try again." = "Se encontró un error al crear una transacción. Por favor intente de nuevo"; "Encrypted" = "Eifrada"; "Enter Label" = "Introduzca la etiqueta"; "Enter PIN" = "Ingrese su PIN"; "Enter a wallet backup passphrase to wipe the current wallet and start/restore another." = "Introduzca una contraseña de copia de seguridad de la cartera para limpiar la cartera actual e iniciar/restaurar otro."; "Enter account private key" = "Introducir la clave privada de la cuenta"; "Enter account public key" = "Introducir clave pública de cuenta"; "Enter address" = "Introducir dirección"; "Enter an account ID and click 'QR Code'. Then on your primary online device, enable Cold Wallet in settings. Then go to the Accounts screen and click 'Import Cold Wallet Account' and scan the Account Public Key QR Code. Afterwards use this cold wallet account as you would a normal account and deposit bitcoins into it. When you want to make a payment from a cold wallet account, go to the next section on the previous screen and follow the step by step instructions there." = "Introduzca un ID de cuenta y haga clic en 'Código QR'. A continuación, en su dispositivo en línea principal, habilitar Cold Wallet en la configuración. A continuación, vaya a la pantalla de cuentas y haga clic en 'Importar cuenta de cartera fría' y escanear el código QR de clave pública de cuenta. Después utilice esta cuenta de cartera fría como lo haría con una cuenta normal y depositará bitcoins en ella. Cuando desee realizar un pago desde una cuenta de cartera fría, vaya a la siguiente sección en la pantalla anterior y siga las instrucciones paso a paso allí."; "Enter backup passphrase" = "Enter backup passphrase"; "Enter passphrase for your iCloud backup wallet." = "Ingrese la frase de contraseña para su billetera de respaldo de iCloud."; "Enter password for encrypted private key" = "Introduzca la contraseña para la clave privada encriptada"; "Enter the 12 word passphrase that belongs to the cold wallet account that you want to make a payment from. This is the passphrase that was used to generate your account public key that was generated on the 'Create Cold Wallet' section found on the previous screen." = "Introduzca la contraseña de 12 palabras que pertenece a la cuenta de cartera fría de la que desea realizar un pago. Esta es la frase de contraseña que se utilizó para generar la clave pública de su cuenta que se generó en la sección 'Crear Cartera fría' encontrada en la pantalla anterior."; "Error" = "Error"; "Error fetching Transaction." = "Error de transacción ir a buscar"; "Error fetching unspent outputs. Try again later." = "Error al recuperar las salidas no utilizadas. Inténtelo de nuevo más tarde."; "Error fetching unspent outputs. Try again." = "Error al recuperar las salidas no utilizadas. Inténtalo de nuevo."; "Error getting block height." = "Error al obtener la altura del bloque"; "Error importing account" = "Error al importar la cuenta"; "Error loading wallet JSON file" = "Error al cargar el archivo JSON de la cartera"; "Explanation" = "Explanation"; "FAQ" = "FAQ"; "Fee:" = "Comisión:"; "Fill address field" = "Fill address field"; "Finished Passing Transaction Data" = "Terminado de pasar datos de transacciones"; "First make sure you are using your secondary offline device for this screen (as mentioned in the overview on the previous screen). Click 'New Wallet' and write down or memorize the generated 12 word passphrase. This passphrase can recover and generate all your accounts and the bitcoins associated with it, so keep it safe and to yourself. Also instead of creating a new wallet, you can also input an existing 12 word passphrase that was generated here to create additional accounts." = "Primero, asegúrese de que está utilizando su dispositivo fuera de línea secundario para esta pantalla (como se menciona en la vista general en la pantalla anterior). Haga clic en 'New Wallet' y anote o memorice la contraseña de 12 palabras generada. Esta frase de paso puede recuperar y generar todas sus cuentas y los bitcoins asociados con él, así que guárdelo seguro ya sí mismo. También en lugar de crear una cartera nueva, también puede introducir una contraseña de 12 palabras existente que se generó aquí para crear cuentas adicionales."; "Follow us on Twitter" = "Síganos en Twitter"; "From:" = "De:"; "Funds have been claimed already." = "Los fondos han sido ya reivindicado."; "Funds imported" = "Fondos importados"; "Ir" = "走"; "Go to the side menu" = "Go to the side menu"; "Have sender scan QR code" = "Have sender scan QR code"; "Have sender send you payment" = "Have sender send you payment"; "Help" = "Ayuda"; "Here are some features that no other mobile bitcoin wallet supports.\n- Reusable address support\n- Ability to import individual account (extended) keys\n- iCloud backup support\n- Over 150 local currencies supported" = "Here are some features that no other mobile bitcoin wallet supports.\n- Reusable address support\n- Ability to import individual account (extended) keys\n- iCloud backup support\n- Over 150 local currencies supported"; "Hierarchical Deterministic Wallet" = "Hierarchical Deterministic Wallet"; "History" = "Historia"; "How To:" = "How To:"; "How do I get bitcoins?" = "How do I get bitcoins?"; "How does ArcBit Wallet work?" = "How does ArcBit Wallet work?"; "Import Account" = "Importar cuenta"; "Import Cold Wallet Account" = "Importar cuenta de cartera fría"; "Import Feature" = "Import Feature"; "Import Private Key" = "Importar clave privada"; "Import Private/Encrypted Key" = "Import Private/Encrypted Key"; "Import Watch Account" = "Importar cuenta del observar"; "Import Watch Address" = "Importar observar dirección"; "Import private key encrypted or unencrypted?" = "Importar clave privada cifrado o sin cifrado?"; "Import with QR code" = "Importar con el código QR"; "Import with text input" = "Importar con la introducción de texto"; "Imported Account %@" = "Importada cuenta %@"; "Imported Accounts" = "Importada Cuentas"; "Imported Address" = "Importada Dirección"; "Imported Addresses" = "Importada Direcciones"; "Imported Cold Wallet Account %@" = "Importada duenta de cartera fría %@"; "Imported Watch Account %@" = "Importada observar de cuenta %@"; "Imported Watch Accounts" = "Cuentas del observar importados"; "Imported Watch Addresses" = "Direcciones Observar Importados"; "Imported Watch Only Accounts can't see reusable address payments, thus this accounts' reusable address is not available. If you want see the reusable address for this account, import the account private key that corresponds to this accounts public key." = "Las cuentas de observar importadas no pueden ver los pagos de direcciones reutilizables, por lo que la dirección reutilizable de estas cuentas no está disponible. Si desea ver la dirección reutilizable para esta cuenta, importe la clave privada de la cuenta que corresponde a esta clave pública de cuentas."; "Importing Account" = "Importación de cuenta"; "Importing Cold Wallet Account" = "Importación de cuenta de cartera fría"; "Importing a Private Key" = "Importing a Private Key"; "Importing a Watch Only Account" = "Importing a Watch Only Account"; "Importing a Watch Only Address" = "Importing a Watch Only Address"; "Importing an Account" = "Importing an Account"; "Importing an encrypted key will require you to input the password every time you want to send bitcoins from it." = "Importar clave cifrada requerirán que introduzca la contraseña cada vez que desee enviar dinero de él."; "In advanced mode, you can import bitcoin keys and addresses from other sources. You can import account private keys, account public keys, private keys, and addresses.\nPlease note that your 12 word passphrase cannot recover your bitcoins, so it is recommended that you backup imported keys and addresses separately." = "In advanced mode, you can import bitcoin keys and addresses from other sources. You can import account private keys, account public keys, private keys, and addresses.\nPlease note that your 12 word passphrase cannot recover your bitcoins, so it is recommended that you backup imported keys and addresses separately."; "Incomplete" = "Incompleto"; "Incorrect passphrase, could not decrypt iCloud wallet backup." = "Frase de contraseña incorrecta, no se pudo descifrar la copia de seguridad de iCloud wallet."; "Input a bitcoin address" = "Input a bitcoin address"; "Input a label" = "Input a label"; "Input a new label" = "Input a new label"; "Input a recommended amount. Somewhere between %@ and %@ BTC" = "Input a recommended amount. Somewhere between %@ and %@ BTC"; "Input amount" = "Input amount"; "Input label" = "Input label"; "Input new account name" = "Input new account name"; "Input transaction fee in bitcoins" = "Input transaction fee in bitcoins"; "Instructions" = "Instructions"; "Insufficient Funds" = "Fondos insuficiente"; "Insufficient Funds. Account balance is %@ when %@ is required." = "Fondos insuficientes. saldo de la cuenta es %@ cuando se requiere %@."; "Insufficient Funds. Account contains bitcoin dust. You can only send up to %@ for now." = "Fondos insuficientes. Cuenta bitcoin contiene polvo. Sólo se puede enviar un máximo de %@ por ahora"; "Internal Wallet Data" = "Los datos de la cartera interna"; "Internal account transfer" = "Transferencia de cuenta interna"; "Invalid Address" = "Dirección inválida"; "Invalid Passphrase" = "Frase de contraseña no válido"; "Invalid URL" = "URL invalida"; "Invalid account private key" = "Clave privada de cuenta no válido"; "Invalid account public Key" = "Clave pública de cuenta no válido"; "Invalid amount" = "Monto invalido"; "Invalid backup passphrase" = "Frase de contraseña de respaldo no válido"; "Invalid private key" = "Clave privada no válido"; "Invalid scanned data" = "Datos escaneados no válido"; "Invalid transaction ID" = "ID de transacción no válido"; "It is not recommended that you manually manage private keys yourself. A leak of a private key can lead to the compromise of your accounts." = "No se recomienda administrar manualmente las claves privadas a sí mismo. Una fuga de una clave privada puede conducir al compromiso de sus cuentas."; "It is not recommended that you use a regular bitcoin address for multiple payments, but instead you should import a reusable address. Add address anyways?" = "No se recomienda que utilice una dirección regular para varios pagos, sino que debe importar una dirección reutilizable. Añadir dirección de todos modos?"; "Label" = "Etiqueta"; "Label Transaction" = "Transacción etiqueta"; "Local backup to wallet failed!" = "Error de respaldo local en la billetera!"; "Local wallet will be lost. Are you sure you want to restore wallet from iCloud?" = "La billetera local se perderá. ¿Seguro que quieres restaurar la billetera desde iCloud?"; "Maximum accounts reached" = "Cuentas máximo alcanzado"; "More" = "Más"; "Name" = "Nombre"; "Network Error" = "Error de red"; "New Wallet" = "Nueva cartera"; "New addresses will be automatically generated and cycled for you as you use your current available addresses." = "Nuevas direcciones se generan automáticamente y se reciclan para usted como usted utiliza sus direcciones disponibles actuales."; "Next" = "Siguiente"; "No" = "No"; "None currently" = "Ninguno actualmente"; "Not now" = "Ahora no"; "Now authorize the transaction on your air gap device. When you have done so, click continue on this device to scan the authorized transaction data and make your payment." = "Ahora autorizar la transacción en su dispositivo de separación de aire. Cuando haya hecho clic en continuar en este dispositivo para escanear los datos de las transacciones autorizadas y hacer su pago."; "OK" = "OK"; "On your primary online device, when you want to make a payment from a cold wallet account, simply do it as you normally would on a normal account. When you click 'Send' on the Review Payment screen, instead of the payment going out immediately, you will be prompted to pass the unauthorized transaction data. Then on your secondary offline device, within this screen click 'Scan' to import the transaction so it can be authorized." = "En su dispositivo en línea principal, cuando desea realizar un pago desde una cuenta de cartera fría, simplemente hágalo como normalmente lo haría en una cuenta normal. Al hacer clic en 'Enviar' en la pantalla Revisar pago, en lugar de que el pago salga inmediatamente, se le pedirá que pase los datos de transacción no autorizados. A continuación, en su dispositivo secundario sin conexión, en esta pantalla haga clic en 'Escanear' para importar la transacción para que pueda ser autorizada."; "Once the transaction has been authorized by completing the above two steps, pass the authorized transaction back to your primary online device to finalize your payment." = "Una vez que la transacción haya sido autorizada, completando los dos pasos anteriores, pasar la transacción autorizada de nuevo a su dispositivo en línea primaria para finalizar su pago."; "Other Links" = "Otros enlaces"; "Pass" = "Pasar"; "Passphrase" = "Frase de contraseña"; "Passphrase does not match the transaction" = "Frase de contraseña no coincide con la transacción"; "Password" = "Contraseña"; "Payment Index: %lu" = "Índice de Pago: %lu"; "Private key does not match address" = "私密金鑰不匹配地址"; "Private key missing" = "Clave privada no coincide con la dirección"; "QR code" = "Código QR"; "Quit and re-enter app" = "Quit and re-enter app"; "Rate" = "Tarifa"; "Rate us in the App Store!" = "Nos clasificaría en el App Store!"; "Receive" = "Recibir"; "Receive Payment" = "Receive Payment"; "Receive Payment From Reusable Address" = "Receive Payment From Reusable Address"; "Remind me Later" = "Recuérdame más tarde"; "Restore" = "Restaurar"; "Restore Wallet" = "Restaurar cartera"; "Restore from iCloud" = "Restaurar desde iCloud"; "Restoring Wallet" = "Restaurar cartera"; "Retry" = "Rever"; "Reusable Address Payment Addresses" = "Reutilizable dirección direcciones de pago"; "Reusable Address:" = "Reutilizable Dirección:"; "Reusable Addresses" = "Reusable Addresses"; "Review Payment" = "Revisión de pagos"; "Save" = "Salvar"; "Scan" = "Escanear"; "Scan For Reusable Address Payment" = "Para escanear reutilizable dirección de pago"; "Scan QR Code" = "Escanear código QR"; "Scan for reusable address transaction" = "Analizar en busca de transacción dirección reutilizable"; "Scan next part" = "Scan parte próxima"; "Scroll down to the section ‘Account Actions’" = "Scroll down to the section ‘Account Actions’"; "Select Account" = "Seleccionar cuenta"; "Select and click a blockexplorer API" = "Select and click a blockexplorer API"; "Select and click a transaction" = "Select and click a transaction"; "Select and click an account" = "Select and click an account"; "Select and click an account to receive from" = "Select and click an account to receive from"; "Select and click an account to view it’s transaction history" = "Select and click an account to view it’s transaction history"; "Select and click an address" = "Select and click an address"; "Send" = "Enviar"; "Send Payment" = "Enviar Pago"; "Send To Address In Contacts" = "Send To Address In Contacts"; "Send authorized payment?" = "Envíe el pago autorizado?"; "Sending" = "Enviando"; "Sending payment to a reusable address might take longer to show up then a normal transaction with the blockchain.info API. You might have to wait until at least 1 confirmation for the transaction to show up. This is due to the limitations of the blockchain.info API. For reusable address payments to show up faster, configure your app to use the Insight API in advance settings." = "Enviar el pago a una dirección reutilizable podría tomar más tiempo para aparecer luego de una transacción normal con la API blockchain.info. Es posible que tenga que esperar al menos hasta el 1 de confirmación de la transacción en aparecer. Esto es debido a las limitaciones de la API blockchain.info. Para los pagos de direcciones reutilizables para mostrar más rápido, configurar la aplicación para utilizar la API de Insight en configuraciones avanzadas."; "Sent %@ to %@" = "Enviado %@ a %@"; "Set Transaction Fee in %@" = "Establecer cuota de transacción en %@"; "Settings" = "Ajustes"; "Some funds may be pending confirmation and cannot be spent yet. (Check your account history) Account only has a spendable balance of %@" = "Algunos fondos pueden estar pendientes de confirmación y no se pueden gastar todavía. (Consulte su historial de la cuenta) cuenta solamente tiene un saldo gastable de %@"; "Some people have compared bitcoin addresses to a bank routing number. It is a good analogy, however bitcoin addresses are public. So if you reuse the same bitcoin address for multiple payments like you would a routing number, people will be able to figure out how much bitcoin you have. Thus it is recommended that you only use one address per payment.\nThis causes usability issues making the user use a new address whenever receiving a payment is cumbersome.\nStealth/reusable addresses provides a better solution. When you give a sender a reusable address, the sender will derive a one time regular bitcoin address from the reusable address. Then the sender will send a payment to that regular bitcoin address. Now you can give many people just one reusable address and have them all send you payments without letting other people know how much bitcoin you have.\nA reusable address looks like this vJmxthatTBXibYe9aZavx18iAT9gyiJETGkhwPX2WbHQGuzX83YvQXynD2t8yHU4Xjfonu5x9m6B4yxquytFP1c2CRbVR9mecxesvE. A reusable address is a lot longer then a regular bitcoin address, it is 102 characters in length.\nReusable addresses are great, however there are no other mobile bitcoin wallets but ArcBit that supports reusable addresses for now. Which is why ArcBit supports receiving payments from both regular bitcoin addresses and reusable addresses.\nFor each account, you have one reusable address. You can find it on your Receive screen. Swipe all the way to right on the QRCode on your Receive screen and you will find a reusable address." = "Some people have compared bitcoin addresses to a bank routing number. It is a good analogy, however bitcoin addresses are public. So if you reuse the same bitcoin address for multiple payments like you would a routing number, people will be able to figure out how much bitcoin you have. Thus it is recommended that you only use one address per payment.\nThis causes usability issues making the user use a new address whenever receiving a payment is cumbersome.\nStealth/reusable addresses provides a better solution. When you give a sender a reusable address, the sender will derive a one time regular bitcoin address from the reusable address. Then the sender will send a payment to that regular bitcoin address. Now you can give many people just one reusable address and have them all send you payments without letting other people know how much bitcoin you have.\nA reusable address looks like this vJmxthatTBXibYe9aZavx18iAT9gyiJETGkhwPX2WbHQGuzX83YvQXynD2t8yHU4Xjfonu5x9m6B4yxquytFP1c2CRbVR9mecxesvE. A reusable address is a lot longer then a regular bitcoin address, it is 102 characters in length.\nReusable addresses are great, however there are no other mobile bitcoin wallets but ArcBit that supports reusable addresses for now. Which is why ArcBit supports receiving payments from both regular bitcoin addresses and reusable addresses.\nFor each account, you have one reusable address. You can find it on your Receive screen. Swipe all the way to right on the QRCode on your Receive screen and you will find a reusable address."; "Spending from a cold wallet account" = "Pasar de una cuenta de cartera fría"; "Start fresh" = "Empezar de nuevo"; "Start/Restore Another Wallet" = "Start/Restore Another Wallet"; "Starting Change address ID:" = "Inicio de la ID de dirección de cambio:"; "Starting Receiving Address ID:" = "Inicio de la ID de dirección de recepción:"; "Step 1: Scan transaction to authorize" = "Paso 1: Escanear para autorizar la transacción"; "Step 2: Input 12 word backup passphrase" = "Paso 2: Entrada 12 palabra frase de contraseña de copia de seguridad"; "Step 3: Pass authorized transaction data" = "Paso 3: Pasar los datos de transacciones autorizadas"; "Steps" = "Steps"; "Success" = "Éxito"; "Swipe right on an address" = "Swipe right on an address"; "Swipe to the right on the QR Code Image until you see the reusable address" = "Swipe to the right on the QR Code Image until you see the reusable address"; "Temporarily import account private key" = "Cuenta de clave privada importación temporal"; "Temporarily import private key" = "Clave privada importación temporal"; "The cold wallet feature will allow you to create accounts which offer better security compared to normal online wallets. You will need 2 devices to use this feature. Your normal day to day device that is connected to the internet and a secondary device that is not connected to the internet (Your secondary device would need to be online once to download the ArcBit app. Afterwards keep the secondary device offline for maximum security). This feature allows you to authorize bitcoin payments from an offline device so that the keys to your bitcoins will never need to be stored on your online device. Follow the step by step instructions by clicking the info buttons within the below sections." = "La función Cartera fría le permitirá crear cuentas que ofrecen una mayor seguridad en línea a continuación, carteras normales. Se necesitan 2 dispositivos para utilizar esta función. Su día normal de dispositivo de día que está conectado a Internet y un dispositivo secundario que no está conectado a Internet (Su dispositivo secundario tendría que estar en línea una vez que descargar la aplicación Arcbit. Posteriormente a mantener el dispositivo fuera de línea secundaria para la seguridad máxima). Esta característica le permite autorizar pagos bitcoin de un dispositivo de conexión para que las llaves de sus bitcoins no tendrán que ser Store en el dispositivo en línea. Siga las instrucciones paso a paso haciendo clic en los botones de información dentro de las siguientes secciones"; "The cold wallet feature will allow you to create accounts which offer better security compared to normal online wallets. You will need 2 devices to use this feature. Your normal day to day device that is connected to the internet and a secondary device that is not connected to the internet (Your secondary device would need to be online once to download the ArcBit app. Afterwards keep the secondary device offline for maximum security). This feature allows you to authorize bitcoin payments from an offline device so that the keys to your bitcoins will never need to be stored on your online device. You can enable the cold wallet feature by going into advanced settings." = "The cold wallet feature will allow you to create accounts which offer better security compared to normal online wallets. You will need 2 devices to use this feature. Your normal day to day device that is connected to the internet and a secondary device that is not connected to the internet (Your secondary device would need to be online once to download the ArcBit app. Afterwards keep the secondary device offline for maximum security). This feature allows you to authorize bitcoin payments from an offline device so that the keys to your bitcoins will never need to be stored on your online device. You can enable the cold wallet feature by going into advanced settings."; "This account type can't see reusable address payments" = "Este tipo de cuenta no puede ver los pagos de direcciones reutilizables"; "This feature allows you to manually input a transaction ID and see if the corresponding transaction contains a reusable address payment to your reusable address. If so, then the funds will be added to your wallet. Normally the app will discover reusable address payments automatically for you, but if you believe a payment is missing you can use this feature." = "Esta característica le permite introducir manualmente un identificador de transacción y ver si la transacción correspondiente contiene un pago reenvío a su dirección reutilizable. Si es así, entonces los fondos se añadirán a su cartera. Normalmente, la aplicación va a descubrir los pagos de reenvío automáticamente para usted, pero si usted cree que un pago no se encuentra usted puede utilizar esta función."; "To:" = "A:"; "Today" = "Hoy"; "Toggle Automatic Transaction Fee" = "Toggle Automatic Transaction Fee"; "Toggle ‘Enable Transaction Fee’" = "Toggle ‘Enable Transaction Fee’"; "Toggle ’Enable advanced mode’" = "Toggle ’Enable advanced mode’"; "Total:" = "Total:"; "Transaction %@ already accounted for." = "Transacción %@ ya contabilizados."; "Transaction %@ does not belong to this account." = "Transacción %@ no pertenece a esta cuenta."; "Transaction Fee" = "Comisión de transacción"; "Transaction ID" = "ID de transacción"; "Transaction ID: %@" = "ID de transacción: %@"; "Transaction authorized" = "Transacción autorizada"; "Transaction confirmations" = "Transaction confirmations"; "Transaction fees impact how quickly the Bitcoin network will confirm your transactions. Higher fees means faster confirmation times. Default fee behavior can be configured in settings." = "Las comisiones de transacción afectan la rapidez con que la red de Bitcoin confirmará sus transacciones. Las tarifas más altas significan tiempos de confirmación más rápidos. El comportamiento de pago predeterminado se puede configurar en la configuración."; "Transaction is not a reusable address transaction." = "La transacción no es una transacción dirección reutilizable."; "Transaction needs to be authorized by an offline and air gap device. Send transaction to an offline device for authorization?" = "Transacción debe ser autorizada por un dispositivo fuera de línea y espacio de aire. Enviar a un dispositivo de transacción fuera de línea para la autorización?"; "Transaction needs to be passed back to your online device in order for the payment to be sent" = "Transacción debe ser aprobada de nuevo a su dispositivo en línea para que el pago que se enviará"; "Try Again" = "Inténtalo de nuevo"; "Try our new cold wallet feature!" = "Pruebe nuestra nueva función de cartera frío!"; "URL does not contain an address." = "La URL no contiene una dirección."; "Unable to get dynamic fees. Falling back on fixed transaction fee. (fee can be configured on review payment)" = ">No es posible obtener tasas dinámicas. Volver a caer sobre el precio de la transacción fijo. (De pago se puede configurar mediante el pago revisión)"; "Unarchive Account" = "Desarchivar cuenta"; "Unarchive address" = "Desarchivar dirección"; "Unarchived address" = "Dirección desarchivados"; "Unconfirmed" = "Inconfirmado"; "Unencrypted" = "Sin cifrar"; "Use ArcBit on your browser to complement the mobile app. The web wallet has all the features that the mobile wallet has plus more!" = "Utilice cartera Arcbit Web para complementar la aplicación móvil. La cartera web tiene todas las características que la billetera móvil tiene mucho más!"; "Use all funds" = "Usa todos los fondos"; "View Account Address" = "View Account Address"; "View Account Address In Web" = "View Account Address In Web"; "View Account Addresses" = "View Account Addresses"; "View Account Private Key" = "View Account Private Key"; "View Account Public Key" = "View Account Public Key"; "View Achievements" = "View Achievements"; "View Addresses" = "Ver Direcciones"; "View ArcBit Brain Wallet Details" = "Ver cartera Arcbit cerebrales detalles"; "View ArcBit Web Wallet Details" = "Ver cartera Arcbit web detalles"; "View History" = "View History"; "View Private Key" = "View Private Key"; "View Transaction In Web" = "View Transaction In Web"; "View account private key QR code" = "Ver código QR clave privada de cuenta"; "View account public key QR code" = "Ver código QR clave pública de cuenta"; "View address QR code" = "Ver dirección de código QR"; "View address in web" = "Ver dirección en la Web"; "View in web" = "Ver en la Web"; "View private key QR code" = "Ver código QR clave privada"; "Visit our home page" = "Visita nuestra página de inicio"; "Wallet backup passphrase" = "Contraseña de copia de seguridad"; "Warning" = "Advertencia"; "Welcome to ArcBit, a user only controlled Bitcoin wallet. Start using the app now by depositing your Bitcoins here." = "Bienvenido a Arcbit, un usuario sólo controlado Bitcoin cartera. Comience a utilizar la aplicación ahora depositando sus bitcoins aquí."; "Welcome!" = "¡Bienvenido!"; "What are Account/Extended Keys?" = "What are Account/Extended Keys?"; "What are accounts?" = "What are accounts?"; "What are reusable addresses?" = "What are reusable addresses?"; "What are the benefits and advantages of Bitcoin?" = "What are the benefits and advantages of Bitcoin?"; "What are transaction confirmations?" = "What are transaction confirmations?"; "What is ArcBit's cold wallet feature?" = "What is ArcBit's cold wallet feature?"; "What is Bitcoin?" = "What is Bitcoin?"; "What is a bitcoin wallet?" = "What is a bitcoin wallet?"; "What makes ArcBit different from other bitcoin wallets?" = "What makes ArcBit different from other bitcoin wallets?"; "With an ArcBit cold wallet feature, you can create wallets and make payments offline without exposing your private keys to an internet connected device. This feature is great for storing large amounts of bitcoin or for the security conscious minded. Check out this feature in the cold wallet section in the side menu." = "Con una función Cartera fría Arcbit, puede crear carteras y realizar pagos en línea sin exponer sus claves privadas a un dispositivo conectado a Internet. Esta característica es ideal para el almacenamiento de grandes cantidades de bitcoins o para la seguridad de mente consciente. Echa un vistazo a esta característica en la sección de cartera frío en el menú lateral."; "Write down backup passphrase" = "Write down backup passphrase"; "Write down or memorize your 12 word wallet backup passphrase. You can view it by clicking \"Show backup passphrase\" in Settings. Your wallet backup passphrase is needed to recover your bitcoins." = "Anotar o memorizar el 12 palabra frase de contraseña de copia de seguridad de la cartera que se puede encontrar en la configuración. Se necesita su contraseña de copia de seguridad de la cartera para recuperar sus bitcoins."; "Write down the 12 word passphrase below and keep it safe. This passphrase alone can restore your entire wallets\' bitcoins (excluding imports)." = "Anote la contraseña de 12 palabras abajo y manténgala segura. Esta frase de contraseña solo puede restaurar los bitcoins de todas sus carteras (excluyendo las importaciones)."; "Write down the 12 word passphrase below and keep it safe. This passphrase alone can restore your entire wallets\' bitcoins." = "Anote la contraseña de 12 palabras abajo y manténgala segura. Esta frase de contraseña solo puede restaurar los bitcoins de toda su cartera."; "Yes" = "Sí"; "You are making a payment to a reusable address. Make sure that the receiver can see the payment made to them. (All ArcBit reusable addresses are compatible with other ArcBit wallets)" = "Usted está haciendo un pago a una dirección reutilizable. Asegúrese de que el receptor puede ver el pago hecho a ellos. (Todas las direcciones reutilizables Arcbit son compatibles con otras carteras ArcBit)"; "You have %@, but %@ is needed. (This includes the transactions fee)" = "Tienes %@, pero se necesita %@. (Esto incluye la cuota de transacciones)"; "You must exit and kill this app in order for this to take effect." = "Debe salir y matar esta aplicación para que esto tenga efecto."; "Your current wallet will be deleted. Your can restore your current wallet later with the wallet passphrase, but any imported accounts or addresses created in advanced mode cannot be recovered. Do you wish to continue?" = "Se eliminará su cartera actual. Su puede restaurar su cartera actual más tarde con la frase de cartera, pero ninguna cuenta o direcciones creadas en el modo avanzado importados no se pueden recuperar. ¿Desea continuar?"; "Your iCloud backup was last saved on %@. Do you want to restore your wallet from iCloud or backup your local wallet to iCloud?" = "Su copia de seguridad de iCloud se guardó por última vez en %@. ¿Desea restaurar su billetera desde iCloud o hacer una copia de seguridad de su billetera local en iCloud?"; "Your new transaction fee is too high" = "Su nueva tarifa de transacción es demasiado alto"; "Your wallet is now restored" = "Su cartera ahora se restaura"; "\nAllow camera access in\n Settings->Privacy->Camera->%@" = "\nPermitir acceso a la cámara en\n Configuraciones->Intimidad->Cámara->%@"; "\tArcBit Web Wallet is a Chrome extension. It has all the features of the mobile wallet plus more. Highlights include the ability to create multiple wallets instead of just one, and a new non-cumbersome way to generate wallets, store and spend bitcoins all from cold storage! ArcBit's new way to manage your cold storage bitcoins also offers a more compelling reason to use ArcBit's watch account feature. Now you can safely watch the balance of your cold storage bitcoins by enabling advance mode in ArcBit and importing your cold storage account public keys.\n\tUse ArcBit Web Wallet in whatever way you wish. You can create a new wallet, or you can input your current 12 word backup passphrase to manage the same bitcoins across different devices. Check out the ArcBit Web Wallet in the Chrome Web Store for more details!\n" = "\tCartera Arcbit web es una extensión de Chrome. Tiene todas las características de la cartera móvil y más. Lo más destacado es la posibilidad de crear varias carteras en lugar de sólo una, y una nueva forma no engorrosa para generar carteras, almacenar y gastar bitcoins todo desde el almacenamiento en frío! La nueva forma de ArcBit para administrar tus bitcoins de almacenamiento en frío también ofrece una razón más convincente para usar la función de cuenta de reloj de ArcBit. Ahora puede ver con seguridad el balance de sus bitcoins de almacenamiento en frío al habilitar el modo avanzado en ArcBit e importar las claves públicas de su cuenta de almacenamiento en frío.\n\tUtilice cartera Arcbit web de la forma que desee. Puede crear una cartera nueva o puede introducir su contraseña de copia de seguridad actual de 12 palabras para administrar los mismos bitcoins entre dispositivos diferentes. Echa un vistazo a cartera Arcbit web en la Chrome Web Store para obtener más detalles.\n"; "\tWith the Arcbit Brain Wallet you can safely spend your bitcoins without ever having your private keys be exposed to the internet. It can be use in conjunction with your Arcbit Wallet or as a stand alone wallet.\n" = "\tCon el Arcbit Brain Wallet puede pasar con seguridad sus bitcoins sin tener sus claves privadas expuestas a Internet. Se puede utilizar en conjunto con su cartera de Arcbit o como una cartera independiente.\n"; "iCloud Error: %@" = "Error de iCloud: %@"; "iCloud backup found" = "Copia de seguridad de iCloud encontrada"; "iCloud backup not found" = "No se encontró la copia de seguridad de iCloud"; "iCloud backup will be lost. Are you sure you want to backup your local wallet to iCloud?" = "La copia de seguridad de iCloud se perderá. ¿Seguro que quieres hacer una copia de seguridad de tu billetera local en iCloud?"; "Wallet backup passphrase will be shown" = "Se mostrará la contraseña de copia de seguridad cartera"; "Write down or memorize your wallet backup passphrase. If you lose your backup passphrase, your wallet cannot be recovered." = "Escriba o memorice su contraseña de contraseña de la billetera. Si pierde su frase de contraseña de respaldo, su billetera no podrá recuperarse."; "I understand" = "entiendo"; "iCloud support for ArcBit discontinued" = "El soporte de iCloud para ArcBit se descontinúa"; "iCloud support for ArcBit is being discontinued. If your backup passphrase has not been backed up already, please do so." = "El soporte de iCloud para ArcBit se descontinúa. Si su frase de paso de seguridad no se ha respaldado ya, hágalo."; "Reusable address payments are disabled until further notice." = "Los pagos de direcciones reutilizables están deshabilitados hasta nuevo aviso."; ================================================ FILE: ArcBit/model/TLAccountObject.swift ================================================ // // TLAccountObject.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation @objc class TLAccountObject: NSObject { let ACCOUNT_UNUSED_ACTIVE_MAIN_ADDRESS_AHEAD_OF_LATEST_USED_ONE_MINIMUM_COUNT = 5 let ACCOUNT_UNUSED_ACTIVE_CHANGE_ADDRESS_AHEAD_OF_LATEST_USED_ONE_MINIMUM_COUNT = 5 let GAP_LIMIT = 20 let MAX_ACTIVE_MAIN_ADDRESS_TO_HAVE = 55 let MAX_ACTIVE_CHANGE_ADDRESS_TO_HAVE = 55 let EXTENDED_KEY_DEFAULT_ACCOUNT_NAME_LENGTH = 50 let MAX_CONSOLIDATE_STEALTH_PAYMENT_UTXOS_COUNT:Int = 12 var appWallet:TLWallet? fileprivate var accountDict: NSMutableDictionary? lazy var haveUpDatedUTXOs: Bool = false lazy var unspentOutputsCount: Int = 0 lazy var stealthPaymentUnspentOutputsCount: Int = 0 var unspentOutputs: NSMutableArray? var stealthPaymentUnspentOutputs: NSMutableArray? fileprivate var mainActiveAddresses = [String]() fileprivate var changeActiveAddresses = [String]() fileprivate var activeAddressesDict = [String:Bool]() fileprivate var mainArchivedAddresses = [String]() fileprivate var changeArchivedAddresses = [String]() fileprivate var address2BalanceDict = [String:TLCoin]() fileprivate var address2HDIndexDict = [String:Int]() fileprivate var address2IsMainAddress = [String:Bool]() fileprivate var address2NumberOfTransactions = [String:Int]() fileprivate var HDIndexToArchivedMainAddress = [Int:String]() fileprivate var HDIndexToArchivedChangeAddress = [Int:String]() fileprivate var txObjectArray = [TLTxObject]() fileprivate var txidToAccountAmountDict = [String:TLCoin]() fileprivate var txidToAccountAmountTypeDict = [String:Int]() fileprivate var receivingAddressesArray = [String]() fileprivate var processedTxSet:NSMutableSet = NSMutableSet() fileprivate var accountType: TLAccountType? var accountBalance = TLCoin.zero() fileprivate var totalUnspentOutputsSum: TLCoin? fileprivate var fetchedAccountData = false var listeningToIncomingTransactions = false fileprivate var positionInWalletArray = 0 fileprivate var extendedPrivateKey: String? var stealthWallet: TLStealthWallet? var downloadState:TLDownloadState = .notDownloading class func MAX_ACCOUNT_WAIT_TO_RECEIVE_ADDRESS() -> (Int) { return 5 } class func NUM_ACCOUNT_STEALTH_ADDRESSES() -> (Int) { return 1 } fileprivate func setUpActiveMainAddresses() -> () { mainActiveAddresses = [String]() let addressesArray = accountDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_MAIN_ADDRESSES) as! NSMutableArray let minAddressIdx = accountDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_MIN_MAIN_ADDRESS_IDX) as! Int let startIdx: Int if (TLWalletUtils.STATIC_MEMBERS.SHOULD_SAVE_ARCHIVED_ADDRESSES_IN_JSON) { startIdx = minAddressIdx } else { startIdx = 0 } for i in stride(from: startIdx, to: addressesArray.count, by: 1) { let addressDict = addressesArray.object(at: i) as! NSDictionary let HDIndex = addressDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_INDEX) as! Int let address = addressDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_ADDRESS) as! String address2HDIndexDict[address] = HDIndex address2IsMainAddress[address] = true address2BalanceDict[address] = TLCoin.zero() address2NumberOfTransactions[address] = 0 mainActiveAddresses.append(address) activeAddressesDict[address] = true } } fileprivate func setUpActiveChangeAddresses() -> () { changeActiveAddresses = [String]() let addressesArray = accountDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_CHANGE_ADDRESSES) as! NSMutableArray let minAddressIdx = accountDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_MIN_CHANGE_ADDRESS_IDX) as! Int var startIdx = 0 if (TLWalletUtils.STATIC_MEMBERS.SHOULD_SAVE_ARCHIVED_ADDRESSES_IN_JSON) { startIdx = minAddressIdx } else { startIdx = 0 } for i in stride(from: startIdx, to: addressesArray.count, by: 1) { let addressDict = addressesArray.object(at: i) as! NSDictionary let HDIndex = addressDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_INDEX) as! Int let address = addressDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_ADDRESS) as! String address2HDIndexDict[address] = HDIndex address2IsMainAddress[address] = false address2BalanceDict[address] = TLCoin.zero() address2NumberOfTransactions[address] = 0 changeActiveAddresses.append(address) activeAddressesDict[address] = true } } fileprivate func setUpArchivedMainAddresses() -> () { mainArchivedAddresses = [String]() let addressesArray = accountDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_MAIN_ADDRESSES) as! NSMutableArray let maxAddressIdx = accountDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_MIN_MAIN_ADDRESS_IDX) as! Int// - 1 for i in stride(from: 0, to: maxAddressIdx, by: 1) { let addressDict = addressesArray.object(at: i) as! NSDictionary assert(addressDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STATUS) as! Int == Int(TLAddressStatus.archived.rawValue), "") let HDIndex = addressDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_INDEX) as! Int let address = addressDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_ADDRESS) as! String address2HDIndexDict[address] = HDIndex address2IsMainAddress[address] = true address2BalanceDict[address] = TLCoin.zero() address2NumberOfTransactions[address] = 0 mainArchivedAddresses.append(address) } } fileprivate func setUpArchivedChangeAddresses() -> () { changeArchivedAddresses = [String]() let addressesArray = accountDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_CHANGE_ADDRESSES) as! NSMutableArray let maxAddressIdx = accountDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_MIN_CHANGE_ADDRESS_IDX) as! Int// - 1 for i in stride(from: 0, to: maxAddressIdx, by: 1) { let addressDict = addressesArray.object(at: i) as! NSDictionary let HDIndex = addressDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_INDEX) as! Int let address = addressDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_ADDRESS) as! String assert((addressDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STATUS) as! Int) == Int(TLAddressStatus.archived.rawValue), "") address2HDIndexDict[address] = HDIndex address2IsMainAddress[address] = false address2BalanceDict[address] = TLCoin.zero() address2NumberOfTransactions[address] = 0 changeArchivedAddresses.append(address) } } init(appWallet: TLWallet, dict: NSDictionary, accountType at: TLAccountType) { super.init() self.appWallet = appWallet accountType = at accountDict = NSMutableDictionary(dictionary: dict) unspentOutputs = nil totalUnspentOutputsSum = nil extendedPrivateKey = nil txidToAccountAmountTypeDict = [String:Int]() address2BalanceDict = [String:TLCoin]() setUpActiveMainAddresses() setUpActiveChangeAddresses() if (TLWalletUtils.STATIC_MEMBERS.SHOULD_SAVE_ARCHIVED_ADDRESSES_IN_JSON) { setUpArchivedMainAddresses() setUpArchivedChangeAddresses() } else { HDIndexToArchivedMainAddress = [Int:String]() HDIndexToArchivedChangeAddress = [Int:String]() } DLog("\(self.getAccountIdxNumber()) getMainActiveAddressesCount \(self.getMainActiveAddressesCount())") DLog("\(self.getAccountIdxNumber()) getMainAddressesCount \(self.getMainAddressesCount())") DLog("\(self.getAccountIdxNumber()) getChangeActiveAddressesCount \(self.getChangeActiveAddressesCount())") DLog("\(self.getAccountIdxNumber()) getChangeAddressesCount \(self.getChangeAddressesCount())") if (accountType == TLAccountType.hdWallet) { positionInWalletArray = getAccountIdxNumber() } else if (accountType == TLAccountType.coldWallet) { //set later in accounts } else if (accountType == TLAccountType.imported) { //set later in accounts } else if (accountType == TLAccountType.importedWatch) { //set later in accounts } if TLWalletUtils.ALLOW_MANUAL_SCAN_FOR_STEALTH_PAYMENT() && accountType != TLAccountType.importedWatch && accountType != TLAccountType.coldWallet { let stealthAddressArray = accountDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STEALTH_ADDRESSES) as! NSArray let stealthWalletDict = stealthAddressArray.object(at: 0) as! NSDictionary self.stealthWallet = TLStealthWallet(stealthDict: stealthWalletDict, accountObject: self, updateStealthPaymentStatuses: !self.isArchived()) //add default zero balance so that if user goes to address list view before account data downloaded, // then accountObject will return a 0 balance for payment address instead of getting optional unwrap nil error for i in 0 ..< self.stealthWallet!.getStealthAddressPaymentsCount() { let address = self.stealthWallet!.getPaymentAddressForIndex(i) address2BalanceDict[address] = TLCoin.zero() } } } func isWatchOnly() -> (Bool) { return accountType == TLAccountType.importedWatch } func isColdWalletAccount() -> (Bool) { return accountType == TLAccountType.coldWallet } func hasSetExtendedPrivateKeyInMemory() -> (Bool) { assert(accountType == TLAccountType.importedWatch, "") return extendedPrivateKey != nil } func setExtendedPrivateKeyInMemory(_ extendedPrivKey: String) -> Bool { assert(accountType == TLAccountType.importedWatch, "") assert(TLHDWalletWrapper.isValidExtendedPrivateKey(extendedPrivKey), "extendedPrivKey isValidExtendedPrivateKey") if (TLHDWalletWrapper.getExtendPubKey(extendedPrivKey) == getExtendedPubKey()) { extendedPrivateKey = extendedPrivKey return true } return false } func clearExtendedPrivateKeyFromMemory() -> () { assert(accountType == TLAccountType.importedWatch, "") extendedPrivateKey = nil } func hasFetchedAccountData() -> (Bool) { return self.fetchedAccountData } @discardableResult func renameAccount(_ accountName: String) -> (Bool) { accountDict!.setObject(accountName, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_NAME as NSCopying) return true } func getAccountName() -> String { return accountDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_NAME) as! String } func getAccountNameOrAccountPublicKey() -> String { let accountName = accountDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_NAME) as! String return accountName != "" ? accountName : getExtendedPubKey() } func archiveAccount(_ enabled: Bool) -> (Bool) { let status = enabled ? TLAddressStatus.archived : TLAddressStatus.active accountDict!.setObject(status.rawValue, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STATUS as NSCopying) return true } func isArchived() -> (Bool) { return (accountDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STATUS) as! Int) == Int(TLAddressStatus.archived.rawValue) } func getAccountID() -> (String) { let accountIdx = accountDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_ACCOUNT_IDX) as! Int return String(accountIdx) } func getAccountIdxNumber() -> (Int) { return accountDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_ACCOUNT_IDX) as! Int } func getAccountHDIndex() -> UInt32 { return TLHDWalletWrapper.getAccountIdxForExtendedKey(getExtendedPubKey()) as UInt32 } func getExtendedPubKey() -> String { return accountDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_EXTENDED_PUBLIC_KEY) as! String } func getExtendedPrivKey() -> String? { if (accountType == TLAccountType.hdWallet) { return accountDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_EXTENDED_PRIVATE_KEY) as? String } else if (accountType == TLAccountType.imported) { return accountDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_EXTENDED_PRIVATE_KEY) as? String } else if (accountType == TLAccountType.importedWatch) { return extendedPrivateKey } return nil } func getAddressBalance(_ address: String) -> TLCoin { if let amount = address2BalanceDict[address] { return amount } else { return TLCoin.zero() } } func getNumberOfTransactionsForAddress(_ address: String) -> Int { assert(self.isHDWalletAddress(address)) if address2NumberOfTransactions[address] == nil { return 0; } return address2NumberOfTransactions[address]! } func isMainAddress(_ address: String) -> Bool { return address2IsMainAddress[address]! } func getAddressHDIndex(_ address: String) -> Int { return address2HDIndexDict[address]! } func getAccountPrivateKey(_ address: String) -> String? { if self.isHDWalletAddress(address) { if (address2IsMainAddress[address] == true) { return getMainPrivateKey(address) } else { return getChangePrivateKey(address) } } return nil } func getMainPrivateKey(_ address: String) -> String { let HDIndexNumber = address2HDIndexDict[address]! let addressSequence = [Int(TLAddressType.main.rawValue), HDIndexNumber] if (accountType == TLAccountType.importedWatch) { assert(extendedPrivateKey != nil, "") return TLHDWalletWrapper.getPrivateKey(extendedPrivateKey! as! NSString, sequence: addressSequence as NSArray, isTestnet: self.appWallet!.walletConfig.isTestnet) } else { return TLHDWalletWrapper.getPrivateKey(accountDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_EXTENDED_PRIVATE_KEY) as! NSString, sequence: addressSequence as NSArray, isTestnet: self.appWallet!.walletConfig.isTestnet) } } func getChangePrivateKey(_ address: String) -> String { let HDIndexNumber = address2HDIndexDict[address]! let addressSequence = [TLAddressType.change.rawValue, HDIndexNumber] if (accountType == TLAccountType.importedWatch) { assert(extendedPrivateKey != nil, "") return TLHDWalletWrapper.getPrivateKey(extendedPrivateKey! as NSString, sequence: addressSequence as NSArray, isTestnet: self.appWallet!.walletConfig.isTestnet) } else { return TLHDWalletWrapper.getPrivateKey(accountDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_EXTENDED_PRIVATE_KEY) as! NSString, sequence: addressSequence as NSArray, isTestnet: self.appWallet!.walletConfig.isTestnet) } } func getTxObjectCount() -> Int { return txObjectArray.count } func getTxObject(_ txIdx: Int) -> TLTxObject { return txObjectArray[txIdx] } fileprivate func isAddressPartOfAccountActiveChangeAddresses(_ address: String) -> (Bool) { return changeActiveAddresses.index(of: address) != nil } fileprivate func isAddressPartOfAccountActiveMainAddresses(_ address: String) -> Bool { return mainActiveAddresses.index(of: address) != nil } func isActiveAddress(_ address: String) -> Bool { return activeAddressesDict[address] != nil } func isHDWalletAddress(_ address: String) -> Bool { return address2HDIndexDict[address] != nil } func isAddressPartOfAccount(_ address: String) -> Bool { if self.stealthWallet == nil { return self.isHDWalletAddress(address) } else { return self.isHDWalletAddress(address) || self.stealthWallet!.isPaymentAddress(address) } } func getBalance() -> TLCoin { return self.accountBalance } func getAccountType() -> TLAccountType { return accountType! } func getAccountAmountChangeForTx(_ txHash: String) -> TLCoin? { return txidToAccountAmountDict[txHash] } func getAccountAmountChangeTypeForTx(_ txHash: String) -> TLAccountTxType { return TLAccountTxType(rawValue: txidToAccountAmountTypeDict[txHash]!)! } fileprivate func addToAddressBalance(_ address: NSString, amount: TLCoin) -> () { var addressBalance = address2BalanceDict[address as String] if (addressBalance == nil) { addressBalance = amount address2BalanceDict[address as String] = addressBalance! } else { addressBalance! = addressBalance!.add(amount) address2BalanceDict[address as String] = addressBalance! } } fileprivate func subtractToAddressBalance(_ address: String, amount: TLCoin) -> () { var addressBalance = address2BalanceDict[address] if (addressBalance == nil) { addressBalance = TLCoin.zero().subtract(amount) address2BalanceDict[address] = addressBalance! } else { addressBalance = addressBalance!.subtract(amount) address2BalanceDict[address] = addressBalance! } } func processNewTx(_ txObject: TLTxObject) -> TLCoin? { if (processedTxSet.contains(txObject.getHash()!)) { return nil } let receivedAmount = processTx(txObject, shouldCheckToAddressesNTxsCount: true, shouldUpdateAccountBalance: true) txObjectArray.insert(txObject, at: 0) checkToArchiveAddresses() updateReceivingAddresses() updateChangeAddresses() return receivedAmount } fileprivate func processTx(_ txObject: TLTxObject, shouldCheckToAddressesNTxsCount: Bool, shouldUpdateAccountBalance: Bool) -> TLCoin? { haveUpDatedUTXOs = false processedTxSet.add(txObject.getHash()!) var currentTxSubtract:UInt64 = 0 var currentTxAdd:UInt64 = 0 let address2hasUpdatedNTxCount = NSMutableDictionary() // DLog("TLAccountObject processTx: \(self.getAccountID()) \(txObject.getTxid()!)") let outputAddressToValueArray = txObject.getOutputAddressToValueArray() for _output in outputAddressToValueArray! { let output = _output as! NSDictionary var value:UInt64 = 0 if let v = output.object(forKey: "value") as? NSNumber { value = UInt64(v.uint64Value) } if let address = output.object(forKey: "addr") as? String { if (isActiveAddress(address)) { currentTxAdd += value //DLog("addToAddressBalance: \(address) \(value)") if (shouldUpdateAccountBalance) { addToAddressBalance(address as NSString, amount: TLCoin(uint64: value)) } if (shouldCheckToAddressesNTxsCount && address2hasUpdatedNTxCount.object(forKey: address) == nil) { address2hasUpdatedNTxCount.setObject("", forKey: address as NSCopying) let ntxs = getNumberOfTransactionsForAddress(address) address2NumberOfTransactions[address] = ntxs + 1 } } else if self.stealthWallet != nil && self.stealthWallet!.isPaymentAddress(address) { currentTxAdd += value //DLog("addToAddressBalance: stealth \(address) \(value)") if shouldUpdateAccountBalance { addToAddressBalance(address as NSString, amount: TLCoin(uint64: value)) } } else { } } } let inputAddressToValueArray = txObject.getInputAddressToValueArray() for _input in inputAddressToValueArray! { let input = _input as! NSDictionary var value:UInt64 = 0 if let v = input.object(forKey: "value") as? NSNumber { value = UInt64(v.uint64Value) } if let address = input.object(forKey: "addr") as? String { if (isActiveAddress(address)) { currentTxSubtract += value //DLog("subtractToAddressBalance: \(address) \(value)") if (shouldUpdateAccountBalance) { subtractToAddressBalance(address, amount: TLCoin(uint64: value)) } if (shouldCheckToAddressesNTxsCount && address2hasUpdatedNTxCount.object(forKey: address) == nil) { address2hasUpdatedNTxCount.setObject("", forKey: address as NSCopying) let ntxs = getNumberOfTransactionsForAddress(address) address2NumberOfTransactions[address] = ntxs + 1 } } else if self.stealthWallet != nil && self.stealthWallet!.isPaymentAddress(address) { currentTxSubtract += value //DLog("subtractToAddressBalance: stealth \(address) \(value)") if shouldUpdateAccountBalance { subtractToAddressBalance(address, amount: TLCoin(uint64: value)) } } else { } } } //DLog("current processTxprocessTx \(self.accountBalance.toUInt64()) + \(currentTxAdd) - \(currentTxSubtract)") if (shouldUpdateAccountBalance) { self.accountBalance = TLCoin(uint64: self.accountBalance.toUInt64() + currentTxAdd - currentTxSubtract) } if (currentTxSubtract > currentTxAdd) { let amountChangeToAccountFromTx = TLCoin(uint64: UInt64(currentTxSubtract - currentTxAdd)) txidToAccountAmountDict[txObject.getHash()! as String] = amountChangeToAccountFromTx txidToAccountAmountTypeDict[txObject.getHash()! as String] = Int(TLAccountTxType.send.rawValue) return nil } else if (currentTxSubtract < currentTxAdd) { let amountChangeToAccountFromTx = TLCoin(uint64: UInt64(currentTxAdd - currentTxSubtract)) txidToAccountAmountDict[txObject.getHash()! as String] = amountChangeToAccountFromTx txidToAccountAmountTypeDict[txObject.getHash()! as String] = Int(TLAccountTxType.receive.rawValue) return amountChangeToAccountFromTx } else { let amountChangeToAccountFromTx = TLCoin.zero() txidToAccountAmountDict[txObject.getHash()! as String] = amountChangeToAccountFromTx txidToAccountAmountTypeDict[txObject.getHash()! as String] = Int(TLAccountTxType.moveBetweenAccount.rawValue) return nil } } func getReceivingAddressesCount() -> Int { return receivingAddressesArray.count } func getReceivingAddress(_ idx: Int) -> (String) { return receivingAddressesArray[idx] } fileprivate func updateReceivingAddresses() -> () { receivingAddressesArray = [String]() var addressIdx = 0 while addressIdx < mainActiveAddresses.count { let address = mainActiveAddresses[addressIdx] if (getNumberOfTransactionsForAddress(address) == 0) { break } addressIdx += 1 } var lookedAtAllAddresses = false var receivingAddressesStartIdx = -1 while addressIdx < addressIdx + TLAccountObject.MAX_ACCOUNT_WAIT_TO_RECEIVE_ADDRESS() { if (addressIdx >= getMainActiveAddressesCount()) { lookedAtAllAddresses = true break } let address = mainActiveAddresses[addressIdx] if (getNumberOfTransactionsForAddress(address) == 0) { receivingAddressesArray.append(address) if receivingAddressesStartIdx == -1 { receivingAddressesStartIdx = addressIdx } } if (receivingAddressesArray.count >= TLAccountObject.MAX_ACCOUNT_WAIT_TO_RECEIVE_ADDRESS() || addressIdx - receivingAddressesStartIdx >= TLAccountObject.MAX_ACCOUNT_WAIT_TO_RECEIVE_ADDRESS()) { break } addressIdx += 1 } while (lookedAtAllAddresses && receivingAddressesArray.count < TLAccountObject.MAX_ACCOUNT_WAIT_TO_RECEIVE_ADDRESS()) { let address = getNewMainAddress(getMainAddressesCount()) addressIdx += 1 if (addressIdx - receivingAddressesStartIdx < TLAccountObject.MAX_ACCOUNT_WAIT_TO_RECEIVE_ADDRESS()) { receivingAddressesArray.append(address) } else { break } } while (getMainActiveAddressesCount() - addressIdx < ACCOUNT_UNUSED_ACTIVE_MAIN_ADDRESS_AHEAD_OF_LATEST_USED_ONE_MINIMUM_COUNT) { getNewMainAddress(getMainAddressesCount()) } NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_UPDATED_RECEIVING_ADDRESSES()), object: nil) } fileprivate func updateChangeAddresses() -> () { var addressIdx = 0 for i in stride(from: addressIdx, to: changeActiveAddresses.count, by: 1) { let address = changeActiveAddresses[addressIdx] if (getNumberOfTransactionsForAddress(address) == 0) { break } } while (getChangeActiveAddressesCount() - addressIdx < ACCOUNT_UNUSED_ACTIVE_CHANGE_ADDRESS_AHEAD_OF_LATEST_USED_ONE_MINIMUM_COUNT) { getNewChangeAddress(getChangeAddressesCount()) } } fileprivate func checkToArchiveAddresses() -> () { self.checkToArchiveMainAddresses() self.checkToArchiveChangeAddresses() } fileprivate func checkToArchiveMainAddresses() -> () { if (getMainActiveAddressesCount() <= MAX_ACTIVE_MAIN_ADDRESS_TO_HAVE) { return } let activeMainAddresses = getActiveMainAddresses()!.copy() as! NSArray for _address in activeMainAddresses { let address = _address as! String if (getAddressBalance(address).lessOrEqual(TLCoin.zero()) && getNumberOfTransactionsForAddress(address) > 0) { let addressIdx = address2HDIndexDict[address]! let accountIdx = accountDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_ACCOUNT_IDX) as! Int if (TLWalletUtils.STATIC_MEMBERS.SHOULD_SAVE_ARCHIVED_ADDRESSES_IN_JSON) { assert(addressIdx == mainArchivedAddresses.count, "") mainArchivedAddresses.append(address) } else { if (accountType == TLAccountType.hdWallet) { assert(addressIdx == self.appWallet!.getMinMainAddressIdxFromHDWallet(accountIdx), "") } else if (accountType == TLAccountType.imported) { assert(addressIdx == self.appWallet!.getMinMainAddressIdxFromImportedAccount(getPositionInWalletArray()), "") } else { assert(addressIdx == self.appWallet!.getMinMainAddressIdxFromImportedWatchAccount(getPositionInWalletArray()), "") } } assert(mainActiveAddresses.first == address, "") mainActiveAddresses.remove(at: 0) activeAddressesDict.removeValue(forKey: address) if (accountType == TLAccountType.hdWallet) { self.appWallet!.updateMainAddressStatusFromHDWallet(accountIdx, addressIdx: addressIdx, addressStatus: TLAddressStatus.archived) } else if (accountType == TLAccountType.imported) { self.appWallet!.updateMainAddressStatusFromImportedAccount(getPositionInWalletArray(), addressIdx: addressIdx, addressStatus: TLAddressStatus.archived) } else { self.appWallet!.updateMainAddressStatusFromImportedWatchAccount(getPositionInWalletArray(), addressIdx: addressIdx, addressStatus: TLAddressStatus.archived) } } else { return } if (getMainActiveAddressesCount() <= MAX_ACTIVE_MAIN_ADDRESS_TO_HAVE) { return } } } fileprivate func checkToArchiveChangeAddresses() -> () { if (getChangeActiveAddressesCount() <= MAX_ACTIVE_CHANGE_ADDRESS_TO_HAVE) { return } let activeChangeAddresses = getActiveChangeAddresses()!.copy() as! NSArray for _address in activeChangeAddresses { let address = _address as! String if (getAddressBalance(address).lessOrEqual(TLCoin.zero()) && getNumberOfTransactionsForAddress(address) > 0) { let addressIdx = address2HDIndexDict[address]! let accountIdx = accountDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_ACCOUNT_IDX) as! Int if (TLWalletUtils.STATIC_MEMBERS.SHOULD_SAVE_ARCHIVED_ADDRESSES_IN_JSON) { assert(addressIdx == changeArchivedAddresses.count, "") changeArchivedAddresses.append(address) } else { if (accountType == TLAccountType.hdWallet) { assert(addressIdx == self.appWallet!.getMinChangeAddressIdxFromHDWallet(accountIdx), "") } else if (accountType == TLAccountType.imported) { assert(addressIdx == self.appWallet!.getMinChangeAddressIdxFromImportedAccount(getPositionInWalletArray()), "") } else { assert(addressIdx == self.appWallet!.getMinChangeAddressIdxFromImportedWatchAccount(getPositionInWalletArray()), "") } } assert(changeActiveAddresses.first == address, "") changeActiveAddresses.remove(at: 0) activeAddressesDict.removeValue(forKey: address) if (accountType == TLAccountType.hdWallet) { self.appWallet!.updateChangeAddressStatusFromHDWallet(accountIdx, addressIdx: addressIdx, addressStatus: TLAddressStatus.archived) } else if (accountType == TLAccountType.imported) { self.appWallet!.updateChangeAddressStatusFromImportedAccount(getPositionInWalletArray(), addressIdx: addressIdx, addressStatus: TLAddressStatus.archived) } else { self.appWallet!.updateChangeAddressStatusFromImportedWatchAccount(getPositionInWalletArray(), addressIdx: addressIdx, addressStatus: TLAddressStatus.archived) } } else { return } if (getChangeActiveAddressesCount() <= MAX_ACTIVE_CHANGE_ADDRESS_TO_HAVE) { return } } } fileprivate func processTxArray(_ txArray: NSArray, shouldResetAccountBalance: (Bool)) -> () { for _tx in txArray { let tx = _tx as! NSDictionary let txObject = TLTxObject(dict: tx) processTx(txObject, shouldCheckToAddressesNTxsCount: true, shouldUpdateAccountBalance: false) txObjectArray.append(txObject) } if (shouldResetAccountBalance) { checkToArchiveAddresses() updateReceivingAddresses() updateChangeAddresses() } } func getPositionInWalletArray() -> Int { return positionInWalletArray } func setPositionInWalletArray(_ idx: Int) -> () { positionInWalletArray = idx } fileprivate func getNewMainAddress(_ expectedAddressIndex: Int) -> String { let addressDict: NSDictionary if (accountType == TLAccountType.hdWallet) { let accountIdx = accountDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_ACCOUNT_IDX) as! Int addressDict = self.appWallet!.getNewMainAddressFromHDWallet(accountIdx, expectedAddressIndex: expectedAddressIndex) } else if (accountType == TLAccountType.coldWallet) { addressDict = self.appWallet!.getNewMainAddressFromColdWalletAccount(positionInWalletArray, expectedAddressIndex: expectedAddressIndex) } else if (accountType == TLAccountType.imported) { addressDict = self.appWallet!.getNewMainAddressFromImportedAccount(positionInWalletArray, expectedAddressIndex: expectedAddressIndex) } else { addressDict = self.appWallet!.getNewMainAddressFromImportedWatchAccount(positionInWalletArray, expectedAddressIndex: expectedAddressIndex) } let address = addressDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_ADDRESS) as! String let HDIndex = addressDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_INDEX) as! Int address2HDIndexDict[address] = HDIndex address2IsMainAddress[address] = true address2BalanceDict[address] = TLCoin.zero() address2NumberOfTransactions[address] = 0 mainActiveAddresses.append(address) activeAddressesDict[address] = true NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_NEW_ADDRESS_GENERATED()), object: address) return address } fileprivate func getNewChangeAddress(_ expectedAddressIndex: Int) -> (String) { let addressDict: NSDictionary if (accountType == TLAccountType.hdWallet) { let accountIdx = accountDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_ACCOUNT_IDX) as! Int addressDict = self.appWallet!.getNewChangeAddressFromHDWallet(accountIdx, expectedAddressIndex: expectedAddressIndex) } else if (accountType == TLAccountType.coldWallet) { addressDict = self.appWallet!.getNewChangeAddressFromColdWalletAccount(UInt(positionInWalletArray), expectedAddressIndex: expectedAddressIndex) } else if (accountType == TLAccountType.imported) { addressDict = self.appWallet!.getNewChangeAddressFromImportedAccount(positionInWalletArray, expectedAddressIndex: expectedAddressIndex) } else { addressDict = self.appWallet!.getNewChangeAddressFromImportedWatchAccount(UInt(positionInWalletArray), expectedAddressIndex: expectedAddressIndex) } let address = addressDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_ADDRESS) as! String let HDIndex = addressDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_INDEX) as! Int address2HDIndexDict[address] = HDIndex address2IsMainAddress[address] = false address2BalanceDict[address] = TLCoin.zero() address2NumberOfTransactions[address] = 0 changeActiveAddresses.append(address) activeAddressesDict[address] = true NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_NEW_ADDRESS_GENERATED()), object: address) return address } fileprivate func removeTopMainAddress() -> (Bool) { if (accountType == TLAccountType.hdWallet) { let accountIdx = accountDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_ACCOUNT_IDX) as! Int self.appWallet!.removeTopMainAddressFromHDWallet(accountIdx)! } else if (accountType == TLAccountType.coldWallet) { self.appWallet!.removeTopMainAddressFromColdWalletAccount(positionInWalletArray)! } else if (accountType == TLAccountType.imported) { self.appWallet!.removeTopMainAddressFromImportedAccount(positionInWalletArray)! } else if (accountType == TLAccountType.importedWatch) { self.appWallet!.removeTopMainAddressFromImportedWatchAccount(positionInWalletArray)! } if (mainActiveAddresses.count > 0) { let address = mainActiveAddresses.last! address2HDIndexDict.removeValue(forKey: address) address2BalanceDict.removeValue(forKey: address) address2NumberOfTransactions.removeValue(forKey: address) mainActiveAddresses.removeLast() activeAddressesDict.removeValue(forKey: address) return true } else if (TLWalletUtils.STATIC_MEMBERS.SHOULD_SAVE_ARCHIVED_ADDRESSES_IN_JSON) { if (mainArchivedAddresses.count > 0) { let address = mainArchivedAddresses.last! address2HDIndexDict.removeValue(forKey: address) address2BalanceDict.removeValue(forKey: address) address2NumberOfTransactions.removeValue(forKey: address) mainArchivedAddresses.removeLast() activeAddressesDict.removeValue(forKey: address) } return true } return false } fileprivate func removeTopChangeAddress() -> (Bool) { if (accountType == TLAccountType.hdWallet) { let accountIdx = accountDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_ACCOUNT_IDX) as! Int self.appWallet!.removeTopChangeAddressFromHDWallet(accountIdx)! } else if (accountType == TLAccountType.coldWallet) { self.appWallet!.removeTopChangeAddressFromColdWalletAccount(positionInWalletArray)! } else if (accountType == TLAccountType.imported) { self.appWallet!.removeTopChangeAddressFromImportedAccount(positionInWalletArray)! } else if (accountType == TLAccountType.importedWatch) { self.appWallet!.removeTopChangeAddressFromImportedWatchAccount(positionInWalletArray)! } if (changeActiveAddresses.count > 0) { let address = changeActiveAddresses.last! address2HDIndexDict.removeValue(forKey: address) address2BalanceDict.removeValue(forKey: address) address2NumberOfTransactions.removeValue(forKey: address) changeActiveAddresses.removeLast() activeAddressesDict.removeValue(forKey: address) return true } else if (TLWalletUtils.STATIC_MEMBERS.SHOULD_SAVE_ARCHIVED_ADDRESSES_IN_JSON) { if (changeArchivedAddresses.count > 0) { let address = changeArchivedAddresses.last! address2HDIndexDict.removeValue(forKey: address) address2BalanceDict.removeValue(forKey: address) address2NumberOfTransactions.removeValue(forKey: address) changeArchivedAddresses.removeLast() activeAddressesDict.removeValue(forKey: address) } return true } return false } func getCurrentChangeAddress() -> String { for address in changeActiveAddresses { if getNumberOfTransactionsForAddress(address) == 0 && self.getAddressBalance(address).equalTo(TLCoin.zero()) { return address } } return getNewChangeAddress(getChangeAddressesCount()) } func getActiveMainAddresses() -> NSArray? { return mainActiveAddresses as NSArray? } func getActiveChangeAddresses() -> NSArray? { return changeActiveAddresses as NSArray? } func getMainActiveAddressesCount() -> Int { return mainActiveAddresses.count } func getMainArchivedAddressesCount() -> Int { if (TLWalletUtils.STATIC_MEMBERS.SHOULD_SAVE_ARCHIVED_ADDRESSES_IN_JSON) { return mainArchivedAddresses.count } else { if (accountType == TLAccountType.hdWallet) { let accountIdx = accountDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_ACCOUNT_IDX) as! Int return self.appWallet!.getMinMainAddressIdxFromHDWallet(accountIdx) } else if (accountType == TLAccountType.coldWallet) { return self.appWallet!.getMinMainAddressIdxFromColdWalletAccount(getPositionInWalletArray()) } else if (accountType == TLAccountType.imported) { return self.appWallet!.getMinMainAddressIdxFromImportedAccount(getPositionInWalletArray()) } else { return self.appWallet!.getMinMainAddressIdxFromImportedWatchAccount(getPositionInWalletArray()) } } } fileprivate func getMainAddressesCount() -> Int { return getMainActiveAddressesCount() + getMainArchivedAddressesCount() } func getChangeActiveAddressesCount() -> Int { return changeActiveAddresses.count } func getChangeArchivedAddressesCount() -> Int { if (TLWalletUtils.STATIC_MEMBERS.SHOULD_SAVE_ARCHIVED_ADDRESSES_IN_JSON) { return changeArchivedAddresses.count } else { if (accountType == TLAccountType.hdWallet) { let accountIdx = accountDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_ACCOUNT_IDX) as! Int return self.appWallet!.getMinChangeAddressIdxFromHDWallet(accountIdx) } else if (accountType == TLAccountType.coldWallet) { return self.appWallet!.getMinChangeAddressIdxFromColdWalletAccount(getPositionInWalletArray()) } else if (accountType == TLAccountType.imported) { return self.appWallet!.getMinChangeAddressIdxFromImportedAccount(getPositionInWalletArray()) } else { return self.appWallet!.getMinChangeAddressIdxFromImportedWatchAccount(getPositionInWalletArray()) } } } fileprivate func getChangeAddressesCount() -> Int { return getChangeActiveAddressesCount() + getChangeArchivedAddressesCount() } func getMainActiveAddress(_ idx: Int) -> String { return mainActiveAddresses[idx] } func getChangeActiveAddress(_ idx: Int) -> String { return changeActiveAddresses[idx] } func getMainArchivedAddress(_ idx: Int) -> String { if (TLWalletUtils.STATIC_MEMBERS.SHOULD_SAVE_ARCHIVED_ADDRESSES_IN_JSON) { return mainArchivedAddresses[idx] } else { let HDIndex = idx var address = HDIndexToArchivedMainAddress[HDIndex] if (address == nil) { let extendedPublicKey = accountDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_EXTENDED_PUBLIC_KEY) as! String let mainAddressSequence = [Int(TLAddressType.main.rawValue), idx] address = TLHDWalletWrapper.getAddress(extendedPublicKey, sequence: mainAddressSequence as NSArray, isTestnet: self.appWallet!.walletConfig.isTestnet) HDIndexToArchivedMainAddress[HDIndex] = address! address2HDIndexDict[address!] = HDIndex address2IsMainAddress[address!] = true } return address! } } func getChangeArchivedAddress(_ idx: Int) -> String { if (TLWalletUtils.STATIC_MEMBERS.SHOULD_SAVE_ARCHIVED_ADDRESSES_IN_JSON) { return changeArchivedAddresses[idx] } else { let HDIndex = idx var address = HDIndexToArchivedChangeAddress[HDIndex] if (address == nil) { let extendedPublicKey = accountDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_EXTENDED_PUBLIC_KEY) as! String let changeAddressSequence = [Int(TLAddressType.change.rawValue), idx] address = TLHDWalletWrapper.getAddress(extendedPublicKey, sequence: changeAddressSequence as NSArray, isTestnet: self.appWallet!.walletConfig.isTestnet) HDIndexToArchivedChangeAddress[HDIndex] = address! address2HDIndexDict[address!] = HDIndex address2IsMainAddress[address!] = false } return address! } } func recoverAccountMainAddresses(_ shouldResetAccountBalance: Bool) -> Int { var lookAheadOffset = 0 var continueLookingAheadAddress = true DLog("recoverAccountMainAddresses: getAccountID: \(getAccountID())") var accountAddressIdx = -1 while (continueLookingAheadAddress) { var addresses = [String]() addresses.reserveCapacity(GAP_LIMIT) let addressToIdxDict = NSMutableDictionary(capacity: GAP_LIMIT) for i in stride(from: lookAheadOffset, to: lookAheadOffset + GAP_LIMIT, by: 1) { let address = getNewMainAddress(i) DLog(String(format:"getNewMainAddress HDIdx: %lu address: %@", i, address)) addresses.append(address) addressToIdxDict.setObject(i, forKey: address as NSCopying) } let jsonData = TLBlockExplorerAPI.instance().getAddressesInfoSynchronous(addresses) if (jsonData.object(forKey: TLNetworking.STATIC_MEMBERS.HTTP_ERROR_CODE) != nil) { DLog("getAccountDataSynchronous error \(jsonData.description)") NSException(name: NSExceptionName(rawValue: "Network Error"), reason: "HTTP Error", userInfo: nil).raise() } let addressesArray = jsonData.object(forKey: "addresses") as! NSArray var balance:UInt64 = 0 for _addressDict in addressesArray { let addressDict = _addressDict as! NSDictionary let n_tx = addressDict.object(forKey: "n_tx") as! Int let address = addressDict.object(forKey: "address") as! String address2NumberOfTransactions[address] = n_tx let addressBalance = (addressDict.object(forKey: "final_balance") as! NSNumber).uint64Value balance += addressBalance address2BalanceDict[address] = TLCoin(uint64: addressBalance) let HDIdx = addressToIdxDict.object(forKey: address) as! Int DLog(String(format: "recoverAccountMainAddresses HDIdx: %d address: %@ n_tx: %d", HDIdx, address, n_tx)) if (n_tx > 0 && HDIdx > accountAddressIdx) { accountAddressIdx = HDIdx } } self.accountBalance = TLCoin(uint64: self.accountBalance.toUInt64() + UInt64(balance)) DLog(String(format: "accountAddressIdx: %ld lookAheadOffset: %lu", accountAddressIdx, lookAheadOffset)) if (accountAddressIdx < lookAheadOffset) { continueLookingAheadAddress = false } lookAheadOffset += GAP_LIMIT } while (getMainAddressesCount() > accountAddressIdx + 1) { removeTopMainAddress() } while (getMainAddressesCount() < accountAddressIdx + 1 + ACCOUNT_UNUSED_ACTIVE_MAIN_ADDRESS_AHEAD_OF_LATEST_USED_ONE_MINIMUM_COUNT) { getNewMainAddress(getMainAddressesCount()) } return accountAddressIdx } fileprivate func recoverAccountChangeAddresses(_ shouldResetAccountBalance: Bool) -> Int { var lookAheadOffset = 0 var continueLookingAheadAddress = true var accountAddressIdx = -1 while (continueLookingAheadAddress) { var addresses = [String]() addresses.reserveCapacity(GAP_LIMIT) let addressToIdxDict = NSMutableDictionary(capacity: GAP_LIMIT) for i in stride(from: lookAheadOffset, to: lookAheadOffset + GAP_LIMIT, by: 1) { let address = getNewChangeAddress(i) DLog(String(format:"getNewChangeAddress HDIdx: %lu address: %@", i, address)) addresses.append(address) addressToIdxDict.setObject(i, forKey: address as NSCopying) } let jsonData = TLBlockExplorerAPI.instance().getAddressesInfoSynchronous(addresses) if (jsonData.object(forKey: TLNetworking.STATIC_MEMBERS.HTTP_ERROR_CODE) != nil) { DLog("getAccountDataSynchronous error \(jsonData.description)") NSException(name: NSExceptionName(rawValue: "Network Error"), reason: "HTTP Error", userInfo: nil).raise() } let addressesArray = jsonData.object(forKey: "addresses") as! NSArray var balance:UInt64 = 0 for _addressDict in addressesArray { let addressDict = _addressDict as! NSDictionary let n_tx = addressDict.object(forKey: "n_tx") as! Int let address = addressDict.object(forKey: "address") as! String address2NumberOfTransactions[address] = n_tx let addressBalance = (addressDict.object(forKey: "final_balance") as! NSNumber).uint64Value balance += addressBalance address2BalanceDict[address] = TLCoin(uint64: addressBalance) let HDIdx = addressToIdxDict.object(forKey: address) as! Int DLog(String(format: "recoverAccountChangeAddresses HDIdx: %d address: %@ n_tx: %d", HDIdx, address, n_tx)) if (n_tx > 0 && HDIdx > accountAddressIdx) { accountAddressIdx = HDIdx } } accountBalance = TLCoin(uint64: self.accountBalance.toUInt64() + UInt64(balance)) if (accountAddressIdx < lookAheadOffset) { continueLookingAheadAddress = false } lookAheadOffset += GAP_LIMIT } while (getChangeAddressesCount() > accountAddressIdx + 1) { removeTopChangeAddress() } while (getChangeAddressesCount() < accountAddressIdx + 1 + ACCOUNT_UNUSED_ACTIVE_CHANGE_ADDRESS_AHEAD_OF_LATEST_USED_ONE_MINIMUM_COUNT) { getNewChangeAddress(getChangeAddressesCount()) } return accountAddressIdx } func recoverAccount(_ shouldResetAccountBalance: Bool, recoverStealthPayments: Bool=false) -> Int { let accountMainAddressMaxIdx = recoverAccountMainAddresses(shouldResetAccountBalance) let accountChangeAddressMaxIdx = recoverAccountChangeAddresses(shouldResetAccountBalance) checkToArchiveAddresses() updateReceivingAddresses() updateChangeAddresses() if recoverStealthPayments && self.stealthWallet != nil { let semaphore = DispatchSemaphore(value: 0) DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.default).async { self.fetchNewStealthPayments(recoverStealthPayments) semaphore.signal() } semaphore.wait(timeout: DispatchTime.distantFuture) } updateAccountNeedsRecovering(false) return accountMainAddressMaxIdx + accountChangeAddressMaxIdx } func updateAccountNeedsRecovering(_ needsRecovering: Bool) -> () { if (accountType == TLAccountType.hdWallet) { let accountIdx = accountDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_ACCOUNT_IDX) as! Int self.appWallet!.updateAccountNeedsRecoveringFromHDWallet(accountIdx, accountNeedsRecovering: needsRecovering) } else if (accountType == TLAccountType.coldWallet) { self.appWallet!.updateAccountNeedsRecoveringFromColdWalletAccount(getPositionInWalletArray(), accountNeedsRecovering: needsRecovering) } else if (accountType == TLAccountType.imported) { self.appWallet!.updateAccountNeedsRecoveringFromImportedAccount(getPositionInWalletArray(), accountNeedsRecovering: needsRecovering) } else { self.appWallet!.updateAccountNeedsRecoveringFromImportedWatchAccount(getPositionInWalletArray(), accountNeedsRecovering: needsRecovering) } accountDict!.setObject(needsRecovering, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_ACCOUNT_NEEDS_RECOVERING as NSCopying) } func clearAllAddresses() -> () { mainActiveAddresses = [String]() mainArchivedAddresses = [String]() changeActiveAddresses = [String]() changeArchivedAddresses = [String]() txidToAccountAmountDict = [String:TLCoin]() txidToAccountAmountTypeDict = [String:Int]() address2HDIndexDict = [String:Int]() address2BalanceDict = [String:TLCoin]() address2NumberOfTransactions = [String:Int]() activeAddressesDict = [String:Bool]() if (accountType == TLAccountType.hdWallet) { let accountIdx = accountDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_ACCOUNT_IDX) as! Int self.appWallet!.clearAllAddressesFromHDWallet(accountIdx) self.appWallet!.clearAllStealthPaymentsFromHDWallet(accountIdx) } else if (accountType == TLAccountType.coldWallet) { self.appWallet!.clearAllAddressesFromColdWalletAccount(getPositionInWalletArray()) } else if (accountType == TLAccountType.imported) { self.appWallet!.clearAllAddressesFromImportedAccount(getPositionInWalletArray()) self.appWallet!.clearAllStealthPaymentsFromImportedAccount(getPositionInWalletArray()) } else { self.appWallet!.clearAllAddressesFromImportedWatchAccount(getPositionInWalletArray()) } } func needsRecovering() -> (Bool) { let needsRecovering = accountDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_ACCOUNT_NEEDS_RECOVERING) as! Bool return needsRecovering } func getUnspentArray() -> NSArray { return unspentOutputs! } func getStealthPaymentUnspentOutputsArray() -> NSArray { return stealthPaymentUnspentOutputs! } func getTotalUnspentSum() -> TLCoin { if (totalUnspentOutputsSum != nil) { return totalUnspentOutputsSum! } if (unspentOutputs == nil) { return TLCoin.zero() } var totalUnspentOutputsSumTemp:UInt64 = 0 for _unspentOutput in stealthPaymentUnspentOutputs! { let unspentOutput = _unspentOutput as! NSDictionary let amount = unspentOutput.object(forKey: "value") as! NSNumber totalUnspentOutputsSumTemp += amount.uint64Value } for _unspentOutput in unspentOutputs! { let unspentOutput = _unspentOutput as! NSDictionary let amount = unspentOutput.object(forKey: "value") as! NSNumber totalUnspentOutputsSumTemp += amount.uint64Value } totalUnspentOutputsSum = TLCoin(uint64: totalUnspentOutputsSumTemp) return totalUnspentOutputsSum! } func getInputsNeededToConsume(_ amountNeeded: TLCoin) -> Int { var valueSelected:UInt64 = 0 var inputCount = 0 for _unspentOutput in stealthPaymentUnspentOutputs! { let unspentOutput = _unspentOutput as! NSDictionary let amount = unspentOutput.object(forKey: "value") as! NSNumber valueSelected += amount.uint64Value inputCount += 1 if (valueSelected >= amountNeeded.toUInt64() && inputCount >= MAX_CONSOLIDATE_STEALTH_PAYMENT_UTXOS_COUNT) { break } } if valueSelected >= amountNeeded.toUInt64() { return inputCount } for _unspentOutput in unspentOutputs! { let unspentOutput = _unspentOutput as! NSDictionary let amount = unspentOutput.object(forKey: "value") as! NSNumber valueSelected += amount.uint64Value inputCount += 1 if valueSelected >= amountNeeded.toUInt64() { return inputCount } } return inputCount } func getUnspentOutputs(_ success: @escaping TLWalletUtils.Success, failure:@escaping TLWalletUtils.Error) { var activeAddresses = getActiveMainAddresses()! as! [String] activeAddresses += getActiveChangeAddresses()! as! [String] if self.stealthWallet != nil { activeAddresses += self.stealthWallet!.getUnspentPaymentAddresses() } unspentOutputs = nil totalUnspentOutputsSum = nil stealthPaymentUnspentOutputs = nil unspentOutputsCount = 0 stealthPaymentUnspentOutputsCount = 0 haveUpDatedUTXOs = false TLBlockExplorerAPI.instance().getUnspentOutputs(activeAddresses, success: { (jsonData) in let unspentOutputs = (jsonData as! NSDictionary).object(forKey: "unspent_outputs") as! NSArray! self.unspentOutputs = NSMutableArray(capacity: unspentOutputs!.count) self.stealthPaymentUnspentOutputs = NSMutableArray(capacity: unspentOutputs!.count) for unspentOutput in unspentOutputs! { let outputScript = (unspentOutput as AnyObject).object(forKey: "script") as! String let address = TLCoreBitcoinWrapper.getAddressFromOutputScript(outputScript, isTestnet: self.appWallet!.walletConfig.isTestnet) if (address == nil) { DLog("address cannot be decoded. not normal pubkeyhash outputScript: \(outputScript)") continue } if self.stealthWallet != nil && self.stealthWallet!.isPaymentAddress(address!) == true { self.stealthPaymentUnspentOutputs!.add(unspentOutput) self.stealthPaymentUnspentOutputsCount += 1 } else { self.unspentOutputs!.add(unspentOutput) self.unspentOutputsCount += 1 } } self.unspentOutputs = NSMutableArray(array: self.unspentOutputs!.sortedArray (comparator: { (obj1, obj2) -> ComparisonResult in var confirmations1 = 0 var confirmations2 = 0 if let c1 = (obj1 as! NSDictionary).object(forKey: "confirmations") as? Int { confirmations1 = c1 } if let c2 = (obj2 as! NSDictionary).object(forKey: "confirmations") as? Int { confirmations2 = c2 } if confirmations1 > confirmations2 { return .orderedAscending } else if confirmations1 < confirmations2 { return .orderedDescending } else { return .orderedSame } })) self.stealthPaymentUnspentOutputs = NSMutableArray(array: self.stealthPaymentUnspentOutputs!.sortedArray (comparator: { (obj1, obj2) -> ComparisonResult in var confirmations1 = 0 var confirmations2 = 0 if let c1 = (obj1 as! NSDictionary).object(forKey: "confirmations") as? Int { confirmations1 = c1 } if let c2 = (obj2 as! NSDictionary).object(forKey: "confirmations") as? Int { confirmations2 = c2 } if confirmations1 > confirmations2 { return .orderedAscending } else if confirmations1 < confirmations2 { return .orderedDescending } else { return .orderedSame } })) self.haveUpDatedUTXOs = true success() }, failure: { (code, status) in failure() }) } func fetchNewStealthPayments(_ isRestoringAccount: Bool) { self.stealthWallet!.checkToWatchStealthAddress() var offset = 0 var currentLatestTxTime:UInt64 = 0 while true { let ret = self.stealthWallet!.getAndStoreStealthPayments(offset) if ret == nil { break } let latestTxTime = ret!.1 if latestTxTime > currentLatestTxTime { currentLatestTxTime = latestTxTime } let gotOldestPaymentAddresses = ret!.0 let newStealthPaymentAddresses = ret!.2 DLog("getAccountData \(self.getAccountIdxNumber()) newStealthPaymentAddresses \(newStealthPaymentAddresses.description)") //TODO: txarray will not be in chronological order because of this, fix this if newStealthPaymentAddresses.count > 0 { self.getAccountDataO(newStealthPaymentAddresses, shouldResetAccountBalance: false) } if gotOldestPaymentAddresses { break } offset += TLStealthExplorerAPI.STATIC_MEMBERS.STEALTH_PAYMENTS_FETCH_COUNT } self.setStealthAddressLastTxTime(TLPreferences.getStealthExplorerURL()!, lastTxTime: currentLatestTxTime) if isRestoringAccount { self.stealthWallet!.setUpStealthPaymentAddresses(true, isSetup: true, async: false) } } func getAccountData(_ addresses: Array, shouldResetAccountBalance: Bool, success: @escaping TLWalletUtils.Success, failure:@escaping TLWalletUtils.Error) -> () { TLBlockExplorerAPI.instance().getAddressesInfo(addresses, success: { (_jsonData) in let jsonData = _jsonData as! NSDictionary if (shouldResetAccountBalance) { self.resetAccountBalances() } let addressesDict = jsonData.object(forKey: "addresses") as! NSArray var balance:UInt64 = 0 for _addressDict in addressesDict { let addressDict = _addressDict as! NSDictionary let n_tx = addressDict.object(forKey: "n_tx") as! Int let address = addressDict.object(forKey: "address") as! String self.address2NumberOfTransactions[address] = n_tx let addressBalance = (addressDict.object(forKey: "final_balance") as! NSNumber).uint64Value balance += addressBalance self.address2BalanceDict[address] = TLCoin(uint64: addressBalance) } self.accountBalance = TLCoin(uint64: self.accountBalance.toUInt64() + balance) self.processTxArray(jsonData.object(forKey: "txs") as! NSArray, shouldResetAccountBalance: true) self.fetchedAccountData = true self.subscribeToWebsockets() self.downloadState = .downloaded DLog("postNotificationName: EVENT_FETCHED_ADDRESSES_DATA \(self.getAccountIdxNumber())") NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_FETCHED_ADDRESSES_DATA()), object: nil) success() }, failure: { (code, status) in failure() }) } fileprivate func getAccountDataSynchronous(_ addresses: Array, shouldResetAccountBalance: Bool, shouldProcessTxArray: Bool) -> NSDictionary? { let jsonData = TLBlockExplorerAPI.instance().getAddressesInfoSynchronous(addresses) if (jsonData.object(forKey: TLNetworking.STATIC_MEMBERS.HTTP_ERROR_CODE) == nil) { if (shouldResetAccountBalance) { resetAccountBalances() } let addressesArray = jsonData.object(forKey: "addresses") as! NSArray var balance:UInt64 = 0 for _addressDict in addressesArray { let addressDict = _addressDict as! NSDictionary let n_tx = addressDict.object(forKey: "n_tx") as! Int let address = addressDict.object(forKey: "address") as! String address2NumberOfTransactions[address] = n_tx let addressBalance = (addressDict.object(forKey: "final_balance") as! NSNumber).uint64Value balance += addressBalance address2BalanceDict[address] = TLCoin(uint64: addressBalance) } self.accountBalance = TLCoin(uint64: self.accountBalance.toUInt64() + balance) if (shouldProcessTxArray) { self.processTxArray(jsonData.object(forKey: "txs") as! NSArray, shouldResetAccountBalance: false) self.fetchedAccountData = false //need to be false because after recovering account need to fetch stealth payments } } else { DLog("getAccountDataSynchronous error \(jsonData.description)") NSException(name: NSExceptionName(rawValue: "Network Error"), reason: "HTTP Error", userInfo: nil).raise() } return jsonData } fileprivate func resetAccountBalances() -> () { txObjectArray = [TLTxObject]() address2BalanceDict = [String:TLCoin]() address2NumberOfTransactions = [String:Int]() accountBalance = TLCoin.zero() for address in mainActiveAddresses { address2BalanceDict[address] = TLCoin.zero() address2NumberOfTransactions[address] = 0 } for address in changeActiveAddresses { address2BalanceDict[address] = TLCoin.zero() address2NumberOfTransactions[address] = 0 } if (TLWalletUtils.STATIC_MEMBERS.SHOULD_SAVE_ARCHIVED_ADDRESSES_IN_JSON) { for address in mainArchivedAddresses { address2BalanceDict[address] = TLCoin.zero() address2NumberOfTransactions[address] = 0 } for address in changeArchivedAddresses { address2BalanceDict[address] = TLCoin.zero() address2NumberOfTransactions[address] = 0 } } } func setStealthAddressServerStatus(_ serverURL: String, isWatching: Bool) -> () { if (self.accountType == TLAccountType.hdWallet) { let accountIdx = self.getAccountIdxNumber() self.appWallet!.setStealthAddressServerStatusHDWallet(accountIdx, serverURL: serverURL, isWatching: isWatching) } else if (self.accountType == TLAccountType.coldWallet) { self.appWallet!.setStealthAddressServerStatusColdWalletAccount(self.getPositionInWalletArray(), serverURL: serverURL, isWatching: isWatching) } else if (self.accountType == TLAccountType.imported) { self.appWallet!.setStealthAddressServerStatusImportedAccount(self.getPositionInWalletArray(), serverURL: serverURL, isWatching: isWatching) } else { self.appWallet!.setStealthAddressServerStatusImportedWatchAccount(self.getPositionInWalletArray(), serverURL: serverURL, isWatching: isWatching) } } func setStealthAddressLastTxTime(_ serverURL: String, lastTxTime: UInt64) -> () { if (self.accountType == TLAccountType.hdWallet) { let accountIdx = self.getAccountIdxNumber() self.appWallet!.setStealthAddressLastTxTimeHDWallet(accountIdx, serverURL: serverURL, lastTxTime: lastTxTime) } else if (self.accountType == TLAccountType.coldWallet) { self.appWallet!.setStealthAddressLastTxTimeColdWalletAccount(self.getPositionInWalletArray(), serverURL: serverURL, lastTxTime: lastTxTime) } else if (self.accountType == TLAccountType.imported) { self.appWallet!.setStealthAddressLastTxTimeImportedAccount(self.getPositionInWalletArray(), serverURL: serverURL, lastTxTime: lastTxTime) } else { self.appWallet!.setStealthAddressLastTxTimeImportedWatchAccount(self.getPositionInWalletArray(), serverURL: serverURL, lastTxTime: lastTxTime) } } func addStealthAddressPaymentKey(_ privateKey:String, address:String, txid: String, txTime: UInt64, stealthPaymentStatus: TLStealthPaymentStatus) -> () { if (accountType == TLAccountType.hdWallet) { let accountIdx = self.getAccountIdxNumber() self.appWallet!.addStealthAddressPaymentKeyHDWallet(accountIdx, privateKey:privateKey, address:address, txid:txid, txTime: txTime, stealthPaymentStatus: stealthPaymentStatus) } else if (accountType == TLAccountType.coldWallet) { self.appWallet!.addStealthAddressPaymentKeyColdWalletAccount(self.getPositionInWalletArray(), privateKey:privateKey, address:address, txid:txid, txTime: txTime, stealthPaymentStatus: stealthPaymentStatus) } else if (accountType == TLAccountType.imported) { self.appWallet!.addStealthAddressPaymentKeyImportedAccount(self.getPositionInWalletArray(), privateKey:privateKey, address:address, txid:txid, txTime: txTime, stealthPaymentStatus: stealthPaymentStatus) } else if (accountType == TLAccountType.importedWatch) { self.appWallet!.addStealthAddressPaymentKeyImportedWatchAccount(self.getPositionInWalletArray(), privateKey:privateKey, address:address, txid:txid, txTime: txTime, stealthPaymentStatus: stealthPaymentStatus) } } func setStealthPaymentStatus(_ txid: String, stealthPaymentStatus: TLStealthPaymentStatus, lastCheckTime: UInt64) -> () { if (accountType == TLAccountType.hdWallet) { let accountIdx = self.getAccountIdxNumber() self.appWallet!.setStealthPaymentStatusHDWallet(accountIdx, txid:txid, stealthPaymentStatus:stealthPaymentStatus, lastCheckTime: lastCheckTime) } else if (accountType == TLAccountType.coldWallet) { self.appWallet!.setStealthPaymentStatusColdWalletAccount(self.getPositionInWalletArray(), txid:txid, stealthPaymentStatus:stealthPaymentStatus, lastCheckTime: lastCheckTime) } else if (accountType == TLAccountType.imported) { self.appWallet!.setStealthPaymentStatusImportedAccount(self.getPositionInWalletArray(), txid:txid, stealthPaymentStatus:stealthPaymentStatus, lastCheckTime: lastCheckTime) } else if (accountType == TLAccountType.importedWatch) { self.appWallet!.setStealthPaymentStatusImportedWatchAccount(self.getPositionInWalletArray(), txid:txid, stealthPaymentStatus:stealthPaymentStatus, lastCheckTime: lastCheckTime) } } func removeOldStealthPayments() -> () { if (accountType == TLAccountType.hdWallet) { let accountIdx = self.getAccountIdxNumber() self.appWallet!.removeOldStealthPaymentsHDWallet(accountIdx) } else if (accountType == TLAccountType.coldWallet) { self.appWallet!.removeOldStealthPaymentsColdWalletAccount(self.getPositionInWalletArray()) } else if (accountType == TLAccountType.imported) { self.appWallet!.removeOldStealthPaymentsImportedAccount(self.getPositionInWalletArray()) } else if (accountType == TLAccountType.importedWatch) { self.appWallet!.removeOldStealthPaymentsImportedWatchAccount(self.getPositionInWalletArray()) } } func setStealthPaymentLastCheckTime(_ txid: String, lastCheckTime: UInt64) -> () { if (accountType == TLAccountType.hdWallet) { let accountIdx = self.getAccountIdxNumber() self.appWallet!.setStealthPaymentLastCheckTimeHDWallet(accountIdx, txid: txid, lastCheckTime: lastCheckTime) } else if (accountType == TLAccountType.coldWallet) { self.appWallet!.setStealthPaymentLastCheckTimeColdWalletAccount(self.getPositionInWalletArray(), txid: txid, lastCheckTime: lastCheckTime) } else if (accountType == TLAccountType.imported) { self.appWallet!.setStealthPaymentLastCheckTimeImportedAccount(self.getPositionInWalletArray(), txid: txid, lastCheckTime: lastCheckTime) } else if (accountType == TLAccountType.importedWatch) { self.appWallet!.setStealthPaymentLastCheckTimeImportedWatchAccount(self.getPositionInWalletArray(), txid: txid, lastCheckTime: lastCheckTime) } } func getAccountDataO() -> () { // if account needs recovering dont fetch account data if (needsRecovering()) { self.downloadState = .failed return } var activeAddresses = getActiveMainAddresses()! as! [String] activeAddresses += getActiveChangeAddresses()! as! [String] if self.stealthWallet != nil { activeAddresses += self.stealthWallet!.getPaymentAddresses() DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.default).async { self.fetchNewStealthPayments(false) } } self.getAccountDataO(activeAddresses, shouldResetAccountBalance: true) } fileprivate func getAccountDataO(_ addresses: Array, shouldResetAccountBalance: Bool) -> () { let jsonData = TLBlockExplorerAPI.instance().getAddressesInfoSynchronous(addresses) if (jsonData.object(forKey: TLNetworking.STATIC_MEMBERS.HTTP_ERROR_CODE) != nil) { self.downloadState = .failed return } if (shouldResetAccountBalance) { self.resetAccountBalances() } let addressesDict = jsonData.object(forKey: "addresses") as! NSArray var balance:UInt64 = 0 for _addressDict in addressesDict { let addressDict = _addressDict as! NSDictionary let n_tx = addressDict.object(forKey: "n_tx") as! Int let address = addressDict.object(forKey: "address") as! String self.address2NumberOfTransactions[address] = n_tx let addressBalance = (addressDict.object(forKey: "final_balance") as! NSNumber).uint64Value balance += addressBalance self.address2BalanceDict[address] = TLCoin(uint64: addressBalance) } self.accountBalance = TLCoin(uint64: self.accountBalance.toUInt64() + balance) self.processTxArray(jsonData.object(forKey: "txs") as! NSArray, shouldResetAccountBalance: true) self.fetchedAccountData = true self.subscribeToWebsockets() self.downloadState = .downloaded DispatchQueue.main.async(execute: { DLog("postNotificationName: EVENT_FETCHED_ADDRESSES_DATA \(self.getAccountIdxNumber())") NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_FETCHED_ADDRESSES_DATA()), object: nil) }) } fileprivate func subscribeToWebsockets() -> () { if self.listeningToIncomingTransactions == false { self.listeningToIncomingTransactions = true let activeMainAddresses = self.getActiveMainAddresses() for address in activeMainAddresses! { TLTransactionListener.instance().listenToIncomingTransactionForAddress(address as! String) } let activeChangeAddresses = self.getActiveChangeAddresses() for address in activeChangeAddresses! { TLTransactionListener.instance().listenToIncomingTransactionForAddress(address as! String) } } if self.stealthWallet != nil { let stealthPaymentAddresses = self.stealthWallet!.getUnspentPaymentAddresses() for address in stealthPaymentAddresses { TLTransactionListener.instance().listenToIncomingTransactionForAddress(address) } if self.stealthWallet!.isListeningToStealthPayment == false { let challenge = TLStealthWebSocket.instance().challenge let addrAndSignature = self.stealthWallet!.getStealthAddressAndSignatureFromChallenge(challenge) TLStealthWebSocket.instance().sendMessageSubscribeToStealthAddress(addrAndSignature.0, signature: addrAndSignature.1) } } } } ================================================ FILE: ArcBit/model/TLAccounts.swift ================================================ // // TLAccounts.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation @objc class TLAccounts:NSObject { fileprivate var appWallet:TLWallet? fileprivate var accountsDict:NSMutableDictionary? fileprivate let accountsArray = NSMutableArray() fileprivate let archivedAccountsArray = NSMutableArray() fileprivate var accountType:TLAccountType? init(appWallet: TLWallet, accountsArray: NSArray, accountType at:TLAccountType) { super.init() self.appWallet = appWallet accountType = at self.accountsDict = NSMutableDictionary(capacity: accountsArray.count) for i in stride(from: 0, to: accountsArray.count, by: 1) { let accountObject = accountsArray.object(at: i) as! TLAccountObject if (accountObject.isArchived()) { self.archivedAccountsArray.add(accountObject) } else { self.accountsArray.add(accountObject) } accountObject.setPositionInWalletArray(i) self.accountsDict!.setObject(accountObject, forKey:i as NSCopying) } } @discardableResult func addAccountWithExtendedKey(_ extendedKey:String, accountName:String) -> TLAccountObject { assert(accountType != TLAccountType.hdWallet, "accountType == TLAccountTypeHDWallet") let accountObject:TLAccountObject if (accountType == TLAccountType.coldWallet) { accountObject = self.appWallet!.addColdWalletAccount(extendedKey) } else if (accountType == TLAccountType.imported) { accountObject = self.appWallet!.addImportedAccount(extendedKey) } else { accountObject = self.appWallet!.addWatchOnlyAccount(extendedKey) } self.accountsArray.add(accountObject) let positionInWalletArray = self.getNumberOfAccounts()+getNumberOfArchivedAccounts()-1 accountObject.setPositionInWalletArray(positionInWalletArray) self.accountsDict!.setObject(accountObject, forKey:accountObject.getPositionInWalletArray() as NSCopying) renameAccount(positionInWalletArray, accountName: accountName) return accountObject } fileprivate func addAccount(_ accountObject: TLAccountObject) -> (Bool){ assert(accountType == TLAccountType.hdWallet, "accountType != TLAccountTypeHDWallet") assert(self.accountsDict!.object(forKey: accountObject.getAccountIdxNumber()) == nil, "") self.accountsDict!.setObject(accountObject, forKey:accountObject.getAccountIdxNumber() as NSCopying) self.accountsArray.add(accountObject) return true } @discardableResult func renameAccount(_ accountIdxNumber:Int, accountName:String) -> (Bool){ if (accountType == TLAccountType.hdWallet) { let accountObject = self.accountsDict!.object(forKey: accountIdxNumber) as! TLAccountObject accountObject.renameAccount(accountName) self.appWallet!.renameAccount(accountObject.getAccountIdxNumber(), accountName:accountName) } else { let accountObject = self.getAccountObjectForAccountIdxNumber(accountIdxNumber) accountObject.renameAccount(accountName) if (accountType == TLAccountType.coldWallet) { self.appWallet!.setColdWalletAccountName(accountName, idx:accountIdxNumber) } else if (accountType == TLAccountType.imported) { self.appWallet!.setImportedAccountName(accountName, idx:accountIdxNumber) } else if (accountType == TLAccountType.importedWatch) { self.appWallet!.setWatchOnlyAccountName(accountName, idx:accountIdxNumber) } } return true } //in this context accountIdx is not the accountID, accountIdx is simply the order in which i want to display the accounts, neccessary cuz accounts can be deleted and such, func getAccountObjectForIdx(_ idx:Int)-> (TLAccountObject) { return self.accountsArray.object(at: idx) as! TLAccountObject } func getArchivedAccountObjectForIdx(_ idx:Int) -> TLAccountObject { return self.archivedAccountsArray.object(at: idx) as! TLAccountObject } func getIdxForAccountObject(_ accountObject:TLAccountObject) -> (Int){ return self.accountsArray.index(of: accountObject) as Int } func getNumberOfAccounts() -> Int { return self.accountsArray.count } func getNumberOfArchivedAccounts() -> Int{ return self.archivedAccountsArray.count } func getAccountObjectForAccountIdxNumber(_ accountIdxNumber:Int) ->TLAccountObject { return self.accountsDict!.object(forKey: accountIdxNumber) as! TLAccountObject } func archiveAccount(_ positionInWalletArray:Int) -> (){ setArchiveAccount(positionInWalletArray, enabled:true) let toMoveAccountObject = self.accountsDict!.object(forKey: positionInWalletArray) as! TLAccountObject self.accountsArray.remove(toMoveAccountObject) for i in stride(from: 0, to: self.archivedAccountsArray.count, by: 1) { let accountObject = self.archivedAccountsArray.object(at: i) as! TLAccountObject if (accountObject.getPositionInWalletArray() > toMoveAccountObject.getPositionInWalletArray()) { self.archivedAccountsArray.insert(toMoveAccountObject, at:i) return } } self.archivedAccountsArray.add(toMoveAccountObject) } func unarchiveAccount(_ positionInWalletArray:Int) -> (){ setArchiveAccount(positionInWalletArray, enabled:false) let toMoveAccountObject = self.accountsDict!.object(forKey: positionInWalletArray) as! TLAccountObject self.archivedAccountsArray.remove(toMoveAccountObject) for i in stride(from: 0, to: self.accountsArray.count, by: 1) { let accountObject = self.accountsArray.object(at: i) as! TLAccountObject if (accountObject.getPositionInWalletArray() > toMoveAccountObject.getPositionInWalletArray()) { self.accountsArray.insert(toMoveAccountObject, at:i) return } } self.accountsArray.add(toMoveAccountObject) } fileprivate func setArchiveAccount(_ accountIdxNumber:Int, enabled:Bool) -> (){ let accountObject = getAccountObjectForAccountIdxNumber(accountIdxNumber) as TLAccountObject accountObject.archiveAccount(enabled) if (accountType == TLAccountType.hdWallet) { self.appWallet!.archiveAccountHDWallet(accountIdxNumber, enabled:enabled) } else if (accountType == TLAccountType.coldWallet) { self.appWallet!.archiveAccountColdWalletAccount(accountIdxNumber, enabled:enabled) } else if (accountType == TLAccountType.imported) { self.appWallet!.archiveAccountImportedAccount(accountIdxNumber, enabled:enabled) } else if (accountType == TLAccountType.importedWatch) { self.appWallet!.archiveAccountImportedWatchAccount(accountIdxNumber, enabled:enabled) } } @discardableResult func createNewAccount(_ accountName:String, accountType:TLAccount) -> TLAccountObject { let accountObject = self.appWallet!.createNewAccount(accountName, accountType:TLAccount.normal, preloadStartingAddresses:true) accountObject.updateAccountNeedsRecovering(false) addAccount(accountObject) return accountObject } func createNewAccount(_ accountName:String, accountType:TLAccount, preloadStartingAddresses:Bool) -> TLAccountObject { let accountObject = self.appWallet!.createNewAccount(accountName, accountType:TLAccount.normal, preloadStartingAddresses:preloadStartingAddresses) addAccount(accountObject) return accountObject } @discardableResult func popTopAccount() -> (Bool){ if (self.accountsArray.count <= 0) { return false } let accountObject = self.accountsArray.lastObject as! TLAccountObject self.accountsDict!.removeObject(forKey: accountObject.getAccountIdxNumber()) self.accountsArray.removeLastObject() self.appWallet!.removeTopAccount() return true } func deleteAccount(_ idx:Int) -> (Bool){ assert(accountType != TLAccountType.hdWallet, "accountType == TLAccountTypeHDWallet") let accountObject = self.archivedAccountsArray.object(at: idx) as! TLAccountObject self.archivedAccountsArray.removeObject(at: idx) if (accountType == TLAccountType.coldWallet) { self.appWallet!.deleteColdWalletAccount(accountObject.getPositionInWalletArray()) } else if (accountType == TLAccountType.imported) { self.appWallet!.deleteImportedAccount(accountObject.getPositionInWalletArray()) } else if (accountType == TLAccountType.importedWatch) { self.appWallet!.deleteWatchOnlyAccount(accountObject.getPositionInWalletArray()) } self.accountsDict!.removeObject(forKey: accountObject.getPositionInWalletArray()) let tmpDict = self.accountsDict!.copy() as! NSDictionary for (key, _) in tmpDict { let ao = self.accountsDict!.object(forKey: key) as! TLAccountObject if (ao.getPositionInWalletArray() > accountObject.getPositionInWalletArray()) { ao.setPositionInWalletArray(ao.getPositionInWalletArray()-1) self.accountsDict!.setObject(ao, forKey:ao.getPositionInWalletArray() as NSCopying) } } if (accountObject.getPositionInWalletArray() < self.accountsDict!.count - 1) { self.accountsDict!.removeObject(forKey: self.accountsDict!.count-1) } return true } } ================================================ FILE: ArcBit/model/TLAchievements.swift ================================================ // // TLAchievements.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation class TLAchievements { struct STATIC_MEMBERS { static var _instance:TLAchievements? = nil } class func instance() -> (TLAchievements) { if(STATIC_MEMBERS._instance == nil) { STATIC_MEMBERS._instance = TLAchievements() } return STATIC_MEMBERS._instance! } func hasDoneAction(_ action:String) -> (Bool) { let userAnalyticsDict = NSMutableDictionary(dictionary:TLPreferences.getAnalyticsDict() ?? NSDictionary()) if userAnalyticsDict.value(forKey: action) == nil { return false } let eventCount = (userAnalyticsDict.value(forKey: action) as! NSNumber).intValue return eventCount > 0 } } ================================================ FILE: ArcBit/model/TLAnalytics.swift ================================================ // // TLAnalytics.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation class TLAnalytics: NSObject { struct STATIC_MEMBERS { static var _instance:TLAnalytics? = nil } class func instance() -> (TLAnalytics) { if(STATIC_MEMBERS._instance == nil) { STATIC_MEMBERS._instance = TLAnalytics() } return STATIC_MEMBERS._instance! } override init() { super.init() observeUserInterfaceInteractions() } fileprivate func observeUserInterfaceInteractionsWithAchievements() -> () { NotificationCenter.default.addObserver(self ,selector:#selector(TLAnalytics.updateSentPayment(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_SEND_PAYMENT()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLAnalytics.updateReceivePayment(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_RECEIVE_PAYMENT()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLAnalytics.updateViewHistoryScreen(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_VIEW_HISTORY()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLAnalytics.updateCreateNewAccount(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_CREATE_NEW_ACCOUNT()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLAnalytics.updateEditAccountName(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_EDIT_ACCOUNT_NAME()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLAnalytics.updateArchiveAccount(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_ARCHIVE_ACCOUNT()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLAnalytics.updateEnablePINCode(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_ENABLE_PIN_CODE()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLAnalytics.updateBackupPassphrase(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_BACKUP_PASSPHRASE()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLAnalytics.updateRestoreWallet(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_RESTORE_WALLET()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLAnalytics.updateAddToAddressBook(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_ADD_TO_ADDRESS_BOOK()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLAnalytics.updateEditEntryAddressBook(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_EDIT_ENTRY_ADDRESS_BOOK()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLAnalytics.updateDeleteEntryAddressBook(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_DELETE_ENTRY_ADDRESS_BOOK()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLAnalytics.updateSendToAddressInAddressBook(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_SEND_TO_ADDRESS_IN_ADDRESS_BOOK()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLAnalytics.updateTagTransaction(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_TAG_TRANSACTION()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLAnalytics.updateToggleAutomaticTxFee(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_TOGGLE_AUTOMATIC_TX_FEE()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLAnalytics.updateChangeAutomaticTxFee(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_CHANGE_AUTOMATIC_TX_FEE()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLAnalytics.updateViewAccountAddresses(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_VIEW_ACCOUNT_ADDRESSES()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLAnalytics.updateViewAccountAddress(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_VIEW_ACCOUNT_ADDRESS()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLAnalytics.updateViewTransactionInWeb(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_VIEW_TRANSACTION_IN_WEB()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLAnalytics.updateViewAccountAddressInWeb(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_VIEW_ACCOUNT_ADDRESS_IN_WEB()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLAnalytics.updateViewEnableAdvancedMode(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_ENABLE_ADVANCE_MODE()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLAnalytics.updateImportAccount(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_IMPORT_ACCOUNT()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLAnalytics.updateImportWatchOnlyAccount(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_IMPORT_WATCH_ONLY_ACCOUNT()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLAnalytics.updateImportPrivateKey(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_IMPORT_PRIVATE_KEY()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLAnalytics.updateImportWatchOnlyAddress(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_IMPORT_WATCH_ONLY_ADDRESS()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLAnalytics.updateChangeBlockExplorerType(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_CHANGE_BLOCKEXPLORER_TYPE()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLAnalytics.updateViewExtendedPublicKey(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_VIEW_EXTENDED_PUBLIC_KEY()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLAnalytics.updateViewExtendedPrivateKey(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_VIEW_EXTENDED_PRIVATE_KEY()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLAnalytics.updateViewAccountsPrivateKey(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_VIEW_ACCOUNT_PRIVATE_KEY()), object:nil) } func observeUserInterfaceInteractions() -> () { NotificationCenter.default.addObserver(self ,selector:#selector(TLAnalytics.updateViewSendScreen(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_VIEW_SEND_SCREEN()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLAnalytics.updateViewReceiveScreen(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_VIEW_RECEIVE_SCREEN()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLAnalytics.updateViewAccountsScreen(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_VIEW_ACCOUNTS_SCREEN()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLAnalytics.updateViewManageAccountsScreen(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_VIEW_MANAGE_ACCOUNTS_SCREEN()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLAnalytics.updateViewHelpScreen(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_VIEW_HELP_SCREEN()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLAnalytics.updateViewSettingsScreen(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_VIEW_SETTINGS_SCREEN()), object:nil) observeUserInterfaceInteractionsWithAchievements() } func updateViewSendScreen(_ notification: Notification) -> () { updateUserAnalyticsWithEvent(TLNotificationEvents.EVENT_VIEW_SEND_SCREEN()) } func updateViewReceiveScreen(_ notification: Notification) -> () { updateUserAnalyticsWithEvent(TLNotificationEvents.EVENT_VIEW_RECEIVE_SCREEN()) } func updateViewAccountsScreen(_ notification: Notification) -> () { updateUserAnalyticsWithEvent(TLNotificationEvents.EVENT_VIEW_ACCOUNTS_SCREEN()) } func updateViewManageAccountsScreen(_ notification: Notification) -> () { updateUserAnalyticsWithEvent(TLNotificationEvents.EVENT_VIEW_MANAGE_ACCOUNTS_SCREEN()) } func updateViewHelpScreen(_ notification: Notification) -> () { updateUserAnalyticsWithEvent(TLNotificationEvents.EVENT_VIEW_HELP_SCREEN()) } func updateViewSettingsScreen(_ notification: Notification) -> () { updateUserAnalyticsWithEvent(TLNotificationEvents.EVENT_VIEW_SETTINGS_SCREEN()) } // Achievements func updateSentPayment(_ notification: Notification) -> () { updateUserAnalyticsWithEvent(TLNotificationEvents.EVENT_SEND_PAYMENT()) } func updateReceivePayment(_ notification: Notification) -> () { updateUserAnalyticsWithEvent(TLNotificationEvents.EVENT_RECEIVE_PAYMENT()) } func updateViewHistoryScreen(_ notification: Notification) -> () { updateUserAnalyticsWithEvent(TLNotificationEvents.EVENT_VIEW_HISTORY()) } func updateCreateNewAccount(_ notification: Notification) -> () { updateUserAnalyticsWithEvent(TLNotificationEvents.EVENT_CREATE_NEW_ACCOUNT()) } func updateEditAccountName(_ notification: Notification) -> () { updateUserAnalyticsWithEvent(TLNotificationEvents.EVENT_EDIT_ACCOUNT_NAME()) } func updateArchiveAccount(_ notification: Notification) -> () { updateUserAnalyticsWithEvent(TLNotificationEvents.EVENT_ARCHIVE_ACCOUNT()) } func updateEnablePINCode(_ notification: Notification) -> () { updateUserAnalyticsWithEvent(TLNotificationEvents.EVENT_ENABLE_PIN_CODE()) } func updateBackupPassphrase(_ notification: Notification) -> () { updateUserAnalyticsWithEvent(TLNotificationEvents.EVENT_BACKUP_PASSPHRASE()) } func updateRestoreWallet(_ notification: Notification) -> () { updateUserAnalyticsWithEvent(TLNotificationEvents.EVENT_RESTORE_WALLET()) } func updateAddToAddressBook(_ notification: Notification) -> () { updateUserAnalyticsWithEvent(TLNotificationEvents.EVENT_ADD_TO_ADDRESS_BOOK()) } func updateEditEntryAddressBook(_ notification: Notification) -> () { updateUserAnalyticsWithEvent(TLNotificationEvents.EVENT_EDIT_ENTRY_ADDRESS_BOOK()) } func updateDeleteEntryAddressBook(_ notification: Notification) -> () { updateUserAnalyticsWithEvent(TLNotificationEvents.EVENT_DELETE_ENTRY_ADDRESS_BOOK()) } func updateSendToAddressInAddressBook(_ notification: Notification) -> () { updateUserAnalyticsWithEvent(TLNotificationEvents.EVENT_SEND_TO_ADDRESS_IN_ADDRESS_BOOK()) } func updateTagTransaction(_ notification: Notification) -> () { updateUserAnalyticsWithEvent(TLNotificationEvents.EVENT_TAG_TRANSACTION()) } func updateToggleAutomaticTxFee(_ notification: Notification) -> () { updateUserAnalyticsWithEvent(TLNotificationEvents.EVENT_TOGGLE_AUTOMATIC_TX_FEE()) } func updateChangeAutomaticTxFee(_ notification: Notification) -> () { updateUserAnalyticsWithEvent(TLNotificationEvents.EVENT_CHANGE_AUTOMATIC_TX_FEE()) } func updateViewAccountAddresses(_ notification: Notification) -> () { updateUserAnalyticsWithEvent(TLNotificationEvents.EVENT_VIEW_ACCOUNT_ADDRESSES()) } func updateViewAccountAddress(_ notification: Notification) -> () { updateUserAnalyticsWithEvent(TLNotificationEvents.EVENT_VIEW_ACCOUNT_ADDRESS()) } func updateViewTransactionInWeb(_ notification: Notification) -> () { updateUserAnalyticsWithEvent(TLNotificationEvents.EVENT_VIEW_TRANSACTION_IN_WEB()) } func updateViewAccountAddressInWeb(_ notification: Notification) -> () { updateUserAnalyticsWithEvent(TLNotificationEvents.EVENT_VIEW_ACCOUNT_ADDRESS_IN_WEB()) } func updateViewEnableAdvancedMode(_ notification: Notification) -> () { updateUserAnalyticsWithEvent(TLNotificationEvents.EVENT_ENABLE_ADVANCE_MODE()) } func updateImportAccount(_ notification: Notification) -> () { updateUserAnalyticsWithEvent(TLNotificationEvents.EVENT_IMPORT_ACCOUNT()) } func updateImportWatchOnlyAccount(_ notification: Notification) -> () { updateUserAnalyticsWithEvent(TLNotificationEvents.EVENT_IMPORT_WATCH_ONLY_ACCOUNT()) } func updateImportPrivateKey(_ notification: Notification) -> () { updateUserAnalyticsWithEvent(TLNotificationEvents.EVENT_IMPORT_PRIVATE_KEY()) } func updateImportWatchOnlyAddress(_ notification: Notification) -> () { updateUserAnalyticsWithEvent(TLNotificationEvents.EVENT_IMPORT_WATCH_ONLY_ADDRESS()) } func updateChangeBlockExplorerType(_ notification: Notification) -> () { updateUserAnalyticsWithEvent(TLNotificationEvents.EVENT_CHANGE_BLOCKEXPLORER_TYPE()) } func updateViewExtendedPublicKey(_ notification: Notification) -> () { updateUserAnalyticsWithEvent(TLNotificationEvents.EVENT_VIEW_EXTENDED_PUBLIC_KEY()) } func updateViewExtendedPrivateKey(_ notification: Notification) -> () { updateUserAnalyticsWithEvent(TLNotificationEvents.EVENT_VIEW_EXTENDED_PRIVATE_KEY()) } func updateViewAccountsPrivateKey(_ notification: Notification) -> () { updateUserAnalyticsWithEvent(TLNotificationEvents.EVENT_VIEW_ACCOUNT_PRIVATE_KEY()) } fileprivate func updateUserAnalyticsWithEvent(_ event: String) -> () { let userAnalyticsDict = NSMutableDictionary(dictionary:TLPreferences.getAnalyticsDict() ?? NSDictionary()) let dict = (userAnalyticsDict.value(forKey: event) as! NSNumber? ?? 0) let eventCount = dict.uintValue userAnalyticsDict.setObject(eventCount + 1, forKey:event as NSCopying) TLPreferences.setAnalyticsDict(userAnalyticsDict) } } ================================================ FILE: ArcBit/model/TLBlockchainStatus.swift ================================================ // // TLBlockchainStatus.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit class TLBlockchainStatus { struct STATIC_MEMBERS{ static var _instance:TLBlockchainStatus? = nil } var blockHeight:UInt64 = 0 class func instance() -> (TLBlockchainStatus) { if(STATIC_MEMBERS._instance == nil) { STATIC_MEMBERS._instance = TLBlockchainStatus() } return STATIC_MEMBERS._instance! } } ================================================ FILE: ArcBit/model/TLCoin.swift ================================================ // // TLCoin.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation enum TLBitcoinDenomination:Int { case bitcoin = 0 case milliBit = 1 case bits = 2 } @objc class TLCoin:NSObject { struct STATIC_MEMBERS { static let numberFormatter: NumberFormatter = NumberFormatter() } fileprivate var coin:BTCMutableBigNumber class func zero() -> (TLCoin) { return TLCoin(btcNumber:BTCBigNumber.zero()) } class func one() -> (TLCoin) { return TLCoin(btcNumber:BTCBigNumber.one()) } class func negativeOne() -> (TLCoin) { return TLCoin(btcNumber:BTCBigNumber.negativeOne()) } init(btcNumber:BTCBigNumber) { coin = btcNumber.mutableCopy() } fileprivate func getBTCNumber() -> (BTCBigNumber) { return coin } func add(_ other:TLCoin) -> (TLCoin) { let tmp = coin.mutableCopy().add(other.getBTCNumber()) return TLCoin(btcNumber: tmp!.copy()) } func subtract(_ other:TLCoin) -> (TLCoin) { let tmp = coin.mutableCopy().subtract(other.getBTCNumber()) return TLCoin(btcNumber: tmp!.copy()) } fileprivate func multiply(_ other:TLCoin) -> (TLCoin) { let tmp = coin.mutableCopy().multiply(other.getBTCNumber()) return TLCoin(btcNumber: tmp!.copy()) } fileprivate func divide(_ other:TLCoin) -> (TLCoin) { let tmp = coin.mutableCopy().divide(other.getBTCNumber()) return TLCoin(btcNumber: tmp!.copy()) } init(uint64:UInt64) { coin = BTCMutableBigNumber(uInt64: uint64) } init(doubleValue:Double) { //TODO: get rid this init method let tmp = NSDecimalNumber(value: doubleValue as Double).multiplying(by: NSDecimalNumber(value: 100000000 as UInt64)) coin = BTCMutableBigNumber(uInt64: tmp.uint64Value) } func toUInt64() -> (UInt64) { return STATIC_MEMBERS.numberFormatter.number(from: coin.decimalString)!.uint64Value } init(bitcoinAmount:(String), bitcoinDenomination:(TLBitcoinDenomination), locale: Locale=Locale.current) { //TODO move to TLCurrencyFormat like an droid, so dont have to create formatter everytime let bitcoinFormatter = NumberFormatter() bitcoinFormatter.numberStyle = .decimal bitcoinFormatter.maximumFractionDigits = 8 bitcoinFormatter.locale = locale let tmpString = bitcoinFormatter.number(from: bitcoinAmount) if tmpString == nil { coin = BTCMutableBigNumber(uInt64:0) return } let satoshis:UInt64 let mericaFormatter = NumberFormatter() mericaFormatter.maximumFractionDigits = 8 mericaFormatter.locale = Locale(identifier: "en_US") let decimalAmount = NSDecimalNumber(string: mericaFormatter.string(from: bitcoinFormatter.number(from: bitcoinAmount)!)) if (bitcoinDenomination == TLBitcoinDenomination.bitcoin) { satoshis = decimalAmount.multiplying(by: NSDecimalNumber(string: "100000000")).uint64Value } else if (bitcoinDenomination == TLBitcoinDenomination.milliBit) { satoshis = (decimalAmount.multiplying(by: NSDecimalNumber(value: 100000 as UInt64))).uint64Value } else { satoshis = (decimalAmount.multiplying(by: NSDecimalNumber(value: 100 as UInt64))).uint64Value } coin = BTCMutableBigNumber(uInt64:satoshis) } func bigIntegerToBitcoinAmountString(_ bitcoinDenomination: TLBitcoinDenomination) -> (String) { //TODO move to TLCurrencyFormat like an droid, so dont have to create formatter everytime let bitcoinFormatter = NumberFormatter() bitcoinFormatter.numberStyle = .decimal if (bitcoinDenomination == TLBitcoinDenomination.bitcoin) { bitcoinFormatter.maximumFractionDigits = 8 return bitcoinFormatter.string(from: NSNumber(value: bigIntegerToBitcoin() as Double))! } else if (bitcoinDenomination == TLBitcoinDenomination.milliBit) { bitcoinFormatter.maximumFractionDigits = 5 return bitcoinFormatter.string(from: NSNumber(value: bigIntegerToMilliBit() as Double))! } else { bitcoinFormatter.maximumFractionDigits = 2 return bitcoinFormatter.string(from: NSNumber(value: bigIntegerToBits() as Double))! } } func toString() -> (String) { return coin.decimalString != nil ? coin.decimalString : "0" } fileprivate func bigIntegerToBits() -> (Double) { return (NSDecimalNumber(string: coin.decimalString as String).multiplying(by: NSDecimalNumber(value: 0.01 as Double))).doubleValue } fileprivate func bigIntegerToMilliBit() -> (Double){ return (NSDecimalNumber(string: coin.decimalString as String).multiplying(by: NSDecimalNumber(value: 0.00001 as Double))).doubleValue } func bigIntegerToBitcoin() -> (Double) { return (NSDecimalNumber(string: coin.decimalString as String).multiplying(by: NSDecimalNumber(value: 0.00000001 as Double))).doubleValue } func less(_ other:TLCoin) -> (Bool) { return coin.less(other.getBTCNumber()) } func lessOrEqual(_ other:TLCoin) -> (Bool) { return coin.lessOrEqual(other.getBTCNumber()) } func greater(_ other:TLCoin) -> (Bool) { return coin.greater(other.getBTCNumber()) } func greaterOrEqual(_ other:TLCoin) -> (Bool) { return coin.greaterOrEqual(other.getBTCNumber()) } func equalTo(_ other:TLCoin) -> (Bool) { return coin.greaterOrEqual(other.getBTCNumber()) && coin.lessOrEqual(other.getBTCNumber()) } } ================================================ FILE: ArcBit/model/TLColdWallet.swift ================================================ // // TLColdWallet.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation class TLColdWallet { enum TLColdWalletError: Error { case InvalidScannedData(String) case InvalidKey(String) case MisMatchExtendedPublicKey(String) } static let SPLIT_SUB_STRING_LENGTH = 100 static let AIR_GAP_DATA_VERSION = "1" static let AIR_GAP_DATA_TRANSPORT_VERSION = "1" struct STATIC_MEMBERS { static var instance:TLSpaghettiGodSend? } class func createUnsignedTxAipGapData(_ unSignedTx: String, extendedPublicKey: String, inputScripts:NSArray, txInputsAccountHDIdxes:NSArray) -> String? { let data = TLWalletUtils.hexStringToData(unSignedTx) if let base64Encoded = data?.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0)) { let dataDictionaryToAirGapPass = [ "v": AIR_GAP_DATA_VERSION, "account_public_key": extendedPublicKey, "unsigned_tx_base64": base64Encoded, "input_scripts": inputScripts, //inputScripts in hex "tx_inputs_account_hd_idxes": txInputsAccountHDIdxes //[["idx":123, "is_change":false], ["idx":124, "is_change":true]] ] as [String : Any] return TLUtils.dictionaryToJSONString(false, dict: dataDictionaryToAirGapPass as NSDictionary) } return nil } class func createSerializedUnsignedTxAipGapData(_ unSignedTx: String, extendedPublicKey: String, inputScripts:NSArray, txInputsAccountHDIdxes:NSArray) -> String? { let aipGapDataJSONString = TLColdWallet.createUnsignedTxAipGapData(unSignedTx, extendedPublicKey: extendedPublicKey, inputScripts: inputScripts, txInputsAccountHDIdxes: txInputsAccountHDIdxes) let data = aipGapDataJSONString?.data(using: String.Encoding.utf8) return data?.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0)) } class func splitStringToArray(_ str: String) -> Array { var partsArray = [String]() var idx = 0 let SPLIT_SUB_STRING_LENGTH = 100 let nsString = str as NSString var partCount = 0 while true { let subString:String partCount += 1 if idx+SPLIT_SUB_STRING_LENGTH >= str.characters.count { subString = nsString.substring(with: NSRange(location: idx, length: nsString.length-idx)) partsArray.append(subString+":"+String(partCount)) break } else { subString = nsString.substring(with: NSRange(location: idx, length: SPLIT_SUB_STRING_LENGTH)) partsArray.append(subString+":"+String(partCount)) } idx += SPLIT_SUB_STRING_LENGTH } for i in stride(from: 0, to: partsArray.count, by: 1) { partsArray[i] = partsArray[i]+"."+String(partCount) } return partsArray } class func parseScannedPart(_ str: String) -> (String, Int, Int) { let parts = str.components(separatedBy: ":") let data = parts[0] let partCountAndTotal = parts[1] let partCountAndTotalArray = partCountAndTotal.components(separatedBy: ".") let partCount = partCountAndTotalArray[0] let totalParts = partCountAndTotalArray[1] return (data, Int(partCount)!, Int(totalParts)!) } class func convertDataToDictionary(_ data: Data) -> [String:AnyObject]? { do { return try JSONSerialization.jsonObject(with: data, options: []) as? [String:AnyObject] } catch let error as NSError { print(error) } return nil } class func createSerializedSignedTxAipGapData(_ aipGapDataBase64: String, mnemonicOrExtendedPrivateKey: String, isTestnet:Bool) throws -> String? { if let signedTxHexAndTxHash = try createSignedTxAipGapData(aipGapDataBase64, mnemonicOrExtendedPrivateKey: mnemonicOrExtendedPrivateKey, isTestnet: isTestnet) { DLog("createSerializedSignedTxAipGapData signedTxHexAndTxHash \(signedTxHexAndTxHash)"); let aipGapDataJSONString = TLUtils.dictionaryToJSONString(false, dict: signedTxHexAndTxHash) let data = aipGapDataJSONString.data(using: String.Encoding.utf8) return data?.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0)) } return nil } class func createSignedTxAipGapData(_ aipGapDataBase64: String, mnemonicOrExtendedPrivateKey: String, isTestnet:Bool) throws -> NSDictionary? { let data = Data(base64Encoded: aipGapDataBase64, options: NSData.Base64DecodingOptions.ignoreUnknownCharacters) if data == nil { throw TLColdWalletError.InvalidScannedData("") return nil } if let result = convertDataToDictionary(data!) { DLog("createSignedTxAipGapData: \(result)") let extendedPublicKey = result["account_public_key"] as! String let txInputsAccountHDIdxes = result["tx_inputs_account_hd_idxes"] as! NSArray let accountIdx = TLHDWalletWrapper.getAccountIdxForExtendedKey(extendedPublicKey) DLog("createSignedTxAipGapData accountIdx extendedPublicKey: \(accountIdx) \(extendedPublicKey)") let mnemonicExtendedPrivateKey:String if TLHDWalletWrapper.phraseIsValid(mnemonicOrExtendedPrivateKey) { let masterHex = TLHDWalletWrapper.getMasterHex(mnemonicOrExtendedPrivateKey) mnemonicExtendedPrivateKey = TLHDWalletWrapper.getExtendPrivKey(masterHex, accountIdx: UInt(accountIdx)) DLog("createSignedTxAipGapData xxxx1 : \(mnemonicExtendedPrivateKey)") } else if TLHDWalletWrapper.isValidExtendedPrivateKey(mnemonicOrExtendedPrivateKey) { mnemonicExtendedPrivateKey = mnemonicOrExtendedPrivateKey DLog("createSignedTxAipGapData xxxx2 : \(mnemonicExtendedPrivateKey)") } else { throw TLColdWalletError.InvalidKey("") } let mnemonicExtendedPublicKey = TLHDWalletWrapper.getExtendPubKey(mnemonicExtendedPrivateKey) DLog("createSignedTxAipGapData xxxx3 : \(extendedPublicKey) \(mnemonicExtendedPublicKey)") if extendedPublicKey != mnemonicExtendedPublicKey { throw TLColdWalletError.MisMatchExtendedPublicKey("") } let privateKeysArray = NSMutableArray(capacity: txInputsAccountHDIdxes.count) for _txInputsAccountHDIdx in txInputsAccountHDIdxes { let txInputsAccountHDIdx = _txInputsAccountHDIdx as! NSDictionary let HDIndexNumber = txInputsAccountHDIdx["idx"] as! Int let isChange = txInputsAccountHDIdx["is_change"] as! Bool let addressSequence = [isChange ? Int(TLAddressType.change.rawValue) : Int(TLAddressType.main.rawValue), HDIndexNumber] let privateKey = TLHDWalletWrapper.getPrivateKey(mnemonicExtendedPrivateKey as NSString, sequence: addressSequence as NSArray, isTestnet: isTestnet) privateKeysArray.add(privateKey) } let base64UnsignedTx = result["unsigned_tx_base64"] as! String let txData = Data(base64Encoded: base64UnsignedTx, options: NSData.Base64DecodingOptions.ignoreUnknownCharacters) // .map({ NSString(data: $0, encoding: NSUTF8StringEncoding) }) DLog("Decoded: \(txData!)") let inputHexScriptsArray = result["input_scripts"] as! NSArray let inputScriptsArray = NSMutableArray(capacity: inputHexScriptsArray.count) for hexScript in inputHexScriptsArray { inputScriptsArray.add(TLWalletUtils.hexStringToData(hexScript as! String)!) } for _ in 0...3 { let txHexAndTxHash = TLCoreBitcoinWrapper.createSignedSerializedTransactionHex(txData!, inputScripts: inputScriptsArray, privateKeys: privateKeysArray, isTestnet: isTestnet) DLog("createSignedTxAipGapData txHexAndTxHash: \(txHexAndTxHash.debugDescription as AnyObject)") // break if txHexAndTxHash != nil { return txHexAndTxHash! } } } return nil } class func getSignedTxData(_ aipGapDataBase64: String) -> NSDictionary? { let data = Data(base64Encoded: aipGapDataBase64, options: NSData.Base64DecodingOptions.ignoreUnknownCharacters) if data == nil { return nil } if let result = convertDataToDictionary(data!) { return result as NSDictionary? } return nil } } ================================================ FILE: ArcBit/model/TLCoreBitcoinWrapper.swift ================================================ // // TLCoreBitcoinWrapper.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation class TLCoreBitcoinWrapper { // WARNING: returns compressed address only class func getAddressFromOutputScript(_ scriptHex:String, isTestnet:Bool) -> (String?){ let scriptData = TLWalletUtils.hexStringToData(scriptHex)! let script = BTCScript(data:scriptData) if let address = script?.standardAddress { if !isTestnet { return address.string } else { return BTCPublicKeyAddressTestnet(data: script?.standardAddress.data)!.string } } return nil } class func getStandardPubKeyHashScriptFromAddress(_ address:String, isTestnet:Bool) -> String { let scriptData = BTCScript(address: BTCAddress(base58String: address)) return scriptData!.hex } class func getAddress(_ privateKey:String, isTestnet:Bool) -> (String?){ if let key = BTCKey(wif: privateKey) { if !isTestnet { return key.address.string } else { return key.addressTestnet.string } } else { return nil } } class func getAddressFromPublicKey(_ publicKey:String, isTestnet:Bool) -> (String?){ if !isTestnet { if let key = BTCKey(publicKey: TLWalletUtils.hexStringToData(publicKey)!) { return key.address.string } else { return nil } } else { if let key = BTCKey(publicKey: TLWalletUtils.hexStringToData(publicKey)!) { return key.addressTestnet.string } else { return nil } } } // WARNING: returns compressed address only class func getAddressFromSecret(_ secret:String, isTestnet:Bool) -> (String?){ if let key = BTCKey(privateKey: BTCDataFromHex(secret)) { if !isTestnet { return key.compressedPublicKeyAddress.string } else { key.isPublicKeyCompressed = true return key.addressTestnet.string } } else { return nil } } class func privateKeyFromEncryptedPrivateKey(_ encryptedPrivateKey:String, password:String, isTestnet:Bool) -> (String?) { if let key = BRKey(bip38Key:encryptedPrivateKey, andPassphrase:password, isTestnet:isTestnet) { return key.privateKey } return nil } // WARNING: returns compressed address only class func privateKeyFromSecret(_ secret:String, isTestnet:Bool) -> (String){ let key = BTCKey(privateKey:BTCDataFromHex(secret)) key?.isPublicKeyCompressed = true if !isTestnet { return key!.privateKeyAddress.string } else { return key!.privateKeyAddressTestnet.string } } class func isAddressVersion0(_ address:String, isTestnet:Bool) -> (Bool){ if !isTestnet { return address.hasPrefix("1") } else { return address.hasPrefix("m") || address.hasPrefix("n") } } /* class func getBitcoinURL(address:String, amount:TLCoin, label:String) -> (String?){ assert(amount.greater(TLCoin.zero()), "BTCBitcoinURL does not allow <= 0 value") let btcAddress = BTCPublicKeyAddress.addressWithBase58String(address) as BTCAddress // not useable because BTCBitcoinURL does not allow 0 amount //let bitcoinURL = BTCBitcoinURL.URLWithAddress(btcAddress, amount:1, label:label) let bitcoinURL = BTCBitcoinURL.URLWithAddress(btcAddress, amount:BTCSatoshi(amount.toUInt64()), label:label) return bitcoinURL.absoluteString? } */ class func isValidAddress(_ address:String, isTestnet:Bool) -> (Bool){ return address.isValidBitcoinAddress(isTestnet) || TLStealthAddress.isStealthAddress(address, isTestnet:isTestnet) } class func isValidPrivateKey(_ privateKey:String, isTestnet:Bool) -> Bool{ return privateKey.isValidBitcoinPrivateKey(isTestnet) } class func isBIP38EncryptedKey(_ privateKey:String, isTestnet:Bool) -> Bool{ return (privateKey as NSString).substring(with: NSMakeRange(0, 2)) == "6P" } class func getSignature(_ privateKey:String, message:String) -> String { let key = BTCKey(privateKey: BTCDataFromHex(privateKey)) let signature = key?.signature(forMessage: message) assert((key?.isValidSignature(signature, forMessage: message))!, "") return signature!.base64EncodedString(options: NSData.Base64EncodingOptions.lineLength64Characters); } class func createSignedSerializedTransactionHex(_ hashes:NSArray, inputIndexes indexes:NSArray, inputScripts scripts:NSArray, outputAddresses:NSArray, outputAmounts amounts:NSArray, privateKeys:NSArray, outputScripts:NSArray?, isTestnet:Bool) -> NSDictionary? { return createSignedSerializedTransactionHex(hashes, inputIndexes: indexes, inputScripts: scripts, outputAddresses: outputAddresses, outputAmounts: amounts, privateKeys: privateKeys, outputScripts: outputScripts, signTx: true, isTestnet: isTestnet) } class func createSignedSerializedTransactionHex(_ hashes:NSArray, inputIndexes indexes:NSArray, inputScripts scripts:NSArray, outputAddresses:NSArray, outputAmounts amounts:NSArray, privateKeys:NSArray, outputScripts:NSArray?, signTx: Bool, isTestnet:Bool) -> NSDictionary? { let tx = BRTransaction(inputHashes:hashes as [AnyObject], inputIndexes:indexes as [AnyObject],inputScripts:scripts as [AnyObject], outputAddresses:outputAddresses as [AnyObject], outputAmounts:amounts as [AnyObject], isTestnet:isTestnet) if (outputScripts != nil) { for i in stride(from: 0, to: outputScripts!.count, by: 1) { let outputScript = outputScripts!.object(at: i) as! String tx?.insertOutputScript(TLWalletUtils.hexStringToData(outputScript), amount:UInt64(0), isTestnet:isTestnet) } } if !signTx { let inputHexScripts = NSMutableArray(capacity: tx!.inputScripts.count) for script in tx!.inputScripts { inputHexScripts.add(TLWalletUtils.dataToHexString(script as! Data)) } return [ "inputScripts": inputHexScripts, "txHex": TLWalletUtils.dataToHexString(tx!.data), ] } tx?.sign(withPrivateKeys: privateKeys as [AnyObject], isTestnet:isTestnet) assert((tx?.isSigned)!, "tx is not signed") let txFromHexData = BRTransaction(message: tx?.data, isTestnet: isTestnet) var expectedOutputCount = outputAddresses.count if outputScripts != nil { expectedOutputCount += outputScripts!.count } if txFromHexData?.outputScripts.count != expectedOutputCount { return nil } return [ "txHex": TLWalletUtils.dataToHexString(tx!.data), "txHash": TLWalletUtils.reverseHexString(TLWalletUtils.dataToHexString(tx!.txHash)), "txSize": tx!.size ] } class func createSignedSerializedTransactionHex(_ unsignedTx:Data, inputScripts:NSArray, privateKeys:NSArray, isTestnet:Bool) -> NSDictionary? { let tx = BRTransaction(message: unsignedTx, isTestnet: isTestnet) let inputHashes = tx!.inputHashes as NSArray let inputIndexes = tx!.inputIndexes as NSArray let outputAmounts = tx!.outputAmounts as NSArray let outputAddresses = tx!.outputAddresses as NSArray let txHexAndTxHash = TLCoreBitcoinWrapper.createSignedSerializedTransactionHex(inputHashes, inputIndexes:inputIndexes, inputScripts:inputScripts, outputAddresses:outputAddresses, outputAmounts:outputAmounts, privateKeys:privateKeys, outputScripts:nil, signTx: true, isTestnet: isTestnet) return txHexAndTxHash } } ================================================ FILE: ArcBit/model/TLCrypto.swift ================================================ // // TLCrypto.swift // ArcBit // // Created by Tim Lee on 8/10/15. // Copyright (c) 2015 ArcBit. All rights reserved. // import Foundation let kRNCryptorAES256Settings:RNCryptorSettings = RNCryptorSettings(algorithm: CCAlgorithm(kCCAlgorithmAES128), blockSize: kCCBlockSizeAES128, IVSize: kCCBlockSizeAES128, options: CCOptions(kCCOptionPKCS7Padding), HMACAlgorithm: CCHmacAlgorithm(kCCHmacAlgSHA256), HMACLength: Int(CC_SHA256_DIGEST_LENGTH), keySettings: RNCryptorKeyDerivationSettings(keySize: kCCKeySizeAES256, saltSize: 8, PBKDFAlgorithm: CCPBKDFAlgorithm(kCCPBKDF2), PRF: CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA1), rounds: 10000, hasV2Password: false), HMACKeySettings: RNCryptorKeyDerivationSettings(keySize: kCCKeySizeAES256, saltSize: 8, PBKDFAlgorithm: CCPBKDFAlgorithm(kCCPBKDF2), PRF: CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA1), rounds: 10000, hasV2Password: false)) class TLCrypto { class func getDefaultPBKDF2Iterations() -> UInt32 { return 10000 } class func encrypt(_ plainText: String, password: String) -> (String) { return TLCrypto.encrypt(plainText, password: password, PBKDF2Iterations: getDefaultPBKDF2Iterations()) } class func decrypt(_ cipherText: String, password: String) -> (String?) { return TLCrypto.decrypt(cipherText, password: password, PBKDF2Iterations: getDefaultPBKDF2Iterations()) } class func encrypt(_ plainText: String, password: String, PBKDF2Iterations: UInt32) -> String { var settings = kRNCryptorAES256Settings settings.keySettings.rounds = PBKDF2Iterations //DLog("saveWalletJson encrypt: %@", plainText) let data = plainText.data(using: String.Encoding.utf8) var error: NSError? = nil var encryptedData: Data! do { encryptedData = try RNEncryptor.encryptData(data, with: settings, password: password) } catch let error1 as NSError { error = error1 encryptedData = nil } if (error != nil) { DLog("TLCrypto encrypt error: \(error!.localizedDescription)") NSException(name: NSExceptionName(rawValue: "Error"), reason: "Error encrypting", userInfo: nil).raise() } let base64EncryptedString = encryptedData.base64EncodedString(options: NSData.Base64EncodingOptions.lineLength64Characters) //DLog("TLCrypto encrypt base64EncryptedData: %@", base64EncryptedString) return base64EncryptedString } class func decrypt(_ cipherText: String, password: String, PBKDF2Iterations: UInt32) -> String? { var settings = kRNCryptorAES256Settings settings.keySettings.rounds = PBKDF2Iterations let encryptedData = Data(base64Encoded: cipherText, options: NSData.Base64DecodingOptions.ignoreUnknownCharacters) var error: NSError? = nil let decryptedData: Data! do { decryptedData = try RNDecryptor.decryptData(encryptedData, with: settings, password: password) } catch let error1 as NSError { error = error1 decryptedData = nil } // Note: there will only be error if password is incorrect, if PBKDF2Iterations dont match then there will be no error, just nil decryptedData if (error != nil) { return nil } let decryptedString = NSString(data: decryptedData!, encoding: String.Encoding.utf8.rawValue) //DLog("TLCrypto decrypt decryptedString: %@", decryptedString!) return decryptedString as? String } class func SHA256HashFor(_ input: NSString) -> String { let str = input.utf8String let result = [CUnsignedChar](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH)) let resultBytes: UnsafeMutablePointer = UnsafeMutablePointer(mutating: result) CC_SHA256(str, CC_LONG(strlen(str)), resultBytes) let ret = NSMutableString(capacity:Int(CC_SHA256_DIGEST_LENGTH)*2) for i in stride(from: 0, to: Int(CC_SHA256_DIGEST_LENGTH), by: 1) { ret.appendFormat("%02x",result[i]) } return ret as String } class func doubleSHA256HashFor(_ input: NSString) -> String { let str = input.utf8String let result = [CUnsignedChar](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH)) let resultBytes: UnsafeMutablePointer = UnsafeMutablePointer(mutating: result) CC_SHA256(str, CC_LONG(strlen(str)), resultBytes) let result2 = [CUnsignedChar](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH)) let result2Bytes: UnsafeMutablePointer = UnsafeMutablePointer(mutating: result2) CC_SHA256(result, CC_LONG(CC_SHA256_DIGEST_LENGTH), result2Bytes) let ret = NSMutableString(capacity:Int(CC_SHA256_DIGEST_LENGTH*2)) for i in stride(from: 0, to: Int(CC_SHA256_DIGEST_LENGTH), by: 1) { ret.appendFormat("%02x",result2[i]) } return ret as String } } ================================================ FILE: ArcBit/model/TLCurrencyFormat.swift ================================================ // // TLCurrencyFormat.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation class TLCurrencyFormat { struct STATIC_MEMBERS { static var _currencies: NSArray? static var _currencySymbols: NSArray? static var _bitcoinDisplays: NSArray? static var _bitcoinDisplayWords: NSArray? } class func bitcoinAmountStringToCoin(_ amount: String, locale: Locale=Locale.current) -> TLCoin { return amountStringToCoin(amount, bitcoinDenomination: TLBitcoinDenomination.bitcoin, locale: locale) } class func properBitcoinAmountStringToCoin(_ amount: String, locale: Locale=Locale.current) -> TLCoin { return amountStringToCoin(amount, bitcoinDenomination: TLPreferences.getBitcoinDenomination(), locale: locale) } fileprivate class func amountStringToCoin(_ amount: String, bitcoinDenomination: TLBitcoinDenomination, locale: Locale) -> TLCoin { if amount.characters.count != 0 { if let _ = amount.rangeOfCharacter(from: CharacterSet(charactersIn: "0123456789.,").inverted) { return TLCoin.zero() } else { return TLCoin(bitcoinAmount: amount, bitcoinDenomination: bitcoinDenomination, locale: locale) } } else { return TLCoin.zero() } } class func coinToProperBitcoinAmountString(_ amount: TLCoin, withCode: Bool = false) -> String { if withCode { return amount.bigIntegerToBitcoinAmountString(TLPreferences.getBitcoinDenomination()) + " " + TLCurrencyFormat.getBitcoinDisplay() } else { return amount.bigIntegerToBitcoinAmountString(TLPreferences.getBitcoinDenomination()) } } class func coinToProperFiatAmountString(_ amount: TLCoin, withCode: Bool = false) -> String { let currency = TLCurrencyFormat.getFiatCurrency() if withCode { return TLExchangeRate.instance().fiatAmountStringFromBitcoin(currency, bitcoinAmount: amount) + " " + TLCurrencyFormat.getFiatCurrency() } else { return TLExchangeRate.instance().fiatAmountStringFromBitcoin(currency, bitcoinAmount: amount) } } class func getProperAmount(_ amount: TLCoin) -> NSString { var balance: NSString? = nil if (TLPreferences.isDisplayLocalCurrency()) { let currency = TLCurrencyFormat.getProperCurrency() balance = TLExchangeRate.instance().fiatAmountStringFromBitcoin(currency, bitcoinAmount: amount) as NSString? } else { balance = coinToProperBitcoinAmountString(amount) as NSString? } balance = String(format: "%@ %@", balance!, getProperCurrency()) as NSString? return balance! } class func getCurrencySymbol() -> String { return getCurrencySymbolArray().object(at: Int(TLPreferences.getCurrencyIdx()!)!) as! String } class func getFiatCurrency() -> String { return getCurrencyArray().object(at: Int(TLPreferences.getCurrencyIdx()!)!) as! String } class func getProperCurrency() -> String { if (TLPreferences.isDisplayLocalCurrency()) { return getCurrencyArray().object(at: Int(TLPreferences.getCurrencyIdx()!)!) as! String } else { return getBitcoinDisplay() } } class func getBitcoinDisplay() -> String { let bitcoinDenomination = TLPreferences.getBitcoinDenomination() if (bitcoinDenomination == TLBitcoinDenomination.bitcoin) { return getBitcoinDisplayArray().object(at: 0) as! String } else if (bitcoinDenomination == TLBitcoinDenomination.milliBit) { return getBitcoinDisplayArray().object(at: 1) as! String } else { return getBitcoinDisplayArray().object(at: 2) as! String } } class func getBitcoinDisplayWord() -> String { let bitcoinDenomination = TLPreferences.getBitcoinDenomination() if (bitcoinDenomination == TLBitcoinDenomination.bitcoin) { return getBitcoinDisplayWordArray().object(at: 0) as! String } else if (bitcoinDenomination == TLBitcoinDenomination.milliBit) { return getBitcoinDisplayWordArray().object(at: 1) as! String } else { return getBitcoinDisplayWordArray().object(at: 2) as! String } } class func getBitcoinDisplayArray() -> NSArray { if (STATIC_MEMBERS._bitcoinDisplays == nil) { STATIC_MEMBERS._bitcoinDisplays = [ "BTC", "mBTC", "uBTC", ] } return STATIC_MEMBERS._bitcoinDisplays! } class func getBitcoinDisplayWordArray() -> NSArray { if (STATIC_MEMBERS._bitcoinDisplayWords == nil) { STATIC_MEMBERS._bitcoinDisplayWords = [ "Bitcoin", "MilliBit", "Bits", ] } return STATIC_MEMBERS._bitcoinDisplayWords! } class func getCurrencySymbolArray() -> (NSArray) { if (STATIC_MEMBERS._currencySymbols == nil) { STATIC_MEMBERS._currencySymbols = [ "$", "R$", "$", "CHF", "$", "¥", "kr", "€", "£", "$", "kr", "¥", "₩", "$", "zł", "RUB", "kr", "$", "฿", "$", "$", "D", "N", "L", "D", "G", "A", "S", "G", "N", "M", "D", "T", "N", "D", "F", "D", "D", "B", "D", "N", "P", "R", "D", "F", "F", "P", "C", "E", "K", "F", "P", "D", "K", "P", "B", "D", "P", "L", "S", "P", "D", "F", "Q", "D", "L", "K", "G", "F", "R", "S", "R", "D", "P", "D", "D", "S", "S", "R", "F", "D", "D", "T", "K", "P", "R", "D", "L", "L", "L", "D", "D", "L", "A", "D", "K", "T", "P", "O", "R", "R", "K", "N", "R", "N", "D", "N", "O", "K", "R", "R", "B", "N", "K", "P", "R", "G", "R", "N", "D", "F", "R", "D", "R", "G", "P", "L", "S", "D", "D", "C", "P", "L", "S", "T", "D", "P", "Y", "D", "S", "H", "X", "U", "S", "F", "D", "V", "T", "F", "G", "U", "D", "F", "F", "R", "R", "W", "L"] } return STATIC_MEMBERS._currencySymbols! } class func getCurrencyArray() -> NSArray { if (STATIC_MEMBERS._currencies == nil) { STATIC_MEMBERS._currencies = [ "AUD", "BRL", "CAD", "CHF", "CLP", "CNY", "DKK", "EUR", "GBP", "HKD", "ISK", "JPY", "KRW", "NZD", "PLN", "RUB", "SEK", "SGD", "THB", "TWD", "USD", "AED", "AFN", "ALL", "AMD", "ANG", "AOA", "ARS", "AWG", "AZN", "BAM", "BBD", "BDT", "BGN", "BHD", "BIF", "BMD", "BND", "BOB", "BSD", "BTN", "BWP", "BYR", "BZD", "CDF", "CLF", "COP", "CRC", "CVE", "CZK", "DJF", "DOP", "DZD", "EEK", "EGP", "ETB", "FJD", "FKP", "GEL", "GHS", "GIP", "GMD", "GNF", "GTQ", "GYD", "HNL", "HRK", "HTG", "HUF", "IDR", "ILS", "INR", "IQD", "JEP", "JMD", "JOD", "KES", "KGS", "KHR", "KMF", "KWD", "KYD", "KZT", "LAK", "LBP", "LKR", "LRD", "LSL", "LTL", "LVL", "LYD", "MAD", "MDL", "MGA", "MKD", "MMK", "MNT", "MOP", "MRO", "MUR", "MVR", "MWK", "MXN", "MYR", "MZN", "NAD", "NGN", "NIO", "NOK", "NPR", "OMR", "PAB", "PEN", "PGK", "PHP", "PKR", "PYG", "QAR", "RON", "RSD", "RWF", "SAR", "SBD", "SCR", "SDG", "SHP", "SLL", "SOS", "SRD", "STD", "SVC", "SYP", "SZL", "TJS", "TMT", "TND", "TOP", "TRY", "TTD", "TZS", "UAH", "UGX", "UYU", "UZS", "VEF", "VND", "VUV", "WST", "XAF", "XAG", "XAU", "XCD", "XOF", "XPF", "YER", "ZAR", "ZMW", "ZWL"] } return STATIC_MEMBERS._currencies! } } ================================================ FILE: ArcBit/model/TLDisplayStrings.swift ================================================ // // TLDisplayStrings.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation class TLDisplayStrings { class func DISMISS_STRING() -> String { return "".localized } class func X_NOT_ALLOWED_TO_ACCESS_THE_CAMERA_STRING() -> String { return "%@ is not allowed to access the camera".localized } class func X_SERVERS_NOT_REACHABLE_STRING() -> String { return "%@ servers not reachable.".localized } class func X_SLASH_Y_PARTS_SCANNED_STRING() -> String { return "%d/%d parts scanned.".localized } class func ONE_CONFIRMATION_STRING() -> String { return "1 Confirmation".localized } class func X_CONFIRMATIONS_STRING() -> String { return "%llu confirmations".localized } class func IMPORTING_A_WATCH_ONLY_ADDRESS_DESC_STRING() -> String { return "A bitcoin address typically begins with a '1' or '3'. You can see the transactions and track the balance of an address, but you cannot spend from an imported address.\nYou can temporarily import this watch addresses private key to spend its bitcoins. Simply go the Send screen and select a watch address to spend from and you will be prompted to temporarily import your addresses' private key when you click 'Review Payment' in the Send screen. The private key will stay in memory until the app exits or until you remove it manually in the Accounts screen.".localized } class func WHAT_IS_A_BITCOIN_WALLET_DESC_STRING() -> String { return "A bitcoin wallet is a software application that allows people to send, receive and manage their bitcoins.\nBe aware of how other bitcoin applications store your bitcoins' private keys, which are needed to spend your bitcoins.\nThere are generally three different ways applications can your store your bitcoins.\n1.\nThe banking model, where your bitcoin private keys are held for you by someone else.\n2.\nThe security box model, where your bitcoin private keys are stored encrypted on someone else’s servers.\n3.\nThe wallet model, where your bitcoin private keys are stored only on your device.".localized } class func IMPORTING_A_PRIVATE_KEY_DESC_STRING() -> String { return "A private key begins with an 'L', 'K', or '5'.\nBIP 38 encrypted private keys can also be imported. They can either be imported encrypted or unencrypted. If you choose to import it encrypted, you will need to input the password each time you spend from your encrypted private key.".localized } class func ACCOUNT_X_IMPORTED_STRING() -> String { return "Account %@ imported".localized } class func ACCOUNT_X_STRING() -> String { return "Account %lu".localized } class func ACCOUNT_1_STRING() -> String { return "Account 1".localized } class func ACCOUNT_ACTIONS_STRING() -> String { return "Actions".localized } class func ACCOUNT_ID_COLON_X_STRING() -> String { return "Account ID: %u".localized } class func ACCOUNT_PRIVATE_KEY_STRING() -> String { return "Account Private Key".localized } class func ACCOUNT_PUBLIC_KEY_STRING() -> String { return "Account Public Key".localized } class func ACCOUNT_NAME_STRING() -> String { return "Name".localized } class func ACCOUNT_PRIVATE_KEY_DOES_NOT_MATCH_STRING() -> String { return "Account private key does not match imported account public key".localized } class func ACCOUNT_PRIVATE_KEY_MISSING_STRING() -> String { return "Account private key missing".localized } class func ACCOUNTS_STRING() -> String { return "Accounts".localized } class func ACHIEVEMENT_LIST_STRING() -> String { return "Achievement List".localized } class func ACHIEVEMENTS_STRING() -> String { return "Achievements".localized } class func ACTIVE_CHANGE_ADDRESSES_STRING() -> String { return "Active Change Addresses".localized } class func ACTIVE_MAIN_ADDRESSES_STRING() -> String { return "Active Main Addresses".localized } class func ADD_CONTACTS_ENTRY_STRING() -> String { return "Add Contacts Entry".localized } class func ADDRESS_STRING() -> String { return "Address".localized } class func IMPORTED_ADDRESS_STRING() -> String { return "Imported Address".localized } class func ADDRESS_ID_STRING() -> String { return "Address ID ".localized } class func ADDRESS_ID_X_STRING_STRING() -> String { return "Address ID: %lu".localized } class func ADDRESSES_STRING() -> String { return "Addresses".localized } class func ADVANCE_ACHIEVEMENT_LIST_STRING() -> String { return "Advanced Achievement List".localized } class func ADVANCE_FAQ_STRING() -> String { return "Advanced FAQ".localized } class func ADVANCE_HOW_TO_COLON_STRING_STRING() -> String { return "Advanced how To:".localized } class func WHAT_ARE_TRANSACTION_CONFIRMATIONS_DESC_STRING() -> String { return "After a transaction is broadcast to the Bitcoin network, it may be included in a block that is published to the network. When that happens, it is said that the transaction has been mined at a depth of 1 block. With each subsequent block that is found, the number of blocks deep is increased by one. To be secure against double spending, a transaction should not be considered as confirmed until it is a certain number of blocks deep.\nA good rule of thumb is that 1 confirmation is good for small value amounts of bitcoins and a user should wait for more confirmations for larger value amounts.\nArcBit will display the confirmation number up until the 6th confirmation.".localized } class func INVALID_AMOUNT_STRING() -> String { return "Invalid amount".localized } class func WHAT_ARE_ACCOUNTS_DESC_STRING() -> String { return "An account is a collection of bitcoin addresses. With accounts, you will no longer have to manage bitcoin addresses directly anymore. Since address reuse results in a loss of privacy for people using Bitcoin, ArcBit’s HD wallet account system will automatically handle the cycling of bitcoin addresses for you. This ensures you don’t use the same bitcoin address more then once.\nEach account also has a reusable address. You can find it in your Receive screen. Swipe all the way to right on the QRCode in your Receive screen and you will find a reusable address.\nYou can create an unlimited amount of accounts with ArcBit. See the help section on how to create a new account in ArcBit.".localized } class func IMPORTING_AN_ACCOUNT_DESC_STRING() -> String { return "An account private key begins with the letters 'xprv'. You can see, spend and recover the transactions and bitcoins of an entire account from an account private key.".localized } class func IMPORTING_A_WATCH_ONLY_ACCOUNT_DESC_STRING() -> String { return "An account public key begins with the letters 'xpub'. You can see the transactions and bitcoins of an entire account from an account private key, with the exception of reusable address payments. Future releases will address this issue.\nYou can temporarily import the corresponding account private key for this accounts' account public key to spend your watch accounts' bitcoins. Simply go to the Send screen and select a watch account to spend from and you will be prompted to temporarily import your account's private key when you click 'Review Payment' on the Send screen. The private key will stay in memory until the app exits or until you remove it manually on the Accounts screen.".localized } class func ARCBIT_BRAIN_WALLET_STRING() -> String { return "ArcBit Brain Wallet".localized } class func ARCBIT_WEB_WALLET_STRING() -> String { return "ArcBit Web Wallet".localized } class func HOW_DOES_ARCBIT_WALLET_WORK_DESC_STRING() -> String { return "ArcBit uses the the bitcoin wallet model (See the section ’What is a bitcoin wallet?’ to understand the 3 different security models of bitcoin software). However, if you use iCloud to backup your wallet, you will be using the security box model. It is recommended that you do not use iCloud and be responsible for your bitcoins yourself. For those who don’t want to remember a simple backup passphrase, iCloud backup is a good alternative.".localized } class func ARCHIVE_ACCOUNT_STRING() -> String { return "Archive Account".localized } class func ARCHIVE_ADDRESS_STRING() -> String { return "Archive address".localized } class func ARCHIVED_ACCOUNTS_STRING() -> String { return "Archived Accounts".localized } class func ARCHIVED_CHANGE_ADDRESSES_STRING() -> String { return "Archived Change Addresses".localized } class func ARCHIVED_COLD_WALLET_ACCOUNTS_STRING() -> String { return "Archived Cold Wallet Accounts".localized } class func ARCHIVED_IMPORTED_ACCOUNTS_STRING() -> String { return "Archived Imported Accounts".localized } class func ARCHIVED_IMPORTED_ADDRESSES_STRING() -> String { return "Archived Imported Addresses".localized } class func ARCHIVED_IMPORTED_WATCH_ACCOUNTS_STRING() -> String { return "Archived Imported Watch Accounts".localized } class func ARCHIVED_IMPORTED_WATCH_ADDRESSES_STRING() -> String { return "Archived Imported Watch Addresses".localized } class func ARCHIVED_MAIN_ADDRESSES_STRING() -> String { return "Archived Main Addresses".localized } class func ARE_YOU_SURE_YOU_WANT_TO_ARCHIVE_ACCOUNT_X_STRING() -> String { return "Are you sure you want to archive account %@?".localized } class func ARE_YOU_SURE_YOU_WANT_TO_ARCHIVE_ADDRESS_X_STRING() -> String { return "Are you sure you want to archive address %@?".localized } class func ARE_YOU_SURE_YOU_WANT_TO_DELETE_THIS_ACCOUNT_STRING() -> String { return "Are you sure you want to delete this account?".localized } class func ARE_YOU_SURE_YOU_WANT_TO_UNARCHIVE_ACCOUNT_X_STRING() -> String { return "Are you sure you want to unarchive account %@".localized } class func ARE_YOU_SURE_YOU_WANT_TO_UNARCHIVE_ADDRESS_X_STRING() -> String { return "Are you sure you want to unarchive address %@?".localized } class func AUTHORIZE_COLD_WALLET_ACCOUNT_PAYMENT_STRING() -> String { return "Authorize Cold Wallet Account Payment".localized } class func AUTHORIZE_PAYMENT_STRING() -> String { return "Authorize Payment".localized } class func BACK_UP_PASSPHRASE_STRING() -> String { return "Backup Passphrase".localized } class func BACK_UP_WALLET_STRING() -> String { return "Backup wallet".localized } class func BACKUP_LOCAL_WALLET_STRING() -> String { return "Backup local wallet".localized } class func BACKUP_PASSPHRASE_FOUND_IN_KEYCHAIN_STRING() -> String { return "Backup passphrase found in keychain".localized } class func WHAT_ARE_THE_BENEFITS_AND_ADVANTAGES_OF_BITCOIN_DESC_STRING() -> String { return "Bitcoin cuts out the middleman and allows you to send money anywhere in the world with an internet connection with minimum to zero fees.".localized } class func WHAT_IS_BITCOIN_DESC_STRING() -> String { return "Bitcoin, uppercase 'B', is an online payment system invented in 2008 and released as open-source software in 2009 by a programmer named Satoshi Nakamoto. The system is decentralized and peer-to-peer allowing users to transact directly without needing an intermediary.\nBitcoin is also a platform of which other decentralized applications can be built upon. Bitcoin, lowercase 'b' is the currency unit that Bitcoin uses.".localized } class func HOW_DO_I_GET_BITCOINS_DESC_STRING() -> String { return "Bitcoins can be purchased from various bitcoin exchanges. ArcBit is not a bitcoin exchange. ArcBit is a bitcoin wallet. After you purchase some bitcoins from an exchange, you can move it to a bitcoin wallet.".localized } class func CANCEL_STRING() -> String { return "Cancel".localized } class func CANNOT_ARCHIVE_YOUR_DEFAULT_ACCOUNT_STRING() -> String { return "Cannot archive your default account".localized } class func CANNOT_ARCHIVE_YOUR_ONE_AND_ONLY_ACCOUNT_STRING() -> String { return "Cannot archive your one and only account".localized } class func CANNOT_CREATE_TRANSACTIONS_WITH_OUTPUTS_LESS_THEN_X_BITCOINS_STRING() -> String { return "Cannot create transactions with outputs less then %@".localized } class func CANNOT_DECRYPT_ICLOUD_BACKUP_WALLET_STRING() -> String { return "Cannot decrypt iCloud backup wallet.".localized } class func CANNOT_IMPORT_REUSABLE_ADDRESS_STRING() -> String { return "Cannot import reusable address".localized } class func CHANGE_ADDRESS_ID_STRING() -> String { return "Change Address ID ".localized } class func CHANGE_AUTOMATIC_TRANSACTION_FEE_STRING() -> String { return "Change Automatic Transaction Fee".localized } class func CHANGE_BLOCKEXPLORER_TYPE_STRING() -> String { return "Change Blockexplorer Type".localized } class func CHECK_OUT_THE_ARCBIT_BRAIN_WALLET_STRING() -> String { return "Check out the ArcBit Brain Wallet".localized } class func CHECK_OUT_THE_ARCBIT_WEB_WALLET_STRING() -> String { return "Check out the ArcBit Web Wallet".localized } class func CHECK_OUT_THE_ARCBIT_WEB_WALLET_EXCLAMATION_STRING() -> String { return "Check out the ArcBit Web Wallet!".localized } class func CHECKING_TRANSACTION_STRING() -> String { return "Checking Transaction".localized } class func CLEAR_ACCOUNT_PRIVATE_KEY_FROM_MEMORY_STRING() -> String { return "Clear account private key from memory".localized } class func CLEAR_PRIVATE_KEY_FROM_MEMORY_STRING() -> String { return "Clear private key from memory".localized } class func CLICK_AN_ADDRESS_STRING() -> String { return "Click an address".localized } class func CLICK_THE_BUTTON_WITH_THE_ARROW_STRING() -> String { return "Click the button with the arrow".localized } class func CLICK_THE_PLUS_BUTTON_AT_THE_TOP_RIGHT_STRING() -> String { return "Click the plus button at the top right".localized } class func CLICK_THE_CONTACTS_BUTTON_STRING() -> String { return "Click the ‘Contacts’ button".localized } class func CLICK_ACCOUNTS_STRING() -> String { return "Click ‘Accounts’".localized } class func CLICK_ADVANCED_SETTINGS_STRING() -> String { return "Click ‘Advanced settings’".localized } class func CLICK_ARCHIVE_ACCOUNT_STRING() -> String { return "Click ‘Archive Account’".localized } class func CLICK_CREATE_NEW_ACCOUNT_STRING() -> String { return "Click ‘Create New Account’".localized } class func CLICK_DELETE_STRING() -> String { return "Click ‘Delete’".localized } class func CLICK_DONE_STRING() -> String { return "Click ‘Done’".localized } class func CLICK_EDIT_ACCOUNT_NAME_STRING() -> String { return "Click ‘Edit Account Name’".localized } class func CLICK_EDIT_STRING() -> String { return "Click ‘Edit’".localized } class func CLICK_ENABLE_PIN_CODE_STRING() -> String { return "Click ‘Enable PIN Code’".localized } class func CLICK_HISTORY_STRING() -> String { return "Click ‘History’".localized } class func CLICK_IMPORT_ACCOUNT_STRING() -> String { return "Click ‘Import Account’".localized } class func CLICK_IMPORT_PRIVATE_KEY_STRING() -> String { return "Click ‘Import Private Key’".localized } class func CLICK_IMPORT_WATCH_ACCOUNT_STRING() -> String { return "Click ‘Import Watch Only Account’".localized } class func CLICK_IMPORT_WATCH_ADDRESS_STRING() -> String { return "Click ‘Import Watch Only Address’".localized } class func CLICK_LABEL_TRANSACTION_STRING() -> String { return "Click ‘Label transaction’".localized } class func CLICK_RESTORE_WALLET_STRING() -> String { return "Click ‘Restore Wallet’".localized } class func CLICK_RESTORE_STRING() -> String { return "Click ‘Restore’".localized } class func CLICK_REVIEW_PAYMENT_STRING() -> String { return "Click ‘Review Payment’".localized } class func CLICK_SEND_STRING() -> String { return "Click ‘Send’".localized } class func CLICK_SET_TRANSACTION_FEE_STRING() -> String { return "Click ‘Set Transaction Fee’".localized } class func CLICK_SETTINGS_STRING() -> String { return "Click ‘Settings’".localized } class func CLICK_SHOW_BACKUP_PASSPHRASE_STRING() -> String { return "Click ‘Show Backup Passphrase’".localized } class func CLICK_VIEW_ADDRESSES_STRING() -> String { return "Click ‘View Addresses’".localized } class func CLICK_VIEW_ACCOUNT_PRIVATE_KEY_QR_CODE_STRING() -> String { return "Click ‘View account private key QR code’".localized } class func CLICK_VIEW_ACCOUNT_PUBLIC_KEY_QR_CODE_STRING() -> String { return "Click ‘View account public key QR code’".localized } class func CLICK_VIEW_ADDRESS_QR_CODE_STRING() -> String { return "Click ‘View address QR code’".localized } class func CLICK_VIEW_IN_WEB_STRING() -> String { return "Click ‘View in web’".localized } class func CLICK_VIEW_PRIVATE_KEY_QR_CODE_STRING() -> String { return "Click ‘View private key QR code’".localized } class func CLICK_BLOCKEXPLORER_API_TYPE_STRING() -> String { return "Click ‘blockexplorer API type’".localized } class func CLICK_RECEIVE_STRING() -> String { return "Click ’Receive’".localized } class func CLOSE_STRING() -> String { return "Close".localized } class func COLD_WALLET_STRING() -> String { return "Cold Wallet".localized } class func COLD_WALLET_ACCOUNTS_STRING() -> String { return "Cold Wallet Accounts".localized } class func IMPORTED_COLD_WALLET_ACCOUNTS_REUSABLE_ADDRESS_INFO_DESC_STRING() -> String { return "Cold Wallet Accounts can't see reusable address payments, thus this accounts' reusable address is not available.".localized } class func COLD_WALLET_OVERVIEW_STRING() -> String { return "Cold Wallet Overview".localized } class func COLD_WALLET_PRIVATE_KEYS_ARE_NOT_STORED_HERE_STRING() -> String { return "Cold wallet private keys are not stored here and cannot be viewed".localized } class func COMPLETE_STRING() -> String { return "Complete".localized } class func COMPLETE_STEP_1_STRING() -> String { return "Complete step 1".localized } class func CONFIRM_PIN_CODE_STRING() -> String { return "Confirm Pin Code".localized } class func CONTINUE_STRING() -> String { return "Continue".localized } class func COPIED_TO_CLIPBOARD_STRING() -> String { return "Copied To clipboard".localized } class func COPY_TO_CLIPBOARD_STRING() -> String { return "Copy".localized } class func COPY_TRANSACTION_ID_TO_CLIPBOARD_STRING() -> String { return "Copy Transaction ID to Clipboard".localized } class func CREATE_COLD_WALLET_STRING() -> String { return "Create Cold Wallet".localized } class func CREATE_NEW_ACCOUNT_STRING() -> String { return "Create New Account".localized } class func CREATE_NEW_CONTACT_STRING() -> String { return "Create new contact".localized } class func DECRYPTING_PRIVATE_KEY_STRING() -> String { return "Decrypting".localized } class func DELETE_STRING() -> String { return "Delete".localized } class func DELETE_X_STRING() -> String { return "Delete %@".localized } class func DELETE_ACCOUNT_STRING() -> String { return "Delete Account".localized } class func DELETE_CONTACTS_ENTRY_STRING() -> String { return "Delete Contact".localized } class func DELETE_ADDRESS_STRING() -> String { return "Delete address".localized } class func DONT_MANAGE_INDIVIDUAL_ACCOUNT_ADDRESS_WARNING_DESC_STRING() -> String { return "Do not use the QR code from here to receive bitcoins. Go to the Receive screen to get a QR code to receive bitcoins.".localized } class func ICLOUD_BACKUP_NOT_FOUND_DESC_STRING() -> String { return "Do you want to load and backup your current local wallet file?".localized } class func DO_YOU_WANT_TO_LOAD_LOCAL_WALLET_FILE_STRING() -> String { return "Do you want to load local wallet file?".localized } class func BACKUP_PASSPHRASE_FOUND_IN_KEYCHAIN_DESC_STRING() -> String { return "Do you want to restore from your backup passphrase or start a new wallet?".localized } class func ASK_TEMPORARY_IMPORT_ACCOUNT_PRIVATE_KEY_STRING() -> String { return "Do you want to temporary import your account private key?".localized } class func DO_YOU_WANT_TO_TEMPORARY_IMPORT_YOUR_PRIVATE_KEY_STRING() -> String { return "Do you want to temporary import your private key?".localized } class func DONT_REMIND_ME_STRING() -> String { return "Don't remind me".localized } class func DONE_STRING() -> String { return "Done".localized } class func WHAT_ARE_ACCOUNT_EXTENDED_KEYS_DESC_STRING() -> String { return "Each account has a public and private account key. Account keys should be kept secret as they are used to view the account's transactions and spend the account's bitcoins.".localized } class func EDIT_STRING() -> String { return "Edit".localized } class func EDIT_ACCOUNT_NAME_STRING() -> String { return "Edit Account Name".localized } class func EDIT_CONTACTS_ENTRY_STRING() -> String { return "Edit Contact Name".localized } class func EDIT_LABEL_STRING() -> String { return "Edit Label".localized } class func EDIT_TRANSACTION_LABEL_STRING() -> String { return "Edit Transaction label".localized } class func EMAIL_SUPPORT_STRING() -> String { return "Email Support".localized } class func ENABLE_PIN_CODE_TO_BETTER_SECURE_WALLET_STRING() -> String { return "Enable PIN code in settings to better secure your wallet.".localized } class func ENABLE_PIN_CODE_STRING() -> String { return "Enable Pin Code".localized } class func ENABLE_TRANSACTION_FEE_STRING() -> String { return "Enable Transaction Fee".localized } class func ENABLE_ADVANCED_MODE_STRING() -> String { return "Enable advanced mode".localized } class func ENCOUNTERED_ERROR_CREATING_TRANSACTION_TRY_AGAIN_STRING() -> String { return "Encountered error creating transaction. Please try again.".localized } class func ENCRYPTED_STRING() -> String { return "Encrypted".localized } class func ENTER_LABEL_STRING() -> String { return "Enter Label".localized } class func ENTER_PIN_CODE_STRING() -> String { return "Enter PIN".localized } class func ENTER_BACKUP_PASSPHRASE_STRING() -> String { return "Enter backup passphrase".localized } class func ENTER_PASSPHRASE_FOR_ICLOUD_BACKUP_WALLET_STRING() -> String { return "Enter passphrase for your iCloud backup wallet.".localized } class func ENTER_PASSWORD_FOR_ENCRYPTED_PRIVATE_KEY_STRING() -> String { return "Enter password for encrypted private key".localized } class func ERROR_STRING() -> String { return "Error".localized } class func ERROR_FETCHING_TRANSACTION_STRING() -> String { return "Error fetching Transaction.".localized } class func ERROR_FETCHING_UNSPENT_OUTPUTS_TRY_AGAIN_LATER_STRING() -> String { return "Error fetching unspent outputs. Try again later.".localized } class func ERROR_FETCHING_UNSPENT_OUTPUTS_TRY_AGAIN_STRING() -> String { return "Error fetching unspent outputs. Try again.".localized } class func ERROR_GETTING_BLOCK_HEIGHT_STRING() -> String { return "Error getting block height.".localized } class func ERROR_IMPORTING_ACCOUNT_STRING() -> String { return "Error importing account".localized } class func ERROR_LOADING_WALLET_JSON_FILE_STRING() -> String { return "Error loading wallet JSON file".localized } class func EXPLANATION_STRING() -> String { return "Explanation".localized } class func FAQ_STRING() -> String { return "FAQ".localized } class func FILL_ADDRESS_FIELD_STRING() -> String { return "Fill address field".localized } class func FINISHED_PASSING_TRANSACTION_DATA_STRING() -> String { return "Finished Passing Transaction Data".localized } class func MNEMONIC_INFO_STRING() -> String { return "First make sure you are using your secondary offline device for this screen (as mentioned in the overview on the previous screen). Click 'New Wallet' and write down or memorize the generated 12 word passphrase. This passphrase can recover and generate all your accounts and the bitcoins associated with it, so keep it safe and to yourself. Also instead of creating a new wallet, you can also input an existing 12 word passphrase that was generated here to create additional accounts.".localized } class func ACCOUNT_ID_INFO_STRING() -> String { return "Enter an account ID and click 'QR Code'. Then on your primary online device, enable Cold Wallet in settings. Then go to the Accounts screen and click 'Import Cold Wallet Account' and scan the Account Public Key QR Code. Afterwards use this cold wallet account as you would a normal account and deposit bitcoins into it. When you want to make a payment from a cold wallet account, go to the next section on the previous screen and follow the step by step instructions there.".localized } class func FOLLOW_US_ON_TWITTER_STRING() -> String { return "Follow us on Twitter".localized } class func FUNDS_HAVE_BEEN_CLAIMED_ALREADY_STRING() -> String { return "Funds have been claimed already.".localized } class func GO_STRING() -> String { return "Go".localized } class func GO_TO_THE_SIDE_MENU_STRING() -> String { return "Go to the side menu".localized } class func HAVE_SENDER_SCAN_QR_CODE_STRING() -> String { return "Have sender scan QR code".localized } class func HAVE_SENDER_SEND_YOU_PAYMENT_STRING() -> String { return "Have sender send you payment".localized } class func HELP_STRING() -> String { return "Help".localized } class func WHAT_MAKES_ARCBIT_DIFFERENT_FROM_OTHER_BITCOIN_WALLETS_DESC_STRING() -> String { return "Here are some features that no other mobile bitcoin wallet supports.\n- Reusable address support\n- Ability to import individual account (extended) keys\n- iCloud backup support\n- Over 150 local currencies supported".localized } class func HIERARCHICAL_DETERMINISTIC_WALLET_STRING() -> String { return "Hierarchical Deterministic Wallet".localized } class func HISTORY_STRING() -> String { return "History".localized } class func HOW_TO_COLON_STRING() -> String { return "How To:".localized } class func HOW_DO_I_GET_BITCOINS_STRING() -> String { return "How do I get bitcoins?".localized } class func HOW_DOES_ARCBIT_WALLET_WORK_STRING() -> String { return "How does ArcBit Wallet work?".localized } class func IMPORT_ACCOUNT_STRING() -> String { return "Import Account".localized } class func IMPORT_COLD_WALLET_ACCOUNT_STRING() -> String { return "Import Cold Wallet Account".localized } class func IMPORT_FEATURE_STRING() -> String { return "Import Feature".localized } class func IMPORT_PRIVATE_KEY_STRING() -> String { return "Import Private Key".localized } class func IMPORT_PRIVATE_ENCRYPTED_KEY_STRING() -> String { return "Import Private/Encrypted Key".localized } class func IMPORT_WATCH_ACCOUNT_STRING() -> String { return "Import Watch Account".localized } class func IMPORT_WATCH_ADDRESS_STRING() -> String { return "Import Watch Address".localized } class func IMPORT_PRIVATE_KEY_ENCRYPTED_OR_UNENCRYPTED_STRING() -> String { return "Import private key encrypted or unencrypted?".localized } class func IMPORT_WITH_QR_CODE_STRING() -> String { return "Import with QR code".localized } class func IMPORT_WITH_TEXT_INPUT_STRING() -> String { return "Import with text input".localized } class func IMPORTED_ACCOUNTS_STRING() -> String { return "Imported Accounts".localized } class func IMPORTED_ADDRESSES_STRING() -> String { return "Imported Addresses".localized } class func IMPORTED_WATCH_ACCOUNTS_STRING() -> String { return "Imported Watch Accounts".localized } class func IMPORTED_WATCH_ADDRESSES_STRING() -> String { return "Imported Watch Addresses".localized } class func IMPORTED_WATCH_ONLY_ACCOUNTS_REUSABLE_ADDRESS_INFO_DESC_STRING() -> String { return "Imported Watch Only Accounts can't see reusable address payments, thus this accounts' reusable address is not available. If you want see the reusable address for this account, import the account private key that corresponds to this accounts public key.".localized } class func IMPORTING_ACCOUNT_STRING() -> String { return "Importing Account".localized } class func IMPORTING_COLD_WALLET_ACCOUNT_STRING() -> String { return "Importing Cold Wallet Account".localized } class func IMPORTING_A_PRIVATE_KEY_STRING() -> String { return "Importing a Private Key".localized } class func IMPORTING_A_WATCH_ONLY_ACCOUNT_STRING() -> String { return "Importing a Watch Only Account".localized } class func IMPORTING_A_WATCH_ONLY_ADDRESS_STRING() -> String { return "Importing a Watch Only Address".localized } class func IMPORTING_AN_ACCOUNT_STRING() -> String { return "Importing an Account".localized } class func IMPORT_PRIVATE_KEY_ENCRYPTED_OR_UNENCRYPTED_DESC_STRING() -> String { return "Importing an encrypted key will require you to input the password every time you want to send bitcoins from it.".localized } class func IMPORT_FEATURE_DESC_STRING() -> String { return "In advanced mode, you can import bitcoin keys and addresses from other sources. You can import account private keys, account public keys, private keys, and addresses.\nPlease note that your 12 word passphrase cannot recover your bitcoins, so it is recommended that you backup imported keys and addresses separately.".localized } class func INCOMPLETE_STRING() -> String { return "Incomplete".localized } class func INCORRECT_PASSPHRASE_FOR_ICLOUD_WALLET_BACKUP_STRING() -> String { return "Incorrect passphrase, could not decrypt iCloud wallet backup.".localized } class func INPUT_A_BITCOIN_ADDRESS_STRING() -> String { return "Input a bitcoin address".localized } class func INPUT_A_LABEL_STRING() -> String { return "Input a label".localized } class func INPUT_A_NEW_LABEL_STRING() -> String { return "Input a new label".localized } class func INPUT_ACCOUNT_PRIVATE_KEY_STRING() -> String { return "Enter account private key".localized } class func INPUT_ACCOUNT_PUBLIC_KEY_STRING() -> String { return "Enter account public key".localized } class func INPUT_ADDRESS_STRING() -> String { return "Enter address".localized } class func INPUT_AMOUNT_STRING() -> String { return "Input amount".localized } class func INPUT_LABEL_STRING() -> String { return "Input label".localized } class func INPUT_NEW_ACCOUNT_NAME_STRING() -> String { return "Input new account name".localized } class func INPUT_COLD_WALLET_KEY_INFO_STRING() -> String { return "Enter the 12 word passphrase that belongs to the cold wallet account that you want to make a payment from. This is the passphrase that was used to generate your account public key that was generated in the \'Create Cold Wallet\' section found in the previous screen.".localized } class func INPUT_TRANSACTION_FEE_IN_BITCOINS_STRING() -> String { return "Input transaction fee in bitcoins".localized } class func INPUT_YOUR_CUSTOM_FEE_IN_X_STRING() -> String { return "Input your custom fee in %@".localized } class func INVALID_TRANSACTION_ID() -> String { return "Invalid transaction ID".localized } class func INSTRUCTIONS_STRING() -> String { return "Instructions".localized } class func INSUFFICIENT_FUNDS_STRING() -> String { return "Insufficient Funds".localized } class func INSUFFICIENT_FUNDS_ACCOUNT_BALANCE_IS_STRING() -> String { return "Insufficient Funds. Account balance is %@ when %@ is required.".localized } class func INSUFFICIENT_FUNDS_ACCOUNT_CONTAINS_BITCOIN_DUST_STRING() -> String { return "Insufficient Funds. Account contains bitcoin dust. You can only send up to %@ for now.".localized } class func INTERNAL_ACCOUNT_TRANSFER_STRING() -> String { return "Internal account transfer".localized } class func INVALID_ADDRESS_STRING() -> String { return "Invalid Address".localized } class func INVALID_URL_STRING() -> String { return "Invalid URL".localized } class func INVALID_ACCOUNT_PRIVATE_KEY_STRING() -> String { return "Invalid account private key".localized } class func INVALID_ACCOUNT_PUBLIC_KEY_STRING() -> String { return "Invalid account public Key".localized } class func INVALID_BACKUP_PASSPHRASE_STRING() -> String { return "Invalid backup passphrase".localized } class func INVALID_PASSPHRASE_STRING() -> String { return "Invalid passphrase".localized } class func INVALID_PRIVATE_KEY_STRING() -> String { return "Invalid private key".localized } class func INVALID_SCANNED_DATA_STRING() -> String { return "Invalid scanned data".localized } class func DONT_MANAGE_INDIVIDUAL_ACCOUNT_PRIVATE_KEY_WARNING_DESC_STRING() -> String { return "It is not recommended that you manually manage private keys yourself. A leak of a private key can lead to the compromise of your accounts.".localized } class func ADD_ADDRESS_TO_CONTACT_WARNING_DESC_STRING() -> String { return "It is not recommended that you use a regular bitcoin address for multiple payments, but instead you should import a reusable address. Add address anyways?".localized } class func LABEL_STRING() -> String { return "Label".localized } class func LABEL_TRANSACTION_STRING() -> String { return "Label Transaction".localized } class func LIKE_USING_ARCBIT_STRING() -> String { return "Do you like using ArcBit?".localized } class func LOCAL_BACK_UP_TO_WALLET_FAILED_STRING() -> String { return "Local backup to wallet failed!".localized } class func RESTORE_WALLET_FROM_ICLOUD_STRING() -> String { return "Local wallet will be lost. Are you sure you want to restore wallet from iCloud?".localized } class func SCAN_REUSABLE_ADDRESS_PAYMENT_STRING() -> String { return "Scan For Reusable Address Payment".localized } class func MAXIMUM_ACCOUNTS_REACHED_STRING() -> String { return "Maximum accounts reached".localized } class func MORE_STRING() -> String { return "More".localized } class func NETWORK_ERROR_STRING() -> String { return "Network Error".localized } class func NEW_ADDRESSES_WILL_BE_AUTOMATICALLY_GENERATED_DESC_STRING() -> String { return "New addresses will be automatically generated and cycled for you as you use your current available addresses.".localized } class func NEXT_STRING() -> String { return "Next".localized } class func NO_STRING() -> String { return "No".localized } class func NONE_CURRENTLY_STRING() -> String { return "None currently".localized } class func NOT_NOW_STRING() -> String { return "Not now".localized } class func FINISHED_PASSING_TRANSACTION_DATA_DESC_STRING() -> String { return "Now authorize the transaction on your air gap device. When you have done so, click continue on this device to scan the authorized transaction data and make your payment.".localized } class func OK_STRING() -> String { return "OK".localized } class func SCAN_UNSIGNED_TX_INFO_STRING() -> String { return "On your primary online device, when you want to make a payment from a cold wallet account, simply do it as you normally would on a normal account. When you click 'Send' on the Review Payment screen, instead of the payment going out immediately, you will be prompted to pass the unauthorized transaction data. Then on your secondary offline device, within this screen click 'Scan' to import the transaction so it can be authorized.".localized } class func PASS_SIGNED_TX_INFO_STRING() -> String { return "Once the transaction has been authorized by completing the above two steps, pass the authorized transaction back to your primary online device to finalize your payment.".localized } class func OTHER_LINKS_STRING() -> String { return "Other Links".localized } class func PASSPHRASE_DOES_NOT_MATCH_THE_TRANSACTION_STRING() -> String { return "Passphrase does not match the transaction".localized } class func PASSWORD_STRING() -> String { return "Password".localized } class func PAYMENT_INDEX_X_STRING() -> String { return "Payment Index: %lu".localized } class func CLEARED_FROM_MEMORY_STRING() -> String { return "Cleared from memory".localized } class func PRIVATE_KEY_DOES_NOT_MATCH_ADDRESS_STRING() -> String { return "Private key does not match address".localized } class func PRIVATE_KEY_MISSING_STRING() -> String { return "Private key missing".localized } class func QR_CODE_STRING() -> String { return "QR code".localized } class func QUIT_AND_RE_ENTER_APP_STRING() -> String { return "Quit and re-enter app".localized } class func RATE_STRING() -> String { return "Rate".localized } class func RATE_US_IN_THE_APP_STORE_STRING() -> String { return "Rate us in the App Store!".localized } class func RECEIVE_STRING() -> String { return "Receive".localized } class func RECEIVE_PAYMENT_STRING() -> String { return "Receive Payment".localized } class func RECEIVE_PAYMENT_FROM_REUSABLE_ADDRESS_STRING() -> String { return "Receive Payment From Reusable Address".localized } class func REMIND_ME_LATER_STRING() -> String { return "Remind me Later".localized } class func RESTORE_STRING() -> String { return "Restore".localized } class func RESTORE_FROM_ICLOUD_STRING() -> String { return "Restore from iCloud".localized } class func RESTORING_WALLET_STRING() -> String { return "Restoring Wallet".localized } class func RETRY_STRING() -> String { return "Retry".localized } class func REUSABLE_ADDRESS_PAYMENT_ADDRESSES_STRING() -> String { return "Reusable Address Payment Addresses".localized } class func REUSABLE_ADDRESS_COLON_STRING() -> String { return "Reusable Address:".localized } class func REUSABLE_ADDRESSES_STRING() -> String { return "Reusable Addresses".localized } class func SAVE_STRING() -> String { return "Save".localized } class func SCAN_STRING() -> String { return "Scan".localized } class func SCAN_FOR_REUSABLE_ADDRESS_PAYMENT_STRING() -> String { return "Scan For Reusable Address Payment".localized } class func SCAN_FOR_REUSABLE_ADDRESS_TRANSACTION_STRING() -> String { return "Scan for reusable address transaction".localized } class func SCAN_NEXT_PART_STRING() -> String { return "Scan next part".localized } class func SCROLL_DOWN_TO_THE_SECTION_ACCOUNT_ACTIONS_STRING() -> String { return "Scroll down to the section ‘Account Actions’".localized } class func INTERNAL_WALLET_DATA_STRING() -> String { return "Internal Wallet Data".localized } class func SELECT_ACCOUNT_STRING() -> String { return "Select Account".localized } class func SELECT_AND_CLICK_A_BLOCKEXPLORER_API_STRING() -> String { return "Select and click a blockexplorer API".localized } class func SELECT_AND_CLICK_A_TRANSACTION_STRING() -> String { return "Select and click a transaction".localized } class func SELECT_AND_CLICK_AN_ACCOUNT_STRING() -> String { return "Select and click an account".localized } class func SELECT_AND_CLICK_AN_ACCOUNT_TO_RECEIVE_FROM_STRING() -> String { return "Select and click an account to receive from".localized } class func SELECT_AND_CLICK_AN_ACCOUNT_TO_VIEW_TRANSACTION_HISTORY_STRING() -> String { return "Select and click an account to view it’s transaction history".localized } class func SELECT_AND_CLICK_AN_ADDRESS_STRING() -> String { return "Select and click an address".localized } class func SEND_STRING() -> String { return "Send".localized } class func SEND_PAYMENT_STRING() -> String { return "Send Payment".localized } class func SEND_TO_ADDRESS_IN_CONTACTS_STRING() -> String { return "Send To Address In Contacts".localized } class func SEND_AUTHORIZED_PAYMENT_STRING() -> String { return "Send authorized payment?".localized } class func SENDING_STRING() -> String { return "Sending".localized } class func REUSABLE_ADDRESS_BLOCKCHAIN_API_WARNING_STRING() -> String { return "Sending payment to a reusable address might take longer to show up then a normal transaction with the blockchain.info API. You might have to wait until at least 1 confirmation for the transaction to show up. This is due to the limitations of the blockchain.info API. For reusable address payments to show up faster, configure your app to use the Insight API in advance settings.".localized } class func SENT_X_TO_Y_STRING() -> String { return "Sent %@ to %@".localized } class func CHANGE_BLOCK_EXPLORER_URL_STRING() -> String { return "Change Block Explorer URL".localized } class func SET_TRANSACTION_FEE_IN_X_STRING() -> String { return "Set Transaction Fee in %@".localized } class func SETTINGS_STRING() -> String { return "Settings".localized } class func SOME_FUNDS_MAY_BE_PENDING_CONFIRMATION_DESC_STRING() -> String { return "Some funds may be pending confirmation and cannot be spent yet. (Check your account history) Account only has a spendable balance of %@".localized } class func WHAT_ARE_REUSABLE_ADDRESSES_DESC_STRING() -> String { return "Some people have compared bitcoin addresses to a bank routing number. It is a good analogy, however bitcoin addresses are public. So if you reuse the same bitcoin address for multiple payments like you would a routing number, people will be able to figure out how much bitcoin you have. Thus it is recommended that you only use one address per payment.\nThis causes usability issues making the user use a new address whenever receiving a payment is cumbersome.\nStealth/reusable addresses provides a better solution. When you give a sender a reusable address, the sender will derive a one time regular bitcoin address from the reusable address. Then the sender will send a payment to that regular bitcoin address. Now you can give many people just one reusable address and have them all send you payments without letting other people know how much bitcoin you have.\nA reusable address looks like this vJmxthatTBXibYe9aZavx18iAT9gyiJETGkhwPX2WbHQGuzX83YvQXynD2t8yHU4Xjfonu5x9m6B4yxquytFP1c2CRbVR9mecxesvE. A reusable address is a lot longer then a regular bitcoin address, it is 102 characters in length.\nReusable addresses are great, however there are no other mobile bitcoin wallets but ArcBit that supports reusable addresses for now. Which is why ArcBit supports receiving payments from both regular bitcoin addresses and reusable addresses.\nFor each account, you have one reusable address. You can find it on your Receive screen. Swipe all the way to right on the QRCode on your Receive screen and you will find a reusable address.".localized } class func SPENDING_FROM_A_COLD_WALLET_ACCOUNT_STRING() -> String { return "Spending from a cold wallet account".localized } class func START_FRESH_STRING() -> String { return "Start fresh".localized } class func START_RESTORE_ANOTHER_WALLET_STRING() -> String { return "Start/Restore Another Wallet".localized } class func STEPS_STRING() -> String { return "Steps".localized } class func SUCCESS_STRING() -> String { return "Success".localized } class func SWIPE_RIGHT_ON_AN_ADDRESS_STRING() -> String { return "Swipe right on an address".localized } class func SWIPE_UNTIL_YOU_SEE_THE_REUSABLE_ADDRESS_STRING() -> String { return "Swipe to the right on the QR Code Image until you see the reusable address".localized } class func TEMPORARY_IMPORT_ACCOUNT_PRIVATE_KEY_STRING() -> String { return "Temporarily import account private key".localized } class func TEMPORARY_IMPORT_PRIVATE_KEY_STRING() -> String { return "Temporarily import private key".localized } class func COLD_WALLET_OVERVIEW_DESC_STRING() -> String { return "The cold wallet feature will allow you to create accounts which offer better security compared to normal online wallets. You will need 2 devices to use this feature. Your normal day to day device that is connected to the internet and a secondary device that is not connected to the internet (Your secondary device would need to be online once to download the ArcBit app. Afterwards keep the secondary device offline for maximum security). This feature allows you to authorize bitcoin payments from an offline device so that the keys to your bitcoins will never need to be stored on your online device. Follow the step by step instructions by clicking the info buttons within the below sections.".localized } class func WHAT_IS_ARCBITS_COLD_WALLET_FEATURE_DESC_STRING() -> String { return "The cold wallet feature will allow you to create accounts which offer better security compared to normal online wallets. You will need 2 devices to use this feature. Your normal day to day device that is connected to the internet and a secondary device that is not connected to the internet (Your secondary device would need to be online once to download the ArcBit app. Afterwards keep the secondary device offline for maximum security). This feature allows you to authorize bitcoin payments from an offline device so that the keys to your bitcoins will never need to be stored on your online device. You can enable the cold wallet feature by going into advanced settings.".localized } class func CANT_SEE_REUSABLE_ADDRESS_PAYMENTS_STRING() -> String { return "This account type can't see reusable address payments".localized } class func MANUALLY_SCAN_TRANSACTION_FOR_STEALTH_TX_INFO_STRING() -> String { return "This feature allows you to manually input a transaction ID and see if the corresponding transaction contains a reusable address payment to your reusable address. If so, then the funds will be added to your wallet. Normally the app will discover reusable address payments automatically for you, but if you believe a payment is missing you can use this feature.".localized } class func TOGGLE_AUTOMATIC_TRANSACTION_FEE_STRING() -> String { return "Toggle Automatic Transaction Fee".localized } class func TOGGLE_ENABLE_TRANSACTION_FEE_STRING() -> String { return "Toggle ‘Enable Transaction Fee’".localized } class func TOGGLE_ENABLE_ADVANCED_MODE_STRING() -> String { return "Toggle ’Enable advanced mode’".localized } class func TRANSACTION_X_ALREADY_ACCOUNTED_FOR_STRING() -> String { return "Transaction %@ already accounted for.".localized } class func FUNDS_IMPORTED() -> String { return "Funds imported".localized } class func TRANSACTION_X_DOES_NOT_BELONG_TO_THIS_ACCOUNT_STRING() -> String { return "Transaction %@ does not belong to this account.".localized } class func TRANSACTION_FEE_STRING() -> String { return "Transaction Fee".localized } class func TRANSACTION_ID_STRING() -> String { return "Transaction ID".localized } class func TRANSACTION_ID_COLON_X_STRING() -> String { return "Transaction ID: %@".localized } class func TRANSACTION_AUTHORIZED_STRING() -> String { return "Transaction authorized".localized } class func TRANSACTION_CONFIRMATIONS_STRING() -> String { return "Transaction confirmations".localized } class func FEE_INFO_DESC_STRING() -> String { return "Transaction fees impact how quickly the Bitcoin network will confirm your transactions. Higher fees means faster confirmation times. Default fee behavior can be configured in settings.".localized } class func SPENDING_FROM_A_COLD_WALLET_ACCOUNT_DESC_STRING() -> String { return "Transaction needs to be authorized by an offline and air gap device. Send transaction to an offline device for authorization?".localized } class func TRANSACTION_AUTHORIZED_DESC_STRING() -> String { return "Transaction needs to be passed back to your online device in order for the payment to be sent".localized } class func TRY_AGAIN_STRING() -> String { return "Try Again".localized } class func TRY_OUR_NEW_COLD_WALLET_FEATURE_STRING() -> String { return "Try our new cold wallet feature!".localized } class func TRANSACTION_NOT_REUSABLE_ADDRESS_TRANSACTION_STRING() -> String { return "Transaction is not a reusable address transaction.".localized } class func URL_DOES_NOT_CONTAIN_AN_ADDRESS_STRING() -> String { return "URL does not contain an address.".localized } class func UNABLE_TO_GET_DYNAMIC_FEES_STRING() -> String { return "Unable to get dynamic fees. Falling back on fixed transaction fee. (fee can be configured on review payment)".localized } class func UNARCHIVE_ACCOUNT_STRING() -> String { return "Unarchive Account".localized } class func UNARCHIVE_ADDRESS_STRING() -> String { return "Unarchive address".localized } class func UNARCHIVED_ADDRESS_STRING() -> String { return "Unarchived address".localized } class func UNCONFIRMED_STRING() -> String { return "Unconfirmed".localized } class func UNENCRYPTED_STRING() -> String { return "Unencrypted".localized } class func CHECK_OUT_THE_ARCBIT_WEB_WALLET_DESC_STRING() -> String { return "Use ArcBit on your browser to complement the mobile app. The web wallet has all the features that the mobile wallet has plus more!".localized } class func USE_ALL_FUNDS_STRING() -> String { return "Use all funds".localized } class func VIEW_ACCOUNT_ADDRESS_STRING() -> String { return "View Account Address".localized } class func VIEW_ACCOUNT_ADDRESS_IN_WEB_STRING() -> String { return "View Account Address In Web".localized } class func VIEW_ACCOUNT_ADDRESSES_STRING() -> String { return "View Account Addresses".localized } class func VIEW_ACCOUNT_PRIVATE_KEY_STRING() -> String { return "View Account Private Key".localized } class func VIEW_ACCOUNT_PUBLIC_KEY_STRING() -> String { return "View Account Public Key".localized } class func VIEW_ACHIEVEMENTS_STRING() -> String { return "View Achievements".localized } class func VIEW_ADDRESSES_STRING() -> String { return "View Addresses".localized } class func VIEW_ARCBIT_BRAIN_WALLET_DETAILS_STRING() -> String { return "View ArcBit Brain Wallet Details".localized } class func VIEW_ARCBIT_WEB_WALLET_DETAILS_STRING() -> String { return "View ArcBit Web Wallet Details".localized } class func VIEW_HISTORY_STRING() -> String { return "View History".localized } class func VIEW_PRIVATE_KEY_STRING() -> String { return "View Private Key".localized } class func VIEW_TRANSACTION_IN_WEB_STRING() -> String { return "View Transaction In Web".localized } class func VIEW_ACCOUNT_PRIVATE_KEY_QR_CODE_STRING() -> String { return "View account private key QR code".localized } class func VIEW_ACCOUNT_PUBLIC_KEY_QR_CODE_STRING() -> String { return "View account public key QR code".localized } class func VIEW_ADDRESS_QR_CODE_STRING() -> String { return "View address QR code".localized } class func VIEW_ADDRESS_IN_WEB_STRING() -> String { return "View address in web".localized } class func VIEW_IN_WEB_STRING() -> String { return "View in web".localized } class func VIEW_PRIVATE_KEY_QR_CODE_STRING() -> String { return "View private key QR code".localized } class func VISIT_OUR_HOME_PAGE_STRING() -> String { return "Visit our home page".localized } class func WARNING_STRING() -> String { return "Warning".localized } class func WELCOME_DESC_STRING() -> String { return "Welcome to ArcBit, a user only controlled Bitcoin wallet. Start using the app now by depositing your Bitcoins here.".localized } class func WELCOME_EXCLAMATION_STRING() -> String { return "Welcome!".localized } class func WHAT_ARE_ACCOUNT_EXTENDED_KEYS_STRING() -> String { return "What are Account/Extended Keys?".localized } class func WHAT_ARE_ACCOUNTS_STRING() -> String { return "What are accounts?".localized } class func WHAT_ARE_REUSABLE_ADDRESSES_STRING() -> String { return "What are reusable addresses?".localized } class func WHAT_ARE_THE_BENEFITS_AND_ADVANTAGES_OF_BITCOIN_STRING() -> String { return "What are the benefits and advantages of Bitcoin?".localized } class func WHAT_ARE_TRANSACTION_CONFIRMATIONS_STRING() -> String { return "What are transaction confirmations?".localized } class func WHAT_IS_ARCBITS_COLD_WALLET_FEATURE_STRING() -> String { return "What is ArcBit's cold wallet feature?".localized } class func WHAT_IS_BITCOIN_STRING() -> String { return "What is Bitcoin?".localized } class func WHAT_IS_A_BITCOIN_WALLET_STRING() -> String { return "What is a bitcoin wallet?".localized } class func WHAT_MAKES_ARCBIT_DIFFERENT_FROM_OTHER_BITCOIN_WALLETS_STRING() -> String { return "What makes ArcBit different from other bitcoin wallets?".localized } class func TRY_OUR_NEW_COLD_WALLET_FEATURE_DESC_STRING() -> String { return "With an ArcBit cold wallet feature, you can create wallets and make payments offline without exposing your private keys to an internet connected device. This feature is great for storing large amounts of bitcoin or for the security conscious minded. Check out this feature in the cold wallet section in the side menu.".localized } class func WRITE_DOWN_BACKUP_PASSPHRASE_STRING() -> String { return "Write down backup passphrase".localized } class func SUGGEST_BACK_UP_WALLET_PASSPHRASE_DESC_STRING() -> String { return "Write down or memorize your 12 word wallet backup passphrase. You can view it by clicking \"Show backup passphrase\" in Settings. Your wallet backup passphrase is needed to recover your bitcoins.".localized } class func BACKUP_PASSPHRASE_ADVANCED_EXPLANATION_STRING() -> String { return "Write down the 12 word passphrase below and keep it safe. This passphrase alone can restore your entire wallets\' bitcoins (excluding imports).".localized } class func BACKUP_PASSPHRASE_EXPLANATION_STRING() -> String { return "Write down the 12 word passphrase below and keep it safe. This passphrase alone can restore your entire wallets\' bitcoins.".localized } class func YES_STRING() -> String { return "Yes".localized } class func STEALTH_PAYMENT_NOTE_STRING() -> String { return "You are making a payment to a reusable address. Make sure that the receiver can see the payment made to them. (All ArcBit reusable addresses are compatible with other ArcBit wallets)".localized } class func YOU_HAVE_X_Y_BUT_Z_IS_NEEDED_STRING() -> String { return "You have %@, but %@ is needed. (This includes the transactions fee)".localized } class func KILL_THIS_APP_DESC_STRING() -> String { return "You must exit and kill this app in order for this to take effect.".localized } class func RESTORING_WALLET_DESC_STRING() -> String { return "Your current wallet will be deleted. Your can restore your current wallet later with the wallet passphrase, but any imported accounts or addresses created in advanced mode cannot be recovered. Do you wish to continue?".localized } class func YOUR_ICLOUD_BACKUP_WAS_LAST_SAVED_ON_X_DATE_STRING() -> String { return "Your iCloud backup was last saved on %@. Do you want to restore your wallet from iCloud or backup your local wallet to iCloud?".localized } class func YOUR_NEW_TRANSACTION_FEE_IS_TOO_HIGH_STRING() -> String { return "Your new transaction fee is too high".localized } class func YOUR_WALLET_IS_NOW_RESTORED_STRING() -> String { return "Your wallet is now restored".localized } class func ALLOW_CAMERA_ACCESS_IN_STRING() -> String { return "\nAllow camera access in\n Settings->Privacy->Camera->%@".localized } class func ARCBIT_WEB_WALLET_DESC_STRING() -> String { return "\tArcBit Web Wallet is a Chrome extension. It has all the features of the mobile wallet plus more. Highlights include the ability to create multiple wallets instead of just one, and a new non-cumbersome way to generate wallets, store and spend bitcoins all from cold storage! ArcBit's new way to manage your cold storage bitcoins also offers a more compelling reason to use ArcBit's watch account feature. Now you can safely watch the balance of your cold storage bitcoins by enabling advance mode in ArcBit and importing your cold storage account public keys.\n\tUse ArcBit Web Wallet in whatever way you wish. You can create a new wallet, or you can input your current 12 word backup passphrase to manage the same bitcoins across different devices. Check out the ArcBit Web Wallet in the Chrome Web Store for more details!\n".localized } class func ARCBIT_BRAIN_WALLET_STRING_DESC_STRING() -> String { return "\tWith the Arcbit Brain Wallet you can safely spend your bitcoins without ever having your private keys be exposed to the internet. It can be use in conjunction with your Arcbit Wallet or as a stand alone wallet.\n".localized } class func ICLOUD_ERROR_COLON_X_STRING() -> String { return "iCloud Error: %@".localized } class func ICLOUD_BACKUP_FOUND_STRING() -> String { return "iCloud backup found".localized } class func ICLOUD_BACKUP_NOT_FOUND_STRING() -> String { return "iCloud backup not found".localized } class func BACKUP_IYOUR_LOCAL_WALLET_TO_ICLOUD_STRING() -> String { return "iCloud backup will be lost. Are you sure you want to backup your local wallet to iCloud?".localized } class func TODAY_STRING() -> String { return "Today".localized } class func CONFIRM_PAYMENT_STRING() -> String { return "Confirm Payment".localized } class func FEE_COLON_STRING() -> String { return "Fee:".localized } class func TOTAL_COLON_STRING() -> String { return "Total:".localized } class func CUSTOMIZE_FEE_STRING() -> String { return "Customize Fee".localized } class func ENTER_A_WALLET_BACKUP_PASSPHRASE_STRING() -> String { return "Enter a wallet backup passphrase to wipe the current wallet and start/restore another.".localized } class func PASSPHRASE_STRING() -> String { return "Passphrase".localized } class func RESTORE_WALLET_STRING() -> String { return "Restore Wallet".localized } class func WALLET_BACKUP_PASSPHRASE_STRING() -> String { return "Wallet backup passphrase".localized } class func FROM_COLON_STRING() -> String { return "From:".localized } class func AMOUNT_COLON_STRING() -> String { return "Amount:".localized } class func TO_COLON_STRING() -> String { return "To:".localized } class func SCAN_QR_STRING() -> String { return "Scan QR Code".localized } class func CONTACTS_STRING() -> String { return "Contacts".localized } class func REVIEW_PAYMENT_STRING() -> String { return "Review Payment".localized } class func ACCOUNT_ID_STRING() -> String { return "Account ID".localized } class func AUTHORIZE_PAYMENT_STEP_1() -> String { return "Step 1: Scan transaction to authorize".localized } class func AUTHORIZE_PAYMENT_STEP_2() -> String { return "Step 2: Input 12 word backup passphrase".localized } class func AUTHORIZE_PAYMENT_STEP_3() -> String { return "Step 3: Pass authorized transaction data".localized } class func STARTING_RECEIVING_ADDRESS_ID() -> String { return "Starting Receiving Address ID:".localized } class func STARTING_CHANGE_ADDRESS_ID() -> String { return "Starting Change address ID:".localized } class func NEW_WALLET() -> String { return "New Wallet".localized } class func SCAN() -> String { return "Scan".localized } class func PASS() -> String { return "Pass".localized } class func IMPORTED_COLD_WALLET_ACCOUNT_STRING() -> String { return "Imported Cold Wallet Account %@".localized } class func IMPORTED_ACCOUNT_STRING() -> String { return "Imported Account %@".localized } class func IMPORTED_WATCH_ACCOUNT_STRING() -> String { return "Imported Watch Account %@".localized } class func WALLET_BACKUP_PASSPHRASE_WILL_BE_SHOWN() -> String { return "Wallet backup passphrase will be shown".localized } class func PLEASE_WRITE_DOWN_OR_MEMORIZE_YOUR_WALLET_BACKUP_PASSPHRASE() -> String { return "Write down or memorize your wallet backup passphrase. If you lose your backup passphrase, your wallet cannot be recovered.".localized } class func I_UNDERSTAND() -> String { return "I understand".localized } class func ICLOUD_SUPPORT_DISCONTINUED() -> String { return "iCloud support for ArcBit discontinued".localized } class func ICLOUD_SUPPORT_DISCONTINUED_DESCRIPTION() -> String { return "iCloud support for ArcBit is being discontinued. If your backup passphrase has not been backed up already, please do so.".localized } class func REUSABLE_ADDRESS_DISABLED() -> String { return "Reusable address payments are disabled until further notice.".localized } } ================================================ FILE: ArcBit/model/TLHDWalletWrapper.swift ================================================ // // TLHDWalletWrapper.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation class TLHDWalletWrapper { class func getBIP44KeyChain(_ masterHex:NSString, accountIdx:UInt) -> BTCKeychain{ let seed = BTCDataWithHexCString(masterHex.utf8String) let masterChain = BTCKeychain(seed:seed) let purposeKeychain = masterChain?.derivedKeychain(at: 44, hardened:true) let coinTypeKeychain = purposeKeychain?.derivedKeychain(at: 0, hardened:true) let accountKeychain = coinTypeKeychain?.derivedKeychain(at: UInt32(accountIdx), hardened:true) return accountKeychain! } class func generateMnemonicPassphrase() -> String? { if let mnemonic = BTCMnemonic(entropy: BTCRandomDataWithLength(16) as Data!, password: nil, wordListType: .english) { return (mnemonic.words as NSArray).componentsJoined(by: " ") } else { return nil } } class func phraseIsValid(_ phrase:String) -> (Bool){ return BTCMnemonic(words: phrase.components(separatedBy: " "), password: nil, wordListType: .english) != nil } class func getMasterHex(_ mnemonic: String) -> String { assert(phraseIsValid(mnemonic), "mnemonic is invalid") let mnemonicData = mnemonic.data(using: String.Encoding.utf8) let salt = "mnemonic".data(using: String.Encoding.utf8) let rounds:UInt = 2048 let derivedKeyLen:Int = 64 let key = [CUnsignedChar](repeating: 0, count: derivedKeyLen) let unsafeMutablePointerOfPassData: UnsafePointer = (mnemonicData! as NSData).bytes.bindMemory(to: Int8.self, capacity: mnemonicData!.count) let unsafeMutablePointerOfSaltData: UnsafePointer = (salt! as NSData).bytes.bindMemory(to: UInt8.self, capacity: salt!.count) let unsafeMutablePointerOfKey: UnsafeMutablePointer = UnsafeMutablePointer(mutating: key) CCKeyDerivationPBKDF(CCPBKDFAlgorithm(kCCPBKDF2),unsafeMutablePointerOfPassData,mnemonicData!.count,unsafeMutablePointerOfSaltData,salt!.count,CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA512),CUnsignedInt(rounds),unsafeMutablePointerOfKey,derivedKeyLen) let len = MemoryLayout.size * Int(derivedKeyLen) let masterSeedData = Data(bytes: UnsafePointer(unsafeMutablePointerOfKey), count: len) return (masterSeedData as NSData).hex() } class func getStealthAddress(_ extendedKey:String, isTestnet:Bool) -> (NSDictionary) { // hd wallet // m / purpose' / coin_type' / account' / change / address_index // stealth // m / purpose' / coin_type' / account' / 100' / scan|spend / 0 // 100' because 0 and 1 are change, and is hardened because if it is not then if an attacker knows a xpub, // then he can can hack stealth server, take scan keys and compromise whole account var scanKeyChain = BTCKeychain(extendedKey:extendedKey) let scanPrivSequence = [["idx":100, "hardened":true], ["idx":0, "hardened":false]] for _idxHardened in scanPrivSequence { let idxHardened = _idxHardened as NSDictionary scanKeyChain = scanKeyChain?.derivedKeychain(at: UInt32(idxHardened.object(forKey: "idx") as! Int), hardened:idxHardened.object(forKey: "hardened") as! Bool) } var spendKeyChain = BTCKeychain(extendedKey:extendedKey) let spendPrivSequence = [["idx":100, "hardened":true], ["idx":1, "hardened":false]] for idxHardened in spendPrivSequence as [NSDictionary] { spendKeyChain = spendKeyChain?.derivedKeychain(at: UInt32(idxHardened.object(forKey: "idx") as! Int), hardened:idxHardened.object(forKey: "hardened") as! Bool) } let scanKey = scanKeyChain?.key let scanPriv = (scanKey?.privateKey.hex())! as String let scanPublicKey = (scanKey?.compressedPublicKey.hex())! as String let spendKey = spendKeyChain?.key let spendPriv = (spendKey?.privateKey.hex())! as String let spendPublicKey = (spendKey?.compressedPublicKey.hex())! as String let stealthAddress = TLStealthAddress.createStealthAddress(scanPublicKey as NSString, spendPublicKey:spendPublicKey as NSString, isTestnet:isTestnet) return ["stealthAddress":stealthAddress, "scanPriv":scanPriv, "spendPriv":spendPriv] } class func getAccountIdxForExtendedKey(_ extendedKey:String) -> UInt32 { let keyChain = BTCKeychain(extendedKey:extendedKey) return keyChain!.index } class func isValidExtendedPublicKey(_ extendedPublicKey:String) -> Bool { let keyChain = BTCKeychain(extendedKey:extendedPublicKey) return (keyChain != nil && !keyChain!.isPrivate) } class func isValidExtendedPrivateKey(_ extendedPrivateKey:String) -> Bool { let keyChain = BTCKeychain(extendedKey:extendedPrivateKey) return (keyChain != nil && keyChain!.isPrivate) } class func getExtendPubKey(_ extendPrivKey:String) -> String{ let keyChain = BTCKeychain(extendedKey:extendPrivKey) return keyChain!.extendedPublicKey } class func getExtendPubKeyFromMasterHex(_ masterHex:String, accountIdx:UInt) -> String{ let accountKeychain = getBIP44KeyChain(masterHex as NSString, accountIdx:accountIdx) return accountKeychain.extendedPublicKey } class func getExtendPrivKey(_ masterHex:String, accountIdx:UInt) -> String{ let accountKeychain = getBIP44KeyChain(masterHex as NSString, accountIdx:accountIdx) return accountKeychain.extendedPrivateKey } class func getAddress(_ extendPubKey:String, sequence:NSArray, isTestnet:Bool) -> String{ var keyChain = BTCKeychain(extendedKey:extendPubKey) for _idx in sequence { let idx = _idx as! Int keyChain = keyChain?.derivedKeychain(at: UInt32(idx), hardened:false) } if !isTestnet { return keyChain!.key.compressedPublicKeyAddress.string } else { return keyChain!.key.addressTestnet.string } } class func getPrivateKey(_ extendPrivKey:NSString, sequence:NSArray, isTestnet:Bool) -> String{ var keyChain = BTCKeychain(extendedKey:extendPrivKey as String) for _idx in sequence { let idx = _idx as! Int keyChain = keyChain?.derivedKeychain(at: UInt32(idx), hardened:false) } keyChain?.key.isPublicKeyCompressed = true if !isTestnet { return keyChain!.key.wif } else { return keyChain!.key.wifTestnet } } } ================================================ FILE: ArcBit/model/TLHelpDoc.swift ================================================ // // TLHelpDoc.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation class TLHelpDoc { struct STATIC_MEMBERS { static var _actionEventToInstructionStepsTitleArray: NSArray? static var _advanceActionInstructionStepsArray: NSArray? static var _actionEventToInstructionsTitleDict: NSDictionary? static var _explanationArray: NSArray? static var _advanceExplanationArray: NSArray? static var _howToDoAdvanceAchievementsArray: NSArray? static var _eventsArray: NSArray? static let FAQ_TRANSACTION_CONFIRMATIONS = TLDisplayStrings.TRANSACTION_CONFIRMATIONS_STRING() static let FAQ_HD_WALLET = TLDisplayStrings.HIERARCHICAL_DETERMINISTIC_WALLET_STRING() static let FAQ_STEALTH_ADDRESS = TLDisplayStrings.REUSABLE_ADDRESSES_STRING() static let ACCOUNT_ACTION_CREATE_NEW_ACCOUNT = TLDisplayStrings.CREATE_NEW_ACCOUNT_STRING() static let ACCOUNT_ACTION_IMPORT_COLD_WALLET_ACCOUNT = TLDisplayStrings.IMPORT_COLD_WALLET_ACCOUNT_STRING() static let ACCOUNT_ACTION_IMPORT_ACCOUNT = TLDisplayStrings.IMPORT_ACCOUNT_STRING() static let ACCOUNT_ACTION_IMPORT_WATCH_ONLY_ACCOUNT = TLDisplayStrings.IMPORT_WATCH_ACCOUNT_STRING() static let ACCOUNT_ACTION_IMPORT_PRIVATE_KEY = TLDisplayStrings.IMPORT_PRIVATE_KEY_STRING() static let ACCOUNT_ACTION_IMPORT_WATCH_ONLY_ADDRESS = TLDisplayStrings.IMPORT_WATCH_ADDRESS_STRING() static let ACTION_SEND_PAYMENT = TLDisplayStrings.SEND_PAYMENT_STRING() static let ACTION_RECEIVE_PAYMENT = TLDisplayStrings.RECEIVE_PAYMENT_STRING() static let ACTION_RECEIVE_PAYMENT_FROM_STEALTH_ADDRESS = TLDisplayStrings.RECEIVE_PAYMENT_FROM_REUSABLE_ADDRESS_STRING() static let ACTION_VIEW_HISTORY = TLDisplayStrings.VIEW_HISTORY_STRING() static let ACTION_CREATE_NEW_ACCOUNT = TLDisplayStrings.CREATE_NEW_ACCOUNT_STRING() static let ACTION_EDIT_ACCOUNT_NAME = TLDisplayStrings.EDIT_ACCOUNT_NAME_STRING() static let ACTION_ARCHIVE_ACCOUNT = TLDisplayStrings.ARCHIVE_ACCOUNT_STRING() static let ACTION_ENABLE_PIN_CODE = TLDisplayStrings.ENABLE_PIN_CODE_STRING() static let ACTION_BACKUP_PASSPHRASE = TLDisplayStrings.BACK_UP_PASSPHRASE_STRING() static let ACTION_RESTORE_WALLET = TLDisplayStrings.START_RESTORE_ANOTHER_WALLET_STRING() static let ACTION_ADD_TO_ADDRESS_BOOK = TLDisplayStrings.ADD_CONTACTS_ENTRY_STRING() static let ACTION_EDIT_ENTRY_ADDRESS_BOOK = TLDisplayStrings.EDIT_CONTACTS_ENTRY_STRING() static let ACTION_DELETE_ENTRY_ADDRESS_BOOK = TLDisplayStrings.DELETE_CONTACTS_ENTRY_STRING() static let ACTION_SEND_TO_ADDRESS_IN_ADDRESS_BOOK = TLDisplayStrings.SEND_TO_ADDRESS_IN_CONTACTS_STRING() static let ACTION_TAG_TRANSACTION = TLDisplayStrings.LABEL_TRANSACTION_STRING() static let ACTION_TOGGLE_AUTOMATIC_TX_FEE = TLDisplayStrings.TOGGLE_AUTOMATIC_TRANSACTION_FEE_STRING() static let ACTION_CHANGE_AUTOMATIC_TX_FEE = TLDisplayStrings.CHANGE_AUTOMATIC_TRANSACTION_FEE_STRING() static let ACTION_VIEW_ACCOUNT_ADDRESSES = TLDisplayStrings.VIEW_ACCOUNT_ADDRESSES_STRING() static let ACTION_VIEW_ACCOUNT_ADDRESS_IN_WEB = TLDisplayStrings.VIEW_ACCOUNT_ADDRESS_IN_WEB_STRING() static let ACTION_VIEW_TRANSACTION_IN_WEB = TLDisplayStrings.VIEW_TRANSACTION_IN_WEB_STRING() static let ACTION_ENABLE_ADVANCE_MODE = TLDisplayStrings.ENABLE_ADVANCED_MODE_STRING() static let ACTION_IMPORT_ACCOUNT = TLDisplayStrings.IMPORT_ACCOUNT_STRING() static let ACTION_IMPORT_WATCH_ONLY_ACCOUNT = TLDisplayStrings.IMPORT_WATCH_ACCOUNT_STRING() static let ACTION_IMPORT_PRIVATE_KEY = TLDisplayStrings.IMPORT_PRIVATE_ENCRYPTED_KEY_STRING() static let ACTION_IMPORT_WATCH_ONLY_ADDRESS = TLDisplayStrings.IMPORT_WATCH_ADDRESS_STRING() static let ACTION_CHANGE_BLOCKEXPLORER_TYPE = TLDisplayStrings.CHANGE_BLOCKEXPLORER_TYPE_STRING() static let ACTION_VIEW_EXTENDED_PUBLIC_KEY = TLDisplayStrings.VIEW_ACCOUNT_PUBLIC_KEY_STRING() static let ACTION_VIEW_EXTENDED_PRIVATE_KEY = TLDisplayStrings.VIEW_ACCOUNT_PRIVATE_KEY_STRING() static let ACTION_VIEW_ACCOUNT_PRIVATE_KEY = TLDisplayStrings.VIEW_PRIVATE_KEY_STRING() static let ACTION_VIEW_ACCOUNT_ADDRESS = TLDisplayStrings.VIEW_ACCOUNT_ADDRESS_STRING() } class func getBasicActionInstructionStepsArray(_ idx: Int) -> NSArray { if (STATIC_MEMBERS._actionEventToInstructionStepsTitleArray == nil) { STATIC_MEMBERS._actionEventToInstructionStepsTitleArray = [ [ TLDisplayStrings.GO_TO_THE_SIDE_MENU_STRING(), TLDisplayStrings.CLICK_SEND_STRING(), TLDisplayStrings.FILL_ADDRESS_FIELD_STRING(), TLDisplayStrings.INPUT_AMOUNT_STRING(), TLDisplayStrings.CLICK_REVIEW_PAYMENT_STRING(), TLDisplayStrings.CLICK_SEND_STRING(), ], [ TLDisplayStrings.GO_TO_THE_SIDE_MENU_STRING(), TLDisplayStrings.CLICK_RECEIVE_STRING(), TLDisplayStrings.CLICK_THE_BUTTON_WITH_THE_ARROW_STRING(), TLDisplayStrings.SELECT_AND_CLICK_AN_ACCOUNT_TO_RECEIVE_FROM_STRING(), TLDisplayStrings.HAVE_SENDER_SCAN_QR_CODE_STRING(), TLDisplayStrings.HAVE_SENDER_SEND_YOU_PAYMENT_STRING(), ], [ TLDisplayStrings.GO_TO_THE_SIDE_MENU_STRING(), TLDisplayStrings.CLICK_RECEIVE_STRING(), TLDisplayStrings.CLICK_THE_BUTTON_WITH_THE_ARROW_STRING(), TLDisplayStrings.SELECT_AND_CLICK_AN_ACCOUNT_TO_RECEIVE_FROM_STRING(), TLDisplayStrings.HAVE_SENDER_SCAN_QR_CODE_STRING(), TLDisplayStrings.SWIPE_UNTIL_YOU_SEE_THE_REUSABLE_ADDRESS_STRING(), TLDisplayStrings.HAVE_SENDER_SEND_YOU_PAYMENT_STRING(), ], [ TLDisplayStrings.GO_TO_THE_SIDE_MENU_STRING(), TLDisplayStrings.CLICK_HISTORY_STRING(), TLDisplayStrings.CLICK_THE_BUTTON_WITH_THE_ARROW_STRING(), TLDisplayStrings.SELECT_AND_CLICK_AN_ACCOUNT_TO_VIEW_TRANSACTION_HISTORY_STRING(), ], [ TLDisplayStrings.GO_TO_THE_SIDE_MENU_STRING(), TLDisplayStrings.CLICK_ACCOUNTS_STRING(), TLDisplayStrings.SCROLL_DOWN_TO_THE_SECTION_ACCOUNT_ACTIONS_STRING(), TLDisplayStrings.CLICK_CREATE_NEW_ACCOUNT_STRING(), ], [ TLDisplayStrings.GO_TO_THE_SIDE_MENU_STRING(), TLDisplayStrings.CLICK_ACCOUNTS_STRING(), TLDisplayStrings.SELECT_AND_CLICK_AN_ACCOUNT_STRING(), TLDisplayStrings.CLICK_EDIT_ACCOUNT_NAME_STRING(), TLDisplayStrings.INPUT_NEW_ACCOUNT_NAME_STRING(), ], [ TLDisplayStrings.GO_TO_THE_SIDE_MENU_STRING(), TLDisplayStrings.CLICK_ACCOUNTS_STRING(), TLDisplayStrings.SELECT_AND_CLICK_AN_ACCOUNT_STRING(), TLDisplayStrings.CLICK_ARCHIVE_ACCOUNT_STRING(), ], [ TLDisplayStrings.GO_TO_THE_SIDE_MENU_STRING(), TLDisplayStrings.CLICK_SETTINGS_STRING(), TLDisplayStrings.CLICK_ENABLE_PIN_CODE_STRING(), TLDisplayStrings.ENTER_PIN_CODE_STRING(), TLDisplayStrings.CONFIRM_PIN_CODE_STRING(), ], [ TLDisplayStrings.GO_TO_THE_SIDE_MENU_STRING(), TLDisplayStrings.CLICK_SETTINGS_STRING(), TLDisplayStrings.CLICK_SHOW_BACKUP_PASSPHRASE_STRING(), TLDisplayStrings.WRITE_DOWN_BACKUP_PASSPHRASE_STRING(), ], [ TLDisplayStrings.GO_TO_THE_SIDE_MENU_STRING(), TLDisplayStrings.CLICK_SETTINGS_STRING(), TLDisplayStrings.CLICK_RESTORE_WALLET_STRING(), TLDisplayStrings.ENTER_BACKUP_PASSPHRASE_STRING(), TLDisplayStrings.CLICK_DONE_STRING(), TLDisplayStrings.CLICK_RESTORE_STRING(), ], [ TLDisplayStrings.GO_TO_THE_SIDE_MENU_STRING(), TLDisplayStrings.CLICK_SEND_STRING(), TLDisplayStrings.CLICK_THE_CONTACTS_BUTTON_STRING(), TLDisplayStrings.CLICK_THE_PLUS_BUTTON_AT_THE_TOP_RIGHT_STRING(), TLDisplayStrings.INPUT_A_BITCOIN_ADDRESS_STRING(), TLDisplayStrings.INPUT_A_LABEL_STRING(), ], [ TLDisplayStrings.GO_TO_THE_SIDE_MENU_STRING(), TLDisplayStrings.CLICK_SEND_STRING(), TLDisplayStrings.SWIPE_RIGHT_ON_AN_ADDRESS_STRING(), TLDisplayStrings.CLICK_EDIT_STRING(), TLDisplayStrings.INPUT_A_NEW_LABEL_STRING(), ], [ TLDisplayStrings.GO_TO_THE_SIDE_MENU_STRING(), TLDisplayStrings.CLICK_SEND_STRING(), TLDisplayStrings.CLICK_THE_CONTACTS_BUTTON_STRING(), TLDisplayStrings.SWIPE_RIGHT_ON_AN_ADDRESS_STRING(), TLDisplayStrings.CLICK_DELETE_STRING(), ], [ TLDisplayStrings.GO_TO_THE_SIDE_MENU_STRING(), TLDisplayStrings.CLICK_SEND_STRING(), TLDisplayStrings.CLICK_THE_CONTACTS_BUTTON_STRING(), TLDisplayStrings.CLICK_AN_ADDRESS_STRING(), ], [ TLDisplayStrings.GO_TO_THE_SIDE_MENU_STRING(), TLDisplayStrings.CLICK_HISTORY_STRING(), TLDisplayStrings.SELECT_AND_CLICK_A_TRANSACTION_STRING(), TLDisplayStrings.CLICK_LABEL_TRANSACTION_STRING(), TLDisplayStrings.INPUT_LABEL_STRING(), ], [ TLDisplayStrings.GO_TO_THE_SIDE_MENU_STRING(), TLDisplayStrings.CLICK_SETTINGS_STRING(), TLDisplayStrings.TOGGLE_ENABLE_TRANSACTION_FEE_STRING(), ], [ TLDisplayStrings.GO_TO_THE_SIDE_MENU_STRING(), TLDisplayStrings.CLICK_SETTINGS_STRING(), TLDisplayStrings.ENABLE_TRANSACTION_FEE_STRING(), TLDisplayStrings.CLICK_SET_TRANSACTION_FEE_STRING(), TLDisplayStrings.INPUT_TRANSACTION_FEE_IN_BITCOINS_STRING(), ], [ TLDisplayStrings.GO_TO_THE_SIDE_MENU_STRING(), TLDisplayStrings.CLICK_ACCOUNTS_STRING(), TLDisplayStrings.SELECT_AND_CLICK_AN_ACCOUNT_STRING(), TLDisplayStrings.CLICK_VIEW_ADDRESSES_STRING(), ], [ TLDisplayStrings.GO_TO_THE_SIDE_MENU_STRING(), TLDisplayStrings.CLICK_ACCOUNTS_STRING(), TLDisplayStrings.SELECT_AND_CLICK_AN_ACCOUNT_STRING(), TLDisplayStrings.CLICK_VIEW_ADDRESSES_STRING(), TLDisplayStrings.SELECT_AND_CLICK_AN_ADDRESS_STRING(), TLDisplayStrings.CLICK_VIEW_ADDRESS_QR_CODE_STRING(), ], [ TLDisplayStrings.GO_TO_THE_SIDE_MENU_STRING(), TLDisplayStrings.CLICK_HISTORY_STRING(), TLDisplayStrings.SELECT_AND_CLICK_A_TRANSACTION_STRING(), TLDisplayStrings.CLICK_VIEW_IN_WEB_STRING(), ], [ TLDisplayStrings.GO_TO_THE_SIDE_MENU_STRING(), TLDisplayStrings.CLICK_ACCOUNTS_STRING(), TLDisplayStrings.SELECT_AND_CLICK_AN_ACCOUNT_STRING(), TLDisplayStrings.CLICK_VIEW_ADDRESSES_STRING(), TLDisplayStrings.SELECT_AND_CLICK_AN_ADDRESS_STRING(), TLDisplayStrings.CLICK_VIEW_IN_WEB_STRING(), ], [ TLDisplayStrings.GO_TO_THE_SIDE_MENU_STRING(), TLDisplayStrings.CLICK_SETTINGS_STRING(), TLDisplayStrings.CLICK_ADVANCED_SETTINGS_STRING(), TLDisplayStrings.TOGGLE_ENABLE_ADVANCED_MODE_STRING(), ], ] } return STATIC_MEMBERS._actionEventToInstructionStepsTitleArray!.object(at: idx) as! NSArray } class func getAdvanceActionInstructionStepsArray(_ idx: Int) -> NSArray { if (STATIC_MEMBERS._advanceActionInstructionStepsArray == nil) { STATIC_MEMBERS._advanceActionInstructionStepsArray = [ [ TLDisplayStrings.GO_TO_THE_SIDE_MENU_STRING(), TLDisplayStrings.CLICK_ACCOUNTS_STRING(), TLDisplayStrings.SCROLL_DOWN_TO_THE_SECTION_ACCOUNT_ACTIONS_STRING(), TLDisplayStrings.CLICK_IMPORT_ACCOUNT_STRING(), ], [ TLDisplayStrings.GO_TO_THE_SIDE_MENU_STRING(), TLDisplayStrings.CLICK_ACCOUNTS_STRING(), TLDisplayStrings.SCROLL_DOWN_TO_THE_SECTION_ACCOUNT_ACTIONS_STRING(), TLDisplayStrings.CLICK_IMPORT_WATCH_ACCOUNT_STRING(), ], [ TLDisplayStrings.GO_TO_THE_SIDE_MENU_STRING(), TLDisplayStrings.CLICK_ACCOUNTS_STRING(), TLDisplayStrings.SCROLL_DOWN_TO_THE_SECTION_ACCOUNT_ACTIONS_STRING(), TLDisplayStrings.CLICK_IMPORT_PRIVATE_KEY_STRING(), ], [ TLDisplayStrings.GO_TO_THE_SIDE_MENU_STRING(), TLDisplayStrings.CLICK_ACCOUNTS_STRING(), TLDisplayStrings.SCROLL_DOWN_TO_THE_SECTION_ACCOUNT_ACTIONS_STRING(), TLDisplayStrings.CLICK_IMPORT_WATCH_ADDRESS_STRING(), ], [ TLDisplayStrings.GO_TO_THE_SIDE_MENU_STRING(), TLDisplayStrings.CLICK_SETTINGS_STRING(), TLDisplayStrings.CLICK_ADVANCED_SETTINGS_STRING(), TLDisplayStrings.CLICK_BLOCKEXPLORER_API_TYPE_STRING(), TLDisplayStrings.SELECT_AND_CLICK_A_BLOCKEXPLORER_API_STRING(), TLDisplayStrings.QUIT_AND_RE_ENTER_APP_STRING(), ], [ TLDisplayStrings.GO_TO_THE_SIDE_MENU_STRING(), TLDisplayStrings.CLICK_ACCOUNTS_STRING(), TLDisplayStrings.SELECT_AND_CLICK_AN_ACCOUNT_STRING(), TLDisplayStrings.CLICK_VIEW_ACCOUNT_PUBLIC_KEY_QR_CODE_STRING(), ], [ TLDisplayStrings.GO_TO_THE_SIDE_MENU_STRING(), TLDisplayStrings.CLICK_ACCOUNTS_STRING(), TLDisplayStrings.SELECT_AND_CLICK_AN_ACCOUNT_STRING(), TLDisplayStrings.CLICK_VIEW_ACCOUNT_PRIVATE_KEY_QR_CODE_STRING(), ], [ TLDisplayStrings.GO_TO_THE_SIDE_MENU_STRING(), TLDisplayStrings.CLICK_ACCOUNTS_STRING(), TLDisplayStrings.SELECT_AND_CLICK_AN_ACCOUNT_STRING(), TLDisplayStrings.CLICK_VIEW_ADDRESSES_STRING(), TLDisplayStrings.SELECT_AND_CLICK_AN_ADDRESS_STRING(), TLDisplayStrings.CLICK_VIEW_PRIVATE_KEY_QR_CODE_STRING(), ], ] } return STATIC_MEMBERS._advanceActionInstructionStepsArray!.object(at: idx) as! NSArray } class func getActionEventToHowToActionTitleDict() -> NSDictionary { if (STATIC_MEMBERS._actionEventToInstructionsTitleDict == nil) { STATIC_MEMBERS._actionEventToInstructionsTitleDict = [ TLNotificationEvents.EVENT_SEND_PAYMENT(): STATIC_MEMBERS.ACTION_SEND_PAYMENT, TLNotificationEvents.EVENT_RECEIVE_PAYMENT(): STATIC_MEMBERS.ACTION_RECEIVE_PAYMENT, TLNotificationEvents.EVENT_RECEIVE_PAYMENT_FROM_STEALTH_ADDRESS(): STATIC_MEMBERS.ACTION_RECEIVE_PAYMENT_FROM_STEALTH_ADDRESS, TLNotificationEvents.EVENT_VIEW_HISTORY(): STATIC_MEMBERS.ACTION_VIEW_HISTORY, TLNotificationEvents.EVENT_CREATE_NEW_ACCOUNT(): STATIC_MEMBERS.ACTION_CREATE_NEW_ACCOUNT, TLNotificationEvents.EVENT_EDIT_ACCOUNT_NAME(): STATIC_MEMBERS.ACTION_EDIT_ACCOUNT_NAME, TLNotificationEvents.EVENT_ARCHIVE_ACCOUNT(): STATIC_MEMBERS.ACTION_ARCHIVE_ACCOUNT, TLNotificationEvents.EVENT_ENABLE_PIN_CODE(): STATIC_MEMBERS.ACTION_ENABLE_PIN_CODE, TLNotificationEvents.EVENT_BACKUP_PASSPHRASE(): STATIC_MEMBERS.ACTION_BACKUP_PASSPHRASE, TLNotificationEvents.EVENT_RESTORE_WALLET(): STATIC_MEMBERS.ACTION_RESTORE_WALLET, TLNotificationEvents.EVENT_ADD_TO_ADDRESS_BOOK(): STATIC_MEMBERS.ACTION_ADD_TO_ADDRESS_BOOK, TLNotificationEvents.EVENT_EDIT_ENTRY_ADDRESS_BOOK(): STATIC_MEMBERS.ACTION_EDIT_ENTRY_ADDRESS_BOOK, TLNotificationEvents.EVENT_DELETE_ENTRY_ADDRESS_BOOK(): STATIC_MEMBERS.ACTION_DELETE_ENTRY_ADDRESS_BOOK, TLNotificationEvents.EVENT_SEND_TO_ADDRESS_IN_ADDRESS_BOOK(): STATIC_MEMBERS.ACTION_SEND_TO_ADDRESS_IN_ADDRESS_BOOK, TLNotificationEvents.EVENT_TAG_TRANSACTION(): STATIC_MEMBERS.ACTION_TAG_TRANSACTION, TLNotificationEvents.EVENT_TOGGLE_AUTOMATIC_TX_FEE(): STATIC_MEMBERS.ACTION_TOGGLE_AUTOMATIC_TX_FEE, TLNotificationEvents.EVENT_CHANGE_AUTOMATIC_TX_FEE(): STATIC_MEMBERS.ACTION_CHANGE_AUTOMATIC_TX_FEE, TLNotificationEvents.EVENT_VIEW_ACCOUNT_ADDRESSES(): STATIC_MEMBERS.ACTION_VIEW_ACCOUNT_ADDRESSES, TLNotificationEvents.EVENT_VIEW_ACCOUNT_ADDRESS(): STATIC_MEMBERS.ACTION_VIEW_ACCOUNT_ADDRESS, TLNotificationEvents.EVENT_VIEW_ACCOUNT_ADDRESS_IN_WEB(): STATIC_MEMBERS.ACTION_VIEW_ACCOUNT_ADDRESS_IN_WEB, TLNotificationEvents.EVENT_VIEW_TRANSACTION_IN_WEB(): STATIC_MEMBERS.ACTION_VIEW_TRANSACTION_IN_WEB, TLNotificationEvents.EVENT_ENABLE_ADVANCE_MODE(): STATIC_MEMBERS.ACTION_ENABLE_ADVANCE_MODE, TLNotificationEvents.EVENT_IMPORT_ACCOUNT(): STATIC_MEMBERS.ACTION_IMPORT_ACCOUNT, TLNotificationEvents.EVENT_IMPORT_WATCH_ONLY_ACCOUNT(): STATIC_MEMBERS.ACTION_IMPORT_WATCH_ONLY_ACCOUNT, TLNotificationEvents.EVENT_IMPORT_PRIVATE_KEY(): STATIC_MEMBERS.ACTION_IMPORT_PRIVATE_KEY, TLNotificationEvents.EVENT_IMPORT_WATCH_ONLY_ADDRESS(): STATIC_MEMBERS.ACTION_IMPORT_WATCH_ONLY_ADDRESS, TLNotificationEvents.EVENT_CHANGE_BLOCKEXPLORER_TYPE(): STATIC_MEMBERS.ACTION_CHANGE_BLOCKEXPLORER_TYPE, TLNotificationEvents.EVENT_VIEW_EXTENDED_PUBLIC_KEY(): STATIC_MEMBERS.ACTION_VIEW_EXTENDED_PUBLIC_KEY, TLNotificationEvents.EVENT_VIEW_EXTENDED_PRIVATE_KEY(): STATIC_MEMBERS.ACTION_VIEW_EXTENDED_PRIVATE_KEY, TLNotificationEvents.EVENT_VIEW_ACCOUNT_PRIVATE_KEY(): STATIC_MEMBERS.ACTION_VIEW_ACCOUNT_PRIVATE_KEY, ] } return STATIC_MEMBERS._actionEventToInstructionsTitleDict! } class func getAccountActionsArray() -> NSArray { if (TLPreferences.enabledAdvancedMode()) { return getAdvanceAccountActionsArray() } else { return getBasicAccountActionsArray() } } class func getBasicAccountActionsArray() -> NSArray { var accountActionsArray: NSArray? if TLPreferences.enabledColdWallet() { accountActionsArray = [ STATIC_MEMBERS.ACCOUNT_ACTION_CREATE_NEW_ACCOUNT, STATIC_MEMBERS.ACCOUNT_ACTION_IMPORT_COLD_WALLET_ACCOUNT ] } else { accountActionsArray = [ STATIC_MEMBERS.ACCOUNT_ACTION_CREATE_NEW_ACCOUNT ] } return accountActionsArray! } class func getAdvanceAccountActionsArray() -> NSArray { var accountActionsArray: NSArray? if TLPreferences.enabledColdWallet() { accountActionsArray = [ STATIC_MEMBERS.ACCOUNT_ACTION_CREATE_NEW_ACCOUNT, STATIC_MEMBERS.ACCOUNT_ACTION_IMPORT_COLD_WALLET_ACCOUNT, STATIC_MEMBERS.ACCOUNT_ACTION_IMPORT_ACCOUNT, STATIC_MEMBERS.ACCOUNT_ACTION_IMPORT_WATCH_ONLY_ACCOUNT, STATIC_MEMBERS.ACCOUNT_ACTION_IMPORT_PRIVATE_KEY, STATIC_MEMBERS.ACCOUNT_ACTION_IMPORT_WATCH_ONLY_ADDRESS ] } else { accountActionsArray = [ STATIC_MEMBERS.ACCOUNT_ACTION_CREATE_NEW_ACCOUNT, STATIC_MEMBERS.ACCOUNT_ACTION_IMPORT_ACCOUNT, STATIC_MEMBERS.ACCOUNT_ACTION_IMPORT_WATCH_ONLY_ACCOUNT, STATIC_MEMBERS.ACCOUNT_ACTION_IMPORT_PRIVATE_KEY, STATIC_MEMBERS.ACCOUNT_ACTION_IMPORT_WATCH_ONLY_ADDRESS ] } return accountActionsArray! } class func getEventsArray() -> NSArray { if (STATIC_MEMBERS._eventsArray == nil) { STATIC_MEMBERS._eventsArray = [ TLNotificationEvents.EVENT_SEND_PAYMENT(), TLNotificationEvents.EVENT_RECEIVE_PAYMENT(), TLNotificationEvents.EVENT_RECEIVE_PAYMENT_FROM_STEALTH_ADDRESS(), TLNotificationEvents.EVENT_VIEW_HISTORY(), TLNotificationEvents.EVENT_CREATE_NEW_ACCOUNT(), TLNotificationEvents.EVENT_EDIT_ACCOUNT_NAME(), TLNotificationEvents.EVENT_ARCHIVE_ACCOUNT(), TLNotificationEvents.EVENT_ENABLE_PIN_CODE(), TLNotificationEvents.EVENT_BACKUP_PASSPHRASE(), TLNotificationEvents.EVENT_RESTORE_WALLET(), TLNotificationEvents.EVENT_ADD_TO_ADDRESS_BOOK(), TLNotificationEvents.EVENT_EDIT_ENTRY_ADDRESS_BOOK(), TLNotificationEvents.EVENT_DELETE_ENTRY_ADDRESS_BOOK(), TLNotificationEvents.EVENT_SEND_TO_ADDRESS_IN_ADDRESS_BOOK(), TLNotificationEvents.EVENT_TAG_TRANSACTION(), TLNotificationEvents.EVENT_TOGGLE_AUTOMATIC_TX_FEE(), TLNotificationEvents.EVENT_CHANGE_AUTOMATIC_TX_FEE(), TLNotificationEvents.EVENT_VIEW_ACCOUNT_ADDRESSES(), TLNotificationEvents.EVENT_VIEW_ACCOUNT_ADDRESS(), TLNotificationEvents.EVENT_VIEW_TRANSACTION_IN_WEB(), TLNotificationEvents.EVENT_VIEW_ACCOUNT_ADDRESS_IN_WEB(), TLNotificationEvents.EVENT_ENABLE_ADVANCE_MODE(), ] } return STATIC_MEMBERS._eventsArray! } class func getAdvanceEventsArray() -> NSArray { if (STATIC_MEMBERS._howToDoAdvanceAchievementsArray == nil) { STATIC_MEMBERS._howToDoAdvanceAchievementsArray = [ TLNotificationEvents.EVENT_IMPORT_ACCOUNT(), TLNotificationEvents.EVENT_IMPORT_WATCH_ONLY_ACCOUNT(), TLNotificationEvents.EVENT_IMPORT_PRIVATE_KEY(), TLNotificationEvents.EVENT_IMPORT_WATCH_ONLY_ADDRESS(), TLNotificationEvents.EVENT_CHANGE_BLOCKEXPLORER_TYPE(), TLNotificationEvents.EVENT_VIEW_EXTENDED_PUBLIC_KEY(), TLNotificationEvents.EVENT_VIEW_EXTENDED_PRIVATE_KEY(), TLNotificationEvents.EVENT_VIEW_ACCOUNT_PRIVATE_KEY(), ] } return STATIC_MEMBERS._howToDoAdvanceAchievementsArray! } class func getFAQArray() -> NSArray { var _faqArray: NSArray? if (_faqArray == nil) { _faqArray = [ TLDisplayStrings.WHAT_IS_BITCOIN_STRING(), TLDisplayStrings.WHAT_ARE_THE_BENEFITS_AND_ADVANTAGES_OF_BITCOIN_STRING(), TLDisplayStrings.HOW_DO_I_GET_BITCOINS_STRING(), TLDisplayStrings.WHAT_IS_A_BITCOIN_WALLET_STRING(), TLDisplayStrings.HOW_DOES_ARCBIT_WALLET_WORK_STRING(), TLDisplayStrings.WHAT_MAKES_ARCBIT_DIFFERENT_FROM_OTHER_BITCOIN_WALLETS_STRING(), TLDisplayStrings.WHAT_ARE_TRANSACTION_CONFIRMATIONS_STRING(), TLDisplayStrings.WHAT_ARE_ACCOUNTS_STRING(), TLDisplayStrings.WHAT_ARE_REUSABLE_ADDRESSES_STRING(), TLDisplayStrings.WHAT_IS_ARCBITS_COLD_WALLET_FEATURE_STRING(), ] } return _faqArray! } class func getExplanation(_ idx: Int) -> String { if (STATIC_MEMBERS._explanationArray == nil) { STATIC_MEMBERS._explanationArray = [ TLDisplayStrings.WHAT_IS_BITCOIN_DESC_STRING(), TLDisplayStrings.WHAT_ARE_THE_BENEFITS_AND_ADVANTAGES_OF_BITCOIN_DESC_STRING(), TLDisplayStrings.HOW_DO_I_GET_BITCOINS_DESC_STRING(), TLDisplayStrings.WHAT_IS_A_BITCOIN_WALLET_DESC_STRING(), TLDisplayStrings.HOW_DOES_ARCBIT_WALLET_WORK_DESC_STRING(), TLDisplayStrings.WHAT_MAKES_ARCBIT_DIFFERENT_FROM_OTHER_BITCOIN_WALLETS_DESC_STRING(), TLDisplayStrings.WHAT_ARE_TRANSACTION_CONFIRMATIONS_DESC_STRING(), TLDisplayStrings.WHAT_ARE_ACCOUNTS_DESC_STRING(), TLDisplayStrings.WHAT_ARE_REUSABLE_ADDRESSES_DESC_STRING(), TLDisplayStrings.WHAT_IS_ARCBITS_COLD_WALLET_FEATURE_DESC_STRING(), ] } return STATIC_MEMBERS._explanationArray!.object(at: idx) as! String } class func getAdvanceFAQArray() -> NSArray { var _faqArray: NSArray? if (_faqArray == nil) { _faqArray = [ TLDisplayStrings.WHAT_ARE_ACCOUNT_EXTENDED_KEYS_STRING(), TLDisplayStrings.IMPORT_FEATURE_STRING(), TLDisplayStrings.IMPORTING_AN_ACCOUNT_STRING(), TLDisplayStrings.IMPORTING_A_WATCH_ONLY_ACCOUNT_STRING(), TLDisplayStrings.IMPORTING_A_PRIVATE_KEY_STRING(), TLDisplayStrings.IMPORTING_A_WATCH_ONLY_ADDRESS_STRING(), ] } return _faqArray! } class func getAdvanceExplanation(_ idx: Int) -> String { if (STATIC_MEMBERS._advanceExplanationArray == nil) { STATIC_MEMBERS._advanceExplanationArray = [ TLDisplayStrings.WHAT_ARE_ACCOUNT_EXTENDED_KEYS_DESC_STRING(), TLDisplayStrings.IMPORT_FEATURE_DESC_STRING(), TLDisplayStrings.IMPORTING_AN_ACCOUNT_DESC_STRING(), TLDisplayStrings.IMPORTING_A_WATCH_ONLY_ACCOUNT_DESC_STRING(), TLDisplayStrings.IMPORTING_A_PRIVATE_KEY_DESC_STRING(), TLDisplayStrings.IMPORTING_A_WATCH_ONLY_ADDRESS_DESC_STRING(), ] } return STATIC_MEMBERS._advanceExplanationArray!.object(at: idx) as! String } } ================================================ FILE: ArcBit/model/TLImportedAddress.swift ================================================ // // TLImportedAddress.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation @objc class TLImportedAddress : NSObject { fileprivate var appWallet:TLWallet? fileprivate var addressDict:NSMutableDictionary? lazy var haveUpDatedUTXOs: Bool = false lazy var unspentOutputsCount: Int = 0 fileprivate var unspentOutputs:NSArray? fileprivate var unspentOutputsSum:TLCoin? var balance = TLCoin.zero() fileprivate var fetchedAccountData = false var listeningToIncomingTransactions = false fileprivate var watchOnly = false fileprivate var archived = false fileprivate var positionInWalletArray:Int? fileprivate var txObjectArray:NSMutableArray? fileprivate var txidToAccountAmountDict:NSMutableDictionary? fileprivate var txidToAccountAmountTypeDict:NSMutableDictionary? fileprivate var processedTxSet:NSMutableSet? fileprivate var privateKey:String? fileprivate var importedAddress:String? var downloadState:TLDownloadState = .notDownloading init(appWallet: TLWallet, dict:NSDictionary) { super.init() self.appWallet = appWallet addressDict = NSMutableDictionary(dictionary:dict) importedAddress = addressDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_ADDRESS) as! String? unspentOutputs = NSMutableArray() processedTxSet = NSMutableSet() if (addressDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_KEY) != nil) { self.watchOnly = false } else { self.watchOnly = true } self.archived = addressDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STATUS) as! Int == TLAddressStatus.archived.rawValue resetAccountBalances() } func hasSetPrivateKeyInMemory() -> (Bool) { return privateKey != nil } func setPrivateKeyInMemory(_ privKey:String) -> (Bool) { if (TLCoreBitcoinWrapper.getAddress(privKey, isTestnet: self.appWallet!.walletConfig.isTestnet) == getAddress()) { privateKey = privKey return true } return false } func clearPrivateKeyFromMemory() -> (){ privateKey = nil } func getDefaultAddressLabel()-> (String?) { return importedAddress } func setHasFetchedAccountData(_ fetched:Bool) -> () { self.fetchedAccountData = fetched if fetched { self.downloadState = .downloaded } if self.fetchedAccountData == true && self.listeningToIncomingTransactions == false { self.listeningToIncomingTransactions = true let address = self.getAddress() TLTransactionListener.instance().listenToIncomingTransactionForAddress(address) } } func hasFetchedAccountData() -> (Bool){ return self.fetchedAccountData } func getUnspentArray() -> (NSArray?) { return unspentOutputs } func getUnspentSum() -> (TLCoin?) { if (unspentOutputsSum != nil) { return unspentOutputsSum } if (unspentOutputs == nil) { return TLCoin.zero() } var unspentOutputsSumTemp:UInt64 = 0 for unspentOutput in unspentOutputs as! [NSDictionary] { let amount = unspentOutput.object(forKey: "value") as! NSNumber unspentOutputsSumTemp += UInt64(amount) } unspentOutputsSum = TLCoin(uint64: unspentOutputsSumTemp) return unspentOutputsSum } func getInputsNeededToConsume(_ amountNeeded: TLCoin) -> Int { var valueSelected:UInt64 = 0 var inputCount = 0 for _unspentOutput in unspentOutputs! { let unspentOutput = _unspentOutput as! NSDictionary let amount = unspentOutput.object(forKey: "value") as! NSNumber valueSelected += amount.uint64Value inputCount += 1 if valueSelected >= amountNeeded.toUInt64() { return inputCount } } return inputCount } func setUnspentOutputs(_ unspentOuts:NSArray)-> () { unspentOutputs = unspentOuts.copy() as? NSArray } func getBalance() -> (TLCoin?) { return self.balance } func isWatchOnly() -> (Bool) { return self.watchOnly } func setArchived(_ archived:Bool) -> () { self.archived = archived } func isArchived() -> (Bool) { return self.archived } func getPositionInWalletArray() -> Int { return positionInWalletArray ?? 0 } func getPositionInWalletArrayNumber() -> (NSNumber) { return NSNumber(value: positionInWalletArray ?? 0 as Int) } func setPositionInWalletArray(_ idx: Int) -> () { positionInWalletArray = idx } func isPrivateKeyEncrypted() -> (Bool) { if (self.watchOnly) { return false } if (TLCoreBitcoinWrapper.isBIP38EncryptedKey(addressDict!.object(forKey: "key") as! String, isTestnet: self.appWallet!.walletConfig.isTestnet)) { return true } return false } func getAddress() -> String { return addressDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_ADDRESS) as! String } func getEitherPrivateKeyOrEncryptedPrivateKey() -> String? { if (self.watchOnly) { return privateKey } else { return addressDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_KEY) as? String } } func getPrivateKey() -> (String?) { if (self.watchOnly) { return privateKey } else if (isPrivateKeyEncrypted()) { return privateKey } else { return addressDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_KEY) as? String } } func getEncryptedPrivateKey() -> (String?) { if (isPrivateKeyEncrypted()) { return addressDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_KEY) as? String } else { return nil } } func getLabel() -> (String) { if (addressDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_LABEL) as? String == nil || addressDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_LABEL) as! String == "") { return addressDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_ADDRESS) as! String } else { return addressDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_LABEL) as! String } } func getTxObjectCount() -> (Int) { return txObjectArray!.count } func getTxObject(_ txIdx: Int) -> TLTxObject { return txObjectArray!.object(at: txIdx) as! TLTxObject } func getAccountAmountChangeForTx(_ txHash: String) -> TLCoin? { return txidToAccountAmountDict!.object(forKey: txHash) as? TLCoin } func getAccountAmountChangeTypeForTx(_ txHash:String) -> TLAccountTxType { return TLAccountTxType(rawValue: Int(txidToAccountAmountTypeDict!.object(forKey: txHash) as! Int))! } func processNewTx(_ txObject: TLTxObject) -> TLCoin? { if (processedTxSet!.contains(txObject.getHash()!)) { // happens when you send coins to the same account, so you get the same tx from the websockets more then once return nil } let doesTxInvolveAddressAndReceivedAmount = processTx(txObject, shouldUpdateAccountBalance: true) txObjectArray!.insert(txObject, at:0) return doesTxInvolveAddressAndReceivedAmount.1 } func processTxArray(_ txArray: NSArray, shouldUpdateAccountBalance: Bool) -> (){ resetAccountBalances() for tx in txArray as! [NSDictionary] { let txObject = TLTxObject(dict:tx) let doesTxInvolveAddressAndReceivedAmount = processTx(txObject, shouldUpdateAccountBalance: shouldUpdateAccountBalance) if (doesTxInvolveAddressAndReceivedAmount.0) { txObjectArray!.add(txObject) } } } fileprivate func processTx(_ txObject: TLTxObject, shouldUpdateAccountBalance: Bool) -> (Bool, TLCoin?) { haveUpDatedUTXOs = false processedTxSet!.add(txObject.getHash()!) var currentTxSubtract:UInt64 = 0 var currentTxAdd:UInt64 = 0 var doesTxInvolveAddress = false let ouputAddressToValueArray = txObject.getOutputAddressToValueArray() for output in ouputAddressToValueArray as! [NSDictionary] { var value:UInt64 = 0; if let v = output.object(forKey: "value") as? NSNumber { value = UInt64(v.uint64Value) } let address = output.object(forKey: "addr") as? String if (address != nil && address == importedAddress) { currentTxAdd += value doesTxInvolveAddress = true } } let inputAddressToValueArray = txObject.getInputAddressToValueArray() for input in inputAddressToValueArray as! [NSDictionary] { var value:UInt64 = 0; if let v = input.object(forKey: "value") as? NSNumber { value = UInt64(v.uint64Value) } let address = input.object(forKey: "addr") as? String if (address != nil && address == importedAddress) { currentTxSubtract += value doesTxInvolveAddress = true } } if (shouldUpdateAccountBalance) { self.balance = TLCoin(uint64: self.balance.toUInt64() + currentTxAdd - currentTxSubtract) } let receivedAmount:TLCoin? if (currentTxSubtract > currentTxAdd) { let amountChangeToAccountFromTx = TLCoin(uint64:currentTxSubtract - currentTxAdd) txidToAccountAmountDict!.setObject(amountChangeToAccountFromTx, forKey:txObject.getHash()!) txidToAccountAmountTypeDict!.setObject(TLAccountTxType.send.rawValue, forKey:txObject.getHash()!) receivedAmount = nil } else if (currentTxSubtract < currentTxAdd) { let amountChangeToAccountFromTx = TLCoin(uint64:currentTxAdd - currentTxSubtract) txidToAccountAmountDict!.setObject(amountChangeToAccountFromTx, forKey:txObject.getHash()!) txidToAccountAmountTypeDict!.setObject(TLAccountTxType.receive.rawValue, forKey:txObject.getHash()!) receivedAmount = amountChangeToAccountFromTx } else { let amountChangeToAccountFromTx = TLCoin.zero() txidToAccountAmountDict!.setObject(amountChangeToAccountFromTx, forKey:txObject.getHash()!) txidToAccountAmountTypeDict!.setObject(TLAccountTxType.moveBetweenAccount.rawValue, forKey:txObject.getHash()!) receivedAmount = nil } return (doesTxInvolveAddress, receivedAmount) } func getSingleAddressData(_ success: @escaping TLWalletUtils.Success, failure:@escaping TLWalletUtils.Error) -> () { TLBlockExplorerAPI.instance().getAddressesInfo([importedAddress!], success:{(jsonData:AnyObject!) in let addressesArray = jsonData.object(forKey: "addresses") as! NSArray for addressDict in addressesArray { let addressBalance = ((addressDict as AnyObject).object(forKey: "final_balance") as! NSNumber).uint64Value self.balance = TLCoin(uint64: addressBalance) self.processTxArray((jsonData as! NSDictionary!).object(forKey: "txs") as! NSArray, shouldUpdateAccountBalance: false) } self.setHasFetchedAccountData(true) DLog("postNotificationName: EVENT_FETCHED_ADDRESSES_DATA \(self.getAddress())") NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_FETCHED_ADDRESSES_DATA()) ,object:self.importedAddress, userInfo:nil) success() }, failure: {(code, status) in failure() } ) } func getSingleAddressDataO(_ fetchDataAgain:Bool) -> () { if self.fetchedAccountData == true && !fetchDataAgain { self.downloadState = .downloaded return } let jsonData = TLBlockExplorerAPI.instance().getAddressesInfoSynchronous([importedAddress!]) let addressesArray = jsonData.object(forKey: "addresses") as! NSArray for addressDict in addressesArray { let addressBalance = ((addressDict as AnyObject).object(forKey: "final_balance") as! NSNumber).uint64Value self.balance = TLCoin(uint64: addressBalance) self.processTxArray(jsonData.object(forKey: "txs") as! NSArray, shouldUpdateAccountBalance: false) } self.setHasFetchedAccountData(true) DispatchQueue.main.async(execute: { DLog("postNotificationName: EVENT_FETCHED_ADDRESSES_DATA \(self.getAddress())") NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_FETCHED_ADDRESSES_DATA()) ,object:self.importedAddress, userInfo:nil) }) } func setLabel(_ label:NSString) -> (){ addressDict!.setObject(label, forKey:TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_LABEL as NSCopying) } fileprivate func resetAccountBalances() -> () { txObjectArray = NSMutableArray() txidToAccountAmountDict = NSMutableDictionary() txidToAccountAmountTypeDict = NSMutableDictionary() } } ================================================ FILE: ArcBit/model/TLImportedAddresses.swift ================================================ // // TLImportedAddresses.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation @objc class TLImportedAddresses:NSObject { fileprivate var appWallet:TLWallet? fileprivate let importedAddresses = NSMutableArray() fileprivate let archivedImportedAddresses = NSMutableArray() fileprivate let addressToIdxDict = NSMutableDictionary() fileprivate let addressToPositionInWalletArrayDict = NSMutableDictionary() fileprivate var accountAddressType:TLAccountAddressType? var downloadState:TLDownloadState = .notDownloading init(appWallet: TLWallet, importedAddresses:NSArray, accountAddressType:(TLAccountAddressType)) { super.init() self.appWallet = appWallet self.accountAddressType = accountAddressType for i in stride(from: 0, to: importedAddresses.count, by: 1) { let importedAddressObject = importedAddresses.object(at: i) as! TLImportedAddress if (importedAddressObject.isArchived()) { self.archivedImportedAddresses.add(importedAddressObject) } else { var indexes = self.addressToIdxDict.object(forKey: importedAddressObject.getAddress()) as? NSMutableArray if (indexes == nil) { indexes = NSMutableArray() self.addressToIdxDict.setObject(indexes!, forKey:importedAddressObject.getAddress() as NSCopying) } indexes!.add(self.importedAddresses.count) self.importedAddresses.add(importedAddressObject) } importedAddressObject.setPositionInWalletArray(i) self.addressToPositionInWalletArrayDict.setObject(importedAddressObject, forKey:importedAddressObject.getPositionInWalletArrayNumber()) } } func getAddressObjectAtIdx(_ idx:Int) -> TLImportedAddress { return self.importedAddresses.object(at: idx) as! TLImportedAddress } func getArchivedAddressObjectAtIdx(_ idx:Int) -> TLImportedAddress{ return self.archivedImportedAddresses.object(at: idx) as! TLImportedAddress } func getCount() -> Int{ return self.importedAddresses.count } func getArchivedCount() -> Int{ return self.archivedImportedAddresses.count } func checkToGetAndSetAddressesData(_ fetchDataAgain:Bool, success:@escaping TLWalletUtils.Success, failure:@escaping TLWalletUtils.Error) -> () { let addresses = NSMutableSet() for importedAddressObject in self.importedAddresses { if (!(importedAddressObject as! TLImportedAddress).hasFetchedAccountData() || fetchDataAgain) { let address = (importedAddressObject as! TLImportedAddress).getAddress() addresses.add(address) } } if (addresses.count == 0) { success() return } TLBlockExplorerAPI.instance().getAddressesInfo(addresses.allObjects as! [String], success:{(jsonData:AnyObject!) in let addressesArray = jsonData.object(forKey: "addresses") as! NSArray let txArray = jsonData.object(forKey: "txs") as! NSArray for addressDict in addressesArray { let address = (addressDict as! NSDictionary).object(forKey: "address") as! String let indexes = self.addressToIdxDict.object(forKey: address) as! NSArray for idx in indexes { let importedAddressObject = self.importedAddresses.object(at: idx as! Int) as! TLImportedAddress let addressBalance = ((addressDict as AnyObject).object(forKey: "final_balance") as! NSNumber).uint64Value importedAddressObject.balance = TLCoin(uint64: addressBalance) importedAddressObject.processTxArray(txArray, shouldUpdateAccountBalance: false) importedAddressObject.setHasFetchedAccountData(true) } } DLog("postNotificationName: EVENT_FETCHED_ADDRESSES_DATA importedAddresses") NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_FETCHED_ADDRESSES_DATA()), object:nil, userInfo:nil) success() } , failure:{(code, status) in failure() }) } func checkToGetAndSetAddressesDataO(_ fetchDataAgain:Bool) -> () { let addresses = NSMutableSet() for importedAddressObject in self.importedAddresses { if (!(importedAddressObject as! TLImportedAddress).hasFetchedAccountData() || fetchDataAgain) { let address = (importedAddressObject as! TLImportedAddress).getAddress() addresses.add(address) } } if (addresses.count == 0) { return } let jsonData = TLBlockExplorerAPI.instance().getAddressesInfoSynchronous(addresses.allObjects as! [String]) if (jsonData.object(forKey: TLNetworking.STATIC_MEMBERS.HTTP_ERROR_CODE) != nil) { self.downloadState = .failed return } let addressesArray = jsonData.object(forKey: "addresses") as! NSArray let txArray = jsonData.object(forKey: "txs") as! NSArray for addressDict in addressesArray { let address = (addressDict as! NSDictionary).object(forKey: "address") as! String let indexes = (self.addressToIdxDict).object(forKey: address) as! NSArray for idx in indexes { let importedAddressObject = self.importedAddresses.object(at: idx as! Int) as! TLImportedAddress let addressBalance = ((addressDict as AnyObject).object(forKey: "final_balance") as! NSNumber).uint64Value importedAddressObject.balance = TLCoin(uint64: addressBalance) importedAddressObject.processTxArray(txArray, shouldUpdateAccountBalance: false) importedAddressObject.setHasFetchedAccountData(true) } } self.downloadState = .downloaded DispatchQueue.main.async(execute: { DLog("postNotificationName: EVENT_FETCHED_ADDRESSES_DATA importedAddresses") NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_FETCHED_ADDRESSES_DATA()), object:nil, userInfo:nil) }) } func addImportedPrivateKey(_ privateKey:String, encryptedPrivateKey:String?) -> (TLImportedAddress) { let importedPrivateKeyDict = self.appWallet!.addImportedPrivateKey(privateKey, encryptedPrivateKey:encryptedPrivateKey) let importedAddressObject = TLImportedAddress(appWallet:self.appWallet!, dict:importedPrivateKeyDict) self.importedAddresses.add(importedAddressObject) importedAddressObject.setPositionInWalletArray(self.importedAddresses.count + self.archivedImportedAddresses.count - 1) self.addressToPositionInWalletArrayDict.setObject(importedAddressObject, forKey:importedAddressObject.getPositionInWalletArrayNumber()) let address = TLCoreBitcoinWrapper.getAddress(privateKey, isTestnet: self.appWallet!.walletConfig.isTestnet) var indexes = self.addressToIdxDict.object(forKey: address!) as! NSMutableArray? if (indexes == nil) { indexes = NSMutableArray() self.addressToIdxDict.setObject(indexes!, forKey:importedAddressObject.getAddress() as NSCopying) } indexes!.add(self.importedAddresses.count-1) setLabel(importedAddressObject.getDefaultAddressLabel()!, positionInWalletArray:importedAddressObject.getPositionInWalletArray()) return importedAddressObject } func addImportedWatchAddress(_ address:String) -> (TLImportedAddress) { let importedDict = self.appWallet!.addWatchOnlyAddress(address as NSString) let importedAddressObject = TLImportedAddress(appWallet:self.appWallet!, dict:importedDict) self.importedAddresses.add(importedAddressObject) importedAddressObject.setPositionInWalletArray(self.importedAddresses.count + self.archivedImportedAddresses.count - 1) self.addressToPositionInWalletArrayDict.setObject(importedAddressObject, forKey:importedAddressObject.getPositionInWalletArrayNumber()) var indexes = self.addressToIdxDict.object(forKey: address) as? NSMutableArray if (indexes == nil) { indexes = NSMutableArray() self.addressToIdxDict.setObject(indexes!, forKey:address as NSCopying) } indexes!.add(self.importedAddresses.count-1) setLabel(importedAddressObject.getDefaultAddressLabel()!, positionInWalletArray:importedAddressObject.getPositionInWalletArray()) return importedAddressObject } func setLabel(_ label:String, positionInWalletArray:Int) { let importedAddressObject = self.addressToPositionInWalletArrayDict.object(forKey: positionInWalletArray) as! TLImportedAddress importedAddressObject.setLabel(label as NSString) if (self.accountAddressType == .imported) { self.appWallet!.setImportedPrivateKeyLabel(label, idx:positionInWalletArray) } else if (self.accountAddressType! == .importedWatch) { self.appWallet!.setWatchOnlyAddressLabel(label, idx:positionInWalletArray) } } func archiveAddress(_ positionInWalletArray:Int) -> () { self.setArchived(positionInWalletArray, archive:true) let toMoveAddressObject = self.addressToPositionInWalletArrayDict.object(forKey: positionInWalletArray) as! TLImportedAddress var indexes = self.addressToIdxDict.object(forKey: toMoveAddressObject.getAddress()) as? NSMutableArray if (indexes == nil) { indexes = NSMutableArray() self.addressToIdxDict.setObject(indexes!, forKey:toMoveAddressObject.getAddress() as NSCopying) } let toMoveIndex = self.importedAddresses.index(of: toMoveAddressObject) as Int for key in self.addressToIdxDict { let indexes: AnyObject = (self.addressToIdxDict.object(forKey: key.key) as! NSArray).copy() as AnyObject for idx in indexes as! [Int] { if (idx > toMoveIndex) { let indexes = self.addressToIdxDict.object(forKey: key.key) as! NSMutableArray indexes.remove(idx) indexes.add(UInt(idx)-1) } } } indexes!.remove(toMoveIndex) self.importedAddresses.remove(toMoveAddressObject) for i in stride(from: 0, to: self.archivedImportedAddresses.count, by: 1) { let importedAddressObject = self.archivedImportedAddresses.object(at: i) as! TLImportedAddress if (importedAddressObject.getPositionInWalletArray() > toMoveAddressObject.getPositionInWalletArray()) { self.archivedImportedAddresses.insert(toMoveAddressObject, at:i) return } } self.archivedImportedAddresses.add(toMoveAddressObject) } func unarchiveAddress(_ positionInWalletArray:Int) -> (){ setArchived(positionInWalletArray, archive:false) let toMoveAddressObject = self.addressToPositionInWalletArrayDict.object(forKey: positionInWalletArray) as! TLImportedAddress self.archivedImportedAddresses.remove(toMoveAddressObject) for i in stride(from: 0, to: self.importedAddresses.count, by: 1) { let importedAddressObject = self.importedAddresses.object(at: i) as! TLImportedAddress if (importedAddressObject.getPositionInWalletArray() > toMoveAddressObject.getPositionInWalletArray()) { self.importedAddresses.insert(toMoveAddressObject, at:i) var indexes = self.addressToIdxDict.object(forKey: toMoveAddressObject.getAddress()) as? NSMutableArray if (indexes == nil) { indexes = NSMutableArray() indexes!.add(i) self.addressToIdxDict.setObject(indexes!, forKey:toMoveAddressObject.getAddress() as NSCopying) } for key in self.addressToIdxDict { let indexes: AnyObject = (self.addressToIdxDict.object(forKey: key.key) as! NSArray).copy() as AnyObject for idx in indexes as! [Int] { if (idx >= i) { let indexes = self.addressToIdxDict.object(forKey: key.key) as! NSMutableArray indexes.remove(idx) indexes.add(UInt(idx)+1) } } } return } } self.importedAddresses.add(toMoveAddressObject) } fileprivate func setArchived(_ positionInWalletArray:Int, archive:Bool) -> Bool{ let importedAddressObject = self.addressToPositionInWalletArrayDict.object(forKey: positionInWalletArray) as! TLImportedAddress importedAddressObject.setArchived(archive) if (self.accountAddressType! == .imported) { self.appWallet!.setImportedPrivateKeyArchive(archive, idx:positionInWalletArray) } else if (self.accountAddressType == .importedWatch) { self.appWallet!.setWatchOnlyAddressArchive(archive, idx:positionInWalletArray) } return true } func deleteAddress(_ idx:Int) -> Bool { let importedAddressObject = self.archivedImportedAddresses.object(at: idx) as! TLImportedAddress self.archivedImportedAddresses.removeObject(at: idx) if (self.accountAddressType == .imported) { self.appWallet!.deleteImportedPrivateKey(importedAddressObject.getPositionInWalletArray()) } else if (self.accountAddressType == .importedWatch) { self.appWallet!.deleteImportedWatchAddress(importedAddressObject.getPositionInWalletArray()) } self.addressToPositionInWalletArrayDict.removeObject(forKey: importedAddressObject.getPositionInWalletArrayNumber()) let tmpDict = self.addressToPositionInWalletArrayDict.copy() as! NSDictionary for (key, _) in tmpDict { let ia = self.addressToPositionInWalletArrayDict.object(forKey: key as! NSNumber) as! TLImportedAddress if (ia.getPositionInWalletArray() > importedAddressObject.getPositionInWalletArray()) { ia.setPositionInWalletArray(ia.getPositionInWalletArray()-1) self.addressToPositionInWalletArrayDict.setObject(ia, forKey:ia.getPositionInWalletArrayNumber()) } } if importedAddressObject.getPositionInWalletArray() < self.addressToPositionInWalletArrayDict.count - 1 { self.addressToPositionInWalletArrayDict.removeObject(forKey: self.addressToPositionInWalletArrayDict.count-1) } return true } func hasFetchedAddressesData() -> Bool { for importedAddressObject in self.importedAddresses { if (!((importedAddressObject as! TLImportedAddress).hasFetchedAccountData())) { return false } } return true } } ================================================ FILE: ArcBit/model/TLOperationsManager.swift ================================================ // // TLOperationsManager.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation class PendingOperations { fileprivate lazy var downloadQueue:OperationQueue = { var queue = OperationQueue() queue.qualityOfService = QualityOfService.userInteractive queue.name = "Fetch addresses data queue" return queue }() func addSetUpImportedAddressesOperation(_ importedAddresses: TLImportedAddresses, fetchDataAgain :Bool, success: @escaping TLWalletUtils.Success) -> Bool { if importedAddresses.downloadState == .queuedForDownloading || importedAddresses.downloadState == .downloading || (!fetchDataAgain && importedAddresses.downloadState == .downloaded) { return false } importedAddresses.downloadState = .queuedForDownloading let downloader = SetUpImportedAddressesOperation(importedAddresses: importedAddresses, fetchDataAgain: fetchDataAgain) downloader.completionBlock = { if downloader.isCancelled { return } DispatchQueue.main.async(execute: { success() }) } self.downloadQueue.addOperation(downloader) return true } func addSetUpImportedAddressOperation(_ importedAddress: TLImportedAddress, fetchDataAgain :Bool, success: @escaping TLWalletUtils.Success) -> Bool { if importedAddress.downloadState == .queuedForDownloading || importedAddress.downloadState == .downloading || (!fetchDataAgain && importedAddress.downloadState == .downloaded) { return false } importedAddress.downloadState = .queuedForDownloading let downloader = SetUpImportedAddressOperation(importedAddress: importedAddress, fetchDataAgain: fetchDataAgain) downloader.completionBlock = { if downloader.isCancelled { DispatchQueue.main.async(execute: { success() }) return } DispatchQueue.main.async(execute: { success() }) } self.downloadQueue.addOperation(downloader) return true } func addSetUpAccountOperation(_ accountObject: TLAccountObject, fetchDataAgain :Bool, success: @escaping TLWalletUtils.Success) -> Bool { if accountObject.downloadState == .queuedForDownloading || accountObject.downloadState == .downloading || (!fetchDataAgain && accountObject.downloadState == .downloaded) { return false } accountObject.downloadState = .queuedForDownloading let downloader = SetUpAccountOperation(accountObject: accountObject) downloader.completionBlock = { if downloader.isCancelled { DispatchQueue.main.async(execute: { success() }) return } DispatchQueue.main.async(execute: { success() }) } self.downloadQueue.addOperation(downloader) return true } } enum TLDownloadState:Int { case notDownloading=0, queuedForDownloading, downloading, downloaded, failed } class SetUpImportedAddressOperation: Operation { let importedAddress: TLImportedAddress let fetchDataAgain: Bool init(importedAddress: TLImportedAddress, fetchDataAgain:Bool) { self.importedAddress = importedAddress self.fetchDataAgain = fetchDataAgain } override func main () { if self.isCancelled { return } self.importedAddress.downloadState = .downloading self.importedAddress.getSingleAddressDataO(self.fetchDataAgain) } } class SetUpImportedAddressesOperation: Operation { let importedAddresses: TLImportedAddresses let fetchDataAgain: Bool init(importedAddresses: TLImportedAddresses, fetchDataAgain:Bool) { self.importedAddresses = importedAddresses self.fetchDataAgain = fetchDataAgain } override func main () { if self.isCancelled { return } self.importedAddresses.downloadState = .downloading self.importedAddresses.checkToGetAndSetAddressesDataO(self.fetchDataAgain) } } class SetUpAccountOperation: Operation { let accountObject: TLAccountObject init(accountObject: TLAccountObject) { self.accountObject = accountObject } override func main () { if self.isCancelled { return } self.accountObject.downloadState = .downloading self.accountObject.getAccountDataO() } } ================================================ FILE: ArcBit/model/TLPreferences.swift ================================================ // // TLPreferences.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation class TLPreferences { struct CLASS_STATIC { static let RESET_CLOUD_BACKUP_WALLET_FILE_NAME = false //keys must match keys defined in Settings.bundle/Root.plist static let INAPPSETTINGS_KIT_RECEIVING_ADDRESS = "address" static let INAPPSETTINGS_KIT_NAME = "name" static let INAPPSETTINGS_KIT_TRANSACTION_FEE = "transactionfee" static let INAPPSETTINGS_KIT_BLOCKEXPLORER_URL = "blockexplorerurl" static let INAPPSETTINGS_KIT_BLOCKEXPLORER_API = "blockexplorerapi" static let INAPPSETTINGS_KIT_STEALTH_EXPLORER_URL = "stealthexplorerurl" static let INAPPSETTINGS_KIT_STEALTH_WEB_SOCKET_URL = "stealthwebsocketurl" static let INAPPSETTINGS_KIT_STEALTH_SERVER_PORT = "stealthwebserverport" static let INAPPSETTINGS_KIT_STEALTH_WEB_SOCKET_PORT = "stealthwebsocketport" static let INAPPSETTINGS_KIT_RECEIVING_CURRENCY = "currency" static let INAPPSETTINGS_KIT_DISPLAY_LOCAL_CURRENCY = "displaylocalcurrency" static let INAPPSETTINGS_KIT_ENABLE_DYNAMIC_FEE = "enabledynamicfee" static let INAPPSETTINGS_KIT_DYNAMIC_FEE_OPTION = "dynamicfeeoption" static let INAPPSETTINGS_KIT_ENABLE_COLD_WALLET = "enablecoldwallet" static let PREFERENCE_INSTALL_DATE = "pref-install-date" static let PREFERENCE_APP_VERSION = "pref-app-version" static let PREFERENCE_PUSH_NOTIFICTION = "pref-push-notification" static let PREFERENCE_FIAT_DISPLAY = "pref-fiat-display" static let PREFERENCE_BITCOIN_DISPLAY = "pref-bitcoin-display" static let PREFERENCE_BLOCKEXPLORER_API = "pref-blockexplorer-api" static let PREFERENCE_BLOCKEXPLORER_API_URL_DICT = "pref-blockexplorer-api-url" static let PREFERENCE_BTC_SYMBOL_TOGGLED = "pref-btc-symbol-toggled" static let PREFERENCE_ENABLE_BACKUP_WITH_ICLOUD = "pref-enable-backup-with-icloud" static let INAPPSETTINGS_KIT_ENABLE_BACKUP_WITH_ICLOUD = "enablebackupwithicloud" static let INAPPSETTINGS_KIT_ENABLE_PIN_CODE = "enablepincode" static let INAPPSETTINGS_CAN_RESTORE_DELETED_APP = "canrestoredeletedapp" static let PREFERENCE_CAN_RESTORE_DELETED_APP = "pref-can-restore-deleted-app" static let PREFERENCE_CLOUD_BACKUP_WALLET_FILE_NAME = "pref-cloud-backup-wallet-file-name" static let PREFERENCE_WALLET_PASSPHRASE = "pref-wallet-passphrase" static let PREFERENCE_ENCRYPTED_WALLET_JSON_PASSPHRASE = "pref-encrypted-wallet-json-passphrase" static let PREFERENCE_ENCRYPTED_WALLET_JSON_CHECKSUM = "pref-encrypted-wallet-json-checksum" static let PREFERENCE_LAST_SAVED_ENCRYPTED_WALLET_JSON_DATE = "pref-last-saved-encrypted-wallet-json-date" static let PREFERENCE_ENABLE_PIN_CODE = "pref-enable-pin-code" static let PREFERENCE_WALLET_COLD_WALLET = "pref-cold-wallet" static let PREFERENCE_WALLET_ADVANCE_MODE = "pref-advance-mode" static let PREFERENCE_DISPLAY_LOCAL_CURRENCY = "pref-display-local-currency" static let PREFERENCE_FEE_AMOUNT = "pref-fee-amount" static let PREFERENCE_SUGGESTIONS_DICT = "pref-suggestions-dict" static let PREFERENCE_ANALYTICS_DICT = "pref-analytics-dict" static let PREFERENCE_SEND_FROM_TYPE = "pref-send-from-type" static let PREFERENCE_SEND_FROM_INDEX = "pref-send-from-index" static let PREFERENCE_HAS_SETUP_HDWALLET = "pref-has-setup-hdwallet" static let PREFERENCE_ENABLE_STEALTH_ADDRESS_DEFAULT = "pref-enable-stealth-address-default" static let PREFERENCE_ENCRYPTED_BACKUP_PASSPHRASE = "pref-encrypted-backup-passphrase" static let PREFERENCE_ENCRYPTED_BACKUP_PASSPHRASE_KEY = "pref-encrypted-backup-passphrase-key" static let PREFERENCE_ENABLED_PROMPT_SHOW_WEB_WALLET = "pref-enabled-prompt-show-web-wallet" static let PREFERENCE_ENABLED_PROMPT_SHOW_TRY_COLD_WALLET = "pref-enabled-prompt-show-try-cold-wallet" static let ENABLE_SHOW_FEE_EXPLANATION_INFO = "pref-disabled=showfee-explanation-info" static let PREFERENCE_ENABLED_PROMPT_RATE_APP = "pref-enabled-prompt-rate-app" static let PREFERENCE_RATED_ONCE = "pref-rated-once" static let HAS_RECEIVED_PAYMENT_FOR_FIRST_TIME = "has-received-payment-for-first-time" static let HAS_SHOWN_BACKUP_PASSPHRASE = "has-shown-backup-passphrase" } class func setHasSetupHDWallet(_ enabled:Bool) -> () { UserDefaults.standard.set(enabled ,forKey:CLASS_STATIC.PREFERENCE_HAS_SETUP_HDWALLET) UserDefaults.standard.synchronize() } class func hasSetupHDWallet() -> (Bool) { return UserDefaults.standard.bool(forKey: CLASS_STATIC.PREFERENCE_HAS_SETUP_HDWALLET) } class func setInstallDate() -> () { if(UserDefaults.standard.object(forKey: CLASS_STATIC.PREFERENCE_INSTALL_DATE) == nil) { UserDefaults.standard.set(Date() ,forKey:CLASS_STATIC.PREFERENCE_INSTALL_DATE) UserDefaults.standard.synchronize() } } class func getInstallDate() -> (Date?) { let joinedDate = UserDefaults.standard.object(forKey: CLASS_STATIC.PREFERENCE_INSTALL_DATE) as! Date? if(joinedDate == nil) { return nil } return joinedDate } class func getAppVersion() -> String { let ver = UserDefaults.standard.string(forKey: CLASS_STATIC.PREFERENCE_APP_VERSION) return ver != nil ? ver! : "0"; } class func setAppVersion(_ version: String) -> () { UserDefaults.standard.set(version, forKey:CLASS_STATIC.PREFERENCE_APP_VERSION) UserDefaults.standard.synchronize() } class func getCurrencyIdx() -> (String?) { return UserDefaults.standard.string(forKey: CLASS_STATIC.PREFERENCE_FIAT_DISPLAY) } class func setCurrency(_ currencyIdx:String) -> () { UserDefaults.standard.set(currencyIdx ,forKey:CLASS_STATIC.PREFERENCE_FIAT_DISPLAY) UserDefaults.standard.synchronize() NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_PREFERENCES_FIAT_DISPLAY_CHANGED()), object:nil, userInfo:nil) } class func getBitcoinDenomination() -> (TLBitcoinDenomination) { let value = UserDefaults.standard.string(forKey: CLASS_STATIC.PREFERENCE_BITCOIN_DISPLAY) as NSString? if(value == nil) { return TLBitcoinDenomination(rawValue: 0)! } return TLBitcoinDenomination(rawValue: value!.integerValue)! } class func setBitcoinDisplay(_ bitcoinDisplayIdx:String) -> (){ UserDefaults.standard.set(bitcoinDisplayIdx ,forKey:CLASS_STATIC.PREFERENCE_BITCOIN_DISPLAY) UserDefaults.standard.synchronize() NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_PREFERENCES_BITCOIN_DISPLAY_CHANGED()) ,object:nil, userInfo:nil) } class func getSendFromType() -> (TLSendFromType) { return TLSendFromType(rawValue:UserDefaults.standard.integer(forKey: CLASS_STATIC.PREFERENCE_SEND_FROM_TYPE))! } class func setSendFromType(_ sendFromType:TLSendFromType) -> () { UserDefaults.standard.set(sendFromType.rawValue ,forKey:CLASS_STATIC.PREFERENCE_SEND_FROM_TYPE) UserDefaults.standard.synchronize() } class func getSendFromIndex() -> (UInt) { return UInt(UserDefaults.standard.integer(forKey: CLASS_STATIC.PREFERENCE_SEND_FROM_INDEX)) } class func setSendFromIndex(_ sendFromIndex:UInt) -> () { UserDefaults.standard.set(Int(sendFromIndex) ,forKey:CLASS_STATIC.PREFERENCE_SEND_FROM_INDEX) UserDefaults.standard.synchronize() } class func getBlockExplorerAPI() -> (TLBlockExplorer) { let value = UserDefaults.standard.string(forKey: CLASS_STATIC.PREFERENCE_BLOCKEXPLORER_API) as NSString? if(value == nil) { return TLBlockExplorer(rawValue: 0)! } return TLBlockExplorer(rawValue: value!.integerValue)! } class func setBlockExplorerAPI(_ blockexplorerIdx:String) -> () { UserDefaults.standard.set(blockexplorerIdx ,forKey:CLASS_STATIC.PREFERENCE_BLOCKEXPLORER_API) UserDefaults.standard.synchronize() } class func getBlockExplorerURL(_ blockExplorer:TLBlockExplorer) -> (String?) { let blockExplorer2blockExplorerURLDict = UserDefaults.standard.object(forKey: CLASS_STATIC.PREFERENCE_BLOCKEXPLORER_API_URL_DICT) as! NSDictionary let key = String(format:"%ld", blockExplorer.rawValue) return blockExplorer2blockExplorerURLDict.value(forKey: key) as? String } class func setBlockExplorerURL(_ blockExplorer:TLBlockExplorer, value:(String)) -> (){ assert(blockExplorer == TLBlockExplorer.insight, "can only change insight URL currently") let blockExplorer2blockExplorerURLDict = (UserDefaults.standard.object(forKey: CLASS_STATIC.PREFERENCE_BLOCKEXPLORER_API_URL_DICT) as! NSDictionary).mutableCopy() as! NSDictionary blockExplorer2blockExplorerURLDict.setValue(value ,forKey:String(format:"%ld", blockExplorer.rawValue)) UserDefaults.standard.set(blockExplorer2blockExplorerURLDict ,forKey:CLASS_STATIC.PREFERENCE_BLOCKEXPLORER_API_URL_DICT) UserDefaults.standard.synchronize() } class func resetBlockExplorerAPIURL() -> (){ let blockExplorer2blockExplorerURLDict = NSMutableDictionary(capacity: 3) blockExplorer2blockExplorerURLDict.setObject("https://blockchain.info/" ,forKey:String(format:"%ld", TLBlockExplorer.blockchain.rawValue) as NSCopying) blockExplorer2blockExplorerURLDict.setObject("https://insight.bitpay.com/" ,forKey:String(format:"%ld", TLBlockExplorer.insight.rawValue) as NSCopying) blockExplorer2blockExplorerURLDict.setObject("https://bitcoin.toshi.io/" ,forKey:String(format:"%ld", TLBlockExplorer.toshi.rawValue) as NSCopying) UserDefaults.standard.set(blockExplorer2blockExplorerURLDict ,forKey:CLASS_STATIC.PREFERENCE_BLOCKEXPLORER_API_URL_DICT) UserDefaults.standard.synchronize() } class func getStealthExplorerURL() -> (String?) { return UserDefaults.standard.string(forKey: CLASS_STATIC.INAPPSETTINGS_KIT_STEALTH_EXPLORER_URL) } class func setStealthExplorerURL(_ value:(String)) -> () { UserDefaults.standard.set(value ,forKey:CLASS_STATIC.INAPPSETTINGS_KIT_STEALTH_EXPLORER_URL) UserDefaults.standard.synchronize() } class func getStealthServerPort() -> (Int?) { return UserDefaults.standard.integer(forKey: CLASS_STATIC.INAPPSETTINGS_KIT_STEALTH_SERVER_PORT) } class func setStealthServerPort(_ value:(Int)) -> () { UserDefaults.standard.set(value ,forKey:CLASS_STATIC.INAPPSETTINGS_KIT_STEALTH_SERVER_PORT) UserDefaults.standard.synchronize() } class func getStealthWebSocketPort() -> (Int?) { return UserDefaults.standard.integer(forKey: CLASS_STATIC.INAPPSETTINGS_KIT_STEALTH_WEB_SOCKET_PORT) } class func setStealthWebSocketPort(_ value:(Int)) -> () { UserDefaults.standard.set(value ,forKey:CLASS_STATIC.INAPPSETTINGS_KIT_STEALTH_WEB_SOCKET_PORT) UserDefaults.standard.synchronize() } class func resetStealthExplorerAPIURL() -> () { self.setStealthExplorerURL(TLStealthServerConfig.instance().getStealthServerUrl()) } class func resetStealthServerPort() -> () { self.setStealthServerPort(TLStealthServerConfig.instance().getStealthServerPort()) } class func resetStealthWebSocketPort() -> () { self.setStealthWebSocketPort(TLStealthServerConfig.instance().getWebSocketServerPort()) } class func getInAppSettingsKitBlockExplorerAPI() -> (String?){ return UserDefaults.standard.string(forKey: CLASS_STATIC.INAPPSETTINGS_KIT_BLOCKEXPLORER_API) } class func setInAppSettingsKitBlockExplorerAPI(_ value:String) -> () { UserDefaults.standard.set(value ,forKey:CLASS_STATIC.INAPPSETTINGS_KIT_BLOCKEXPLORER_API) UserDefaults.standard.synchronize() } class func getInAppSettingsKitBlockExplorerURL() -> (String?) { return UserDefaults.standard.string(forKey: CLASS_STATIC.INAPPSETTINGS_KIT_BLOCKEXPLORER_URL) } class func setInAppSettingsKitBlockExplorerURL(_ value: String) -> (){ UserDefaults.standard.set(value ,forKey:CLASS_STATIC.INAPPSETTINGS_KIT_BLOCKEXPLORER_URL) UserDefaults.standard.synchronize() } class func getInAppSettingsKitCurrencyIdx() -> (String?) { return UserDefaults.standard.string(forKey: CLASS_STATIC.INAPPSETTINGS_KIT_RECEIVING_CURRENCY) } class func setInAppSettingsKitCurrency(_ currencyIdx:String) -> () { UserDefaults.standard.set(currencyIdx, forKey:CLASS_STATIC.INAPPSETTINGS_KIT_RECEIVING_CURRENCY) UserDefaults.standard.synchronize() } class func isInAppSettingsKitDisplayLocalCurrency() -> (Bool) { return UserDefaults.standard.bool(forKey: CLASS_STATIC.INAPPSETTINGS_KIT_DISPLAY_LOCAL_CURRENCY) } class func setInAppSettingsKitDisplayLocalCurrency(_ enabled:Bool) -> () { UserDefaults.standard.set(enabled ,forKey:CLASS_STATIC.INAPPSETTINGS_KIT_DISPLAY_LOCAL_CURRENCY) UserDefaults.standard.synchronize() } class func getInAppSettingsKitName() -> (String?) { return UserDefaults.standard.string(forKey: CLASS_STATIC.INAPPSETTINGS_KIT_NAME) } class func setInAppSettingsKitName(_ value:String) -> () { UserDefaults.standard.set(value ,forKey:CLASS_STATIC.INAPPSETTINGS_KIT_NAME) UserDefaults.standard.synchronize() } class func getInAppSettingsKitEnablePinCode() -> (Bool) { return UserDefaults.standard.bool(forKey: CLASS_STATIC.INAPPSETTINGS_KIT_ENABLE_PIN_CODE) } class func setInAppSettingsKitEnablePinCode(_ value:Bool) -> () { UserDefaults.standard.set(value ,forKey:CLASS_STATIC.INAPPSETTINGS_KIT_ENABLE_PIN_CODE) UserDefaults.standard.synchronize() } class func getInAppSettingsKitTransactionFee() -> (String?) { return UserDefaults.standard.string(forKey: CLASS_STATIC.INAPPSETTINGS_KIT_TRANSACTION_FEE) } class func setInAppSettingsKitTransactionFee(_ value:String) -> () { UserDefaults.standard.set(value ,forKey:CLASS_STATIC.INAPPSETTINGS_KIT_TRANSACTION_FEE) UserDefaults.standard.synchronize() } class func getInAppSettingsKitEnableBackupWithiCloud() -> (Bool) { return UserDefaults.standard.bool(forKey: CLASS_STATIC.INAPPSETTINGS_KIT_ENABLE_BACKUP_WITH_ICLOUD) } class func setInAppSettingsKitEnableBackupWithiCloud(_ value:Bool) -> () { UserDefaults.standard.set(value ,forKey:CLASS_STATIC.INAPPSETTINGS_KIT_ENABLE_BACKUP_WITH_ICLOUD) UserDefaults.standard.synchronize() } class func getEnableBackupWithiCloud() -> (Bool) { return UserDefaults.standard.bool(forKey: CLASS_STATIC.PREFERENCE_ENABLE_BACKUP_WITH_ICLOUD) } class func setEnableBackupWithiCloud(_ value:Bool) -> () { UserDefaults.standard.set(value ,forKey:CLASS_STATIC.PREFERENCE_ENABLE_BACKUP_WITH_ICLOUD) UserDefaults.standard.synchronize() } class func deleteAllKeychainItems() -> () { JNKeychain.deleteValue(forKey: CLASS_STATIC.PREFERENCE_CLOUD_BACKUP_WALLET_FILE_NAME) JNKeychain.deleteValue(forKey: CLASS_STATIC.PREFERENCE_WALLET_PASSPHRASE) JNKeychain.deleteValue(forKey: CLASS_STATIC.PREFERENCE_ENCRYPTED_WALLET_JSON_PASSPHRASE) JNKeychain.deleteValue(forKey: CLASS_STATIC.PREFERENCE_CAN_RESTORE_DELETED_APP) } class func getCloudBackupWalletFileName() -> (String?) { //CLASS_STATIC.RESET_CLOUD_BACKUP_WALLET_FILE_NAME = true // debug only, to reset cloud backup file name install fresh app with this set once, and comment out again if (CLASS_STATIC.RESET_CLOUD_BACKUP_WALLET_FILE_NAME) { return nil } return JNKeychain.loadValue(forKey: CLASS_STATIC.PREFERENCE_CLOUD_BACKUP_WALLET_FILE_NAME) as! String? } class func setCloudBackupWalletFileName() -> (){ if (JNKeychain.loadValue(forKey: CLASS_STATIC.PREFERENCE_CLOUD_BACKUP_WALLET_FILE_NAME) != nil && !CLASS_STATIC.RESET_CLOUD_BACKUP_WALLET_FILE_NAME) { NSException(name:NSExceptionName(rawValue: "Cannot set cloud backup wallet file name"), reason:"Cloud backup wallet file name is already set", userInfo:nil).raise() } let cloudBackupWalletFileName = String(format:"%@.%@.%@", TLWalletUtils.STATIC_MEMBERS.WALLET_JSON_CLOUD_BACKUP_FILE_NAME, TLStealthAddress.generateEphemeralPrivkey(), TLWalletUtils.STATIC_MEMBERS.WALLET_JSON_CLOUD_BACKUP_FILE_EXTENSION) DLog("cloudBackupWalletFileName \(cloudBackupWalletFileName)") JNKeychain.saveValue(cloudBackupWalletFileName ,forKey:CLASS_STATIC.PREFERENCE_CLOUD_BACKUP_WALLET_FILE_NAME) } class func deleteWalletPassphrase() -> (){ JNKeychain.deleteValue(forKey: CLASS_STATIC.PREFERENCE_WALLET_PASSPHRASE) UserDefaults.standard.removeObject(forKey: CLASS_STATIC.PREFERENCE_ENCRYPTED_BACKUP_PASSPHRASE) UserDefaults.standard.synchronize() } class func getWalletPassphrase(_ useKeychain:Bool) -> (String?){ if useKeychain { return JNKeychain.loadValue(forKey: CLASS_STATIC.PREFERENCE_WALLET_PASSPHRASE) as! String? } else { return UserDefaults.standard.string(forKey: CLASS_STATIC.PREFERENCE_ENCRYPTED_BACKUP_PASSPHRASE) } } class func setWalletPassphrase(_ value:String, useKeychain:Bool) -> (){ if useKeychain { JNKeychain.saveValue(value ,forKey:CLASS_STATIC.PREFERENCE_WALLET_PASSPHRASE) UserDefaults.standard.removeObject(forKey: CLASS_STATIC.PREFERENCE_ENCRYPTED_BACKUP_PASSPHRASE) UserDefaults.standard.synchronize() } else { UserDefaults.standard.set(value ,forKey:CLASS_STATIC.PREFERENCE_ENCRYPTED_BACKUP_PASSPHRASE) UserDefaults.standard.synchronize() JNKeychain.deleteValue(forKey: CLASS_STATIC.PREFERENCE_WALLET_PASSPHRASE) } } class func deleteEncryptedWalletJSONPassphrase() -> (){ JNKeychain.deleteValue(forKey: CLASS_STATIC.PREFERENCE_ENCRYPTED_WALLET_JSON_PASSPHRASE) UserDefaults.standard.removeObject(forKey: CLASS_STATIC.PREFERENCE_ENCRYPTED_BACKUP_PASSPHRASE) UserDefaults.standard.synchronize() } class func getEncryptedWalletPassphraseKey() -> (String?) { return UserDefaults.standard.string(forKey: CLASS_STATIC.PREFERENCE_ENCRYPTED_BACKUP_PASSPHRASE_KEY) } class func setEncryptedWalletPassphraseKey(_ value:String) -> (){ UserDefaults.standard.set(value ,forKey:CLASS_STATIC.PREFERENCE_ENCRYPTED_BACKUP_PASSPHRASE_KEY) UserDefaults.standard.synchronize() } class func clearEncryptedWalletPassphraseKey() -> () { UserDefaults.standard.removeObject(forKey: CLASS_STATIC.PREFERENCE_ENCRYPTED_BACKUP_PASSPHRASE_KEY) UserDefaults.standard.synchronize() } class func setCanRestoreDeletedApp(_ enabled:Bool) -> (){ if (enabled) { JNKeychain.saveValue("true" ,forKey:CLASS_STATIC.PREFERENCE_CAN_RESTORE_DELETED_APP) } else { JNKeychain.saveValue("false" ,forKey:CLASS_STATIC.PREFERENCE_CAN_RESTORE_DELETED_APP) } } class func canRestoreDeletedApp() -> (Bool) { let enabled = JNKeychain.loadValue(forKey: CLASS_STATIC.PREFERENCE_CAN_RESTORE_DELETED_APP) as! NSString? return enabled == "true" ? true : false } class func getInAppSettingsKitcanRestoreDeletedApp() -> (Bool) { return UserDefaults.standard.bool(forKey: CLASS_STATIC.INAPPSETTINGS_CAN_RESTORE_DELETED_APP) } class func setInAppSettingsCanRestoreDeletedApp(_ value: Bool) -> () { UserDefaults.standard.set(value ,forKey:CLASS_STATIC.INAPPSETTINGS_CAN_RESTORE_DELETED_APP) UserDefaults.standard.synchronize() } class func getEncryptedWalletJSONPassphrase(_ useKeychain:Bool) -> (String?){ if useKeychain { return JNKeychain.loadValue(forKey: CLASS_STATIC.PREFERENCE_ENCRYPTED_WALLET_JSON_PASSPHRASE) as! String? } else { return UserDefaults.standard.string(forKey: CLASS_STATIC.PREFERENCE_ENCRYPTED_WALLET_JSON_PASSPHRASE) as String? } } class func setEncryptedWalletJSONPassphrase(_ value: String, useKeychain:Bool) -> () { if useKeychain { JNKeychain.saveValue(value ,forKey:CLASS_STATIC.PREFERENCE_ENCRYPTED_WALLET_JSON_PASSPHRASE) UserDefaults.standard.removeObject(forKey: CLASS_STATIC.PREFERENCE_ENCRYPTED_WALLET_JSON_PASSPHRASE) UserDefaults.standard.synchronize() } else { UserDefaults.standard.set(value ,forKey:CLASS_STATIC.PREFERENCE_ENCRYPTED_WALLET_JSON_PASSPHRASE) UserDefaults.standard.synchronize() JNKeychain.deleteValue(forKey: CLASS_STATIC.PREFERENCE_ENCRYPTED_WALLET_JSON_PASSPHRASE) } } class func getEncryptedWalletJSONChecksum() -> (String?) { return UserDefaults.standard.string(forKey: CLASS_STATIC.PREFERENCE_ENCRYPTED_WALLET_JSON_CHECKSUM) as String? } class func setEncryptedWalletJSONChecksum(_ value:String) -> (){ UserDefaults.standard.set(value ,forKey:CLASS_STATIC.PREFERENCE_ENCRYPTED_WALLET_JSON_CHECKSUM) UserDefaults.standard.synchronize() } class func getLastSavedEncryptedWalletJSONDate() -> (Date) { return UserDefaults.standard.object(forKey: CLASS_STATIC.PREFERENCE_LAST_SAVED_ENCRYPTED_WALLET_JSON_DATE) as! Date } class func setLastSavedEncryptedWalletJSONDate(_ value:Date) -> () { UserDefaults.standard.set(value ,forKey:CLASS_STATIC.PREFERENCE_LAST_SAVED_ENCRYPTED_WALLET_JSON_DATE) UserDefaults.standard.synchronize() } class func isDisplayLocalCurrency() ->(Bool){ return UserDefaults.standard.bool(forKey: CLASS_STATIC.PREFERENCE_DISPLAY_LOCAL_CURRENCY) } class func setDisplayLocalCurrency(_ enabled:Bool) -> () { UserDefaults.standard.set(enabled ,forKey:CLASS_STATIC.PREFERENCE_DISPLAY_LOCAL_CURRENCY) UserDefaults.standard.synchronize() NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_DISPLAY_LOCAL_CURRENCY_TOGGLED()), object:nil) } class func enabledColdWallet() -> (Bool) { return UserDefaults.standard.bool(forKey: CLASS_STATIC.PREFERENCE_WALLET_COLD_WALLET) } class func setEnableColdWallet(_ enabled:Bool) -> () { UserDefaults.standard.set(enabled ,forKey:CLASS_STATIC.PREFERENCE_WALLET_COLD_WALLET) UserDefaults.standard.synchronize() } class func enabledInAppSettingsKitColdWallet() -> (Bool) { return UserDefaults.standard.bool(forKey: CLASS_STATIC.INAPPSETTINGS_KIT_ENABLE_COLD_WALLET) } class func setEnableInAppSettingsKitColdWallet(_ enabled:Bool) -> () { UserDefaults.standard.set(enabled ,forKey:CLASS_STATIC.INAPPSETTINGS_KIT_ENABLE_COLD_WALLET) UserDefaults.standard.synchronize() } class func enabledAdvancedMode() -> (Bool) { return UserDefaults.standard.bool(forKey: CLASS_STATIC.PREFERENCE_WALLET_ADVANCE_MODE) } class func setAdvancedMode(_ enabled:Bool) -> () { UserDefaults.standard.set(enabled ,forKey:CLASS_STATIC.PREFERENCE_WALLET_ADVANCE_MODE) UserDefaults.standard.synchronize() NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_ADVANCE_MODE_TOGGLED()), object:enabled) if enabled { NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_ENABLE_ADVANCE_MODE()), object:enabled) } } class func isEnablePINCode() -> (Bool) { return UserDefaults.standard.bool(forKey: CLASS_STATIC.PREFERENCE_ENABLE_PIN_CODE) } class func setEnablePINCode(_ enabled:Bool) -> () { UserDefaults.standard.set(enabled ,forKey:CLASS_STATIC.PREFERENCE_ENABLE_PIN_CODE) UserDefaults.standard.synchronize() } class func getSuggestionsDict() -> (NSDictionary?){ return UserDefaults.standard.object(forKey: CLASS_STATIC.PREFERENCE_SUGGESTIONS_DICT) as! NSDictionary? } class func setSuggestionsDict(_ value:NSDictionary) -> () { UserDefaults.standard.set(value ,forKey:CLASS_STATIC.PREFERENCE_SUGGESTIONS_DICT) UserDefaults.standard.synchronize() } class func getAnalyticsDict() -> (NSDictionary?) { return UserDefaults.standard.object(forKey: CLASS_STATIC.PREFERENCE_ANALYTICS_DICT) as! NSDictionary? } class func setAnalyticsDict(_ value:NSDictionary) -> (){ UserDefaults.standard.set(value ,forKey:CLASS_STATIC.PREFERENCE_ANALYTICS_DICT) UserDefaults.standard.synchronize() } class func enabledStealthAddressDefault() -> (Bool){ return UserDefaults.standard.bool(forKey: CLASS_STATIC.PREFERENCE_ENABLE_STEALTH_ADDRESS_DEFAULT) } class func setEnabledStealthAddressDefault(_ enabled:Bool) -> () { UserDefaults.standard.set(enabled ,forKey:CLASS_STATIC.PREFERENCE_ENABLE_STEALTH_ADDRESS_DEFAULT) UserDefaults.standard.synchronize() } class func disabledPromptShowWebWallet() -> (Bool) { return UserDefaults.standard.bool(forKey: CLASS_STATIC.PREFERENCE_ENABLED_PROMPT_SHOW_WEB_WALLET) } class func setDisabledPromptShowWebWallet(_ disabled:Bool) -> () { UserDefaults.standard.set(disabled ,forKey:CLASS_STATIC.PREFERENCE_ENABLED_PROMPT_SHOW_WEB_WALLET) UserDefaults.standard.synchronize() } class func disabledPromptShowTryColdWallet() -> (Bool) { return UserDefaults.standard.bool(forKey: CLASS_STATIC.PREFERENCE_ENABLED_PROMPT_SHOW_TRY_COLD_WALLET) } class func setDisabledPromptShowTryColdWallet(_ disabled:Bool) -> () { UserDefaults.standard.set(disabled ,forKey:CLASS_STATIC.PREFERENCE_ENABLED_PROMPT_SHOW_TRY_COLD_WALLET) UserDefaults.standard.synchronize() } class func disabledShowFeeExplanationInfo() -> (Bool){ return UserDefaults.standard.bool(forKey: CLASS_STATIC.ENABLE_SHOW_FEE_EXPLANATION_INFO) } class func setDisableShowFeeExplanationInfo(_ disabled:Bool) -> (){ UserDefaults.standard.set(disabled ,forKey:CLASS_STATIC.ENABLE_SHOW_FEE_EXPLANATION_INFO) UserDefaults.standard.synchronize() } class func disabledPromptRateApp() -> (Bool) { return UserDefaults.standard.bool(forKey: CLASS_STATIC.PREFERENCE_ENABLED_PROMPT_RATE_APP) } class func setDisabledPromptRateApp(_ disabled:Bool) -> () { UserDefaults.standard.set(disabled ,forKey:CLASS_STATIC.PREFERENCE_ENABLED_PROMPT_RATE_APP) UserDefaults.standard.synchronize() } class func hasRatedOnce() -> (Bool) { return UserDefaults.standard.bool(forKey: CLASS_STATIC.PREFERENCE_RATED_ONCE) } class func setHasRatedOnce() -> () { UserDefaults.standard.set(true ,forKey:CLASS_STATIC.PREFERENCE_RATED_ONCE) UserDefaults.standard.synchronize() } class func hasReceivePaymentForFirstTime() -> (Bool) { return UserDefaults.standard.bool(forKey: CLASS_STATIC.HAS_RECEIVED_PAYMENT_FOR_FIRST_TIME) } class func setHasReceivePaymentForFirstTime(_ received:Bool) -> () { UserDefaults.standard.set(received ,forKey:CLASS_STATIC.HAS_RECEIVED_PAYMENT_FOR_FIRST_TIME) UserDefaults.standard.synchronize() } class func hasShownBackupPassphrase() -> (Bool) { return UserDefaults.standard.bool(forKey: CLASS_STATIC.HAS_SHOWN_BACKUP_PASSPHRASE) } class func setHasShownBackupPassphrase(_ hasShown:Bool) -> () { UserDefaults.standard.set(hasShown ,forKey:CLASS_STATIC.HAS_SHOWN_BACKUP_PASSPHRASE) UserDefaults.standard.synchronize() } class func enabledInAppSettingsKitDynamicFee() -> (Bool){ return UserDefaults.standard.bool(forKey: CLASS_STATIC.INAPPSETTINGS_KIT_ENABLE_DYNAMIC_FEE) } class func setInAppSettingsKitEnabledDynamicFee(_ enabled:Bool) -> () { UserDefaults.standard.set(enabled ,forKey:CLASS_STATIC.INAPPSETTINGS_KIT_ENABLE_DYNAMIC_FEE) UserDefaults.standard.synchronize() } //WARNING: TLDynamicFeeSetting, therefore InAppSettingsKit must match the keys for 21's dynamic fee api return object keys called in TLTxFeeAPI class func getInAppSettingsKitDynamicFeeSetting() -> TLDynamicFeeSetting { if let fee = UserDefaults.standard.string(forKey: CLASS_STATIC.INAPPSETTINGS_KIT_DYNAMIC_FEE_OPTION) { return TLDynamicFeeSetting(rawValue:fee)! } else { return TLDynamicFeeSetting.FastestFee } } class func setInAppSettingsKitDynamicFeeSettingIdx(_ dynamicFeeSetting:TLDynamicFeeSetting) -> () { UserDefaults.standard.set(dynamicFeeSetting.rawValue ,forKey:CLASS_STATIC.INAPPSETTINGS_KIT_DYNAMIC_FEE_OPTION) UserDefaults.standard.synchronize() } } ================================================ FILE: ArcBit/model/TLSelectedObject.swift ================================================ // // TLSelectedObject.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation @objc class TLSelectedObject:NSObject { fileprivate var accountObject:TLAccountObject? fileprivate var importedAddress:TLImportedAddress? func setSelectedAccount(_ accountObject: TLAccountObject) -> () { self.accountObject = accountObject self.importedAddress = nil } func setSelectedAddress(_ importedAddress: TLImportedAddress) -> () { self.importedAddress = importedAddress self.accountObject = nil } func getDownloadState() -> (TLDownloadState) { if (self.accountObject != nil) { return self.accountObject!.downloadState } else if (self.importedAddress != nil) { return self.importedAddress!.downloadState } return .failed } func getBalanceForSelectedObject() -> (TLCoin?) { if (self.accountObject != nil) { return self.accountObject!.getBalance() } else if (self.importedAddress != nil) { return self.importedAddress!.getBalance() } return nil } func getLabelForSelectedObject() -> (String?) { if (self.accountObject != nil) { return self.accountObject!.getAccountName() } else if (self.importedAddress != nil) { return self.importedAddress!.getLabel() } return nil } func getReceivingAddressesCount() -> (UInt) { if (self.accountObject != nil) { return UInt(self.accountObject!.getReceivingAddressesCount()) } else if (self.importedAddress != nil) { return 1 } return 0 } func getReceivingAddressForSelectedObject(_ idx:Int) -> (String?) { if (self.accountObject != nil) { return self.accountObject!.getReceivingAddress(idx) } else if (self.importedAddress != nil) { return self.importedAddress!.getAddress() } return nil } func getStealthAddress() -> String? { if self.accountObject != nil && self.accountObject!.getAccountType() != .importedWatch { if self.accountObject!.stealthWallet != nil { return self.accountObject!.stealthWallet!.getStealthAddress() } } return nil } func hasFetchedCurrentFromData() -> (Bool) { if (self.accountObject != nil) { return self.accountObject!.hasFetchedAccountData() } else if (self.importedAddress != nil) { return self.importedAddress!.hasFetchedAccountData() } return true } func isAddressPartOfAccount(_ address: String) -> (Bool) { if (self.accountObject != nil) { return self.accountObject!.isAddressPartOfAccount(address) } else if (self.importedAddress != nil) { return self.importedAddress!.getAddress() == address } return true } func getTxObjectCount() -> (UInt) { if (self.accountObject != nil) { return UInt(self.accountObject!.getTxObjectCount()) } else if (self.importedAddress != nil) { return UInt(self.importedAddress!.getTxObjectCount()) } return 1 } func getTxObject(_ txIdx:Int) -> (TLTxObject?) { if (self.accountObject != nil) { return self.accountObject!.getTxObject(txIdx) } else if (self.importedAddress != nil) { return self.importedAddress!.getTxObject(txIdx) } return nil } func getAccountAmountChangeForTx(_ txHash: String) -> (TLCoin?) { if (self.accountObject != nil) { return self.accountObject!.getAccountAmountChangeForTx(txHash)! } else if (self.importedAddress != nil) { return self.importedAddress!.getAccountAmountChangeForTx(txHash) } return nil } func getAccountAmountChangeTypeForTx(_ txHash: String) -> (TLAccountTxType) { if (self.accountObject != nil) { return self.accountObject!.getAccountAmountChangeTypeForTx(txHash) } else if (self.importedAddress != nil) { return self.importedAddress!.getAccountAmountChangeTypeForTx(txHash) } return TLAccountTxType(rawValue: 0)! } func getSelectedObjectType() -> (TLSelectObjectType) { if (self.accountObject != nil) { return TLSelectObjectType.account } else { return TLSelectObjectType.address } } func getSelectedObject() -> AnyObject? { if (self.accountObject != nil) { return self.accountObject } else { return self.importedAddress } } func getAccountType() -> (TLAccountType) { if (self.accountObject != nil) { return self.accountObject!.getAccountType() } else { return TLAccountType.unknown } } } ================================================ FILE: ArcBit/model/TLSendFormData.swift ================================================ // // TLSendFormData.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation enum TLSelectObjectType:Int { case unknown = 0 case account = 1 case address = 2 } class TLSendFormData { struct STATIC_MEMBERS{ static var _instance:TLSendFormData? = nil } var useAllFunds = false var beforeSendBalance: TLCoin? = nil var fromLabel:String? fileprivate var address:String? fileprivate var amount:String? fileprivate var fiatAmount:String? var toAmount:TLCoin? var feeAmount:TLCoin? class func instance() -> (TLSendFormData) { if(STATIC_MEMBERS._instance == nil) { STATIC_MEMBERS._instance = TLSendFormData() } return STATIC_MEMBERS._instance! } init() { self.address = nil self.amount = nil self.fiatAmount = nil } func setAddress(_ address:String?) -> () { self.address = address } func getAddress() -> (String?) { return self.address } func setAmount(_ amount:String?) -> () { self.amount = amount } func getAmount() -> (String?) { return self.amount } func setFiatAmount(_ fiatAmount:String?) -> () { self.fiatAmount = fiatAmount } func getFiatAmount() -> (String?) { return self.fiatAmount } } ================================================ FILE: ArcBit/model/TLSpaghettiGodSend.swift ================================================ // // TLSpaghettiGodSend.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA @objc class TLSpaghettiGodSend:NSObject { let DUST_AMOUNT:UInt64 = 546 fileprivate var appWallet: TLWallet fileprivate var sendFromAccounts:NSMutableArray? fileprivate var sendFromAddresses:NSMutableArray? struct STATIC_MEMBERS { static var instance:TLSpaghettiGodSend? } init(appWallet: TLWallet) { self.appWallet = appWallet sendFromAccounts = NSMutableArray() sendFromAddresses = NSMutableArray() super.init() } fileprivate func clearFromAccountsAndAddresses() -> () { sendFromAccounts = NSMutableArray() sendFromAddresses = NSMutableArray() } func getSelectedSendObject() -> AnyObject? { if (sendFromAccounts != nil && sendFromAccounts!.count != 0) { return sendFromAccounts!.object(at: 0) as AnyObject? } else if (sendFromAddresses != nil && sendFromAddresses!.count != 0) { return sendFromAddresses!.object(at: 0) as AnyObject? } return nil } func getSelectedObjectType() -> (TLSelectObjectType) { if (sendFromAccounts != nil && sendFromAccounts!.count != 0) { return TLSelectObjectType.account } else if (sendFromAddresses != nil && sendFromAddresses!.count != 0) { return TLSelectObjectType.address } return TLSelectObjectType.unknown } func isPaymentToOwnAccount(_ address: String) -> Bool { if (sendFromAccounts != nil && sendFromAccounts!.count != 0) { let accountObject = sendFromAccounts!.object(at: 0) as! TLAccountObject if address == accountObject.stealthWallet?.getStealthAddress() { return true } if accountObject.isAddressPartOfAccount(address) { return true } return false } else if (sendFromAddresses != nil && sendFromAddresses!.count != 0) { let importedAddress = sendFromAddresses!.object(at: 0) as! TLImportedAddress if address == importedAddress.getAddress() { return true } return false } return false } func haveUpDatedUTXOs() -> Bool { if (sendFromAccounts != nil && sendFromAccounts!.count != 0) { let accountObject = sendFromAccounts!.object(at: 0) as! TLAccountObject return accountObject.haveUpDatedUTXOs } else if (sendFromAddresses != nil && sendFromAddresses!.count != 0) { let importedAddress = sendFromAddresses!.object(at: 0) as! TLImportedAddress return importedAddress.haveUpDatedUTXOs } return false } fileprivate func getLabelForSelectedSendObject() -> String? { if (sendFromAccounts != nil && sendFromAccounts!.count != 0) { let accountObject = sendFromAccounts!.object(at: 0) as! TLAccountObject return accountObject.getAccountName() } else if (sendFromAddresses != nil && sendFromAddresses!.count != 0) { let importedAddress = sendFromAddresses!.object(at: 0) as! TLImportedAddress return importedAddress.getLabel() } return nil } func getCurrentFromLabel() -> String? { if (sendFromAccounts != nil && sendFromAccounts!.count != 0) { let accountObject = sendFromAccounts!.object(at: 0) as! TLAccountObject return accountObject.getAccountName() } else if (sendFromAddresses != nil && sendFromAddresses!.count != 0) { let importedAddress = sendFromAddresses!.object(at: 0) as! TLImportedAddress return importedAddress.getLabel() } return nil } func getStealthAddress() -> String? { if (sendFromAccounts != nil && sendFromAccounts!.count != 0) { let accountObject = sendFromAccounts!.object(at: 0) as! TLAccountObject return accountObject.stealthWallet?.getStealthAddress() } return nil } func getExtendedPubKey() -> String? { if (sendFromAccounts != nil && sendFromAccounts!.count != 0) { let accountObject = sendFromAccounts!.object(at: 0) as! TLAccountObject return accountObject.getExtendedPubKey() } return nil } func setOnlyFromAccount(_ accountObject:TLAccountObject) -> () { sendFromAddresses = nil sendFromAccounts = NSMutableArray(objects:accountObject) } func setOnlyFromAddress(_ importedAddress:TLImportedAddress) -> () { sendFromAccounts = nil sendFromAddresses = NSMutableArray(objects:importedAddress) } fileprivate func addSendAccount(_ accountObject: TLAccountObject) -> () { sendFromAccounts!.add(accountObject) } fileprivate func addImportedAddress(_ importedAddress:TLImportedAddress) -> () { sendFromAddresses!.add(importedAddress) } func isColdWalletAccount() -> (Bool) { if (sendFromAccounts != nil && sendFromAccounts!.count != 0) { let accountObject = sendFromAccounts!.object(at: 0) as! TLAccountObject return accountObject.isColdWalletAccount() } return false } func needWatchOnlyAccountPrivateKey() -> (Bool) { if (sendFromAccounts != nil && sendFromAccounts!.count != 0) { let accountObject = sendFromAccounts!.object(at: 0) as! TLAccountObject return accountObject.isWatchOnly() && !accountObject.hasSetExtendedPrivateKeyInMemory() } return false } func needWatchOnlyAddressPrivateKey() -> (Bool) { if (sendFromAddresses != nil && sendFromAddresses!.count != 0) { let importedAddress = sendFromAddresses!.object(at: 0) as! TLImportedAddress return importedAddress.isWatchOnly() && !importedAddress.hasSetPrivateKeyInMemory() } return false } func needEncryptedPrivateKeyPassword() -> Bool { if (sendFromAddresses != nil && sendFromAddresses!.count != 0) { let importedAddress = sendFromAddresses!.object(at: 0) as! TLImportedAddress if (importedAddress.isWatchOnly()) { return false } else { return importedAddress.isPrivateKeyEncrypted() && !importedAddress.hasSetPrivateKeyInMemory() } } return false } func getEncryptedPrivateKey () -> (String) { assert(sendFromAddresses!.count != 0, "sendFromAddresses!.count == 0") let importedAddress = sendFromAddresses!.object(at: 0) as! TLImportedAddress assert(importedAddress.isPrivateKeyEncrypted() != false, "! importedAddress isPrivateKeyEncrypted]") return importedAddress.getEncryptedPrivateKey()! } func hasFetchedCurrentFromData() -> (Bool) { if (sendFromAccounts != nil && sendFromAccounts!.count != 0) { let accountObject = sendFromAccounts!.object(at: 0) as! TLAccountObject return accountObject.hasFetchedAccountData() } else if (sendFromAddresses != nil && sendFromAddresses!.count != 0) { let importedAddress = sendFromAddresses!.object(at: 0) as! TLImportedAddress return importedAddress.hasFetchedAccountData() } return true } func setCurrentFromBalance(_ balance: TLCoin) { if (sendFromAccounts != nil && sendFromAccounts!.count != 0) { let accountObject = sendFromAccounts!.object(at: 0) as! TLAccountObject accountObject.accountBalance = balance } else if (sendFromAddresses != nil && sendFromAddresses!.count != 0) { let importedAddress = sendFromAddresses!.object(at: 0) as! TLImportedAddress importedAddress.balance = balance } } func getCurrentFromBalance() -> (TLCoin) { if (sendFromAccounts != nil && sendFromAccounts!.count != 0) { let accountObject = sendFromAccounts!.object(at: 0) as! TLAccountObject return accountObject.getBalance() } else if (sendFromAddresses != nil && sendFromAddresses!.count != 0) { let importedAddress = sendFromAddresses!.object(at: 0) as! TLImportedAddress return importedAddress.getBalance()! } return TLCoin.zero() } func getCurrentFromUnspentOutputsSum() -> (TLCoin) { if (sendFromAccounts != nil && sendFromAccounts!.count != 0) { let accountObject = sendFromAccounts!.object(at: 0) as! TLAccountObject return accountObject.getTotalUnspentSum() } else if (sendFromAddresses != nil && sendFromAddresses!.count != 0) { let importedAddress = sendFromAddresses!.object(at: 0) as! TLImportedAddress return importedAddress.getUnspentSum()! } return TLCoin.zero() } func getAndSetUnspentOutputs(_ success:@escaping TLWalletUtils.Success, failure:@escaping TLWalletUtils.Error) -> () { if (sendFromAccounts != nil && sendFromAccounts!.count != 0) { let accountObject = sendFromAccounts!.object(at: 0) as! TLAccountObject let amount = accountObject.getBalance() if (amount.greater(TLCoin.zero())) { accountObject.getUnspentOutputs({() in success() }, failure:{() in failure() } ) } } else { var addresses: [String] = [] addresses.reserveCapacity(sendFromAddresses!.count) let importedAddress = sendFromAddresses!.object(at: 0) as! TLImportedAddress let amount = importedAddress.getBalance() if (amount!.greater(TLCoin.zero())) { addresses.append(importedAddress.getAddress()) } if (addresses.count > 0) { importedAddress.haveUpDatedUTXOs = false TLBlockExplorerAPI.instance().getUnspentOutputs(addresses, success:{(jsonData:AnyObject!) in let unspentOutputs = (jsonData as! NSDictionary).object(forKey: "unspent_outputs") as! NSArray let address2UnspentOutputs = NSMutableDictionary(capacity:addresses.count) for _unspentOutput in unspentOutputs { let unspentOutput = _unspentOutput as! NSDictionary let outputScript = unspentOutput.object(forKey: "script") as! String let address = TLCoreBitcoinWrapper.getAddressFromOutputScript(outputScript, isTestnet: self.appWallet.walletConfig.isTestnet) if (address == nil) { DLog("address cannot be decoded. not normal pubkeyhash outputScript: \(outputScript)") continue } var cachedUnspentOutputs = address2UnspentOutputs.object(forKey: address!) as! NSMutableArray? if (cachedUnspentOutputs == nil) { cachedUnspentOutputs = NSMutableArray() address2UnspentOutputs.setObject(cachedUnspentOutputs!, forKey:address! as NSCopying) } cachedUnspentOutputs!.add(unspentOutput) } for _address in address2UnspentOutputs { let address = _address.key as! String let idx = addresses.index(of: address) let importedAddress = self.sendFromAddresses!.object(at: idx!) as! TLImportedAddress let unspentOutputsArray = address2UnspentOutputs.object(forKey: address) as! NSArray importedAddress.unspentOutputsCount = unspentOutputsArray.count importedAddress.setUnspentOutputs(unspentOutputsArray) importedAddress.haveUpDatedUTXOs = true } success() }, failure:{(code, status) in failure() } ) } } } class func getEstimatedTxSize(_ inputCount: Int, outputCount: Int) -> UInt64 { return UInt64(10 + 159*inputCount + 34*outputCount) } func createSignedSerializedTransactionHex(_ toAddressesAndAmounts:NSArray, feeAmount:TLCoin, signTx: Bool = true, nonce: UInt32? = nil, ephemeralPrivateKeyHex: String? = nil, error:TLWalletUtils.ErrorWithString) -> (NSDictionary?, Array, NSArray?) { let inputsData = NSMutableArray() let outputsData = NSMutableArray() var outputValueSum = TLCoin.zero() for _toAddressAndAmount in toAddressesAndAmounts { let amount = (_toAddressAndAmount as! NSDictionary).object(forKey: "amount") as! TLCoin outputValueSum = outputValueSum.add(amount) } let valueNeeded = outputValueSum.add(feeAmount) var valueSelected = TLCoin.zero() var changeAddress:NSString? = nil var dustAmount:UInt64 = 0 if sendFromAddresses != nil { for _importedAddress in sendFromAddresses! { let importedAddress = _importedAddress as! TLImportedAddress if (changeAddress == nil) { changeAddress = importedAddress.getAddress() as NSString? } let unspentOutputs = importedAddress.getUnspentArray() for _unspentOutput in unspentOutputs! { let unspentOutput = _unspentOutput as! NSDictionary let amount = (unspentOutput.object(forKey: "value") as! NSNumber).uint64Value if (amount < DUST_AMOUNT) { dustAmount += amount continue } valueSelected = valueSelected.add(TLCoin(uint64:amount)) let outputScript = unspentOutput.object(forKey: "script") as! String let address = TLCoreBitcoinWrapper.getAddressFromOutputScript(outputScript, isTestnet: self.appWallet.walletConfig.isTestnet) if (address == nil) { DLog("address cannot be decoded. not normal pubkeyhash outputScript: \(outputScript)") continue } assert(address == changeAddress as? String, "! address == changeAddress") if signTx { inputsData.add([ "tx_hash": TLWalletUtils.hexStringToData(unspentOutput.object(forKey: "tx_hash") as! String)!, "txid": TLWalletUtils.hexStringToData(unspentOutput.object(forKey: "tx_hash_big_endian") as! String)!, "tx_output_n": unspentOutput.object(forKey: "tx_output_n")!, "script": TLWalletUtils.hexStringToData(outputScript)!, "private_key": importedAddress.getPrivateKey()!]) } else { inputsData.add([ "tx_hash": TLWalletUtils.hexStringToData(unspentOutput.object(forKey: "tx_hash") as! String)!, "txid": TLWalletUtils.hexStringToData(unspentOutput.object(forKey: "tx_hash_big_endian") as! String)!, "tx_output_n": unspentOutput.object(forKey: "tx_output_n")!, "script": TLWalletUtils.hexStringToData(outputScript)!]) } if (valueSelected.greaterOrEqual(valueNeeded)) { break } } } } if (valueSelected.less(valueNeeded)) { changeAddress = nil if sendFromAccounts != nil { for _accountObject in sendFromAccounts! { let accountObject = _accountObject as! TLAccountObject if (changeAddress == nil) { changeAddress = accountObject.getCurrentChangeAddress() as NSString? } // move some stealth payments to HD wallet as soon as possible var stealthPaymentUnspentOutputs = NSArray() if accountObject.stealthWallet != nil { stealthPaymentUnspentOutputs = accountObject.getStealthPaymentUnspentOutputsArray() } var unspentOutputsUsingCount = 0 for _unspentOutput in stealthPaymentUnspentOutputs { let unspentOutput = _unspentOutput as! NSDictionary let amount = (unspentOutput.object(forKey: "value") as! NSNumber).uint64Value if (amount < DUST_AMOUNT) { // if commented out, app will try to spend dust inputs dustAmount += amount continue } valueSelected = valueSelected.add(TLCoin(uint64:amount)) let outputScript = unspentOutput.object(forKey: "script") as! String DLog("createSignedSerializedTransactionHex outputScript: \(outputScript)") let address = TLCoreBitcoinWrapper.getAddressFromOutputScript(outputScript, isTestnet: self.appWallet.walletConfig.isTestnet) if (address == nil) { DLog("address cannot be decoded. not normal pubkeyhash outputScript: \(outputScript)") continue } if signTx { inputsData.add([ "tx_hash": TLWalletUtils.hexStringToData(unspentOutput.object(forKey: "tx_hash") as! String)!, "txid": TLWalletUtils.hexStringToData(unspentOutput.object(forKey: "tx_hash_big_endian") as! String)!, "tx_output_n": unspentOutput.object(forKey: "tx_output_n")!, "script": TLWalletUtils.hexStringToData(outputScript)!, "private_key": accountObject.stealthWallet!.getPaymentAddressPrivateKey(address!)!]) } else { inputsData.add([ "tx_hash": TLWalletUtils.hexStringToData(unspentOutput.object(forKey: "tx_hash") as! String)!, "txid": TLWalletUtils.hexStringToData(unspentOutput.object(forKey: "tx_hash_big_endian") as! String)!, "tx_output_n": unspentOutput.object(forKey: "tx_output_n")!, "script": TLWalletUtils.hexStringToData(outputScript)!]) } unspentOutputsUsingCount += 1 if (valueSelected.greaterOrEqual(valueNeeded) && unspentOutputsUsingCount >= accountObject.MAX_CONSOLIDATE_STEALTH_PAYMENT_UTXOS_COUNT) { // limit amount of stealth payment unspent outputs to use break } } if (valueSelected.greaterOrEqual(valueNeeded)) { break } let unspentOutputs = accountObject.getUnspentArray() for _unspentOutput in unspentOutputs { let unspentOutput = _unspentOutput as! NSDictionary let amount = (unspentOutput.object(forKey: "value") as! NSNumber).uint64Value if (amount < DUST_AMOUNT) { // if commented out, app will try to spend dust inputs dustAmount += amount continue } valueSelected = valueSelected.add(TLCoin(uint64:amount)) let outputScript = unspentOutput.object(forKey: "script") as! String DLog("createSignedSerializedTransactionHex outputScript: \(outputScript)") let address = TLCoreBitcoinWrapper.getAddressFromOutputScript(outputScript, isTestnet: self.appWallet.walletConfig.isTestnet) if (address == nil) { DLog("address cannot be decoded. not normal pubkeyhash outputScript: \(outputScript)") continue } if signTx { inputsData.add([ "tx_hash": TLWalletUtils.hexStringToData(unspentOutput.object(forKey: "tx_hash") as! String)!, "txid": TLWalletUtils.hexStringToData(unspentOutput.object(forKey: "tx_hash_big_endian") as! String)!, "tx_output_n": unspentOutput.object(forKey: "tx_output_n")!, "script": TLWalletUtils.hexStringToData(outputScript)!, "private_key": accountObject.getAccountPrivateKey(address!)! ]) } else { inputsData.add([ "tx_hash": TLWalletUtils.hexStringToData(unspentOutput.object(forKey: "tx_hash") as! String)!, "txid": TLWalletUtils.hexStringToData(unspentOutput.object(forKey: "tx_hash_big_endian") as! String)!, "tx_output_n": unspentOutput.object(forKey: "tx_output_n")!, "script": TLWalletUtils.hexStringToData(outputScript)!, "hd_account_info": ["idx": accountObject.getAddressHDIndex(address!), "is_change": !accountObject.isMainAddress(address!)] ]) } if (valueSelected.greaterOrEqual(valueNeeded)) { break } } } } } DLog("createSignedSerializedTransactionHex valueSelected \(valueSelected.toString())") DLog("createSignedSerializedTransactionHex valueNeeded \(valueNeeded.toString())") var realToAddresses = [String]() if (valueSelected.less(valueNeeded)) { if (dustAmount > 0) { let dustCoinAmount = TLCoin(uint64:dustAmount) DLog("createSignedSerializedTransactionHex dustAmount \(Int(dustAmount))") DLog("createSignedSerializedTransactionHex dustCoinAmount \(dustCoinAmount.toString())") DLog("createSignedSerializedTransactionHex valueNeeded \(valueNeeded.toString())") let amountCanSendString = TLCurrencyFormat.coinToProperBitcoinAmountString(valueNeeded.subtract(dustCoinAmount)) error(String(format: TLDisplayStrings.INSUFFICIENT_FUNDS_ACCOUNT_CONTAINS_BITCOIN_DUST_STRING(), "\(amountCanSendString) \(TLCurrencyFormat.getBitcoinDisplay())")) return (nil, realToAddresses, nil) } let valueSelectedString = TLCurrencyFormat.coinToProperBitcoinAmountString(valueSelected) let valueNeededString = TLCurrencyFormat.coinToProperBitcoinAmountString(valueNeeded) error(String(format: TLDisplayStrings.INSUFFICIENT_FUNDS_ACCOUNT_BALANCE_IS_STRING(), "\(valueSelectedString) \(TLCurrencyFormat.getBitcoinDisplay())", "\(valueNeededString) \(TLCurrencyFormat.getBitcoinDisplay())")) return (nil, realToAddresses, nil) } var stealthOutputScripts:NSMutableArray? = nil for i in stride(from: 0, to: toAddressesAndAmounts.count, by: 1) { let toAddress = (toAddressesAndAmounts.object(at: i) as AnyObject).object(forKey: "address") as! String let amount = (toAddressesAndAmounts.object(at: i) as AnyObject).object(forKey: "amount") as! TLCoin! if (!TLStealthAddress.isStealthAddress(toAddress, isTestnet:self.appWallet.walletConfig.isTestnet)) { realToAddresses.append(toAddress) outputsData.add([ "to_address":toAddress, "amount": NSNumber (value: amount!.toUInt64() as UInt64)]) } else { if (stealthOutputScripts == nil) { stealthOutputScripts = NSMutableArray(capacity:1) } let ephemeralPrivateKey = ephemeralPrivateKeyHex != nil ? ephemeralPrivateKeyHex! : TLStealthAddress.generateEphemeralPrivkey() let stealthDataScriptNonce = nonce != nil ? nonce! : TLStealthAddress.generateNonce() let stealthDataScriptAndPaymentAddress = TLStealthAddress.createDataScriptAndPaymentAddress(toAddress, ephemeralPrivateKey: ephemeralPrivateKey, nonce: stealthDataScriptNonce, isTestnet: self.appWallet.walletConfig.isTestnet) DLog("createSignedSerializedTransactionHex stealthDataScript: \(stealthDataScriptAndPaymentAddress.0)") DLog("createSignedSerializedTransactionHex paymentAddress: \(stealthDataScriptAndPaymentAddress.1)") stealthOutputScripts!.add(stealthDataScriptAndPaymentAddress.0) let paymentAddress = stealthDataScriptAndPaymentAddress.1 realToAddresses.append(paymentAddress) outputsData.add([ "to_address":paymentAddress, "amount": NSNumber (value: amount!.toUInt64() as UInt64)]) } } var changeAmount = TLCoin.zero() if (valueSelected.greater(valueNeeded)) { if (changeAddress != nil) { changeAmount = valueSelected.subtract(valueNeeded) outputsData.add([ "to_address":changeAddress!, "amount": NSNumber(value: changeAmount.toUInt64() as UInt64)]) } } DLog("createSignedSerializedTransactionHex changeAmount : \(changeAmount.toString())") DLog("createSignedSerializedTransactionHex feeAmount: \(feeAmount.toString())") DLog("createSignedSerializedTransactionHex valueSelected: \(valueSelected.toString())") DLog("createSignedSerializedTransactionHex valueNeeded: \(valueNeeded.toString())") if valueNeeded.greater(valueSelected) { NSException(name:NSExceptionName(rawValue: "Send Error"), reason:"not enough unspent outputs", userInfo:nil).raise() } for outputData in outputsData { let outputAmount = ((outputData as! NSDictionary).object(forKey: "amount") as! NSNumber).uint64Value if outputAmount <= DUST_AMOUNT { let dustAmountBitcoins = TLCurrencyFormat.coinToProperBitcoinAmountString(TLCoin(uint64: DUST_AMOUNT), withCode: true) error(String(format: TLDisplayStrings.CANNOT_CREATE_TRANSACTIONS_WITH_OUTPUTS_LESS_THEN_X_BITCOINS_STRING(), dustAmountBitcoins)) return (nil, realToAddresses, nil) } } let sortedInputs = inputsData.sortedArray (comparator: { (obj1, obj2) -> ComparisonResult in let firstTxid = (obj1 as! NSDictionary).object(forKey: "txid") as! Data let secondTxid = (obj2 as! NSDictionary).object(forKey: "txid") as! Data let firstTxBytes = (firstTxid as NSData).bytes.bindMemory(to: UInt8.self, capacity: firstTxid.count) let secondTxBytes = (secondTxid as NSData).bytes.bindMemory(to: UInt8.self, capacity: secondTxid.count) for i in stride(from: 0, to: firstTxid.count, by: 1) { if firstTxBytes[i] < secondTxBytes[i] { return ComparisonResult.orderedAscending } else if firstTxBytes[i] > secondTxBytes[i] { return ComparisonResult.orderedDescending } } let firstTxOutputN = (obj1 as! NSDictionary).object(forKey: "tx_output_n") as! NSNumber let secondTxOutputN = (obj2 as! NSDictionary).object(forKey: "tx_output_n") as! NSNumber if firstTxOutputN.uint64Value < secondTxOutputN.uint64Value { return ComparisonResult.orderedAscending } else if firstTxOutputN.uint64Value > secondTxOutputN.uint64Value { return ComparisonResult.orderedDescending } return ComparisonResult.orderedSame }) let hashes = NSMutableArray() let inputIndexes = NSMutableArray() let inputScripts = NSMutableArray() let privateKeys = NSMutableArray() let txInputsAccountHDIdxes = NSMutableArray() var isInputsAllFromHDAccountAddresses = true //only used for cold wallet accounts, and cant have addresses from other then hd account addresses for _sortedInput in sortedInputs { let sortedInput = _sortedInput as! NSDictionary hashes.add(sortedInput.object(forKey: "tx_hash")!) inputIndexes.add(sortedInput.object(forKey: "tx_output_n")!) if signTx { privateKeys.add(sortedInput.object(forKey: "private_key")!) } else { if let hdAccountInfo = sortedInput.object(forKey: "hd_account_info") { txInputsAccountHDIdxes.add(hdAccountInfo) } else { isInputsAllFromHDAccountAddresses = false } } inputScripts.add(sortedInput.object(forKey: "script")!) } let sortedOutputs = outputsData.sortedArray (comparator: { (obj1, obj2) -> ComparisonResult in let firstAmount = (obj1 as! NSDictionary).object(forKey: "amount") as! NSNumber let secondAmount = (obj2 as! NSDictionary).object(forKey: "amount") as! NSNumber if firstAmount.uint64Value < secondAmount.uint64Value { return ComparisonResult.orderedAscending } else if firstAmount.uint64Value > secondAmount.uint64Value { return ComparisonResult.orderedDescending } else { let firstAddress = (obj1 as! NSDictionary).object(forKey: "to_address") as! String let secondAddress = (obj2 as! NSDictionary).object(forKey: "to_address") as! String let firstScript = TLCoreBitcoinWrapper.getStandardPubKeyHashScriptFromAddress(firstAddress, isTestnet: self.appWallet.walletConfig.isTestnet) let secondScript = TLCoreBitcoinWrapper.getStandardPubKeyHashScriptFromAddress(secondAddress, isTestnet: self.appWallet.walletConfig.isTestnet) let firstScriptData = TLWalletUtils.hexStringToData(firstScript)! let secondScriptData = TLWalletUtils.hexStringToData(secondScript)! let firstScriptBytes = (firstScriptData as NSData).bytes.bindMemory(to: UInt8.self, capacity: firstScriptData.count) let secondScriptBytes = (secondScriptData as NSData).bytes.bindMemory(to: UInt8.self, capacity: secondScriptData.count) for i in stride(from: 0, to: firstScript.characters.count/2, by: 1) { if firstScriptBytes[i] < secondScriptBytes[i] { return ComparisonResult.orderedAscending } else if firstScriptBytes[i] > secondScriptBytes[i] { return ComparisonResult.orderedDescending } } return ComparisonResult.orderedSame } }) let outputAmounts = NSMutableArray() let outputAddresses = NSMutableArray() for _sortedOutput in sortedOutputs { let sortedOutput = _sortedOutput as! NSDictionary outputAddresses.add(sortedOutput.object(forKey: "to_address")!) outputAmounts.add(sortedOutput.object(forKey: "amount")!) } DLog("createSignedSerializedTransactionHex hashes: \(hashes.debugDescription)") DLog("createSignedSerializedTransactionHex inputIndexes: \(inputIndexes.debugDescription)") DLog("createSignedSerializedTransactionHex inputScripts: \(inputScripts.debugDescription)") DLog("createSignedSerializedTransactionHex outputAddresses: \(outputAddresses.debugDescription)") DLog("createSignedSerializedTransactionHex outputAmounts: \(outputAmounts.debugDescription)") DLog("createSignedSerializedTransactionHex privateKeys: \(privateKeys.debugDescription)") for _ in 0...3 { let txHexAndTxHash = TLCoreBitcoinWrapper.createSignedSerializedTransactionHex(hashes, inputIndexes:inputIndexes, inputScripts:inputScripts, outputAddresses:outputAddresses, outputAmounts:outputAmounts, privateKeys:privateKeys, outputScripts:stealthOutputScripts, signTx: signTx, isTestnet: self.appWallet.walletConfig.isTestnet) DLog("createSignedSerializedTransactionHex txHexAndTxHash: \(txHexAndTxHash.debugDescription)") if txHexAndTxHash != nil { return (txHexAndTxHash!, realToAddresses, isInputsAllFromHDAccountAddresses ? txInputsAccountHDIdxes : nil) } } error(TLDisplayStrings.ENCOUNTERED_ERROR_CREATING_TRANSACTION_TRY_AGAIN_STRING()) return (nil, realToAddresses, nil) } } ================================================ FILE: ArcBit/model/TLStealthAddress.swift ================================================ // // TLStealthAddress.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation class TLStealthAddress { struct STATIC_MEMBERS { static let STEALTH_ADDRESS_MSG_SIZE: UInt8 = 0x26 static let STEALTH_ADDRESS_TRANSACTION_VERSION: UInt8 = 0x06 static let BTC_MAGIC_BYTE: UInt8 = 0x2a static let BTC_TESTNET_MAGIC_BYTE: UInt8 = 0x2b } class func getStealthAddressTransacionVersion() -> UInt8 { return STATIC_MEMBERS.STEALTH_ADDRESS_TRANSACTION_VERSION } class func getMagicByte(_ isTestnet: Bool) -> UInt8 { if (isTestnet) { return STATIC_MEMBERS.BTC_TESTNET_MAGIC_BYTE } else { return STATIC_MEMBERS.BTC_MAGIC_BYTE } } class func getStealthAddressMsgSize() -> UInt8 { return STATIC_MEMBERS.STEALTH_ADDRESS_MSG_SIZE } class func isStealthAddress(_ stealthAddress: String, isTestnet: Bool) -> Bool { let data = BTCDataFromBase58Check(stealthAddress) if(data == nil) { return false } let stealthAddressHex = data?.hex() if (stealthAddressHex != nil && stealthAddressHex?.characters.count != 142) { return false } var bytes = [UInt8](repeating: 0, count: (data?.length)!) data?.getBytes(&bytes, length: (data?.length)!) if (bytes[0] != getMagicByte(isTestnet)) { return false } let scanPublicKey = (stealthAddressHex! as NSString).substring(with: NSMakeRange(4, 66)) let spendPublicKey = (stealthAddressHex! as NSString).substring(with: NSMakeRange(72, 66)) let stealthAddr = createStealthAddress(scanPublicKey as NSString, spendPublicKey: spendPublicKey as NSString, isTestnet: isTestnet) return stealthAddress == stealthAddr } class func createStealthAddress(_ scanPublicKey: NSString, spendPublicKey: NSString, isTestnet: (Bool)) -> (String) { let hexString = NSString(format: "%02x00%@01%@0100", getMagicByte(isTestnet), scanPublicKey, spendPublicKey) return BTCBase58CheckStringWithData(BTCDataFromHex(hexString as String)) } class func generateEphemeralPrivkey() -> (String) { let key = BTCKey() let newKey: BTCKey = BTCKey(privateKey: key!.privateKey as Data!) assert(newKey.privateKey.hex() == key?.privateKey.hex(), "") return key!.privateKey.hex() } class func generateNonce() -> UInt32 { return arc4random() } class func createDataScriptAndPaymentAddress(_ stealthAddress: String, isTestnet: Bool) -> (String, String) { let ephemeralPrivateKey = generateEphemeralPrivkey() let nonce = generateNonce() return createDataScriptAndPaymentAddress(stealthAddress, ephemeralPrivateKey: ephemeralPrivateKey, nonce: nonce, isTestnet: isTestnet) } class func createDataScriptAndPaymentAddress(_ stealthAddress: String, ephemeralPrivateKey: String, nonce: UInt32, isTestnet: Bool) -> (String, String) { let publicKeys = getScanPublicKeyAndSpendPublicKey(stealthAddress, isTestnet: isTestnet) let scanPublicKey = publicKeys.0 let spendPublicKey = publicKeys.1 assert(createStealthAddress(scanPublicKey as NSString, spendPublicKey: spendPublicKey as NSString, isTestnet: isTestnet) == stealthAddress, "Invalid stealth address") let key = BTCKey(privateKey: BTCDataFromHex(ephemeralPrivateKey)) let ephemeralPublicKey = key?.compressedPublicKey.hex() let stealthDataScript = NSString(format: "%02x%02x%02x%08x%@", BTCOpcode.OP_RETURN.rawValue, getStealthAddressMsgSize(), getStealthAddressTransacionVersion(), nonce, ephemeralPublicKey!) let paymentPublicKey = getPaymentPublicKeySender(scanPublicKey, spendPublicKey: spendPublicKey, ephemeralPrivateKey: ephemeralPrivateKey) let paymentAddress: String if (!isTestnet) { let key = BTCKey(publicKey: BTCDataFromHex(paymentPublicKey!)) paymentAddress = (key?.address.base58String)! } else { //TODO: let key = BTCKey(publicKey: BTCDataFromHex(paymentPublicKey!)) paymentAddress = (key?.address.base58String)! } return (stealthDataScript as String, paymentAddress) } class func getEphemeralPublicKeyFromStealthDataScript(_ scriptHex: String) -> (String?) { if (scriptHex.characters.count != 80) { return nil } return (scriptHex as NSString).substring(with: NSMakeRange(14, scriptHex.characters.count - 14)) } class func getPaymentAddressPrivateKeySecretFromScript(_ stealthDataScript: String, scanPrivateKey: String, spendPrivateKey: String) -> (String?) { let ephemeralPublicKey = getEphemeralPublicKeyFromStealthDataScript(stealthDataScript) if (ephemeralPublicKey == nil) { return nil } return getPaymentPrivateKey(scanPrivateKey, spendPrivateKey: spendPrivateKey, ephemeralPublicKey: ephemeralPublicKey!) } class func getPaymentAddressPublicKeyFromScript(_ stealthDataScript: String, scanPrivateKey: String, spendPublicKey: String) -> (String?) { let ephemeralPublicKey = getEphemeralPublicKeyFromStealthDataScript(stealthDataScript) if (ephemeralPublicKey == nil) { return nil } return getPaymentPublicKeyForReceiver(scanPrivateKey, spendPublicKey: spendPublicKey, ephemeralPublicKey: ephemeralPublicKey!) } class func getScanPublicKeyAndSpendPublicKey(_ stealthAddress: (String), isTestnet: (Bool)) -> (String, String) { let data = BTCDataFromBase58Check(stealthAddress) let stealthAddressHex = data?.hex() assert(stealthAddressHex?.characters.count == 142, "stealthAddressHex.length != 142") var bytes = [UInt8](repeating: 0, count: (data?.length)!) data?.getBytes(&bytes, length: (data?.length)!) assert(bytes[0] == getMagicByte(isTestnet), "stealth address contains invalid magic byte") let scanPublicKey = (stealthAddressHex! as NSString).substring(with: NSMakeRange(4, 66)) let spendPublicKey = (stealthAddressHex! as NSString).substring(with: NSMakeRange(72, 66)) return (scanPublicKey, spendPublicKey) } class func getSharedSecretForSender(_ scanPublicKey: String, ephemeralPrivateKey: String) -> (String?) { let scanPublicKeyPoint = BTCCurvePoint(data: BTCDataFromHex(scanPublicKey)) let ephemeralPrivateKeySecret = BTCBigNumber(string: ephemeralPrivateKey, base: 16) let key = BTCKey(curvePoint: scanPublicKeyPoint?.multiply(ephemeralPrivateKeySecret)) return key?.compressedPublicKey.sha256().map{ String(format: "%02x", $0) }.joined() } class func getSharedSecretForReceiver(_ ephemeralPublicKey: String, scanPrivateKey: String) -> (String?) { let ephemeralPublicKeyPoint = BTCCurvePoint(data: BTCDataFromHex(ephemeralPublicKey)) let scanPrivateKeySecret = BTCBigNumber(string: scanPrivateKey, base: 16) let key = BTCKey(curvePoint: ephemeralPublicKeyPoint?.multiply(scanPrivateKeySecret)) return key?.compressedPublicKey.sha256().map{ String(format: "%02x", $0) }.joined() } class func getPaymentPublicKeyForReceiver(_ scanPrivateKey: String, spendPublicKey: String, ephemeralPublicKey: String) -> (String?) { let sharedSecret = getSharedSecretForReceiver(ephemeralPublicKey, scanPrivateKey: scanPrivateKey) if (sharedSecret == nil) { return nil } else { let key = BTCKey(privateKey: BTCDataFromHex(sharedSecret)) return addPublicKeys(spendPublicKey, rhsPublicKey: (key?.compressedPublicKey.hex())!) } } class func getPaymentPublicKeySender(_ scanPublicKey: String, spendPublicKey: String, ephemeralPrivateKey: String) -> (String?) { let sharedSecret = getSharedSecretForSender(scanPublicKey, ephemeralPrivateKey: ephemeralPrivateKey) if (sharedSecret == nil) { return nil } else { let key = BTCKey(privateKey: BTCDataFromHex(sharedSecret)) return addPublicKeys(spendPublicKey, rhsPublicKey: (key?.compressedPublicKey.hex())!) } } class func getPaymentPrivateKey(_ scanPrivateKey:String, spendPrivateKey:String, ephemeralPublicKey:String) -> (String?) { let sharedSecret = getSharedSecretForReceiver(ephemeralPublicKey, scanPrivateKey:scanPrivateKey) if (sharedSecret == nil) { return nil } else { return addPrivateKeys(spendPrivateKey, rhsPrivateKey:sharedSecret!) } } class func addPublicKeys(_ lhsPublicKey:String, rhsPublicKey:String) -> (String) { let lhsPoint = BTCCurvePoint(data: BTCDataFromHex(lhsPublicKey)) let rhsPoint = BTCCurvePoint(data: BTCDataFromHex(rhsPublicKey)) return lhsPoint!.add(rhsPoint).data.map{ String(format: "%02x", $0) }.joined() } class func addPrivateKeys(_ lhsPrivateKey:String, rhsPrivateKey:String) -> (String) { let lhsBigNumber = BTCBigNumber(unsignedBigEndian: BTCDataFromHex(lhsPrivateKey)) let rhsBigNumber = BTCBigNumber(unsignedBigEndian: BTCDataFromHex(rhsPrivateKey)) var hexString = (lhsBigNumber?.mutableCopy().add(rhsBigNumber, mod:BTCCurvePoint.curveOrder()).hexString)! as String while hexString.characters.count < 64 { hexString = "0" + hexString } return hexString } } ================================================ FILE: ArcBit/model/TLStealthWallet.swift ================================================ // // TLStealthWallet.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation @objc class TLStealthWallet: NSObject { struct Challenge { static var challenge = "" static var needsRefreshing = true } struct STATIC_MEMBERS { static let MAX_CONSECUTIVE_INVALID_SIGNATURES = 4 static let PREVIOUS_TX_CONFIRMATIONS_TO_COUNT_AS_SPENT:UInt64 = 12 static let TIME_TO_WAIT_TO_CHECK_FOR_SPENT_TX:UInt64 = 86400 // 1 day in seconds } fileprivate var stealthWalletDict: NSDictionary? fileprivate var unspentPaymentAddress2PaymentTxid = [String:String]() fileprivate var paymentAddress2PrivateKeyDict = [String:String]() fileprivate var paymentTxid2PaymentAddressDict = [String:String]() fileprivate var scanPublicKey: String? = nil fileprivate var spendPublicKey: String? = nil fileprivate var accountObject: TLAccountObject? var hasUpdateStealthPaymentStatuses = false var isListeningToStealthPayment: Bool = false init(stealthDict: NSDictionary, accountObject: TLAccountObject, updateStealthPaymentStatuses: Bool) { super.init() self.stealthWalletDict = stealthDict self.accountObject = accountObject self.setUpStealthPaymentAddresses(updateStealthPaymentStatuses, isSetup: true) } func getStealthAddress() -> String { return self.stealthWalletDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STEALTH_ADDRESS) as! String } func getStealthAddressScanKey() -> String { return self.stealthWalletDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STEALTH_ADDRESS_SCAN_KEY) as! String } func getStealthAddressSpendKey() -> String { return self.stealthWalletDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STEALTH_ADDRESS_SPEND_KEY) as! String } func getStealthAddressLastTxTime() -> UInt64 { return (self.stealthWalletDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_LAST_TX_TIME) as! NSNumber).uint64Value } func getStealthAddressServers() -> NSMutableDictionary { return self.stealthWalletDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_SERVERS) as! NSMutableDictionary } func paymentTxidExist(_ txid: String) -> Bool { return self.paymentTxid2PaymentAddressDict[txid] != nil } func isPaymentAddress(_ address: String) -> Bool { return self.paymentAddress2PrivateKeyDict[address] != nil } func getPaymentAddressPrivateKey(_ address: String) -> String? { return self.paymentAddress2PrivateKeyDict[address] } func setPaymentAddressPrivateKey(_ address: String, privateKey: String) -> () { let lock = NSLock() lock.lock() self.paymentAddress2PrivateKeyDict[address] = privateKey lock.unlock() } func getStealthAddressPayments() -> NSArray { return self.stealthWalletDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_PAYMENTS) as! NSArray } func getPaymentAddressForIndex(_ index: Int) -> String { let paymentDict = (self.stealthWalletDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_PAYMENTS) as! NSArray).object(at: index) as! NSDictionary return paymentDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_ADDRESS) as! String } func getStealthAddressPaymentsCount() -> Int { return (self.stealthWalletDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_PAYMENTS) as! NSArray).count } func getPaymentAddresses() -> Array { return [String](self.paymentAddress2PrivateKeyDict.keys) } func getUnspentPaymentAddresses() -> Array { return [String](self.unspentPaymentAddress2PaymentTxid.keys) } func getStealthAddressSpendPublicKey() -> String { if spendPublicKey == nil { let publicKeys = TLStealthAddress.getScanPublicKeyAndSpendPublicKey(self.getStealthAddress(), isTestnet: self.accountObject!.appWallet!.walletConfig.isTestnet) scanPublicKey = publicKeys.0 spendPublicKey = publicKeys.1 } return spendPublicKey! } func getStealthAddressScanPublicKey() -> String { if scanPublicKey == nil { let publicKeys = TLStealthAddress.getScanPublicKeyAndSpendPublicKey(self.getStealthAddress(), isTestnet: self.accountObject!.appWallet!.walletConfig.isTestnet) scanPublicKey = publicKeys.0 spendPublicKey = publicKeys.1 } return scanPublicKey! } func setUpStealthPaymentAddresses(_ updateStealthPaymentStatuses: Bool, isSetup: Bool, async: Bool=true) -> () { DLog("\(self.accountObject!.getAccountIdxNumber()) setUpStealthPaymentAddresses0 \(self.getStealthAddressPayments().count)") if isSetup { self.accountObject!.removeOldStealthPayments() } DLog("\(self.accountObject!.getAccountIdxNumber()) setUpStealthPaymentAddresses1 \(self.getStealthAddressPayments().count)") let paymentsArray = self.getStealthAddressPayments() if isSetup { self.unspentPaymentAddress2PaymentTxid = [String:String]() self.paymentAddress2PrivateKeyDict = [String:String]() self.paymentTxid2PaymentAddressDict = [String:String]() } var possiblyClaimedTxidArray = [String]() var possiblyClaimedAddressArray = [String]() var possiblyClaimedTxTimeArray = [UInt64]() let nowTime = UInt64(Date().timeIntervalSince1970) for i in stride(from: 0, to: paymentsArray.count, by: 1) { if let paymentDict = paymentsArray.object(at: i) as? NSDictionary { let address = paymentDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_ADDRESS) as! String let txid = paymentDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_TXID) as! String let privateKey = paymentDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_KEY) as! String if isSetup { self.paymentTxid2PaymentAddressDict[txid] = address self.paymentAddress2PrivateKeyDict[address] = privateKey } let stealthPaymentStatus = paymentDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STATUS) as! Int if isSetup { if stealthPaymentStatus == TLStealthPaymentStatus.unspent.rawValue { self.unspentPaymentAddress2PaymentTxid[address] = txid } } // dont check to remove last STEALTH_PAYMENTS_FETCH_COUNT payment addresses if i >= paymentsArray.count - Int(TLStealthExplorerAPI.STATIC_MEMBERS.STEALTH_PAYMENTS_FETCH_COUNT) { continue } if stealthPaymentStatus == TLStealthPaymentStatus.claimed.rawValue || stealthPaymentStatus == TLStealthPaymentStatus.unspent.rawValue { let lastCheckTime = UInt64((paymentDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_CHECK_TIME) as! NSNumber).uint64Value) if (nowTime - lastCheckTime) > STATIC_MEMBERS.TIME_TO_WAIT_TO_CHECK_FOR_SPENT_TX { possiblyClaimedTxidArray.append(txid) possiblyClaimedAddressArray.append(address) possiblyClaimedTxTimeArray.append(lastCheckTime) } } } } if updateStealthPaymentStatuses { hasUpdateStealthPaymentStatuses = true if async { DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.low).async { self.addOrSetStealthPaymentsWithStatus(possiblyClaimedTxidArray, addressArray: possiblyClaimedAddressArray, txTimeArray: possiblyClaimedTxTimeArray, isAddingPayments: false, waitForCompletion: false) } } else { self.addOrSetStealthPaymentsWithStatus(possiblyClaimedTxidArray, addressArray: possiblyClaimedAddressArray, txTimeArray: possiblyClaimedTxTimeArray, isAddingPayments: false, waitForCompletion: true) } } } func updateStealthPaymentStatusesAsync() -> () { self.setUpStealthPaymentAddresses(true, isSetup: false) } func getPrivateKeyForAddress(_ expectedAddress: String, script: String) -> String? { let scanKey = self.getStealthAddressScanKey() let spendKey = self.getStealthAddressSpendKey() if let secret = TLStealthAddress.getPaymentAddressPrivateKeySecretFromScript(script, scanPrivateKey: scanKey, spendPrivateKey: spendKey) { let outputAddress = TLCoreBitcoinWrapper.getAddressFromSecret(secret, isTestnet: self.accountObject!.appWallet!.walletConfig.isTestnet) if outputAddress! == expectedAddress { return TLCoreBitcoinWrapper.privateKeyFromSecret(secret, isTestnet: self.accountObject!.appWallet!.walletConfig.isTestnet) } } return nil } func isCurrentServerWatching() -> Bool { let currentServerURL = TLPreferences.getStealthExplorerURL()! let stealthAddressServersDict = self.getStealthAddressServers() if let stealthServerDict: AnyObject = stealthAddressServersDict.object(forKey: currentServerURL) as AnyObject? { return (stealthServerDict as! NSDictionary).object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_WATCHING) as! Bool } else { self.accountObject!.setStealthAddressServerStatus(currentServerURL, isWatching: false) let serverAttributes = [ TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_WATCHING: false, ] let serverAttributesDict = NSMutableDictionary(dictionary: serverAttributes) stealthAddressServersDict.setObject(serverAttributesDict, forKey: currentServerURL as NSCopying) return false } } func checkIfHaveStealthPayments() -> Bool { let stealthAddress = self.getStealthAddress() let scanKey = self.getStealthAddressScanKey() let spendKey = self.getStealthAddressSpendKey() let scanPublicKey = self.getStealthAddressScanPublicKey() let success = TLStealthWallet.watchStealthAddress(stealthAddress, scanPriv: scanKey, spendPriv: spendKey, scanPublicKey: scanPublicKey) if success { let gotOldestPaymentAddressesAndPayments = self.getStealthPayments(stealthAddress, scanPriv: scanKey, spendPriv: spendKey, scanPublicKey: scanPublicKey, offset: 0) if gotOldestPaymentAddressesAndPayments != nil && (gotOldestPaymentAddressesAndPayments!.2 != nil && gotOldestPaymentAddressesAndPayments!.2!.count > 0) { return true } } return false } func checkToWatchStealthAddress() -> () { let stealthAddress = self.getStealthAddress() if self.isCurrentServerWatching() != true { let scanKey = self.getStealthAddressScanKey() let spendKey = self.getStealthAddressSpendKey() let scanPublicKey = self.getStealthAddressScanPublicKey() let success = TLStealthWallet.watchStealthAddress(stealthAddress, scanPriv: scanKey, spendPriv: spendKey, scanPublicKey: scanPublicKey) if success { let stealthAddressServersDict = self.getStealthAddressServers() let currentServerURL = TLPreferences.getStealthExplorerURL()! (stealthAddressServersDict.object(forKey: currentServerURL) as! NSMutableDictionary).setObject(true, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_WATCHING as NSCopying) self.accountObject!.setStealthAddressServerStatus(currentServerURL, isWatching: true) } } } func getStealthAddressAndSignatureFromChallenge(_ challenge: String) -> (String, String) { let privKey = self.getStealthAddressScanKey() let signature = TLCoreBitcoinWrapper.getSignature(privKey, message: challenge); let stealthAddress = self.getStealthAddress() return (stealthAddress, signature); } class func getChallengeAndSign(_ stealthAddress: String, privKey: String, pubKey: String) -> String? { if TLStealthWallet.Challenge.needsRefreshing == true { let jsonData = TLStealthExplorerAPI.instance().getChallenge() if let _: AnyObject = jsonData.object(forKey: TLNetworking.STATIC_MEMBERS.HTTP_ERROR_CODE) as AnyObject? { return nil } TLStealthWallet.Challenge.challenge = jsonData["challenge"] as! String TLStealthWallet.Challenge.needsRefreshing = false } let challenge = TLStealthWallet.Challenge.challenge DLog("getChallengeAndSign \(challenge)") return TLCoreBitcoinWrapper.getSignature(privKey, message: challenge); } func addOrSetStealthPaymentsWithStatus(_ txidArray: [String], addressArray: [String], txTimeArray: [UInt64], isAddingPayments: Bool, waitForCompletion: Bool) -> () { var jsonData:NSDictionary? = nil if txidArray.count > 0 { jsonData = TLBlockExplorerAPI.instance().getUnspentOutputsSynchronous(addressArray as NSArray) if let _: AnyObject = jsonData!.object(forKey: TLNetworking.STATIC_MEMBERS.HTTP_ERROR_CODE) as AnyObject? { let msg = jsonData![TLNetworking.STATIC_MEMBERS.HTTP_ERROR_MSG] as? String if msg != "No free outputs to spend" { return } else { jsonData = ["unspent_outputs":[]]; } } } var txid2hasUnspentOutputs = [String:Bool]() for txid in txidArray { txid2hasUnspentOutputs[txid] = false } if jsonData != nil { let unspentOutputs = jsonData!.object(forKey: "unspent_outputs") as! NSArray for _unspentOutput in unspentOutputs { let unspentOutput = _unspentOutput as! NSDictionary let unspentOutputTxid = unspentOutput.object(forKey: "tx_hash_big_endian") as! String txid2hasUnspentOutputs[unspentOutputTxid] = true } } let group = DispatchGroup() let nowTime = UInt64(Date().timeIntervalSince1970) for i in 0 ..< txidArray.count { let txid = txidArray[i] let paymentAddress = addressArray[i] let txTime = txTimeArray[i] if txid2hasUnspentOutputs[txid] == false { // means blockexplorer has not seen tx yet OR stealth payment already been spent if waitForCompletion { group.enter() } // cant figure out whether stealth payments has been spent by getting unspent outputs because // blockexplorer api might receive tx yet, if we are pushing tx from a source that is not the blockexplorer api TLBlockExplorerAPI.instance().getTxBackground(txid, success: { (jsonData:AnyObject?) -> () in if (jsonData == nil) { if waitForCompletion { group.leave() } return } let stealthDataScriptAndOutputAddresses = TLStealthWallet.getStealthDataScriptAndOutputAddresses((jsonData as? NSDictionary)!) if stealthDataScriptAndOutputAddresses == nil || stealthDataScriptAndOutputAddresses!.0 == nil { if waitForCompletion { group.leave() } return } if (stealthDataScriptAndOutputAddresses!.1).index(of: paymentAddress) != nil { let txObject = TLTxObject(dict:jsonData as! NSDictionary) //Note: this confirmation count is not the confirmations for the tx that spent the stealth payment let confirmations = txObject.getConfirmations() if confirmations >= STATIC_MEMBERS.PREVIOUS_TX_CONFIRMATIONS_TO_COUNT_AS_SPENT { if isAddingPayments { if let privateKey = self.generateAndAddStealthAddressPaymentKey(stealthDataScriptAndOutputAddresses!.0!, expectedAddress: paymentAddress, txid: txid, txTime: txTime, stealthPaymentStatus: TLStealthPaymentStatus.spent) { self.setPaymentAddressPrivateKey(paymentAddress, privateKey: privateKey) } else { DLog("no privateKey for \(paymentAddress)") } } else { self.accountObject!.setStealthPaymentStatus(txid, stealthPaymentStatus: TLStealthPaymentStatus.spent, lastCheckTime: nowTime) } } else { if isAddingPayments { if let privateKey = self.generateAndAddStealthAddressPaymentKey(stealthDataScriptAndOutputAddresses!.0!, expectedAddress: paymentAddress, txid: txid, txTime: txTime, stealthPaymentStatus: TLStealthPaymentStatus.claimed) { self.setPaymentAddressPrivateKey(paymentAddress, privateKey: privateKey) } else { DLog("no privateKey for \(paymentAddress)") } } else { self.accountObject!.setStealthPaymentStatus(txid, stealthPaymentStatus: TLStealthPaymentStatus.claimed, lastCheckTime: nowTime) } } } if waitForCompletion { group.leave() } }, failure: { (code, status) -> () in if waitForCompletion { group.leave() } }) } else { if waitForCompletion { group.enter() } TLBlockExplorerAPI.instance().getTxBackground(txid, success: { (jsonData:AnyObject?) -> () in if (jsonData == nil) { if waitForCompletion { group.leave() } return } if let stealthDataScriptAndOutputAddresses = TLStealthWallet.getStealthDataScriptAndOutputAddresses((jsonData as? NSDictionary)!) { if stealthDataScriptAndOutputAddresses.0 == nil { if waitForCompletion { group.leave() } return } if (stealthDataScriptAndOutputAddresses.1).index(of: paymentAddress) != nil { if isAddingPayments { if let privateKey = self.generateAndAddStealthAddressPaymentKey(stealthDataScriptAndOutputAddresses.0!, expectedAddress: paymentAddress, txid: txid, txTime: txTime, stealthPaymentStatus: TLStealthPaymentStatus.unspent) { self.setPaymentAddressPrivateKey(paymentAddress, privateKey: privateKey) } else { DLog("no privateKey for \(paymentAddress)") } } else { self.accountObject!.setStealthPaymentStatus(txid, stealthPaymentStatus: TLStealthPaymentStatus.unspent, lastCheckTime: nowTime) } } } if waitForCompletion { group.leave() } }, failure: { (code, status) -> () in if waitForCompletion { group.leave() } }) } } if waitForCompletion { group.wait(timeout: DispatchTime.distantFuture) } } func getAndStoreStealthPayments(_ offset: Int) -> (Bool, UInt64, [String])? { let stealthAddress = self.getStealthAddress() let scanKey = self.getStealthAddressScanKey() let spendKey = self.getStealthAddressSpendKey() let scanPublicKey = self.getStealthAddressScanPublicKey() let ret = self.getStealthPayments(stealthAddress, scanPriv: scanKey, spendPriv: spendKey, scanPublicKey: scanPublicKey, offset: offset) if ret == nil { return nil } let gotOldestPaymentAddresses = ret!.0 let latestTxTime = ret!.1 let payments = ret!.2 if payments == nil { return (gotOldestPaymentAddresses, latestTxTime, []) } var txidArray = [String]() var addressArray = [String]() var txTimeArray = [UInt64]() for _payment in payments!.reverseObjectEnumerator() { let payment = _payment as! NSDictionary let txid = payment.object(forKey: "txid") as! String if self.paymentTxidExist(txid) == true { continue } let addr = payment.object(forKey: "addr") as! String txidArray.append(txid) addressArray.append(addr) txTimeArray.append(UInt64((payment.object(forKey: "time") as! NSNumber).uint64Value)) } if txidArray.count == 0 { return (gotOldestPaymentAddresses, latestTxTime, []) } // must check if txids exist and are stealth payments that belong to this account before storing it self.addOrSetStealthPaymentsWithStatus(txidArray, addressArray: addressArray, txTimeArray: txTimeArray, isAddingPayments: true, waitForCompletion: true) return (gotOldestPaymentAddresses, latestTxTime, addressArray) } class func getStealthDataScriptAndOutputAddresses(_ jsonTxData: NSDictionary) -> (stealthDataScript: String?, outputAddresses:Array)? { let outsArray = jsonTxData.object(forKey: "out") as? NSArray if (outsArray != nil) { var outputAddresses = [String]() var stealthDataScript:String? = nil for _output in outsArray! { let output = _output as! NSDictionary if let addr = output.object(forKey: "addr") as? String { outputAddresses.append(addr) } else { let script = output.object(forKey: "script") as! String if script.characters.count == 80 { stealthDataScript = script } } } return (stealthDataScript, outputAddresses) } return nil } func generateAndAddStealthAddressPaymentKey(_ stealthAddressDataScript: String, expectedAddress: String, txid: String, txTime: UInt64, stealthPaymentStatus: TLStealthPaymentStatus) -> String? { if self.paymentTxidExist(txid) == true { return nil } if let privateKey = self.getPrivateKeyForAddress(expectedAddress, script: stealthAddressDataScript) { self.unspentPaymentAddress2PaymentTxid[expectedAddress] = txid self.paymentTxid2PaymentAddressDict[txid] = expectedAddress self.setPaymentAddressPrivateKey(expectedAddress, privateKey: privateKey) self.accountObject!.addStealthAddressPaymentKey(privateKey, address: expectedAddress, txid: txid, txTime: txTime, stealthPaymentStatus: stealthPaymentStatus) return privateKey } else { DLog("error key not found for address \(expectedAddress)") return nil } } func addStealthAddressPaymentKey(_ privateKey: String, paymentAddress: String, txid: String, txTime: UInt64, stealthPaymentStatus: TLStealthPaymentStatus) -> Bool { self.unspentPaymentAddress2PaymentTxid[paymentAddress] = txid self.paymentTxid2PaymentAddressDict[txid] = paymentAddress self.setPaymentAddressPrivateKey(paymentAddress, privateKey: privateKey) self.accountObject!.addStealthAddressPaymentKey(privateKey, address: paymentAddress, txid: txid, txTime: txTime, stealthPaymentStatus: stealthPaymentStatus) return true } func getStealthPayments(_ stealthAddress: String, scanPriv: String, spendPriv: String, scanPublicKey: String, offset:Int) -> (Bool, UInt64, NSArray?)? { var signature = TLStealthWallet.getChallengeAndSign(stealthAddress, privKey: scanPriv, pubKey: scanPublicKey) if signature == nil { return nil } var gotOldestPaymentAddresses = false var jsonData:NSDictionary? = nil var consecutiveInvalidSignatures = 0 while true { jsonData = TLStealthExplorerAPI.instance().getStealthPaymentsSynchronous(stealthAddress, signature: signature!, offset: offset) if let _: AnyObject = jsonData?.object(forKey: TLNetworking.STATIC_MEMBERS.HTTP_ERROR_CODE) as AnyObject? { return nil } if let errorCode = jsonData!.object(forKey: TLStealthExplorerAPI.STATIC_MEMBERS.SERVER_ERROR_CODE) as? Int { if errorCode == TLStealthExplorerAPI.STATIC_MEMBERS.INVALID_SIGNATURE_ERROR { consecutiveInvalidSignatures += 1 if (consecutiveInvalidSignatures > STATIC_MEMBERS.MAX_CONSECUTIVE_INVALID_SIGNATURES) { return nil } TLStealthWallet.Challenge.needsRefreshing = true signature = TLStealthWallet.getChallengeAndSign(stealthAddress, privKey: scanPriv, pubKey: scanPublicKey) if signature == nil { return nil } } continue } break } let stealthPayments = jsonData!["payments"] as! NSArray if stealthPayments.count == 0 { gotOldestPaymentAddresses = true return (true, 0, stealthPayments) } let txTimeLowerBound = self.getStealthAddressLastTxTime() let olderTxTime = ((stealthPayments.lastObject as! NSDictionary)["time"] as! NSNumber).uint64Value if olderTxTime < txTimeLowerBound || stealthPayments.count < TLStealthExplorerAPI.STATIC_MEMBERS.STEALTH_PAYMENTS_FETCH_COUNT { gotOldestPaymentAddresses = true } let latestTxTime = ((stealthPayments.firstObject as! NSDictionary)["time"] as! NSNumber).uint64Value return (gotOldestPaymentAddresses, latestTxTime, stealthPayments) } class func watchStealthAddress(_ stealthAddress: String, scanPriv: String, spendPriv: String, scanPublicKey: String) -> Bool { var signature = self.getChallengeAndSign(stealthAddress, privKey: scanPriv, pubKey: scanPublicKey) if signature == nil { return false } var consecutiveInvalidSignatures = 0 while true { let jsonData = TLStealthExplorerAPI.instance().watchStealthAddressSynchronous(stealthAddress, scanPriv: scanPriv, signature: signature!) if let _: AnyObject = jsonData.object(forKey: TLNetworking.STATIC_MEMBERS.HTTP_ERROR_CODE) as AnyObject? { return false } if let errorCode = (jsonData as NSDictionary).object(forKey: TLStealthExplorerAPI.STATIC_MEMBERS.SERVER_ERROR_CODE) as? Int { if errorCode == TLStealthExplorerAPI.STATIC_MEMBERS.INVALID_SIGNATURE_ERROR { TLStealthWallet.Challenge.needsRefreshing = true signature = self.getChallengeAndSign(stealthAddress, privKey: scanPriv, pubKey: scanPublicKey) if signature == nil { return false } consecutiveInvalidSignatures += 1 if (consecutiveInvalidSignatures > STATIC_MEMBERS.MAX_CONSECUTIVE_INVALID_SIGNATURES) { return false } else { continue } } } else { if let success: AnyObject = jsonData.object(forKey: "success") as AnyObject? { return success as! Bool } return false } } } } ================================================ FILE: ArcBit/model/TLSuggestions.swift ================================================ // // TLSuggestions.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit class TLSuggestions { fileprivate var suggestions:NSMutableDictionary? let VIEW_RECEIVE_SCREEN_GAP_COUNT_TO_SHOW_SUGGESTION_TO_ENABLE_PIN = 13 // use prime numbers to avoid multiple prompts to be displayed at once let VIEW_SEND_SCREEN_GAP_COUNT_TO_SHOW_SUGGESTION_TO_BACKUP_WALLET_PASSPHRASE = 3 let VIEW_SEND_SCREEN_GAP_COUNT_TO_SHOW_WEB_WALLET = 31 let VIEW_SEND_SCREEN_GAP_COUNT_TO_SHOW_TRY_COLD_WALLET = 37 let VIEW_SEND_SCREEN_GAP_COUNT_TO_SHOW_RATE_APP_ONCE = 47 let VIEW_SEND_SCREEN_GAP_COUNT_TO_SHOW_RATE_APP = 89 let ENABLE_SUGGESTED_ENABLE_PIN = "enableSuggestedEnablePin" let ENABLE_SUGGESTED_BACKUP_WALLET_PASSPHRASE = "enableSuggestBackUpWalletPassphrase" let ENABLE_SUGGESTED_DONT_MANAGE_INDIVIDUAL_ACCOUNT_ADDRESSES = "enableSuggestDontManageIndividualAccountAddresses" let ENABLE_SUGGESTED_DONT_MANAGE_INDIVIDUAL_ACCOUNT_PRIVATE_KEYS = "enableSuggestDontManageIndividualAccountPrivateKeys" let ENABLE_SUGGESTED_DONT_ADD_falseRMAL_ADDRESS_TO_ADDRESS_BOOK = "enableSuggestDontAddNormalAddressToAddressBook" let ENABLE_SHOW_MANUALLY_SCAN_TRANSACTION_FOR_STEALTH_TX_INFO = "enableShowManuallyScanTransactionForStealthTxInfo" let ENABLE_SHOW_STEALTH_PAYMENT_DELAY_INFO = "enableShowStealthPaymentDelayInfo" let ENABLE_SHOW_STEALTH_PAYMENT_NOTE = "enableShowStealthPaymentNote" struct STATIC_MEMBERS { static var instance:TLSuggestions? } class func instance() -> (TLSuggestions) { if(STATIC_MEMBERS.instance == nil) { STATIC_MEMBERS.instance = TLSuggestions() } return STATIC_MEMBERS.instance! } init() { suggestions = NSMutableDictionary(dictionary:TLPreferences.getSuggestionsDict() ?? NSDictionary()) } //Deprecated, just use TLPreferences, see method disabledShowFeeExplanationInfo/setDisableShowFeeExplanationInfo func enabledAllSuggestions() -> () { suggestions = NSMutableDictionary(dictionary:[ ENABLE_SUGGESTED_ENABLE_PIN : true, ENABLE_SUGGESTED_BACKUP_WALLET_PASSPHRASE : true, ENABLE_SUGGESTED_DONT_MANAGE_INDIVIDUAL_ACCOUNT_ADDRESSES : true, ENABLE_SUGGESTED_DONT_MANAGE_INDIVIDUAL_ACCOUNT_PRIVATE_KEYS : true, ENABLE_SUGGESTED_DONT_ADD_falseRMAL_ADDRESS_TO_ADDRESS_BOOK : true, ENABLE_SHOW_MANUALLY_SCAN_TRANSACTION_FOR_STEALTH_TX_INFO : true, ENABLE_SHOW_STEALTH_PAYMENT_DELAY_INFO : true, ]) TLPreferences.setSuggestionsDict(suggestions!) } //should have done negation of enabled as default for suggestions when first built app, instead now have to return true for any new suggestions func enabledShowStealthPaymentNote() -> (Bool) { if (suggestions!.object(forKey: ENABLE_SHOW_STEALTH_PAYMENT_NOTE) == nil) { return true; } return suggestions!.object(forKey: ENABLE_SHOW_STEALTH_PAYMENT_NOTE) as! Bool } func setEnableShowStealthPaymentNote(_ enabled:Bool) -> (){ suggestions!.setObject(enabled, forKey:ENABLE_SHOW_STEALTH_PAYMENT_NOTE as NSCopying) TLPreferences.setSuggestionsDict(suggestions!) } func enabledShowStealthPaymentDelayInfo() -> (Bool){ return suggestions!.object(forKey: ENABLE_SHOW_STEALTH_PAYMENT_DELAY_INFO) as! Bool } func setEnableShowStealthPaymentDelayInfo(_ enabled:Bool) -> (){ suggestions!.setObject(enabled, forKey:ENABLE_SHOW_STEALTH_PAYMENT_DELAY_INFO as NSCopying) TLPreferences.setSuggestionsDict(suggestions!) } func enabledShowManuallyScanTransactionForStealthTxInfo() -> (Bool){ return suggestions!.object(forKey: ENABLE_SHOW_MANUALLY_SCAN_TRANSACTION_FOR_STEALTH_TX_INFO) as! Bool } func setEnabledShowManuallyScanTransactionForStealthTxInfo(_ enabled:Bool) -> (){ suggestions!.setObject(enabled, forKey:ENABLE_SHOW_MANUALLY_SCAN_TRANSACTION_FOR_STEALTH_TX_INFO as NSCopying) TLPreferences.setSuggestionsDict(suggestions!) } func conditionToPromptRateAppSatisfied() -> (Bool) { let userAnalyticsDict = NSMutableDictionary(dictionary:TLPreferences.getAnalyticsDict() ?? NSDictionary()) let viewSendScreenCount = userAnalyticsDict.object(forKey: TLNotificationEvents.EVENT_VIEW_SEND_SCREEN()) as! Int? ?? 0 if !TLPreferences.disabledPromptRateApp() && viewSendScreenCount > 0 && ((!TLPreferences.hasRatedOnce() && viewSendScreenCount % VIEW_SEND_SCREEN_GAP_COUNT_TO_SHOW_RATE_APP_ONCE == 0) || (TLPreferences.hasRatedOnce() && viewSendScreenCount % VIEW_SEND_SCREEN_GAP_COUNT_TO_SHOW_RATE_APP == 0)) { return true } else { return false } } func conditionToPromptShowWebWallet() -> (Bool) { let userAnalyticsDict = NSMutableDictionary(dictionary:TLPreferences.getAnalyticsDict() ?? NSDictionary()) let viewSendScreenCount = userAnalyticsDict.object(forKey: TLNotificationEvents.EVENT_VIEW_SEND_SCREEN()) as! Int? ?? 0 if !TLPreferences.disabledPromptShowWebWallet() && viewSendScreenCount > 0 && viewSendScreenCount % VIEW_SEND_SCREEN_GAP_COUNT_TO_SHOW_WEB_WALLET == 0 { return true } else { return false } } func conditionToPromptTryColdWallet() -> (Bool) { let userAnalyticsDict = NSMutableDictionary(dictionary:TLPreferences.getAnalyticsDict() ?? NSDictionary()) let viewSendScreenCount = userAnalyticsDict.object(forKey: TLNotificationEvents.EVENT_VIEW_SEND_SCREEN()) as! Int? ?? 0 if TLPreferences.getInstallDate() == nil { return false } if !TLPreferences.disabledPromptShowTryColdWallet() && !TLPreferences.enabledColdWallet() && TLUtils.daysSinceDate(TLPreferences.getInstallDate()!) > 60 && // 60 days viewSendScreenCount > 0 && viewSendScreenCount % VIEW_SEND_SCREEN_GAP_COUNT_TO_SHOW_TRY_COLD_WALLET == 0 { return true } else { return false } } fileprivate func setEnabledSuggestedEnablePin(_ enabled:Bool) -> (){ suggestions!.setObject(enabled, forKey:ENABLE_SUGGESTED_ENABLE_PIN as NSCopying) TLPreferences.setSuggestionsDict(suggestions!) } fileprivate func enabledSuggestedEnablePin() -> (Bool) { return suggestions!.object(forKey: ENABLE_SUGGESTED_ENABLE_PIN) as! Bool } func conditionToPromptToSuggestEnablePinSatisfied() -> (Bool) { let userAnalyticsDict = NSMutableDictionary(dictionary:TLPreferences.getAnalyticsDict() ?? NSDictionary()) let viewReceiveScreenCount = (userAnalyticsDict.object(forKey: TLNotificationEvents.EVENT_VIEW_RECEIVE_SCREEN()) as! Int? ?? 0) if (enabledSuggestedEnablePin() && viewReceiveScreenCount > 0 && viewReceiveScreenCount % VIEW_RECEIVE_SCREEN_GAP_COUNT_TO_SHOW_SUGGESTION_TO_ENABLE_PIN == 0) { return true } else { return false } } func promptToSuggestEnablePin(_ vc:UIViewController) -> () { UIAlertController.showAlert(in: vc, withTitle: TLDisplayStrings.ENABLE_PIN_CODE_STRING(), message: TLDisplayStrings.ENABLE_PIN_CODE_TO_BETTER_SECURE_WALLET_STRING(), cancelButtonTitle: TLDisplayStrings.REMIND_ME_LATER_STRING(), destructiveButtonTitle: nil, otherButtonTitles: [TLDisplayStrings.DONT_REMIND_ME_STRING()], tap: {(alertView, action, buttonIndex) in if (buttonIndex == alertView?.firstOtherButtonIndex) { self.setEnabledSuggestedEnablePin(false) } else if (buttonIndex == alertView?.cancelButtonIndex) { } }) } fileprivate func setEnabledSuggestedBackUpWalletPassphrase(_ enabled:Bool) -> (){ suggestions!.setObject(enabled, forKey:ENABLE_SUGGESTED_BACKUP_WALLET_PASSPHRASE as NSCopying) TLPreferences.setSuggestionsDict(suggestions!) } fileprivate func enabledSuggestedBackUpWalletPassphrase() -> (Bool) { return (suggestions!.object(forKey: ENABLE_SUGGESTED_BACKUP_WALLET_PASSPHRASE) as! Bool) } func conditionToPromptToSuggestedBackUpWalletPassphraseSatisfied() -> (Bool) { let userAnalyticsDict = NSMutableDictionary(dictionary:TLPreferences.getAnalyticsDict() ?? NSDictionary()) let viewSendScreenCount = userAnalyticsDict.object(forKey: TLNotificationEvents.EVENT_VIEW_SEND_SCREEN()) as! Int? ?? 0 if (enabledSuggestedBackUpWalletPassphrase() && viewSendScreenCount > 0 && viewSendScreenCount % VIEW_SEND_SCREEN_GAP_COUNT_TO_SHOW_SUGGESTION_TO_BACKUP_WALLET_PASSPHRASE == 0) { return true } else { return false } } func promptToSuggestBackUpWalletPassphrase(_ vc:UIViewController) -> () { UIAlertController.showAlert(in: vc, withTitle: TLDisplayStrings.BACK_UP_WALLET_STRING(), message: TLDisplayStrings.SUGGEST_BACK_UP_WALLET_PASSPHRASE_DESC_STRING(), cancelButtonTitle: TLDisplayStrings.REMIND_ME_LATER_STRING(), destructiveButtonTitle: nil, otherButtonTitles: [TLDisplayStrings.DONT_REMIND_ME_STRING()], tap: {(alertView, action, buttonIndex) in if (buttonIndex == alertView?.firstOtherButtonIndex) { self.setEnabledSuggestedBackUpWalletPassphrase(false) } else if (buttonIndex == alertView?.cancelButtonIndex) { } })} func setEnableSuggestDontManageIndividualAccountAddress(_ enabled:Bool) -> () { suggestions!.setObject(enabled, forKey:ENABLE_SUGGESTED_DONT_MANAGE_INDIVIDUAL_ACCOUNT_ADDRESSES as NSCopying) TLPreferences.setSuggestionsDict(suggestions!) } func enabledSuggestDontManageIndividualAccountAddress() -> (Bool) { return suggestions!.object(forKey: ENABLE_SUGGESTED_DONT_MANAGE_INDIVIDUAL_ACCOUNT_ADDRESSES) as! Bool } func setEnableSuggestDontManageIndividualAccountPrivateKeys(_ enabled:Bool) -> () { suggestions!.setObject(enabled, forKey:ENABLE_SUGGESTED_DONT_MANAGE_INDIVIDUAL_ACCOUNT_PRIVATE_KEYS as NSCopying) TLPreferences.setSuggestionsDict(suggestions!) } func enabledSuggestDontManageIndividualAccountPrivateKeys() -> (Bool) { return suggestions!.object(forKey: ENABLE_SUGGESTED_DONT_MANAGE_INDIVIDUAL_ACCOUNT_PRIVATE_KEYS) as! Bool } func setEnableSuggestDontAddNormalAddressToAddressBook(_ enabled:Bool) -> () { suggestions!.setObject(enabled, forKey:ENABLE_SUGGESTED_DONT_ADD_falseRMAL_ADDRESS_TO_ADDRESS_BOOK as NSCopying) TLPreferences.setSuggestionsDict(suggestions!) } func enabledSuggestDontAddNormalAddressToAddressBook() -> (Bool) { return suggestions!.object(forKey: ENABLE_SUGGESTED_DONT_ADD_falseRMAL_ADDRESS_TO_ADDRESS_BOOK) as! Bool } } ================================================ FILE: ArcBit/model/TLTxObject.swift ================================================ // // TLTxObject.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation @objc class TLTxObject : NSObject { fileprivate var txDict : NSDictionary fileprivate var inputAddressToValueArray : NSMutableArray? fileprivate var outputAddressToValueArray : NSMutableArray? fileprivate var addresses:[String]? = nil fileprivate var txid : String? init(dict: NSDictionary) { txDict = NSDictionary(dictionary:dict) super.init() txid = nil buildTxObject(txDict) } fileprivate func buildTxObject(_ tx: NSDictionary) -> (){ inputAddressToValueArray = NSMutableArray() let inputsArray = tx.object(forKey: "inputs") as? NSArray if (inputsArray != nil) { for _input in inputsArray! { let input = _input as! NSDictionary let prevOut = input.object(forKey: "prev_out") as? NSDictionary if (prevOut != nil) { let addr = prevOut!.object(forKey: "addr") as? String let inp = NSMutableDictionary() if (addr != nil) { inp.setObject(addr!, forKey:"addr" as NSCopying) inp.setObject(prevOut!.object(forKey: "value") as! Int, forKey:"value" as NSCopying) } inputAddressToValueArray!.add(inp) } } } outputAddressToValueArray = NSMutableArray() let outsArray = tx.object(forKey: "out") as? NSArray if (outsArray != nil) { for _output in outsArray! { let output = _output as! NSDictionary let addr = output.object(forKey: "addr") as? String let outt = NSMutableDictionary() if (addr != nil) { outt.setObject(addr!, forKey:"addr" as NSCopying) outt.setObject(output.object(forKey: "value") as! Int, forKey:"value" as NSCopying) } outt.setObject(output.object(forKey: "script") as! String, forKey:"script" as NSCopying) outputAddressToValueArray!.add(outt) } } } func getAddresses() -> [String] { if (addresses != nil) { return addresses! } addresses = [String]() for addressTovalueDict in inputAddressToValueArray! { if let address = (addressTovalueDict as! NSDictionary).object(forKey: "addr") as? String { addresses!.append(address) } } for addressTovalueDict in outputAddressToValueArray! { if let address = (addressTovalueDict as! NSDictionary).object(forKey: "addr") as? String { addresses!.append(address) } } return addresses! } func getInputAddressToValueArray() -> (NSArray?) { return inputAddressToValueArray } func getInputAddressArray() -> [String] { var addresses = [String]() addresses.reserveCapacity(inputAddressToValueArray!.count) for _input in inputAddressToValueArray! { let input = _input as! NSDictionary if let address = input.object(forKey: "addr") as? String { addresses.append(address) } } return addresses } func getOutputAddressArray() -> [String] { var addresses = [String]() addresses.reserveCapacity(outputAddressToValueArray!.count) for _output in outputAddressToValueArray! { let output = _output as! NSDictionary if let address = output.object(forKey: "addr") as? String { addresses.append(address) } } return addresses } func getPossibleStealthDataScripts() -> [String] { var possibleStealthDataScripts = [String]() possibleStealthDataScripts.reserveCapacity(outputAddressToValueArray!.count) for _output in outputAddressToValueArray! { let output = _output as! NSDictionary let script = output.object(forKey: "script") as! String if script.characters.count == 80 { possibleStealthDataScripts.append(script) } } return possibleStealthDataScripts } func getOutputAddressToValueArray() -> (NSArray?) { return outputAddressToValueArray } func getHash() -> NSString? { return txDict.object(forKey: "hash") as! NSString? } func getTxid() -> (String?) { if (txid == nil) { txid = TLWalletUtils.reverseHexString(txDict.object(forKey: "hash") as! String) } return txid } func getTxUnixTime() -> UInt64 { let timeNumber = txDict.object(forKey: "time") as? NSNumber if (timeNumber != nil) { return timeNumber!.uint64Value } return 0 } fileprivate func getTxUnixTimeInterval() -> TimeInterval { return TimeInterval((txDict.object(forKey: "time") as! NSNumber).int64Value) } func getTime() -> (String){ let interval = getTxUnixTimeInterval() //TODO: specific to insight api, later dont use confirmations but block_height for all apis if (txDict.object(forKey: "confirmations") != nil && interval <= 0) { return "" } let transactionDate = Date(timeIntervalSince1970:interval) let formatterTime = DateFormatter() formatterTime.dateFormat = "@ hh:mm a" if ((transactionDate as NSDate).isToday()) { return String(format:"%@ %@", TLDisplayStrings.TODAY_STRING(), formatterTime.string(from: transactionDate)) } else { let formatterDate = DateFormatter() formatterDate.dateFormat = "EEE dd MMM YYYY" return String(format:"%@ %@", formatterDate.string(from: transactionDate), formatterTime.string(from: transactionDate)) } } func getConfirmations() -> UInt64 { //TODO: specific to insight api, later dont use confirmations but block_height for all apis if (txDict.object(forKey: "confirmations") as? NSNumber? != nil) { if let conf:NSNumber = txDict.object(forKey: "confirmations") as? NSNumber { return UInt64(conf.int64Value) } } if (txDict.object(forKey: "block_height") != nil && (txDict.object(forKey: "block_height") as! NSNumber).uint64Value > 0) { let height = UInt64(TLBlockchainStatus.instance().blockHeight) let txBlockHeight = UInt64((txDict.object(forKey: "block_height") as! NSNumber).uint64Value) + UInt64(1) if txBlockHeight < height { return height - txBlockHeight } else { return 6 //FIXME } } return 0 } } ================================================ FILE: ArcBit/model/TLWallet+Stealth.swift ================================================ // // TLWallet+Stealth.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation extension TLWallet { func setStealthAddressServerStatus(_ accountDict: NSMutableDictionary, serverURL: String, isWatching: Bool) -> () { let stealthAddressArray = accountDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STEALTH_ADDRESSES) as! NSMutableArray let stealthAddressServersDict = (stealthAddressArray.object(at: 0) as! NSDictionary).object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_SERVERS) as! NSMutableDictionary if let stealthServerDict = stealthAddressServersDict.object(forKey: serverURL) as? NSMutableDictionary { stealthServerDict.setObject(isWatching, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_WATCHING as NSCopying) } else { let serverAttributes = [ TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_WATCHING: isWatching, ] let serverAttributesDict = NSMutableDictionary(dictionary: serverAttributes) stealthAddressServersDict.setObject(serverAttributesDict, forKey: serverURL as NSCopying) } NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } func setStealthAddressServerStatusHDWallet(_ accountIdx: Int, serverURL: String, isWatching: Bool) -> () { let accountDict = getAccountDict(accountIdx) setStealthAddressServerStatus(accountDict, serverURL: serverURL, isWatching:isWatching) } func setStealthAddressServerStatusColdWalletAccount(_ idx: Int, serverURL: String, isWatching: Bool) -> () { let accountDict = getColdWalletAccountAtIndex(idx) setStealthAddressServerStatus(accountDict, serverURL: serverURL, isWatching: isWatching) } func setStealthAddressServerStatusImportedAccount(_ idx: Int, serverURL: String, isWatching: Bool) -> () { let accountDict = getImportedAccountAtIndex(idx) setStealthAddressServerStatus(accountDict, serverURL: serverURL, isWatching: isWatching) } func setStealthAddressServerStatusImportedWatchAccount(_ idx: Int, serverURL: String, isWatching: Bool) -> () { let accountDict = getImportedWatchOnlyAccountAtIndex(idx) setStealthAddressServerStatus(accountDict, serverURL: serverURL, isWatching: isWatching) } func setStealthAddressLastTxTime(_ accountDict: NSMutableDictionary, serverURL: String, lastTxTime: UInt64) -> () { let stealthAddressArray = accountDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STEALTH_ADDRESSES) as! NSMutableArray let stealthAddressDict = (stealthAddressArray.object(at: 0) as! NSMutableDictionary) stealthAddressDict.setObject(NSNumber(value: lastTxTime as UInt64), forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_LAST_TX_TIME as NSCopying) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } func setStealthAddressLastTxTimeHDWallet(_ accountIdx: Int, serverURL: String, lastTxTime: UInt64) -> () { let accountDict = getAccountDict(accountIdx) setStealthAddressLastTxTime(accountDict, serverURL: serverURL, lastTxTime: lastTxTime) } func setStealthAddressLastTxTimeColdWalletAccount(_ idx: Int, serverURL: String, lastTxTime: UInt64) -> () { let accountDict = getColdWalletAccountAtIndex(idx) setStealthAddressLastTxTime(accountDict, serverURL: serverURL, lastTxTime: lastTxTime) } func setStealthAddressLastTxTimeImportedAccount(_ idx: Int, serverURL: String, lastTxTime: UInt64) -> () { let accountDict = getImportedAccountAtIndex(idx) setStealthAddressLastTxTime(accountDict, serverURL: serverURL, lastTxTime: lastTxTime) } func setStealthAddressLastTxTimeImportedWatchAccount(_ idx: Int, serverURL: String, lastTxTime: UInt64) -> () { let accountDict = getImportedWatchOnlyAccountAtIndex(idx) setStealthAddressLastTxTime(accountDict, serverURL: serverURL, lastTxTime: lastTxTime) } fileprivate func addStealthAddressPaymentKey(_ accountDict: NSMutableDictionary, privateKey: String, address: String, txid: String, txTime: UInt64, stealthPaymentStatus: TLStealthPaymentStatus) -> () { let paymentDict = [ TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_KEY: privateKey, TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_ADDRESS: address, TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_TXID: txid, TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_TIME: NSNumber(value: txTime as UInt64), TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_CHECK_TIME: NSNumber(value: 0 as UInt64), TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STATUS: stealthPaymentStatus.rawValue ] as [String : Any] let stealthAddressPaymentDict = NSMutableDictionary(dictionary: paymentDict) let lock = NSLock() lock.lock() let stealthAddressArray = accountDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STEALTH_ADDRESSES) as! NSMutableArray let stealthAddressPaymentsArray = (stealthAddressArray.object(at: 0) as! NSDictionary).object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_PAYMENTS) as! NSMutableArray var indexToInsert = stealthAddressPaymentsArray.count-1 while indexToInsert >= 0 { if let currentStealthAddressPaymentDict = stealthAddressPaymentsArray.object(at: indexToInsert) as? NSDictionary { if (currentStealthAddressPaymentDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_TIME) as! NSNumber).uint64Value < txTime { break } } indexToInsert -= 1 } if stealthAddressPaymentDict != nil { stealthAddressPaymentsArray.insert(stealthAddressPaymentDict, at: indexToInsert+1) } lock.unlock() DispatchQueue.main.async { NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } } func addStealthAddressPaymentKeyHDWallet(_ accountIdx: Int, privateKey: String, address: String, txid: String, txTime: UInt64, stealthPaymentStatus: TLStealthPaymentStatus) -> () { let accountDict = getAccountDict(accountIdx) addStealthAddressPaymentKey(accountDict, privateKey: privateKey, address: address, txid: txid, txTime: txTime, stealthPaymentStatus: stealthPaymentStatus) } func addStealthAddressPaymentKeyColdWalletAccount(_ idx: Int, privateKey: String, address: String, txid: String, txTime: UInt64, stealthPaymentStatus: TLStealthPaymentStatus) -> () { let accountDict = getColdWalletAccountAtIndex(idx) addStealthAddressPaymentKey(accountDict, privateKey: privateKey, address: address, txid: txid, txTime: txTime, stealthPaymentStatus: stealthPaymentStatus) } func addStealthAddressPaymentKeyImportedAccount(_ idx: Int, privateKey: String, address: String, txid: String, txTime: UInt64, stealthPaymentStatus: TLStealthPaymentStatus) -> () { let accountDict = getImportedAccountAtIndex(idx) addStealthAddressPaymentKey(accountDict, privateKey: privateKey, address: address, txid: txid, txTime: txTime, stealthPaymentStatus: stealthPaymentStatus) } func addStealthAddressPaymentKeyImportedWatchAccount(_ idx: Int, privateKey: String, address: String, txid: String, txTime: UInt64, stealthPaymentStatus: TLStealthPaymentStatus) -> () { let accountDict = getImportedWatchOnlyAccountAtIndex(idx) addStealthAddressPaymentKey(accountDict, privateKey: privateKey, address: address, txid: txid, txTime: txTime, stealthPaymentStatus: stealthPaymentStatus) } func setStealthPaymentLastCheckTime(_ accountDict: NSMutableDictionary, txid: String, lastCheckTime: UInt64) -> () { let stealthAddressArray = accountDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STEALTH_ADDRESSES) as! NSMutableArray let paymentsArray = (stealthAddressArray.object(at: 0) as! NSDictionary).object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_PAYMENTS) as! NSMutableArray for _payment in paymentsArray { let payment = _payment as! NSMutableDictionary if payment.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_TXID) as? String == txid { payment.setObject(NSNumber(value: lastCheckTime as UInt64), forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_CHECK_TIME as NSCopying) break } } NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } func setStealthPaymentLastCheckTimeHDWallet(_ accountIdx: Int, txid: String, lastCheckTime: UInt64) -> () { let accountDict = getAccountDict(accountIdx) setStealthPaymentLastCheckTime(accountDict, txid: txid, lastCheckTime: lastCheckTime) } func setStealthPaymentLastCheckTimeColdWalletAccount(_ idx: Int, txid: String, lastCheckTime: UInt64) -> () { let accountDict = getColdWalletAccountAtIndex(idx) setStealthPaymentLastCheckTime(accountDict, txid: txid, lastCheckTime: lastCheckTime) } func setStealthPaymentLastCheckTimeImportedAccount(_ idx: Int, txid: String, lastCheckTime: UInt64) -> () { let accountDict = getImportedAccountAtIndex(idx) setStealthPaymentLastCheckTime(accountDict, txid: txid, lastCheckTime: lastCheckTime) } func setStealthPaymentLastCheckTimeImportedWatchAccount(_ idx: Int, txid: String, lastCheckTime: UInt64) -> () { let accountDict = getImportedWatchOnlyAccountAtIndex(idx) setStealthPaymentLastCheckTime(accountDict, txid: txid, lastCheckTime: lastCheckTime) } func setStealthPaymentStatus(_ accountDict: NSMutableDictionary, txid: String, stealthPaymentStatus: TLStealthPaymentStatus, lastCheckTime: UInt64) -> () { let stealthAddressArray = accountDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STEALTH_ADDRESSES) as! NSMutableArray let paymentsArray = (stealthAddressArray.object(at: 0) as! NSDictionary).object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_PAYMENTS) as! NSMutableArray for _payment in paymentsArray { let payment = _payment as! NSMutableDictionary if payment.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_TXID) as? String == txid { payment.setObject(stealthPaymentStatus.rawValue, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STATUS as NSCopying) payment.setObject(NSNumber(value: lastCheckTime as UInt64), forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_CHECK_TIME as NSCopying) break } } DispatchQueue.main.async { NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } } func setStealthPaymentStatusHDWallet(_ accountIdx: Int, txid: String, stealthPaymentStatus: TLStealthPaymentStatus, lastCheckTime: UInt64) -> () { let accountDict = getAccountDict(accountIdx) setStealthPaymentStatus(accountDict, txid: txid, stealthPaymentStatus: stealthPaymentStatus, lastCheckTime: lastCheckTime) } func setStealthPaymentStatusColdWalletAccount(_ idx: Int, txid: String, stealthPaymentStatus: TLStealthPaymentStatus, lastCheckTime: UInt64) -> () { let accountDict = getColdWalletAccountAtIndex(idx) setStealthPaymentStatus(accountDict, txid: txid, stealthPaymentStatus: stealthPaymentStatus, lastCheckTime: lastCheckTime) } func setStealthPaymentStatusImportedAccount(_ idx: Int, txid: String, stealthPaymentStatus: TLStealthPaymentStatus, lastCheckTime: UInt64) -> () { let accountDict = getImportedAccountAtIndex(idx) setStealthPaymentStatus(accountDict, txid: txid, stealthPaymentStatus: stealthPaymentStatus, lastCheckTime: lastCheckTime) } func setStealthPaymentStatusImportedWatchAccount(_ idx: Int, txid: String, stealthPaymentStatus: TLStealthPaymentStatus, lastCheckTime: UInt64) -> () { let accountDict = getImportedWatchOnlyAccountAtIndex(idx) setStealthPaymentStatus(accountDict, txid: txid, stealthPaymentStatus: stealthPaymentStatus, lastCheckTime: lastCheckTime) } func removeOldStealthPayments(_ accountDict: NSMutableDictionary) -> () { let stealthAddressArray = accountDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STEALTH_ADDRESSES) as! NSMutableArray let stealthAddressPaymentsArray = (stealthAddressArray.object(at: 0) as! NSDictionary).object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_PAYMENTS) as! NSMutableArray let startCount = stealthAddressPaymentsArray.count var stealthAddressPaymentsArrayCount = stealthAddressPaymentsArray.count while (stealthAddressPaymentsArray.count > TLStealthExplorerAPI.STATIC_MEMBERS.STEALTH_PAYMENTS_FETCH_COUNT) { if let stealthAddressPaymentDict = stealthAddressPaymentsArray.object(at: 0) as? NSDictionary { if (stealthAddressPaymentDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STATUS) as! Int == TLStealthPaymentStatus.spent.rawValue) { stealthAddressPaymentsArray.removeObject(at: 0) stealthAddressPaymentsArrayCount -= 1 } else { break } } else { stealthAddressPaymentsArray.removeObject(at: 0) stealthAddressPaymentsArrayCount -= 1 } } if startCount != stealthAddressPaymentsArrayCount { DispatchQueue.main.async { NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } } } func removeOldStealthPaymentsHDWallet(_ accountIdx: Int) -> () { let accountDict = getAccountDict(accountIdx) removeOldStealthPayments(accountDict) } func removeOldStealthPaymentsColdWalletAccount(_ idx: Int) -> () { let accountDict = getColdWalletAccountAtIndex(idx) removeOldStealthPayments(accountDict) } func removeOldStealthPaymentsImportedAccount(_ idx: Int) -> () { let accountDict = getImportedAccountAtIndex(idx) removeOldStealthPayments(accountDict) } func removeOldStealthPaymentsImportedWatchAccount(_ idx: Int) -> () { let accountDict = getImportedWatchOnlyAccountAtIndex(idx) removeOldStealthPayments(accountDict) } func clearAllStealthPayments(_ accountDict: NSMutableDictionary) -> () { let stealthAddressArray = accountDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STEALTH_ADDRESSES) as! NSMutableArray let stealthAddressDict = stealthAddressArray.object(at: 0) as! NSMutableDictionary stealthAddressDict.setObject(NSMutableArray(), forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_PAYMENTS as NSCopying) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } func clearAllStealthPaymentsFromHDWallet(_ accountIdx: Int) -> () { let accountDict = getAccountDict(accountIdx) clearAllStealthPayments(accountDict) } func clearAllStealthPaymentsFromColdWalletAccount(_ idx: Int) -> () { let accountDict = getColdWalletAccountAtIndex(idx) clearAllStealthPayments(accountDict) } func clearAllStealthPaymentsFromImportedAccount(_ idx: Int) -> () { let accountDict = getImportedAccountAtIndex(idx) clearAllStealthPayments(accountDict) } func clearAllStealthPaymentsFromImportedWatchAccount(_ idx: Int) -> () { let accountDict = getImportedWatchOnlyAccountAtIndex(idx) clearAllStealthPayments(accountDict) } } ================================================ FILE: ArcBit/model/TLWallet.swift ================================================ // // TLWallet.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation class TLWallet { fileprivate var walletName: String? var walletConfig: TLWalletConfig fileprivate var rootDict: NSMutableDictionary? fileprivate var currentHDWalletIdx: Int? fileprivate var masterHex: String? init(walletName: String, walletConfig: TLWalletConfig) { self.walletName = walletName self.walletConfig = walletConfig currentHDWalletIdx = 0 } //---------------------------------------------------------------------------------------------------------------- fileprivate func createStealthAddressDict(_ extendKey: String, isPrivateExtendedKey: (Bool)) -> (NSMutableDictionary) { assert(isPrivateExtendedKey == true, "Cant generate stealth address scan key from xpub key") let stealthAddressDict = NSMutableDictionary() let stealthAddressObject = TLHDWalletWrapper.getStealthAddress(extendKey, isTestnet: self.walletConfig.isTestnet) stealthAddressDict.setObject((stealthAddressObject.object(forKey: "stealthAddress"))!, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STEALTH_ADDRESS as NSCopying) stealthAddressDict.setObject((stealthAddressObject.object(forKey: "scanPriv"))!, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STEALTH_ADDRESS_SCAN_KEY as NSCopying) stealthAddressDict.setObject((stealthAddressObject.object(forKey: "spendPriv"))!, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STEALTH_ADDRESS_SPEND_KEY as NSCopying) stealthAddressDict.setObject(NSMutableDictionary(), forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_SERVERS as NSCopying) stealthAddressDict.setObject(NSMutableArray(), forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_PAYMENTS as NSCopying) stealthAddressDict.setObject(NSNumber(value: 0 as UInt64), forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_LAST_TX_TIME as NSCopying) return stealthAddressDict } fileprivate func createAccountDictWithPreload(_ accountName: String, extendedKey: String, isPrivateExtendedKey: Bool, accountIdx: Int, preloadStartingAddresses: Bool) -> (NSMutableDictionary) { let account = NSMutableDictionary() account.setObject(accountName, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_NAME as NSCopying) account.setObject(accountIdx, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_ACCOUNT_IDX as NSCopying) if (isPrivateExtendedKey) { let extendedPublickey = TLHDWalletWrapper.getExtendPubKey(extendedKey) account.setObject(extendedPublickey, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_EXTENDED_PUBLIC_KEY as NSCopying) account.setObject(extendedKey, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_EXTENDED_PRIVATE_KEY as NSCopying) let stealthAddressesArray = NSMutableArray() let stealthAddressDict = createStealthAddressDict(extendedKey, isPrivateExtendedKey: isPrivateExtendedKey) stealthAddressesArray.add(stealthAddressDict) account.setObject(stealthAddressesArray, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STEALTH_ADDRESSES as NSCopying) } else { account.setObject(extendedKey, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_EXTENDED_PUBLIC_KEY as NSCopying) } account.setValue(TLAddressStatus.active.rawValue, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STATUS) account.setObject(true, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_ACCOUNT_NEEDS_RECOVERING as NSCopying) let mainAddressesArray = NSMutableArray() account.setObject(mainAddressesArray, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_MAIN_ADDRESSES as NSCopying) let changeAddressesArray = NSMutableArray() account.setObject(changeAddressesArray, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_CHANGE_ADDRESSES as NSCopying) account.setObject(0, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_MIN_MAIN_ADDRESS_IDX as NSCopying) account.setObject(0, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_MIN_CHANGE_ADDRESS_IDX as NSCopying) if (!preloadStartingAddresses) { return account } let extendedPublicKey = account.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_EXTENDED_PUBLIC_KEY) as! String //create initial receiving address for i in stride(from: 0, to: TLAccountObject.MAX_ACCOUNT_WAIT_TO_RECEIVE_ADDRESS(), by: 1) { let mainAddressDict = NSMutableDictionary() let mainAddressIdx = i let mainAddressSequence = [TLAddressType.main.rawValue, (mainAddressIdx)] let address = TLHDWalletWrapper.getAddress(extendedPublicKey, sequence: mainAddressSequence as NSArray, isTestnet: self.walletConfig.isTestnet) mainAddressDict.setObject(address, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_ADDRESS as NSCopying) mainAddressDict.setObject(TLAddressStatus.active.rawValue, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STATUS as NSCopying) mainAddressDict.setObject(i, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_INDEX as NSCopying) mainAddressesArray.add(mainAddressDict) } let changeAddressDict = NSMutableDictionary() let changeAddressIdx = 0 let changeAddressSequence = [TLAddressType.change.rawValue, changeAddressIdx] let address = TLHDWalletWrapper.getAddress(extendedPublicKey, sequence: changeAddressSequence as NSArray, isTestnet: self.walletConfig.isTestnet) changeAddressDict.setObject(address, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_ADDRESS as NSCopying) changeAddressDict.setObject(TLAddressStatus.active.rawValue, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STATUS as NSCopying) changeAddressDict.setObject(0, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_INDEX as NSCopying) changeAddressesArray.add(changeAddressDict) return account } internal func getAccountDict(_ accountIdx: Int) -> (NSMutableDictionary) { let accountsArray = getAccountsArray() let accountDict = accountsArray.object(at: accountIdx) as! NSMutableDictionary return accountDict } //---------------------------------------------------------------------------------------------------------------- func clearAllAddressesFromHDWallet(_ accountIdx: Int) -> () { let accountDict = getAccountDict(accountIdx) let mainAddressesArray = NSMutableArray() accountDict.setObject(mainAddressesArray, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_MAIN_ADDRESSES as NSCopying) let changeAddressesArray = NSMutableArray() accountDict.setObject(changeAddressesArray, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_CHANGE_ADDRESSES as NSCopying) accountDict.setObject(0, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_MIN_MAIN_ADDRESS_IDX as NSCopying) accountDict.setObject(0, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_MIN_CHANGE_ADDRESS_IDX as NSCopying) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } func clearAllAddressesFromColdWalletAccount(_ idx: Int) -> () { let accountDict = getColdWalletAccountAtIndex(idx) let mainAddressesArray = NSMutableArray() accountDict.setObject(mainAddressesArray, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_MAIN_ADDRESSES as NSCopying) let changeAddressesArray = NSMutableArray() accountDict.setObject(changeAddressesArray, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_CHANGE_ADDRESSES as NSCopying) accountDict.setObject(0, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_MIN_MAIN_ADDRESS_IDX as NSCopying) accountDict.setObject(0, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_MIN_CHANGE_ADDRESS_IDX as NSCopying) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } func clearAllAddressesFromImportedAccount(_ idx: Int) -> () { let accountDict = getImportedAccountAtIndex(idx) let mainAddressesArray = NSMutableArray() accountDict.setObject(mainAddressesArray, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_MAIN_ADDRESSES as NSCopying) let changeAddressesArray = NSMutableArray() accountDict.setObject(changeAddressesArray, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_CHANGE_ADDRESSES as NSCopying) accountDict.setObject(0, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_MIN_MAIN_ADDRESS_IDX as NSCopying) accountDict.setObject(0, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_MIN_CHANGE_ADDRESS_IDX as NSCopying) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } func clearAllAddressesFromImportedWatchAccount(_ idx: Int) -> () { let accountDict = getImportedWatchOnlyAccountAtIndex(idx) let mainAddressesArray = NSMutableArray() accountDict.setObject(mainAddressesArray, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_MAIN_ADDRESSES as NSCopying) let changeAddressesArray = NSMutableArray() accountDict.setObject(changeAddressesArray, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_CHANGE_ADDRESSES as NSCopying) accountDict.setObject(0, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_MIN_MAIN_ADDRESS_IDX as NSCopying) accountDict.setObject(0, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_MIN_CHANGE_ADDRESS_IDX as NSCopying) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } //---------------------------------------------------------------------------------------------------------------- func updateAccountNeedsRecoveringFromHDWallet(_ accountIdx: Int, accountNeedsRecovering: Bool) -> () { let accountDict = getAccountDict(accountIdx) accountDict.setObject(accountNeedsRecovering, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_ACCOUNT_NEEDS_RECOVERING as NSCopying) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } func updateAccountNeedsRecoveringFromColdWalletAccount(_ idx: Int, accountNeedsRecovering: Bool) -> () { let accountDict = getColdWalletAccountAtIndex(idx) accountDict.setObject(accountNeedsRecovering, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_ACCOUNT_NEEDS_RECOVERING as NSCopying) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } func updateAccountNeedsRecoveringFromImportedAccount(_ idx: Int, accountNeedsRecovering: Bool) -> () { let accountDict = getImportedAccountAtIndex(idx) accountDict.setObject(accountNeedsRecovering, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_ACCOUNT_NEEDS_RECOVERING as NSCopying) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } func updateAccountNeedsRecoveringFromImportedWatchAccount(_ idx: Int, accountNeedsRecovering: Bool) -> () { let accountDict = getImportedWatchOnlyAccountAtIndex(idx) accountDict.setObject(accountNeedsRecovering, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_ACCOUNT_NEEDS_RECOVERING as NSCopying) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } //---------------------------------------------------------------------------------------------------------------- func updateMainAddressStatusFromHDWallet(_ accountIdx: Int, addressIdx: Int, addressStatus: TLAddressStatus) -> () { let accountDict = getAccountDict(accountIdx) self.updateMainAddressStatus(accountDict, addressIdx: addressIdx, addressStatus: addressStatus) } func updateMainAddressStatusFromColdWalletAccount(_ idx: Int, addressIdx: Int, addressStatus: TLAddressStatus) -> () { let accountDict = getColdWalletAccountAtIndex(idx) self.updateMainAddressStatus(accountDict, addressIdx: addressIdx, addressStatus: addressStatus) } func updateMainAddressStatusFromImportedAccount(_ idx: Int, addressIdx: Int, addressStatus: TLAddressStatus) -> () { let accountDict = getImportedAccountAtIndex(idx) self.updateMainAddressStatus(accountDict, addressIdx: addressIdx, addressStatus: addressStatus) } func updateMainAddressStatusFromImportedWatchAccount(_ idx: Int, addressIdx: Int, addressStatus: TLAddressStatus) -> () { let accountDict = getImportedWatchOnlyAccountAtIndex(idx) self.updateMainAddressStatus(accountDict, addressIdx: addressIdx, addressStatus: addressStatus) } func updateMainAddressStatus(_ accountDict: NSMutableDictionary, addressIdx: Int, addressStatus: TLAddressStatus) -> () { let minMainAddressIdx = accountDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_MIN_MAIN_ADDRESS_IDX) as! Int DLog("updateMainAddressStatus accountIdx \(accountDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_ACCOUNT_IDX) as! Int)") DLog("updateMainAddressStatus minMainAddressIdx \(minMainAddressIdx) addressIdx: \(addressIdx)") assert(Int(addressIdx) == minMainAddressIdx, "addressIdx != minMainAddressIdx") accountDict.setObject((minMainAddressIdx+1), forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_MIN_MAIN_ADDRESS_IDX as NSCopying) let mainAddressesArray = accountDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_MAIN_ADDRESSES) as! NSMutableArray if (addressStatus == .archived && !TLWalletUtils.STATIC_MEMBERS.SHOULD_SAVE_ARCHIVED_ADDRESSES_IN_JSON) { mainAddressesArray.removeObject(at: 0) } else { let mainAddressDict = mainAddressesArray.object(at: addressIdx) as! NSMutableDictionary mainAddressDict.setObject(addressStatus.rawValue, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STATUS as NSCopying) } NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } func updateChangeAddressStatusFromHDWallet(_ accountIdx: Int, addressIdx: Int, addressStatus: TLAddressStatus) -> () { let accountDict = getAccountDict(accountIdx) self.updateChangeAddressStatus(accountDict, addressIdx: addressIdx, addressStatus: addressStatus) } func updateChangeAddressStatusFromImportedAccount(_ idx: Int, addressIdx: Int, addressStatus: TLAddressStatus) -> () { let accountDict = getImportedAccountAtIndex(idx) self.updateChangeAddressStatus(accountDict, addressIdx: addressIdx, addressStatus: addressStatus) } func updateChangeAddressStatusFromColdWalletAccount(_ idx: Int, addressIdx: Int, addressStatus: TLAddressStatus) -> () { let accountDict = getColdWalletAccountAtIndex(idx) self.updateChangeAddressStatus(accountDict, addressIdx: addressIdx, addressStatus: addressStatus) } func updateChangeAddressStatusFromImportedWatchAccount(_ idx: Int, addressIdx: Int, addressStatus: TLAddressStatus) -> () { let accountDict = getImportedWatchOnlyAccountAtIndex(idx) self.updateChangeAddressStatus(accountDict, addressIdx: addressIdx, addressStatus: addressStatus) } func updateChangeAddressStatus(_ accountDict: NSMutableDictionary, addressIdx: Int, addressStatus: TLAddressStatus) -> () { let minChangeAddressIdx = accountDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_MIN_CHANGE_ADDRESS_IDX) as! Int DLog("updateChangeAddressStatus accountIdx \(accountDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_ACCOUNT_IDX) as! Int)") DLog("updateChangeAddressStatus minChangeAddressIdx \(minChangeAddressIdx) addressIdx: \(addressIdx)") assert(Int(addressIdx) == minChangeAddressIdx, "addressIdx != minChangeAddressIdx") accountDict.setObject((minChangeAddressIdx + 1), forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_MIN_CHANGE_ADDRESS_IDX as NSCopying) let changeAddressesArray = accountDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_CHANGE_ADDRESSES) as! NSMutableArray if (addressStatus == .archived && !TLWalletUtils.STATIC_MEMBERS.SHOULD_SAVE_ARCHIVED_ADDRESSES_IN_JSON) { changeAddressesArray.removeObject(at: 0) } else { let changeAddressDict = changeAddressesArray.object(at: addressIdx) as! NSMutableDictionary changeAddressDict.setObject(addressStatus.rawValue, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STATUS as NSCopying) } NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } //---------------------------------------------------------------------------------------------------------------- func getMinMainAddressIdxFromHDWallet(_ accountIdx: Int) -> Int { let accountDict = getAccountDict(accountIdx) return self.getMinMainAddressIdx(accountDict) } func getMinMainAddressIdxFromColdWalletAccount(_ idx: Int) -> (Int) { let accountDict = getColdWalletAccountAtIndex(idx) return self.getMinMainAddressIdx(accountDict) } func getMinMainAddressIdxFromImportedAccount(_ idx: Int) -> (Int) { let accountDict = getImportedAccountAtIndex(idx) return self.getMinMainAddressIdx(accountDict) } func getMinMainAddressIdxFromImportedWatchAccount(_ idx: Int) -> (Int) { let accountDict = getImportedWatchOnlyAccountAtIndex(idx) return self.getMinMainAddressIdx(accountDict) } func getMinMainAddressIdx(_ accountDict: NSMutableDictionary) -> (Int) { return accountDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_MIN_MAIN_ADDRESS_IDX) as! Int } func getMinChangeAddressIdxFromHDWallet(_ accountIdx: Int) -> (Int) { let accountDict = getAccountDict(accountIdx) return self.getMinChangeAddressIdx(accountDict) as Int } func getMinChangeAddressIdxFromColdWalletAccount(_ idx: Int) -> (Int) { let accountDict = getColdWalletAccountAtIndex(idx) return self.getMinChangeAddressIdx(accountDict) as Int } func getMinChangeAddressIdxFromImportedAccount(_ idx: Int) -> (Int) { let accountDict = getImportedAccountAtIndex(idx) return self.getMinChangeAddressIdx(accountDict) as Int } func getMinChangeAddressIdxFromImportedWatchAccount(_ idx: Int) -> (Int) { let accountDict = getImportedWatchOnlyAccountAtIndex(idx) return self.getMinChangeAddressIdx(accountDict) as Int } func getMinChangeAddressIdx(_ accountDict: NSMutableDictionary) -> (Int) { return accountDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_MIN_CHANGE_ADDRESS_IDX) as! Int } //---------------------------------------------------------------------------------------------------------------- func getNewMainAddressFromHDWallet(_ accountIdx: Int, expectedAddressIndex: Int) -> (NSDictionary) { let accountDict = getAccountDict(accountIdx) return getNewMainAddress(accountDict, expectedAddressIndex: expectedAddressIndex) } func getNewMainAddressFromColdWalletAccount(_ idx: Int, expectedAddressIndex: Int) -> (NSDictionary) { let accountDict = getColdWalletAccountAtIndex(idx) return getNewMainAddress(accountDict, expectedAddressIndex: expectedAddressIndex) } func getNewMainAddressFromImportedAccount(_ idx: Int, expectedAddressIndex: Int) -> (NSDictionary) { let accountDict = getImportedAccountAtIndex(idx) return getNewMainAddress(accountDict, expectedAddressIndex: expectedAddressIndex) } func getNewMainAddressFromImportedWatchAccount(_ idx: Int, expectedAddressIndex: Int) -> (NSDictionary) { let accountDict = getImportedWatchOnlyAccountAtIndex(idx) return getNewMainAddress(accountDict, expectedAddressIndex: expectedAddressIndex) } fileprivate func getNewMainAddress(_ accountDict: NSDictionary, expectedAddressIndex: Int) -> (NSDictionary) { let mainAddressesArray = accountDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_MAIN_ADDRESSES) as! NSMutableArray DLog(String(format:"getNewMainAddress expectedAddressIndex %lu mainAddressesArray.count %lu", expectedAddressIndex, mainAddressesArray.count)) let mainAddressIdx:Int if (TLWalletUtils.STATIC_MEMBERS.SHOULD_SAVE_ARCHIVED_ADDRESSES_IN_JSON) { assert(expectedAddressIndex == mainAddressesArray.count, "expectedAddressIndex != mainAddressesArray.count") mainAddressIdx = (expectedAddressIndex) } else { let minMainAddressIdx = accountDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_MIN_MAIN_ADDRESS_IDX) as! Int assert(expectedAddressIndex == mainAddressesArray.count + minMainAddressIdx, "expectedAddressIndex != mainAddressesArray.count + minMainAddressIdx") mainAddressIdx = (expectedAddressIndex) } if (mainAddressIdx >= NSIntegerMax) { NSException(name: NSExceptionName(rawValue: "Universe ended"), reason: "reached max hdwallet index", userInfo: nil).raise() } let mainAddressSequence = [TLAddressType.main.rawValue, mainAddressIdx] let mainAddressDict = NSMutableDictionary() let extendedPublicKey = accountDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_EXTENDED_PUBLIC_KEY) as! String let address = TLHDWalletWrapper.getAddress(extendedPublicKey, sequence: mainAddressSequence as NSArray, isTestnet: self.walletConfig.isTestnet) mainAddressDict.setObject(address, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_ADDRESS as NSCopying) mainAddressDict.setObject((TLAddressStatus.active.rawValue), forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STATUS as NSCopying) mainAddressDict.setObject(mainAddressIdx, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_INDEX as NSCopying) mainAddressesArray.add(mainAddressDict) DispatchQueue.main.async { NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } return mainAddressDict } //---------------------------------------------------------------------------------------------------------------- func getNewChangeAddressFromHDWallet(_ accountIdx: Int, expectedAddressIndex: Int) -> (NSDictionary) { let accountDict = getAccountDict(accountIdx) return getNewChangeAddress(accountDict, expectedAddressIndex: expectedAddressIndex) } func getNewChangeAddressFromColdWalletAccount(_ idx: UInt, expectedAddressIndex: Int) -> (NSDictionary) { let accountDict = getColdWalletAccountAtIndex(Int(idx)) return getNewChangeAddress(accountDict, expectedAddressIndex: expectedAddressIndex) } func getNewChangeAddressFromImportedAccount(_ idx: Int, expectedAddressIndex: Int) -> (NSDictionary) { let accountDict = getImportedAccountAtIndex(idx) return getNewChangeAddress(accountDict, expectedAddressIndex: expectedAddressIndex) } func getNewChangeAddressFromImportedWatchAccount(_ idx: UInt, expectedAddressIndex: Int) -> (NSDictionary) { let accountDict = getImportedWatchOnlyAccountAtIndex(Int(idx)) return getNewChangeAddress(accountDict, expectedAddressIndex: expectedAddressIndex) } fileprivate func getNewChangeAddress(_ accountDict: NSDictionary, expectedAddressIndex: Int) -> (NSDictionary) { let changeAddressesArray = accountDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_CHANGE_ADDRESSES) as! NSMutableArray DLog(String(format: "getNewChangeAddress expectedAddressIndex %lu changeAddressesArray.count %lu", expectedAddressIndex, changeAddressesArray.count)) let changeAddressIdx:Int if (TLWalletUtils.STATIC_MEMBERS.SHOULD_SAVE_ARCHIVED_ADDRESSES_IN_JSON) { assert(expectedAddressIndex == changeAddressesArray.count, "expectedAddressIndex != changeAddressesArray.count") changeAddressIdx = (changeAddressesArray.count) } else { let minChangeAddressIdx = accountDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_MIN_CHANGE_ADDRESS_IDX) as! Int assert(expectedAddressIndex == changeAddressesArray.count + minChangeAddressIdx, "expectedAddressIndex != changeAddressesArray.count + minChangeAddressIdx") changeAddressIdx = (expectedAddressIndex) } if (changeAddressIdx >= NSIntegerMax) { NSException(name: NSExceptionName(rawValue: "Universe ended"), reason: "reached max hdwallet index", userInfo: nil).raise() } let changeAddressSequence = [TLAddressType.change.rawValue, changeAddressIdx] let changeAddressDict = NSMutableDictionary() let extendedPublicKey = accountDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_EXTENDED_PUBLIC_KEY) as! String let address = TLHDWalletWrapper.getAddress(extendedPublicKey, sequence: changeAddressSequence as NSArray, isTestnet: self.walletConfig.isTestnet) changeAddressDict.setObject(address, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_ADDRESS as NSCopying) changeAddressDict.setObject(TLAddressStatus.active.rawValue, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STATUS as NSCopying) changeAddressDict.setObject(changeAddressIdx, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_INDEX as NSCopying) changeAddressesArray.add(changeAddressDict) DispatchQueue.main.async { NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } return changeAddressDict } //---------------------------------------------------------------------------------------------------------------- func removeTopMainAddressFromHDWallet(_ accountIdx: Int) -> (String?) { let accountDict = getAccountDict(accountIdx) return removeTopMainAddress(accountDict) } func removeTopMainAddressFromColdWalletAccount(_ idx: Int) -> (String?) { let accountDict = getColdWalletAccountAtIndex(idx) return removeTopMainAddress(accountDict) } func removeTopMainAddressFromImportedAccount(_ idx: Int) -> (String?) { let accountDict = getImportedAccountAtIndex(idx) return removeTopMainAddress(accountDict) } func removeTopMainAddressFromImportedWatchAccount(_ idx: Int) -> (String?) { let accountDict = getImportedWatchOnlyAccountAtIndex(idx) return removeTopMainAddress(accountDict) } fileprivate func removeTopMainAddress(_ accountDict: NSMutableDictionary) -> (String?) { let mainAddressesArray = accountDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_MAIN_ADDRESSES) as! NSMutableArray if (mainAddressesArray.count > 0) { let mainAddressDict = mainAddressesArray.lastObject as! NSDictionary mainAddressesArray.removeLastObject() NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) return mainAddressDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_ADDRESS) as? String } return nil } //---------------------------------------------------------------------------------------------------------------- func removeTopChangeAddressFromHDWallet(_ accountIdx: Int) -> (String?) { let accountDict = getAccountDict(accountIdx) return removeTopChangeAddress(accountDict) } func removeTopChangeAddressFromColdWalletAccount(_ idx: Int) -> (String?) { let accountDict = getColdWalletAccountAtIndex(idx) return removeTopChangeAddress(accountDict) } func removeTopChangeAddressFromImportedAccount(_ idx: Int) -> (String?) { let accountDict = getImportedAccountAtIndex(idx) return removeTopChangeAddress(accountDict) } func removeTopChangeAddressFromImportedWatchAccount(_ idx: Int) -> (String?) { let accountDict = getImportedWatchOnlyAccountAtIndex(idx) return removeTopChangeAddress(accountDict) } fileprivate func removeTopChangeAddress(_ accountDict: NSMutableDictionary) -> (String?) { let changeAddressesArray = accountDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_CHANGE_ADDRESSES) as! NSMutableArray if (changeAddressesArray.count > 0) { let changeAddressDict = changeAddressesArray.lastObject as! NSDictionary changeAddressesArray.removeLastObject() NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) return changeAddressDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_ADDRESS) as? String } return nil } //---------------------------------------------------------------------------------------------------------------- func archiveAccountHDWallet(_ accountIdx: Int, enabled: Bool) -> () { let accountsArray = getAccountsArray() assert(accountsArray.count > 1, "") let accountDict = accountsArray.object(at: accountIdx) as! NSDictionary let status = enabled ? TLAddressStatus.archived : TLAddressStatus.active accountDict.setValue(status.rawValue, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STATUS) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } func archiveAccountColdWalletAccount(_ idx: Int, enabled: Bool) -> () { let accountDict = getColdWalletAccountAtIndex(idx) let status = enabled ? TLAddressStatus.archived : TLAddressStatus.active accountDict.setValue(status.rawValue, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STATUS) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } func archiveAccountImportedAccount(_ idx: Int, enabled: Bool) -> () { let accountDict = getImportedAccountAtIndex(idx) let status = enabled ? TLAddressStatus.archived : TLAddressStatus.active accountDict.setValue(status.rawValue, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STATUS) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } func archiveAccountImportedWatchAccount(_ idx: Int, enabled: Bool) -> () { let accountDict = getImportedWatchOnlyAccountAtIndex(idx) let status = enabled ? TLAddressStatus.archived : TLAddressStatus.active accountDict.setValue(status.rawValue, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STATUS) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } //---------------------------------------------------------------------------------------------------------------- fileprivate func getAccountsArray() -> NSMutableArray { let hdWalletDict = getHDWallet() let accountsArray = hdWalletDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_ACCOUNTS) as! NSMutableArray return accountsArray } func removeTopAccount() -> (Bool) { let accountsArray = getAccountsArray() if (accountsArray.count > 0) { accountsArray.removeLastObject() let hdWalletDict = getHDWallet() let maxAccountIDCreated = hdWalletDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_MAX_ACCOUNTS_CREATED) as! Int hdWalletDict.setObject((maxAccountIDCreated - 1), forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_MAX_ACCOUNTS_CREATED as NSCopying) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) return true } return false } fileprivate func createNewAccount(_ accountName: String, accountType: TLAccount) -> (TLAccountObject) { return createNewAccount(accountName, accountType: accountType, preloadStartingAddresses: true) } func createNewAccount(_ accountName: String, accountType: TLAccount, preloadStartingAddresses: Bool) -> TLAccountObject { assert(self.masterHex != nil, "") let hdWalletDict = getHDWallet() let accountsArray = getAccountsArray() let maxAccountIDCreated = hdWalletDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_MAX_ACCOUNTS_CREATED) as! Int let extendPrivKey = TLHDWalletWrapper.getExtendPrivKey(self.masterHex!, accountIdx: UInt(maxAccountIDCreated)) let accountDict = createAccountDictWithPreload(accountName, extendedKey: extendPrivKey, isPrivateExtendedKey: true, accountIdx: Int(maxAccountIDCreated), preloadStartingAddresses: preloadStartingAddresses) accountsArray.add(accountDict) hdWalletDict.setObject((maxAccountIDCreated + 1), forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_MAX_ACCOUNTS_CREATED as NSCopying) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) if (getCurrentAccountID() == nil) { setCurrentAccountID("0") } return TLAccountObject(appWallet: self, dict: accountDict, accountType: .hdWallet) } fileprivate func createWallet(_ passphrase: String, masterHex: String, walletName: String) -> (NSMutableDictionary) { let createdWalletDict = NSMutableDictionary() let hdWalletDict = NSMutableDictionary() hdWalletDict.setObject(walletName, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_NAME as NSCopying) hdWalletDict.setObject(masterHex, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_MASTER_HEX as NSCopying) hdWalletDict.setObject(passphrase, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_PASSPHRASE as NSCopying) hdWalletDict.setObject(0, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_MAX_ACCOUNTS_CREATED as NSCopying) let accountsArray = NSMutableArray() hdWalletDict.setObject(accountsArray, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_ACCOUNTS as NSCopying) let hdWalletsArray = NSMutableArray() hdWalletsArray.add(hdWalletDict) createdWalletDict.setValue(hdWalletsArray, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_HDWALLETS) let importedKeysDict = NSMutableDictionary() let coldWalletAccountsArray = NSMutableArray() let importedAccountsArray = NSMutableArray() let watchOnlyAccountsArray = NSMutableArray() let importedPrivateKeysArray = NSMutableArray() let watchOnlyAddressesArray = NSMutableArray() importedKeysDict.setObject(coldWalletAccountsArray, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_COLD_WALLET_ACCOUNTS as NSCopying) importedKeysDict.setObject(importedAccountsArray, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_IMPORTED_ACCOUNTS as NSCopying) importedKeysDict.setObject(watchOnlyAccountsArray, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_WATCH_ONLY_ACCOUNTS as NSCopying) importedKeysDict.setObject(importedPrivateKeysArray, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_IMPORTED_PRIVATE_KEYS as NSCopying) importedKeysDict.setObject(watchOnlyAddressesArray, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_WATCH_ONLY_ADDRESSES as NSCopying) createdWalletDict.setObject(importedKeysDict, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_IMPORTS as NSCopying) return createdWalletDict } fileprivate func getImportedKeysDict() -> (NSMutableDictionary) { let hdWallet = getCurrentWallet() return hdWallet.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_IMPORTS) as! NSMutableDictionary } internal func getColdWalletAccountAtIndex(_ idx: Int) -> (NSMutableDictionary) { let importedKeysDict = getImportedKeysDict() let coldWalletAccountsArray = importedKeysDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_COLD_WALLET_ACCOUNTS) as! NSArray let accountDict = coldWalletAccountsArray.object(at: idx) as! NSMutableDictionary return accountDict } internal func getImportedAccountAtIndex(_ idx: Int) -> (NSMutableDictionary) { let importedKeysDict = getImportedKeysDict() let importedAccountsArray = importedKeysDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_IMPORTED_ACCOUNTS) as! NSArray let accountDict = importedAccountsArray.object(at: idx) as! NSMutableDictionary return accountDict } internal func getImportedWatchOnlyAccountAtIndex(_ idx: Int) -> (NSMutableDictionary) { let importedKeysDict = getImportedKeysDict() let watchOnlyAccountsArray = importedKeysDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_WATCH_ONLY_ACCOUNTS) as! NSArray let accountDict = watchOnlyAccountsArray.object(at: idx) as! NSMutableDictionary return accountDict } //------------------------------------------------------------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------------------------------------------------------------ func addColdWalletAccount(_ extendedPublicKey: String) -> (TLAccountObject) { let importedKeysDict = getImportedKeysDict() let coldWalletAccountsArray = importedKeysDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_COLD_WALLET_ACCOUNTS) as! NSMutableArray let accountIdx = coldWalletAccountsArray.count // "accountIdx" key is different for ImportedAccount then hdwallet account let coldWalletAccountDict = createAccountDictWithPreload("", extendedKey: extendedPublicKey, isPrivateExtendedKey: false, accountIdx: accountIdx, preloadStartingAddresses: false) coldWalletAccountsArray.add(coldWalletAccountDict) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) return TLAccountObject(appWallet: self, dict: coldWalletAccountDict, accountType: .coldWallet) } func deleteColdWalletAccount(_ idx: Int) -> () { let importedKeysDict = getImportedKeysDict() let coldWalletAccountsArray = importedKeysDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_COLD_WALLET_ACCOUNTS) as! NSMutableArray coldWalletAccountsArray.removeObject(at: idx) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } func setColdWalletAccountName(_ name: String, idx: Int) -> () { let importedKeysDict = getImportedKeysDict() let coldWalletAccountsArray = importedKeysDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_COLD_WALLET_ACCOUNTS) as! NSArray let accountDict = coldWalletAccountsArray.object(at: idx) as! NSMutableDictionary accountDict.setObject(name, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_NAME as NSCopying) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } func getColdWalletAccountArray() -> (NSArray) { let importedKeysDict = getImportedKeysDict() let accountsArray = importedKeysDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_COLD_WALLET_ACCOUNTS) as! NSArray let accountObjectArray = NSMutableArray() for accountDict in accountsArray as! [NSDictionary] { let accountObject = TLAccountObject(appWallet: self, dict: accountDict, accountType: .coldWallet) accountObjectArray.add(accountObject) } return accountObjectArray } func addImportedAccount(_ extendedPrivateKey: String) -> (TLAccountObject) { let importedKeysDict = getImportedKeysDict() let importedAccountsArray = importedKeysDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_IMPORTED_ACCOUNTS) as! NSMutableArray let accountIdx = importedAccountsArray.count // "accountIdx" key is different for ImportedAccount then hdwallet account let accountDict = createAccountDictWithPreload("", extendedKey: extendedPrivateKey, isPrivateExtendedKey: true, accountIdx: accountIdx, preloadStartingAddresses: false) importedAccountsArray.add(accountDict) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) return TLAccountObject(appWallet: self, dict: accountDict, accountType: .imported) } func deleteImportedAccount(_ idx: Int) -> () { let importedKeysDict = getImportedKeysDict() let importedAccountsArray = importedKeysDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_IMPORTED_ACCOUNTS) as! NSMutableArray importedAccountsArray.removeObject(at: idx) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } func setImportedAccountName(_ name: String, idx: Int) -> () { let accountDict = getImportedAccountAtIndex(idx) accountDict.setObject(name, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_NAME as NSCopying) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } func getImportedAccountArray() -> (NSArray) { let importedKeysDict = getImportedKeysDict() let accountsArray = importedKeysDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_IMPORTED_ACCOUNTS) as! NSArray let accountObjectArray = NSMutableArray() for accountDict in accountsArray as! [NSDictionary] { let accountObject = TLAccountObject(appWallet: self, dict: accountDict, accountType: .imported) accountObjectArray.add(accountObject) } return accountObjectArray } func addWatchOnlyAccount(_ extendedPublicKey: String) -> (TLAccountObject) { let importedKeysDict = getImportedKeysDict() let watchOnlyAccountsArray = importedKeysDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_WATCH_ONLY_ACCOUNTS) as! NSMutableArray let accountIdx = watchOnlyAccountsArray.count // "accountIdx" key is different for ImportedAccount then hdwallet account let watchOnlyAccountDict = createAccountDictWithPreload("", extendedKey: extendedPublicKey, isPrivateExtendedKey: false, accountIdx: accountIdx, preloadStartingAddresses: false) watchOnlyAccountsArray.add(watchOnlyAccountDict) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) return TLAccountObject(appWallet: self, dict: watchOnlyAccountDict, accountType: .importedWatch) } func deleteWatchOnlyAccount(_ idx: Int) -> () { let importedKeysDict = getImportedKeysDict() let watchOnlyAccountsArray = importedKeysDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_WATCH_ONLY_ACCOUNTS) as! NSMutableArray watchOnlyAccountsArray.removeObject(at: idx) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } func setWatchOnlyAccountName(_ name: String, idx: Int) -> () { let importedKeysDict = getImportedKeysDict() let watchOnlyAccountsArray = importedKeysDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_WATCH_ONLY_ACCOUNTS) as! NSArray let accountDict = watchOnlyAccountsArray.object(at: idx) as! NSMutableDictionary accountDict.setObject(name, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_NAME as NSCopying) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } func getWatchOnlyAccountArray() -> (NSArray) { let importedKeysDict = getImportedKeysDict() let accountsArray = importedKeysDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_WATCH_ONLY_ACCOUNTS) as! NSArray let accountObjectArray = NSMutableArray() for accountDict in accountsArray as! [NSDictionary] { let accountObject = TLAccountObject(appWallet: self, dict: accountDict, accountType: .importedWatch) accountObjectArray.add(accountObject) } return accountObjectArray } func addImportedPrivateKey(_ privateKey: String, encryptedPrivateKey: String?) -> (NSDictionary) { let importedKeysDict = getImportedKeysDict() let importedPrivateKeyArray = importedKeysDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_IMPORTED_PRIVATE_KEYS) as! NSMutableArray var importedPrivateKey = NSDictionary() if (encryptedPrivateKey == nil) { importedPrivateKey = [ TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_KEY: privateKey, TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_ADDRESS: TLCoreBitcoinWrapper.getAddress(privateKey, isTestnet: self.walletConfig.isTestnet)!, TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_LABEL: String(""), TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STATUS: Int(TLAddressStatus.active.rawValue) ] } else { importedPrivateKey = [ TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_KEY: encryptedPrivateKey!, TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_ADDRESS: TLCoreBitcoinWrapper.getAddress(privateKey, isTestnet: self.walletConfig.isTestnet)!, TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_LABEL: String(), TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STATUS: Int(TLAddressStatus.active.rawValue) ] } let importedPrivateKeyDict = NSMutableDictionary(dictionary: importedPrivateKey) importedPrivateKeyArray.add(importedPrivateKeyDict) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) return importedPrivateKey } func deleteImportedPrivateKey(_ idx: Int) -> () { let importedKeysDict = getImportedKeysDict() let importedPrivateKeyArray = importedKeysDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_IMPORTED_PRIVATE_KEYS) as! NSMutableArray importedPrivateKeyArray.removeObject(at: idx) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } func setImportedPrivateKeyLabel(_ label: String, idx: Int) -> () { let importedKeysDict = getImportedKeysDict() let importedPrivateKeyArray = importedKeysDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_IMPORTED_PRIVATE_KEYS) as! NSArray let privateKeyDict = importedPrivateKeyArray.object(at: idx) as! NSMutableDictionary privateKeyDict.setObject(label, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_LABEL as NSCopying) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } func setImportedPrivateKeyArchive(_ archive: Bool, idx: Int) -> () { let importedKeysDict = getImportedKeysDict() let importedPrivateKeyArray = importedKeysDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_IMPORTED_PRIVATE_KEYS) as! NSArray let privateKeyDict = importedPrivateKeyArray.object(at: idx) as! NSMutableDictionary let status = archive ? TLAddressStatus.archived : TLAddressStatus.active privateKeyDict.setObject(status.rawValue, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STATUS as NSCopying) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } func getImportedPrivateKeyArray() -> (NSArray) { let importedKeysDict = getImportedKeysDict() let importedAddresses = importedKeysDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_IMPORTED_PRIVATE_KEYS) as! NSArray let importedAddressesObjectArray = NSMutableArray(capacity: importedAddresses.count) for addressDict in importedAddresses as! [NSDictionary] { let importedAddressObject = TLImportedAddress(appWallet: self, dict: addressDict) importedAddressesObjectArray.add(importedAddressObject) } return importedAddressesObjectArray } func addWatchOnlyAddress(_ address: NSString) -> (NSDictionary) { let importedKeysDict = getImportedKeysDict() let watchOnlyAddressArray = importedKeysDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_WATCH_ONLY_ADDRESSES) as! NSMutableArray let watchOnlyAddress = [ TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_ADDRESS: address, TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_LABEL: "", TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STATUS: (TLAddressStatus.active.rawValue) ] as [String : Any] let watchOnlyAddressDict = NSMutableDictionary(dictionary: watchOnlyAddress) watchOnlyAddressArray.add(watchOnlyAddressDict) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) return watchOnlyAddress as (NSDictionary) } func deleteImportedWatchAddress(_ idx: Int) -> () { let importedKeysDict = getImportedKeysDict() let watchOnlyAddressArray = importedKeysDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_WATCH_ONLY_ADDRESSES) as! NSMutableArray watchOnlyAddressArray.removeObject(at: idx) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } func setWatchOnlyAddressLabel(_ label: String, idx: Int) -> () { let importedKeysDict = getImportedKeysDict() let watchOnlyAddressArray = importedKeysDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_WATCH_ONLY_ADDRESSES) as! NSArray let addressDict = watchOnlyAddressArray.object(at: idx) as! NSMutableDictionary addressDict.setObject(label, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_LABEL as NSCopying) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } func setWatchOnlyAddressArchive(_ archive: Bool, idx: Int) -> () { let importedKeysDict = getImportedKeysDict() let watchOnlyAddressArray = importedKeysDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_WATCH_ONLY_ADDRESSES) as! NSArray let addressDict = watchOnlyAddressArray.object(at: idx) as! NSMutableDictionary let status = archive ? TLAddressStatus.archived : TLAddressStatus.active addressDict.setObject(status.rawValue, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_STATUS as NSCopying) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } func getWatchOnlyAddressArray() -> (NSArray) { let importedKeysDict = getImportedKeysDict() let importedAddresses = importedKeysDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_WATCH_ONLY_ADDRESSES) as! NSArray let importedAddressesObjectArray = NSMutableArray(capacity: importedAddresses.count) for addressDict in importedAddresses as! [NSDictionary] { let importedAddressObject = TLImportedAddress(appWallet: self, dict: addressDict) importedAddressesObjectArray.add(importedAddressObject) } return importedAddressesObjectArray } //------------------------------------------------------------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------------------------------------------------------------ func getAddressBook() -> (NSArray) { return self.getCurrentWallet().object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_ADDRESS_BOOK) as! NSArray } func addAddressBookEntry(_ address: String, label: String) -> () { let addressBookArray = getCurrentWallet().object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_ADDRESS_BOOK) as! NSMutableArray addressBookArray.add([TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_ADDRESS: address, TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_LABEL: label]) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } func getLabelForAddress(_ address: String) -> String? { //if duplicate labels return first one let addressBookArray = getCurrentWallet().object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_ADDRESS_BOOK) as! NSMutableArray for i in stride(from: 0, to: addressBookArray.count, by: 1) { let addressBook: NSDictionary = addressBookArray.object(at: i) as! NSDictionary if address == addressBook.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_ADDRESS) as! String { return addressBook.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_LABEL) as? String } } return nil } func editAddressBookEntry(_ index: Int, label: String) -> () { let addressBookArray = getCurrentWallet().object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_ADDRESS_BOOK) as! NSMutableArray let oldEntry = addressBookArray.object(at: index) as! NSDictionary addressBookArray.replaceObject(at: index, with: [TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_ADDRESS: oldEntry.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_ADDRESS) as! String, TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_LABEL: label]) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } func deleteAddressBookEntry(_ idx: Int) -> () { let addressBookArray = getCurrentWallet().object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_ADDRESS_BOOK) as! NSMutableArray addressBookArray.removeObject(at: idx) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } func setTransactionTag(_ txid: String, tag: String) -> () { let transactionLabelDict = getCurrentWallet().object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_TRANSACTION_TAGS) as! NSMutableDictionary transactionLabelDict.setObject(tag, forKey: txid as NSCopying) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } func deleteTransactionTag(_ txid: String) -> () { let transactionLabelDict = getCurrentWallet().object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_TRANSACTION_TAGS) as! NSMutableDictionary transactionLabelDict.removeObject(forKey: txid) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } func getTransactionTag(_ txid: String) -> String? { let transactionLabelDict = getCurrentWallet().object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_TRANSACTION_TAGS) as! NSDictionary return transactionLabelDict.object(forKey: txid) as! String? } fileprivate func createNewWallet(_ passphrase: String, masterHex: String, walletName: String) -> () { let walletsArray = getWallets() let walletDict = createWallet(passphrase, masterHex: masterHex, walletName: walletName) walletDict.setValue(NSMutableArray(), forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_ADDRESS_BOOK) walletDict.setValue(NSMutableDictionary(), forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_TRANSACTION_TAGS) walletsArray.add(walletDict) } func createInitialWalletPayload(_ passphrase: String, masterHex: String) -> () { self.masterHex = masterHex rootDict = NSMutableDictionary() let walletsArray = NSMutableArray() rootDict!.setValue(TLWalletJSONKeys.getLastestVersion(), forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_VERSION) let payload = NSMutableDictionary() rootDict!.setValue(payload, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_PAYLOAD) payload.setValue(walletsArray, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_WALLETS) createNewWallet(passphrase, masterHex: masterHex, walletName: "default") NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } func loadWalletPayload(_ walletPayload: NSDictionary, masterHex: String) -> () { self.masterHex = masterHex rootDict = NSMutableDictionary(dictionary: walletPayload) let walletDict = getCurrentWallet().mutableCopy() as! NSMutableDictionary let accountsArray = getAccountsArray().mutableCopy() as! NSMutableArray for i in stride(from: 0, to: accountsArray.count, by: 1) { let accountDict: NSMutableDictionary = (accountsArray.object(at: i) as! NSDictionary).mutableCopy() as! NSMutableDictionary accountsArray.replaceObject(at: i, with: accountDict) } DLog(String(format: "loadWalletPayload rootDict: 1 \n%@", rootDict!.description)) // migrate to version 2 of wallet payload let version = rootDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_VERSION) as! String if version == TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_VERSION_ONE { rootDict!.setObject(TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_VERSION_TWO, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_VERSION as NSCopying) getImportedKeysDict().setObject(NSMutableArray(), forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_COLD_WALLET_ACCOUNTS as NSCopying) getCurrentWallet().setObject(getImportedKeysDict(), forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_IMPORTS as NSCopying) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) } let importedKeysDict = getImportedKeysDict().mutableCopy() as! NSMutableDictionary walletDict.setObject(importedKeysDict, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_IMPORTS as NSCopying) let coldWalletAccountsArray: AnyObject = (importedKeysDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_COLD_WALLET_ACCOUNTS) as! NSArray).mutableCopy() as AnyObject importedKeysDict.setObject(coldWalletAccountsArray, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_COLD_WALLET_ACCOUNTS as NSCopying) let importedAccountsArray: AnyObject = (importedKeysDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_IMPORTED_ACCOUNTS) as! NSArray).mutableCopy() as AnyObject importedKeysDict.setObject(importedAccountsArray, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_IMPORTED_ACCOUNTS as NSCopying) let watchOnlyAccountsArray: AnyObject = (importedKeysDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_WATCH_ONLY_ACCOUNTS) as! NSArray).mutableCopy() as AnyObject importedKeysDict.setObject(watchOnlyAccountsArray, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_WATCH_ONLY_ACCOUNTS as NSCopying) let importedPrivateKeysArray: AnyObject = (importedKeysDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_IMPORTED_PRIVATE_KEYS) as! NSArray).mutableCopy() as AnyObject importedKeysDict.setObject(importedPrivateKeysArray, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_IMPORTED_PRIVATE_KEYS as NSCopying) let watchOnlyAddressesArray: AnyObject = (importedKeysDict.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_WATCH_ONLY_ADDRESSES) as! NSArray).mutableCopy() as AnyObject importedKeysDict.setObject(watchOnlyAddressesArray, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_WATCH_ONLY_ADDRESSES as NSCopying) } func getWalletsJson() -> (NSDictionary?) { return rootDict?.copy() as? NSDictionary } fileprivate func getWallets() -> (NSMutableArray) { return (rootDict!.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_PAYLOAD) as! NSDictionary).object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_WALLETS) as! NSMutableArray } fileprivate func getFirstWallet() -> (NSMutableDictionary) { return getWallets().object(at: 0) as! NSMutableDictionary } fileprivate func getCurrentWallet() -> (NSMutableDictionary) { return getFirstWallet() } fileprivate func getHDWallet() -> (NSMutableDictionary) { return (getCurrentWallet().object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_HDWALLETS) as! NSArray).object(at: 0) as! NSMutableDictionary } fileprivate func getCurrentAccountID() -> (String?) { let hdWallet = getHDWallet() return hdWallet.object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_CURRENT_ACCOUNT_ID) as? String } fileprivate func setCurrentAccountID(_ accountID: String) -> () { let hdWallet = getHDWallet() hdWallet.setObject(accountID, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_CURRENT_ACCOUNT_ID as NSCopying) } func renameAccount(_ accountIdxNumber: Int, accountName: String) -> (Bool) { let accountsArray = getAccountsArray() let accountDict = accountsArray.object(at: accountIdxNumber) as! NSMutableDictionary accountDict.setObject(accountName, forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_NAME as NSCopying) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_WALLET_PAYLOAD_UPDATED()), object: nil, userInfo: nil) return true } func getAccountObjectArray() -> (NSArray) { let accountsArray = getAccountsArray() let accountObjectArray = NSMutableArray() for accountDict in accountsArray { let accountObject = TLAccountObject(appWallet: self, dict: accountDict as! NSDictionary, accountType: .hdWallet) accountObjectArray.add(accountObject) } return accountObjectArray } fileprivate func getAccountObjectForIdx(_ accountIdx: Int) -> (TLAccountObject) { let accountsArray = getAccountsArray() let accountDict = accountsArray.object(at: accountIdx) as! NSDictionary return TLAccountObject(appWallet: self, dict: accountDict, accountType: .hdWallet) } } ================================================ FILE: ArcBit/model/TLWalletConfig.swift ================================================ // // TLWalletConfig.swift // ArcBit // // Created by Tim Lee on 8/27/15. // Copyright (c) 2015 ArcBit. All rights reserved. // import Foundation class TLWalletConfig { var isTestnet:Bool = false init(isTestnet:Bool) { self.isTestnet = isTestnet } } ================================================ FILE: ArcBit/model/TLWalletJSONKeys.swift ================================================ // // TLWalletJsonKeys.swift // ArcBit // // Created by Tim Lee on 9/22/15. // Copyright © 2015 ArcBit. All rights reserved. // import Foundation enum TLAccount:Int { case normal = 0 case multisig = 1 } enum TLAddressStatus:Int { case archived = 0 //archived: passed window case active = 1 } enum TLAddressType:Int { case main = 0 case change = 1 case stealth = 2 } enum TLStealthPaymentStatus:Int { case unspent = 0 // >=0 confirmations for payment tx case claimed = 1 // 0-5 confirmations for payment tx and >=0 confirm for claimed tx case spent = 2 // > 6 confirmations for payment tx and >=0 confirm for claimed tx } class TLWalletJSONKeys { struct STATIC_MEMBERS { static let WALLET_PAYLOAD_VERSION_ONE = "1" static let WALLET_PAYLOAD_VERSION_TWO = "2" static let WALLET_PAYLOAD_KEY_VERSION = "version" static let WALLET_PAYLOAD_KEY_PAYLOAD = "payload" static let WALLET_PAYLOAD_KEY_WALLETS = "wallets" static let WALLET_PAYLOAD_KEY_HDWALLETS = "hd_wallets" static let WALLET_PAYLOAD_KEY_ACCOUNTS = "accounts" static let WALLET_PAYLOAD_CURRENT_ACCOUNT_ID = "current_account_id" static let WALLET_PAYLOAD_IMPORTS = "imports" static let WALLET_PAYLOAD_COLD_WALLET_ACCOUNTS = "cold_wallet_accounts" static let WALLET_PAYLOAD_IMPORTED_ACCOUNTS = "imported_accounts" static let WALLET_PAYLOAD_WATCH_ONLY_ACCOUNTS = "watch_only_accounts" static let WALLET_PAYLOAD_IMPORTED_PRIVATE_KEYS = "imported_private_keys" static let WALLET_PAYLOAD_WATCH_ONLY_ADDRESSES = "watch_only_addresses" static let WALLET_PAYLOAD_ACCOUNT_IDX = "account_idx" static let WALLET_PAYLOAD_EXTENDED_PRIVATE_KEY = "xprv" static let WALLET_PAYLOAD_EXTENDED_PUBLIC_KEY = "xpub" static let WALLET_PAYLOAD_ACCOUNT_NEEDS_RECOVERING = "needs_recovering" static let WALLET_PAYLOAD_KEY_MAIN_ADDRESSES = "main_addresses" static let WALLET_PAYLOAD_KEY_CHANGE_ADDRESSES = "change_addresses" static let WALLET_PAYLOAD_KEY_STEALTH_ADDRESSES = "stealth_addresses" static let WALLET_PAYLOAD_KEY_STEALTH_ADDRESS = "stealth_address" static let WALLET_PAYLOAD_KEY_STEALTH_ADDRESS_SCAN_KEY = "scan_key" static let WALLET_PAYLOAD_KEY_STEALTH_ADDRESS_SPEND_KEY = "spend_key" static let WALLET_PAYLOAD_KEY_PAYMENTS = "payments" static let WALLET_PAYLOAD_KEY_SERVERS = "servers" static let WALLET_PAYLOAD_KEY_WATCHING = "watching" static let WALLET_PAYLOAD_KEY_TXID = "txid" static let WALLET_PAYLOAD_KEY_MIN_MAIN_ADDRESS_IDX = "min_main_address_idx" static let WALLET_PAYLOAD_KEY_MIN_CHANGE_ADDRESS_IDX = "min_change_address_vidx" static let WALLET_PAYLOAD_KEY_TIME = "time" static let WALLET_PAYLOAD_KEY_CHECK_TIME = "check_time" static let WALLET_PAYLOAD_KEY_LAST_TX_TIME = "last_tx_time" static let WALLET_PAYLOAD_KEY_KEY = "key" static let WALLET_PAYLOAD_KEY_ADDRESS = "address" static let WALLET_PAYLOAD_KEY_STATUS = "status" static let WALLET_PAYLOAD_KEY_INDEX = "index" static let WALLET_PAYLOAD_KEY_LABEL = "label" static let WALLET_PAYLOAD_KEY_NAME = "name" static let WALLET_PAYLOAD_KEY_MAX_ACCOUNTS_CREATED = "max_account_id_created" static let WALLET_PAYLOAD_KEY_MASTER_HEX = "master_hex" static let WALLET_PAYLOAD_KEY_PASSPHRASE = "passphrase" static let WALLET_PAYLOAD_KEY_ADDRESS_BOOK = "address_book" static let WALLET_PAYLOAD_KEY_TRANSACTION_TAGS = "tx_tags" } // Notes: // version 2: Add 'cold_wallet_accounts' dictionary to 'imports' dictionary class func getLastestVersion () -> String { return "2" } } ================================================ FILE: ArcBit/model/TLWalletJson.swift ================================================ // // TLWalletJson.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation class TLWalletJson { class func getDecryptedEncryptedWalletJSONPassphrase() -> String? { let encryptedWalletPassphraseKey = TLPreferences.getEncryptedWalletPassphraseKey() if encryptedWalletPassphraseKey != nil { let encryptedWalletPassphrase = TLPreferences.getEncryptedWalletJSONPassphrase(TLPreferences.canRestoreDeletedApp()) let decryptedEncryptedWalletPassphrase = TLWalletPassphrase.decryptWalletPassphrase(encryptedWalletPassphrase!, key: encryptedWalletPassphraseKey!) assert(decryptedEncryptedWalletPassphrase != nil) return decryptedEncryptedWalletPassphrase } else { return TLPreferences.getEncryptedWalletJSONPassphrase(TLPreferences.canRestoreDeletedApp()) } } class func getWalletJsonFileName() -> (String) { return "wallet.json.asc" } class func generatePayloadChecksum(_ payload: String) -> String { return TLCrypto.doubleSHA256HashFor(payload as NSString) } class func getEncryptedWalletJsonContainer(_ walletJson: NSDictionary, password: String) -> (String) { assert(TLHDWalletWrapper.phraseIsValid(password), "phrase is invalid") var str = TLUtils.dictionaryToJSONString(false, dict: walletJson) //DLog("getEncryptedWalletJsonContainer str: %@", str) let encryptJSONPassword = TLCrypto.doubleSHA256HashFor(password as NSString) str = TLCrypto.encrypt(str, password: encryptJSONPassword) let walletJsonEncryptedWrapperDict = ["version":1, "payload":str] as [String : Any] let walletJsonEncryptedWrapperString = TLUtils.dictionaryToJSONString(true, dict: walletJsonEncryptedWrapperDict as NSDictionary) return walletJsonEncryptedWrapperString } class func getWalletJsonDict(_ encryptedWalletJSONFileContent: String?, password: String?) -> (NSDictionary?) { if encryptedWalletJSONFileContent == nil { return nil } let walletJsonEncryptedWrapperDict = TLUtils.JSONStringToDictionary(encryptedWalletJSONFileContent!) let version = walletJsonEncryptedWrapperDict.object(forKey: "version") as! Int assert(version == 1, "Incorrect encryption version") let encryptedWalletJSONPayloadString = walletJsonEncryptedWrapperDict.object(forKey: "payload") as! String let walletJsonString = decryptWalletJSONFile(encryptedWalletJSONPayloadString, password: password) if (walletJsonString == nil) { return nil } let walletJsonData = walletJsonString!.data(using: String.Encoding.utf8) let error: NSError? = nil let walletDict = (try! JSONSerialization.jsonObject(with: walletJsonData!, options: JSONSerialization.ReadingOptions.mutableContainers)) as! NSDictionary assert(error == nil, "Error serializing wallet json string") //DLog("getWalletJsonDict: %@", walletDict.description) return walletDict } class func decryptWalletJSONFile(_ encryptedWalletJSONFile: String?, password: String?) -> (String?) { if (encryptedWalletJSONFile == nil || password == nil) { return nil } assert(TLHDWalletWrapper.phraseIsValid(password!), "phrase is invalid") let encryptJSONPassword = TLCrypto.doubleSHA256HashFor(password! as NSString) let str = TLCrypto.decrypt(encryptedWalletJSONFile!, password: encryptJSONPassword) //DLog("getWalletJsonString: %@", str) return str } class func saveWalletJson(_ walletFile: String, date: Date) -> (Bool) { let paths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true) as NSArray let documentDirectory = paths.object(at: 0) as! NSString let filePath = documentDirectory.appendingPathComponent(TLWalletJson.getWalletJsonFileName()) var error: NSError? = nil do { try walletFile.write(toFile: filePath, atomically: true, encoding: String.Encoding(rawValue: String.Encoding.utf8.rawValue)) } catch let error1 as NSError { error = error1 } if (error != nil) { return false } else { return true } } class func getLocalWalletJSONFile() -> (String?) { let paths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true) as NSArray let documentDirectory = paths.object(at: 0) as! NSString let filePath = documentDirectory.appendingPathComponent(TLWalletJson.getWalletJsonFileName()) let error: NSError? = nil if (error != nil) { DLog("TLWalletJson error getWalletJsonString: \(error!.localizedDescription)") return nil } do { let contents = try NSString(contentsOfFile: filePath, encoding: String.Encoding.utf8.rawValue) return contents as String } catch _ { return nil } } } ================================================ FILE: ArcBit/model/TLWalletPassphrase.swift ================================================ // // TLWalletPassphrase.swift // ArcBit // // Created by Tim Lee on 8/10/15. // Copyright (c) 2015 ArcBit. All rights reserved. // import Foundation class TLWalletPassphrase { class func enableRecoverableFeature(_ useKeychain:Bool) { let encryptedWalletPassphraseKey = TLPreferences.getEncryptedWalletPassphraseKey() let encryptedWalletPassphrase = TLPreferences.getWalletPassphrase(useKeychain) assert(encryptedWalletPassphraseKey != nil) let walletPassphrase = TLWalletPassphrase.decryptWalletPassphrase(encryptedWalletPassphrase!, key: encryptedWalletPassphraseKey!) TLPreferences.setWalletPassphrase(walletPassphrase!, useKeychain: true) TLPreferences.setEncryptedWalletJSONPassphrase(walletPassphrase!, useKeychain: true) TLPreferences.clearEncryptedWalletPassphraseKey() } class func disableRecoverableFeature(_ useKeychain:Bool) { let encryptedWalletPassphraseKey = TLWalletPassphrase.generateWalletPassphraseKey() let walletPassphrase = TLPreferences.getWalletPassphrase(useKeychain) let encryptedWalletPassphrase = TLWalletPassphrase.encryptWalletPassphrase(walletPassphrase!, key: encryptedWalletPassphraseKey) assert(TLPreferences.getEncryptedWalletPassphraseKey() == nil) TLPreferences.setEncryptedWalletPassphraseKey(encryptedWalletPassphraseKey) TLPreferences.setWalletPassphrase(encryptedWalletPassphrase, useKeychain: false) TLPreferences.setEncryptedWalletJSONPassphrase(encryptedWalletPassphrase, useKeychain: false) } class func getDecryptedWalletPassphrase() -> String? { if TLUpdateAppData.instance().beforeUpdatedAppVersion != nil && TLUpdateAppData.instance().beforeUpdatedAppVersion!.hasPrefix("1.0") { if !TLPreferences.canRestoreDeletedApp() { TLWalletPassphrase.disableRecoverableFeature(true) } TLUpdateAppData.instance().beforeUpdatedAppVersion = nil return TLPreferences.getWalletPassphrase(true) } else { let encryptedWalletPassphraseKey = TLPreferences.getEncryptedWalletPassphraseKey() if encryptedWalletPassphraseKey != nil { let encryptedWalletPassphrase = TLPreferences.getWalletPassphrase(TLPreferences.canRestoreDeletedApp()) return TLWalletPassphrase.decryptWalletPassphrase(encryptedWalletPassphrase!, key: encryptedWalletPassphraseKey!) } else { return TLPreferences.getWalletPassphrase(TLPreferences.canRestoreDeletedApp()) } } } class func generateWalletPassphraseKey() -> String { return BTCKey().privateKeyAddress.base58String } class func decryptWalletPassphrase(_ encryptedWalletPassphrase: String, key: String) -> String? { return TLCrypto.decrypt(encryptedWalletPassphrase, password: key) } class func encryptWalletPassphrase(_ walletPassphrase: String, key: String) -> String { return TLCrypto.encrypt(walletPassphrase, password: key) } } ================================================ FILE: ArcBit/model/TLWalletUtils.swift ================================================ // // TLWalletUtils.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation enum TLSendFromType: Int { case hdWallet = 0 case importedAccount = 1 case importedWatchAccount = 2 case importedAddress = 3 case importedWatchAddress = 4 case coldWalletAccount = 5 } enum TLAccountTxType: Int { case send = 0 case receive = 1 case moveBetweenAccount = 2 } enum TLAccountType: Int { case unknown = 0 case hdWallet = 1 case imported = 2 case importedWatch = 3 case coldWallet = 4 } enum TLAccountAddressType: Int { case imported = 1 case importedWatch = 2 } class TLWalletUtils { typealias Error = () -> () typealias Success = () -> () typealias SuccessWithDictionary = (NSDictionary) -> () typealias SuccessWithString = (String!) -> () typealias ErrorWithString = (String?) -> () class func APP_NAME() -> String { return STATIC_MEMBERS.APP_NAME } class func DEFAULT_FEE_AMOUNT_IN_BITCOINS() -> (String) { return TLCurrencyFormat.bitcoinAmountStringToCoin(STATIC_MEMBERS.DEFAULT_FEE_AMOUNT, locale: Locale(identifier: "en_US")).bigIntegerToBitcoinAmountString(TLBitcoinDenomination.bitcoin) } class func RECEIVE_ICON_IMAGE_NAME() -> (String) { return STATIC_MEMBERS.RECEIVE_ICON_IMAGE_NAME } class func SEND_ICON_IMAGE_NAME() -> (String) { return STATIC_MEMBERS.SEND_ICON_IMAGE_NAME } class func HISTORY_ICON_IMAGE_NAME() -> (String) { return STATIC_MEMBERS.HISTORY_ICON_IMAGE_NAME } class func ACCOUNT_ICON_IMAGE_NAME() -> (String) { return STATIC_MEMBERS.ACCOUNT_ICON_IMAGE_NAME } class func HELP_ICON_IMAGE_NAME() -> (String) { return STATIC_MEMBERS.HELP_ICON_IMAGE_NAME } class func LINK_ICON_IMAGE_NAME() -> (String) { return STATIC_MEMBERS.LINK_ICON_IMAGE_NAME } class func SETTINGS_ICON_IMAGE_NAME() -> (String) { return STATIC_MEMBERS.SETTINGS_ICON_IMAGE_NAME } class func VAULT_ICON_IMAGE_NAME() -> (String) { return STATIC_MEMBERS.VAULT_ICON_IMAGE_NAME } struct STATIC_MEMBERS { static let APP_NAME = "ArcBit Wallet" static let WALLET_JSON_CLOUD_BACKUP_FILE_NAME = "wallet.json.asc" static let WALLET_JSON_CLOUD_BACKUP_FILE_EXTENSION = "backup" static let SHOULD_SAVE_ARCHIVED_ADDRESSES_IN_JSON = false static let ENABLE_STEALTH_ADDRESS = false static let ALLOW_MANUAL_SCAN_FOR_STEALTH_PAYMENT = true static let SEND_ICON_IMAGE_NAME = "upload.png" static let RECEIVE_ICON_IMAGE_NAME = "download.png" static let SEND_ICON_2_IMAGE_NAME = "upload2.png" static let RECEIVE_ICON_2_IMAGE_NAME = "download2.png" static let HISTORY_ICON_IMAGE_NAME = "newspaper-alt.png" static let ACCOUNT_ICON_IMAGE_NAME = "data.png" static let HELP_ICON_IMAGE_NAME = "book.png" static let LINK_ICON_IMAGE_NAME = "link.png" static let SETTINGS_ICON_IMAGE_NAME = "settings.png" static let SELECT_ACCOUNT_ICON_IMAGE_NAME = "arrow-right7.png" static let VAULT_ICON_IMAGE_NAME = "vault.png" static let BITCOIN_URI_BASE = "bitcoin:" static let BITCOIN_ISO_CODE = "BTC" static let BITCOIN_SYMBOL = "B" static let DEFAULT_FEE_AMOUNT = "0.0001" } class func ENABLE_STEALTH_ADDRESS() -> (Bool) { return STATIC_MEMBERS.ENABLE_STEALTH_ADDRESS } class func ALLOW_MANUAL_SCAN_FOR_STEALTH_PAYMENT() -> (Bool) { return STATIC_MEMBERS.ALLOW_MANUAL_SCAN_FOR_STEALTH_PAYMENT } class func dataToHexString(_ data: Data) -> String { return (data as NSData).hex() } class func hexStringToData(_ hexString: String) -> Data? { return BTCDataFromHex(hexString) } class func reverseHexString(_ txHashHex: String) -> String { return dataToHexString((hexStringToData(txHashHex)! as NSData).reverse()) } class func getBitcoinURI(_ address: String, amount: TLCoin, label: String?, message: String?) -> String { let bitcoinURI = NSMutableString(string: STATIC_MEMBERS.BITCOIN_URI_BASE) bitcoinURI.append(address) bitcoinURI.append("?amount=") bitcoinURI.append(amount.toString()) if (label != nil && label! != "") { bitcoinURI.append("&label=") bitcoinURI.append(label!) } if (message != nil && message! != "") { bitcoinURI.append("&message=") bitcoinURI.append(message!) } return bitcoinURI as String } class func parseBitcoinURI(_ urlString: String) -> NSDictionary? { if !urlString.hasPrefix("bitcoin:") { return nil } var replaced = urlString.replacingOccurrences(of: "bitcoin:", with: "bitcoin://").replacingOccurrences(of: "////", with: "//") if replaced.range(of: "&") == nil && replaced.range(of: "?") == nil { replaced = replaced+"?" } let url = URL(string: replaced) let dict = parseBitcoinQueryString(url!.query!) if (url!.host != nil) { dict.setObject(url!.host!, forKey: "address" as NSCopying) } return dict } class func parseBitcoinQueryString(_ query: String) -> NSMutableDictionary { let dict = NSMutableDictionary(capacity: 6) let pairs = query.components(separatedBy: "&") for _pair in pairs { let pair = _pair as String let elements = pair.components(separatedBy: "=") if (elements.count >= 2) { let key = elements[0].replacingPercentEscapes(using: String.Encoding.utf8)! let val = elements[1].replacingPercentEscapes(using: String.Encoding.utf8)! dict.setObject(val, forKey: key as NSCopying) } } return dict } class func getQRCodeImage(_ data: String, imageDimension: Int) -> UIImage { let dataMatrix = QREncoder.encode(withECLevel: 1, version: 1, string: data) let image = QREncoder.renderDataMatrix(dataMatrix, imageDimension: Int32(imageDimension)) return image! } } ================================================ FILE: ArcBit/ru.lproj/Localizable.strings ================================================ "" = ""; "%@ is not allowed to access the camera" = "%@ не разрешено доступ к камере"; "%@ servers not reachable." = "%@ серверов недоступны."; "%d/%d parts scanned." = "%d/%d Сканированные части."; "%llu confirmations" = "%lluПодтверждени"; "1 Confirmation" = "1Подтверждение"; "A bitcoin address typically begins with a '1' or '3'. You can see the transactions and track the balance of an address, but you cannot spend from an imported address.\nYou can temporarily import this watch addresses private key to spend its bitcoins. Simply go the Send screen and select a watch address to spend from and you will be prompted to temporarily import your addresses' private key when you click 'Review Payment' in the Send screen. The private key will stay in memory until the app exits or until you remove it manually in the Accounts screen." = "A bitcoin address typically begins with a '1' or '3'. You can see the transactions and track the balance of an address, but you cannot spend from an imported address.\nYou can temporarily import this watch addresses private key to spend its bitcoins. Simply go the Send screen and select a watch address to spend from and you will be prompted to temporarily import your addresses' private key when you click 'Review Payment' in the Send screen. The private key will stay in memory until the app exits or until you remove it manually in the Accounts screen."; "A bitcoin wallet is a software application that allows people to send, receive and manage their bitcoins.\nBe aware of how other bitcoin applications store your bitcoins' private keys, which are needed to spend your bitcoins.\nThere are generally three different ways applications can your store your bitcoins.\n1.\nThe banking model, where your bitcoin private keys are held for you by someone else.\n2.\nThe security box model, where your bitcoin private keys are stored encrypted on someone else’s servers.\n3.\nThe wallet model, where your bitcoin private keys are stored only on your device." = "A bitcoin wallet is a software application that allows people to send, receive and manage their bitcoins.\nBe aware of how other bitcoin applications store your bitcoins' private keys, which are needed to spend your bitcoins.\nThere are generally three different ways applications can your store your bitcoins.\n1.\nThe banking model, where your bitcoin private keys are held for you by someone else.\n2.\nThe security box model, where your bitcoin private keys are stored encrypted on someone else’s servers.\n3.\nThe wallet model, where your bitcoin private keys are stored only on your device."; "A private key begins with an 'L', 'K', or '5'.\nBIP 38 encrypted private keys can also be imported. They can either be imported encrypted or unencrypted. If you choose to import it encrypted, you will need to input the password each time you spend from your encrypted private key." = "A private key begins with an 'L', 'K', or '5'.\nBIP 38 encrypted private keys can also be imported. They can either be imported encrypted or unencrypted. If you choose to import it encrypted, you will need to input the password each time you spend from your encrypted private key."; "Account %@ imported" = "Учетная запись %@已 импортирована"; "Account %lu" = "Учетная запись %lu"; "Account 1" = "Учетная запись 1"; "Account ID" = "ID учетной записи"; "Account ID: %u" = "ID учетной записи: %u"; "Account Private Key" = "Закрытый ключ учетной записи"; "Account Public Key" = "Открытый ключ учетной записи"; "Account private key does not match imported account public key" = "Закрытый ключ учетной записи не соответствует открытому ключу импортированной учетной записи"; "Account private key missing" = "Отсутствует закрытый ключ учетной записи"; "Accounts" = "Учетные записи"; "Achievement List" = "Achievement List"; "Achievements" = "Achievements"; "Actions" = "Действия"; "Active Change Addresses" = "Активные изменяемые адреса"; "Active Main Addresses" = "Активные основные адреса"; "Add Contacts Entry" = "Add Contacts Entry"; "Address" = "Адреса"; "Address ID " = "ID адреса"; "Address ID: %lu" = "ID адреса: %lu"; "Addresses" = "Адреса"; "Advanced Achievement List" = "Advanced Achievement List"; "Advanced FAQ" = "Advanced FAQ"; "Advanced how To:" = "Advanced how To:"; "After a transaction is broadcast to the Bitcoin network, it may be included in a block that is published to the network. When that happens, it is said that the transaction has been mined at a depth of 1 block. With each subsequent block that is found, the number of blocks deep is increased by one. To be secure against double spending, a transaction should not be considered as confirmed until it is a certain number of blocks deep.\nA good rule of thumb is that 1 confirmation is good for small value amounts of bitcoins and a user should wait for more confirmations for larger value amounts.\nArcBit will display the confirmation number up until the 6th confirmation." = "After a transaction is broadcast to the Bitcoin network, it may be included in a block that is published to the network. When that happens, it is said that the transaction has been mined at a depth of 1 block. With each subsequent block that is found, the number of blocks deep is increased by one. To be secure against double spending, a transaction should not be considered as confirmed until it is a certain number of blocks deep.\nA good rule of thumb is that 1 confirmation is good for small value amounts of bitcoins and a user should wait for more confirmations for larger value amounts.\nArcBit will display the confirmation number up until the 6th confirmation."; "Amount:" = "Сумма:"; "An account is a collection of bitcoin addresses. With accounts, you will no longer have to manage bitcoin addresses directly anymore. Since address reuse results in a loss of privacy for people using Bitcoin, ArcBit’s HD wallet account system will automatically handle the cycling of bitcoin addresses for you. This ensures you don’t use the same bitcoin address more then once.\nEach account also has a reusable address. You can find it in your Receive screen. Swipe all the way to right on the QRCode in your Receive screen and you will find a reusable address.\nYou can create an unlimited amount of accounts with ArcBit. See the help section on how to create a new account in ArcBit." = "An account is a collection of bitcoin addresses. With accounts, you will no longer have to manage bitcoin addresses directly anymore. Since address reuse results in a loss of privacy for people using Bitcoin, ArcBit’s HD wallet account system will automatically handle the cycling of bitcoin addresses for you. This ensures you don’t use the same bitcoin address more then once.\nEach account also has a reusable address. You can find it in your Receive screen. Swipe all the way to right on the QRCode in your Receive screen and you will find a reusable address.\nYou can create an unlimited amount of accounts with ArcBit. See the help section on how to create a new account in ArcBit."; "An account private key begins with the letters 'xprv'. You can see, spend and recover the transactions and bitcoins of an entire account from an account private key." = "An account private key begins with the letters 'xprv'. You can see, spend and recover the transactions and bitcoins of an entire account from an account private key."; "An account public key begins with the letters 'xpub'. You can see the transactions and bitcoins of an entire account from an account private key, with the exception of reusable address payments. Future releases will address this issue.\nYou can temporarily import the corresponding account private key for this accounts' account public key to spend your watch accounts' bitcoins. Simply go to the Send screen and select a watch account to spend from and you will be prompted to temporarily import your account's private key when you click 'Review Payment' on the Send screen. The private key will stay in memory until the app exits or until you remove it manually on the Accounts screen." = "An account public key begins with the letters 'xpub'. You can see the transactions and bitcoins of an entire account from an account private key, with the exception of reusable address payments. Future releases will address this issue.\nYou can temporarily import the corresponding account private key for this accounts' account public key to spend your watch accounts' bitcoins. Simply go to the Send screen and select a watch account to spend from and you will be prompted to temporarily import your account's private key when you click 'Review Payment' on the Send screen. The private key will stay in memory until the app exits or until you remove it manually on the Accounts screen."; "ArcBit Brain Wallet" = "Arcbit мысликошелек"; "ArcBit Web Wallet" = "Arcbit Веб-кошелек"; "ArcBit uses the the bitcoin wallet model (See the section ’What is a bitcoin wallet?’ to understand the 3 different security models of bitcoin software). However, if you use iCloud to backup your wallet, you will be using the security box model. It is recommended that you do not use iCloud and be responsible for your bitcoins yourself. For those who don’t want to remember a simple backup passphrase, iCloud backup is a good alternative." = "ArcBit uses the the bitcoin wallet model (See the section ’What is a bitcoin wallet?’ to understand the 3 different security models of bitcoin software). However, if you use iCloud to backup your wallet, you will be using the security box model. It is recommended that you do not use iCloud and be responsible for your bitcoins yourself. For those who don’t want to remember a simple backup passphrase, iCloud backup is a good alternative."; "Archive Account" = "Архивировать учетную запись"; "Archive address" = "Архивировать адрес"; "Archived Accounts" = "Заархивированная учетная запись"; "Archived Change Addresses" = "Заархивированный адрес для сдачи"; "Archived Cold Wallet Accounts" = "Заархивированные учетные записи холодного кошелька"; "Archived Imported Accounts" = "Заархивированные импортированные учетные записи"; "Archived Imported Addresses" = "Заархивированные импортированные адреса"; "Archived Imported Watch Accounts" = "Заархивированная импортированная учетная запись для просмотра"; "Archived Imported Watch Addresses" = "Заархивированные импортированные адреса для просмотра"; "Archived Main Addresses" = "Заархивированные основные адреса"; "Are you sure you want to archive account %@?" = "Вы уверены, что хотите заархивировать учетную запись %@?"; "Are you sure you want to archive address %@?" = "Вы уверены, что хотите заархивировать адреса %@?"; "Are you sure you want to delete this account?" = "Вы уверены, что хотите удалить учетную запись?"; "Are you sure you want to unarchive account %@" = "Вы уверены, что хотите разархивировать учетную запись %@?"; "Are you sure you want to unarchive address %@?" = "Вы уверены, что хотите разархивировать адреса %@?"; "Authorize Cold Wallet Account Payment" = "Авторизовать платеж учетной записи холодного кошелька"; "Authorize Payment" = "Разрешить оплату"; "Backup Passphrase" = "Восстановительная фраза-пароль"; "Backup wallet" = "Резервная копия кошелька"; "Backup local wallet" = "Резервное копирование локального кошелька"; "Backup passphrase found in keychain" = "Резервная парольная фраза найдена в брелках"; "Bitcoin cuts out the middleman and allows you to send money anywhere in the world with an internet connection with minimum to zero fees." = "Bitcoin cuts out the middleman and allows you to send money anywhere in the world with an internet connection with minimum to zero fees."; "Bitcoin, uppercase 'B', is an online payment system invented in 2008 and released as open-source software in 2009 by a programmer named Satoshi Nakamoto. The system is decentralized and peer-to-peer allowing users to transact directly without needing an intermediary.\nBitcoin is also a platform of which other decentralized applications can be built upon. Bitcoin, lowercase 'b' is the currency unit that Bitcoin uses." = "Bitcoin, uppercase 'B', is an online payment system invented in 2008 and released as open-source software in 2009 by a programmer named Satoshi Nakamoto. The system is decentralized and peer-to-peer allowing users to transact directly without needing an intermediary.\nBitcoin is also a platform of which other decentralized applications can be built upon. Bitcoin, lowercase 'b' is the currency unit that Bitcoin uses."; "Bitcoins can be purchased from various bitcoin exchanges. ArcBit is not a bitcoin exchange. ArcBit is a bitcoin wallet. After you purchase some bitcoins from an exchange, you can move it to a bitcoin wallet." = "Bitcoins can be purchased from various bitcoin exchanges. ArcBit is not a bitcoin exchange. ArcBit is a bitcoin wallet. After you purchase some bitcoins from an exchange, you can move it to a bitcoin wallet."; "Cancel" = "Отмена"; "Cannot archive your default account" = "Не удается активировать Вашу учетную запись по умолчанию"; "Cannot archive your one and only account" = "Не удается заархивировать Вашу единственную учетную запись"; "Cannot create transactions with outputs less then %@" = "Невозможно создать транзакции с неизрасходованными средствами, менее чем %@"; "Cannot decrypt iCloud backup wallet." = "Невозможно расшифровать резервный кошелек iCloud."; "Cannot import reusable address" = "Невозможно импортировать многоразовый адрес"; "Change Address ID " = "Изменить ID адреса "; "Change Automatic Transaction Fee" = "Change Automatic Transaction Fee"; "Change Block Explorer URL" = "Изменить блок-проводник URL"; "Change Blockexplorer Type" = "Change Blockexplorer Type"; "Check out the ArcBit Brain Wallet" = "Проверить ArcBit мыслекошелек"; "Check out the ArcBit Web Wallet" = "Проверить ArcBit Веб-кошелек"; "Check out the ArcBit Web Wallet!" = "Проверить ArcBit Веб-кошелек!"; "Checking Transaction" = "Проверка транзакции"; "Clear account private key from memory" = "Очистить закрытый ключ учетной записи из памяти"; "Clear private key from memory" = "Очистить закрытый ключ из памяти"; "Cleared from memory" = "Удалено из памяти"; "Click an address" = "Click an address"; "Click the button with the arrow" = "Click the button with the arrow"; "Click the plus button at the top right" = "Click the plus button at the top right"; "Click the ‘Contacts’ button" = "Click the ‘Contacts’ button"; "Click ‘Accounts’" = "Click ‘Accounts’"; "Click ‘Advanced settings’" = "Click ‘Advanced settings’"; "Click ‘Archive Account’" = "Click ‘Archive Account’"; "Click ‘Create New Account’" = "Click ‘Create New Account’"; "Click ‘Delete’" = "Click ‘Delete’"; "Click ‘Done’" = "Click ‘Done’"; "Click ‘Edit Account Name’" = "Click ‘Edit Account Name’"; "Click ‘Edit’" = "Click ‘Edit’"; "Click ‘Enable PIN Code’" = "Click ‘Enable PIN Code’"; "Click ‘History’" = "Click ‘History’"; "Click ‘Import Account’" = "Click ‘Import Account’"; "Click ‘Import Private Key’" = "Click ‘Import Private Key’"; "Click ‘Import Watch Only Account’" = "Click ‘Import Watch Only Account’"; "Click ‘Import Watch Only Address’" = "Click ‘Import Watch Only Address’"; "Click ‘Label transaction’" = "Click ‘Label transaction’"; "Click ‘Restore Wallet’" = "Click ‘Restore Wallet’"; "Click ‘Restore’" = "Click ‘Restore’"; "Click ‘Review Payment’" = "Click ‘Review Payment’"; "Click ‘Send’" = "Click ‘Send’"; "Click ‘Set Transaction Fee’" = "Click ‘Set Transaction Fee’"; "Click ‘Settings’" = "Click ‘Settings’"; "Click ‘Show Backup Passphrase’" = "Click ‘Show Backup Passphrase’"; "Click ‘View Addresses’" = "Click ‘View Addresses’"; "Click ‘View account private key QR code’" = "Click ‘View account private key QR code’"; "Click ‘View account public key QR code’" = "Click ‘View account public key QR code’"; "Click ‘View address QR code’" = "Click ‘View address QR code’"; "Click ‘View in web’" = "Click ‘View in web’"; "Click ‘View private key QR code’" = "Click ‘View private key QR code’"; "Click ‘blockexplorer API type’" = "Click ‘blockexplorer API type’"; "Click ’Receive’" = "Click ’Receive’"; "Close" = "Закрыть"; "Cold Wallet" = "Холодный Кошелек"; "Cold Wallet Accounts" = "Учетные записи Холодного Кошелька"; "Cold Wallet Accounts can't see reusable address payments, thus this accounts' reusable address is not available." = "Учетные записи холодного кошелька не имеют доступа к просмотру платежей на многоразовые адреса, поэтому многоразовые адреса данной учетной записи недоступны."; "Cold Wallet Overview" = "Обзор холодного кошелька"; "Cold wallet private keys are not stored here and cannot be viewed" = "Закрытые ключи холодного кошелька здесь не хранятся и не могут быть просмотрены."; "Complete" = "Завершить"; "Complete step 1" = "Завершите шаг 1"; "Confirm Payment" = "Подтвердить платеж"; "Confirm Pin Code" = "Подтвердите PIN"; "Contacts" = "Контакты"; "Continue" = "Продолжить"; "Copied To clipboard" = "Скопировано в буфер обмена"; "Copy" = "Копировать"; "Copy Transaction ID to Clipboard" = "Скопировать ID транзакции в буфер обмена"; "Create Cold Wallet" = "Создайте холодный кошелек"; "Create New Account" = "Создайте новую учетную запись"; "Create new contact" = "Создать новый контакт"; "Customize Fee" = "Настроить плату"; "Decrypting" = "Расшифровка"; "Delete" = "Удалить"; "Delete %@" = "Удалить %@"; "Delete Account" = "Удалить учетную запись"; "Delete Contact" = "Удалить контакт?"; "Delete address" = "Удалить адрес"; "Do not use the QR code from here to receive bitcoins. Go to the Receive screen to get a QR code to receive bitcoins." = "Не используйте данный QR-код для получения биткоинов. Перейдите на экран «Получить» для получения QR-кода, чтобы получить биткоины."; "Do you like using ArcBit?" = "Нравится ли Вам использовать ArcBit?"; "Do you want to load and backup your current local wallet file?" = "Вы хотите загрузить и создать резервную копию текущего файла локального кошелька?"; "Do you want to load local wallet file?" = "Вы хотите загрузить локальный файл кошелька?"; "Do you want to restore from your backup passphrase or start a new wallet?" = "Вы хотите восстановить с помощью восстановительной фразы-пароля или начать новый кошелек?"; "Do you want to temporary import your account private key?" = "Вы хотите временно импортировать закрытый ключ Вашей учетной записи?"; "Do you want to temporary import your private key?" = "Вы хотите временно импортировать открытый ключ Вашей учетной записи?"; "Don't remind me" = "Больше не напоминать"; "Done" = "Готово"; "Each account has a public and private account key. Account keys should be kept secret as they are used to view the account's transactions and spend the account's bitcoins." = "Each account has a public and private account key. Account keys should be kept secret as they are used to view the account's transactions and spend the account's bitcoins."; "Edit" = "Изменить"; "Edit Account Name" = "Изменить имя учетной записи"; "Edit Contact Name" = "Изменить имя контакта"; "Edit Label" = "Изменить метку"; "Edit Transaction label" = "Изменить метку транзакции"; "Email Support" = "Написать Email в Службу Поддержки"; "Enable PIN code in settings to better secure your wallet." = "Включите PIN код в настройках для лучшей безопасности Вашего кошелька."; "Enable Pin Code" = "Включить PIN код"; "Enable Transaction Fee" = "Enable Transaction Fee"; "Enable advanced mode" = "Активировать расширенный режим"; "Encountered error creating transaction. Please try again." = "Ошибка при создании транзакции. Пожалуйста, попробуйте еще раз."; "Encrypted" = "Зашифровано"; "Enter Label" = "Введите метку"; "Enter PIN" = "Введите PIN"; "Enter a wallet backup passphrase to wipe the current wallet and start/restore another." = "Введите восстановительную фразу-пароль кошелька, чтобы стереть текущий кошелек и запустить / восстановить другой."; "Enter account private key" = "Введите закрытый ключ учетной записи"; "Enter account public key" = "Введите открытый ключ учетной записи"; "Enter address" = "Введите адрес"; "Enter an account ID and click 'QR Code'. Then on your primary online device, enable Cold Wallet in settings. Then go to the Accounts screen and click 'Import Cold Wallet Account' and scan the Account Public Key QR Code. Afterwards use this cold wallet account as you would a normal account and deposit bitcoins into it. When you want to make a payment from a cold wallet account, go to the next section on the previous screen and follow the step by step instructions there." = "Введите ID учетной записи и нажмите «QR-код». Затем в основном онлайн-устройстве включите параметр «Холодный кошелек» в настройках. Перейдите на экран учетных записей и нажмите «Импортировать учетную запись холодного кошелька» и сканируйте QR-код учетной записи открытого ключа. После этого используйте эту учетную запись холодного кошелька, как обычную учетную запись, и кладите на нее биткоины. Если Вы хотите произвести платеж с учетной записи холодного кошелька, перейдите к следующему разделу на предыдущем экране и следуйте пошаговым инструкциям."; "Enter backup passphrase" = "Enter backup passphrase"; "Enter passphrase for your iCloud backup wallet." = "Введите кодовую фразу для своего резервного кошелька iCloud."; "Enter password for encrypted private key" = "Введите пароль от зашифрованного закрытого ключа"; "Enter the 12 word passphrase that belongs to the cold wallet account that you want to make a payment from. This is the passphrase that was used to generate your account public key that was generated on the 'Create Cold Wallet' section found on the previous screen." = "Введите ключевую фразу-пароль из 12 слов, принадлежащую учетной записи холодного кошелька, из которого Вы хотите произвести платеж. Это ключевая фраза-пароль, которая использовалась для создания открытого ключа Вашей учетной записи, который был создан в разделе «Создать холодный кошелек» на предыдущем экране."; "Error" = "Ошибка"; "Error fetching Transaction." = "Ошибка получения транзакции"; "Error fetching unspent outputs. Try again later." = "Ошибка получения непотраченного. Попробуйте еще раз позже."; "Error fetching unspent outputs. Try again." = "Ошибка получения непотраченного. Попробуйте еще раз."; "Error getting block height." = "Ошибка при получении высоты блока."; "Error importing account" = "Ошибка импорта учетной записи"; "Error loading wallet JSON file" = "Ошибка загрузки файла JSON кошелька"; "Explanation" = "Explanation"; "FAQ" = "FAQ"; "Fee:" = "Комиссия:"; "Fill address field" = "Fill address field"; "Finished Passing Transaction Data" = "Передача данных транзакции завершена"; "First make sure you are using your secondary offline device for this screen (as mentioned in the overview on the previous screen). Click 'New Wallet' and write down or memorize the generated 12 word passphrase. This passphrase can recover and generate all your accounts and the bitcoins associated with it, so keep it safe and to yourself. Also instead of creating a new wallet, you can also input an existing 12 word passphrase that was generated here to create additional accounts." = "Сначала убедитесь, что Вы используете вторичное оффлайн устройство для этого экрана (как указано в обзоре на предыдущем экране). Нажмите «Новый кошелек» и запишите или запомните созданную кодовую фразу из 12 слов. Эта кодовая фраза может восстанавливать и генерировать все Ваши учетные записи и связанные с ней биткоины, поэтому храните ее в безопасности и только у себя. Кроме того, вместо создания нового кошелька Вы также можете ввести существующую кодовую фразу из 12 слов, которая была создана здесь, для создания дополнительных учетных записей."; "Follow us on Twitter" = "Подписывайтесь на нас вTwitter"; "From:" = "От:"; "Funds have been claimed already." = "Средства уже были заявлены."; "Funds imported" = "Средства импортированы"; "Go" = "Идти"; "Go to the side menu" = "Go to the side menu"; "Have sender scan QR code" = "Have sender scan QR code"; "Have sender send you payment" = "Have sender send you payment"; "Help" = "Помогите"; "Here are some features that no other mobile bitcoin wallet supports.\n- Reusable address support\n- Ability to import individual account (extended) keys\n- iCloud backup support\n- Over 150 local currencies supported" = "Here are some features that no other mobile bitcoin wallet supports.\n- Reusable address support\n- Ability to import individual account (extended) keys\n- iCloud backup support\n- Over 150 local currencies supported"; "Hierarchical Deterministic Wallet" = "Hierarchical Deterministic Wallet"; "History" = "История"; "How To:" = "How To:"; "How do I get bitcoins?" = "How do I get bitcoins?"; "How does ArcBit Wallet work?" = "How does ArcBit Wallet work?"; "Import Account" = "Импорт учетной записи"; "Import Cold Wallet Account" = "Импорт учетной записи холодного кошелька"; "Import Feature" = "Import Feature"; "Import Private Key" = "Импорт закрытого ключа"; "Import Private/Encrypted Key" = "Import Private/Encrypted Key"; "Import Watch Account" = "Импорт учетной записи для просмотра"; "Import Watch Address" = "Импорт адреса для просмотра"; "Import private key encrypted or unencrypted?" = "Импорт закрытого ключа зашифрованным или незашифрованным?"; "Import with QR code" = "Импорт с QR кодом"; "Import with text input" = "Импорт с текстовым вводом"; "Imported Account %@" = "Импортирована учетная запись %@"; "Imported Accounts" = "Импортированные учетные записи"; "Imported Address" = "Импортированный адрес"; "Imported Addresses" = "Импортированные адреса"; "Imported Cold Wallet Account %@" = "Импортирована учетная запись холодного кошелька %@"; "Imported Watch Account %@" = "мпортирована учетная запись для просмотра %@"; "Imported Watch Accounts" = "Импортированы учетные записи для просмотра"; "Imported Watch Addresses" = "Импортированы адреса для просмотра"; "Imported Watch Only Accounts can't see reusable address payments, thus this accounts' reusable address is not available. If you want see the reusable address for this account, import the account private key that corresponds to this accounts public key." = "Импортированные учетные записи не имеют доступа к просмотру платежей многоразовых адресов, поэтому этот адрес повторного использования недоступен. Если Вы хотите увидеть многоразовый адрес для этой учетной записи, импортируйте закрытый ключ учетной записи, соответствующий открытому ключу этой учетной записи."; "Importing Account" = "Импорт учетной записи"; "Importing Cold Wallet Account" = "Импорт учетной записи холодного кошелька"; "Importing a Private Key" = "Importing a Private Key"; "Importing a Watch Only Account" = "Importing a Watch Only Account"; "Importing a Watch Only Address" = "Importing a Watch Only Address"; "Importing an Account" = "Importing an Account"; "Importing an encrypted key will require you to input the password every time you want to send bitcoins from it." = "Импортирование зашифрованого ключа потребует ввода пароля каждый раз, когда Вы хотите отправить деньги."; "In advanced mode, you can import bitcoin keys and addresses from other sources. You can import account private keys, account public keys, private keys, and addresses.\nPlease note that your 12 word passphrase cannot recover your bitcoins, so it is recommended that you backup imported keys and addresses separately." = "In advanced mode, you can import bitcoin keys and addresses from other sources. You can import account private keys, account public keys, private keys, and addresses.\nPlease note that your 12 word passphrase cannot recover your bitcoins, so it is recommended that you backup imported keys and addresses separately."; "Incomplete" = "Не завершено"; "Incorrect passphrase, could not decrypt iCloud wallet backup." = "Неправильная кодовая фраза, не может расшифровать резервную копию кошелька iCloud."; "Input a bitcoin address" = "Input a bitcoin address"; "Input a label" = "Input a label"; "Input a new label" = "Input a new label"; "Input a recommended amount. Somewhere between %@ and %@ BTC" = "Input a recommended amount. Somewhere between %@ and %@ BTC"; "Input amount" = "Input amount"; "Input label" = "Input label"; "Input new account name" = "Input new account name"; "Input transaction fee in bitcoins" = "Input transaction fee in bitcoins"; "Instructions" = "Instructions"; "Insufficient Funds" = "Недостаточно средств"; "Insufficient Funds. Account balance is %@ when %@ is required." = "Недостаточно средств. Баланс учетной записи %@ в то время как необходимы %@."; "Insufficient Funds. Account contains bitcoin dust. You can only send up to %@ for now." = "Недостаточно средств. Учетная запись содержит биткоиновую “пыль”. Вы можете отправлять только до %@."; "Internal Wallet Data" = "Данные внутреннего кошелька"; "Internal account transfer" = "Трансфер внутри учетной записи"; "Invalid Address" = "Неверный Биткоин адрес"; "Invalid Passphrase" = "Неверная кодовая фраза-пароль"; "Invalid URL" = "Неверный URL"; "Invalid account private key" = "Неверный закрытый ключ учетной записи"; "Invalid account public Key" = "Неверный открытый ключ учетной записи"; "Invalid amount" = "Неверная сумма"; "Invalid backup passphrase" = "Неверная восстановительная фраза-пароль"; "Invalid private key" = "Неверный закрытый ключ"; "Invalid scanned data" = "Неверные сканированные данные"; "Invalid transaction ID" = "Неверный ID транзакции"; "It is not recommended that you manually manage private keys yourself. A leak of a private key can lead to the compromise of your accounts." = "Не рекомендуется самостоятельно вручную управлять личными ключами. Утечка закрытого ключа может привести к компрометации Ваших учетных записей."; "It is not recommended that you use a regular bitcoin address for multiple payments, but instead you should import a reusable address. Add address anyways?" = "Не рекомендуется использовать обычный адрес для нескольких платежей - рекомендуется импортировать адрес для многоразового использования. Все равно хотите добавить адрес?"; "Label" = "Метка"; "Label Transaction" = "Метка транзакции"; "Local backup to wallet failed!" = "Локальное резервное копирование в кошелек не удалось!"; "Local wallet will be lost. Are you sure you want to restore wallet from iCloud?" = "Локальный кошелек будет потерян. Вы действительно хотите восстановить кошелек от iCloud?"; "Maximum accounts reached" = "Достигнуто максимальное количество учетных записей"; "More" = "Более"; "Name" = "Имя"; "Network Error" = "Ошибка сети"; "New Wallet" = "Новый кошелек"; "New addresses will be automatically generated and cycled for you as you use your current available addresses." = "Новые адреса будут автоматически генерироваться и циклироваться для Вас, когда Вы используете текущие доступные адреса."; "Next" = "Далее"; "No" = "Нет"; "None currently" = "Сейчас нет"; "Not now" = "Не сейчас"; "Now authorize the transaction on your air gap device. When you have done so, click continue on this device to scan the authorized transaction data and make your payment." = "Теперь авторизуйте транзакцию на своем физически изолированном устройстве. Когда Вы это сделаете, нажмите на этом устройстве, чтобы просмотреть авторизированные данные транзакции и внести платеж."; "OK" = "OK"; "On your primary online device, when you want to make a payment from a cold wallet account, simply do it as you normally would on a normal account. When you click 'Send' on the Review Payment screen, instead of the payment going out immediately, you will be prompted to pass the unauthorized transaction data. Then on your secondary offline device, within this screen click 'Scan' to import the transaction so it can be authorized." = "Когда Вы хотите сделать платеж с учетной записи холодного кошелька на своем основном онлайн-устройстве просто сделайте, как обычно, на обычной учетной записи. Когда Вы нажимаете «Отправить» на экране «Обзор платежей», вместо немедленного платежа Вам будет предложено передать данные несанкционированной транзакции. Затем на своем вторичном оффлайн устройстве на этом экране нажмите «Сканировать», чтобы импортировать транзакцию для авторизации."; "Once the transaction has been authorized by completing the above two steps, pass the authorized transaction back to your primary online device to finalize your payment." = "После того, как транзакция будет авторизована путем выполнения вышеуказанных двух шагов, переведите авторизованную транзакцию обратно на основное онлайн-устройство, чтобы завершить платеж"; "Other Links" = "Другие ссылки"; "Pass" = "Передать"; "Passphrase" = "Фраза-пароль"; "Passphrase does not match the transaction" = "Фраза-пароль не соответствует транзакции"; "Password" = "Пароль"; "Payment Index: %lu" = "Индекс платежа: %lu"; "Private key does not match address" = "Закрытый ключ не соответствует адресу"; "Private key missing" = "Не хватает закрытого ключа"; "QR code" = "QR код"; "Quit and re-enter app" = "Quit and re-enter app"; "Rate" = "Оценка"; "Rate us in the App Store!" = "Оцените нас на App Store!"; "Receive" = "Получить"; "Receive Payment" = "Receive Payment"; "Receive Payment From Reusable Address" = "Receive Payment From Reusable Address"; "Remind me Later" = "Напомните мне позже"; "Restore" = "Восстановить"; "Restore Wallet" = "Восстановить кошелек"; "Restore from iCloud" = "Восстановить от iCloud"; "Restoring Wallet" = "Восстановление кошелька"; "Retry" = "Повторите попытку"; "Reusable Address Payment Addresses" = "Адреса платежей на многоразовые адреса"; "Reusable Address:" = "Многоразовые адреса:"; "Reusable Addresses" = "Reusable Addresses"; "Review Payment" = "Обзор платежа"; "Save" = "Сохранить"; "Scan" = "Сканировать"; "Scan For Reusable Address Payment" = "Сканировать платежи на адреса многоразового использования"; "Scan QR Code" = "канировать QR код"; "Scan for reusable address transaction" = "Сканировать транзакции на многоразовые адреса"; "Scan next part" = "Сканируйте следующую часть"; "Scroll down to the section ‘Account Actions’" = "Scroll down to the section ‘Account Actions’"; "Select Account" = "Выбрать учетную запись"; "Select and click a blockexplorer API" = "Select and click a blockexplorer API"; "Select and click a transaction" = "Select and click a transaction"; "Select and click an account" = "Select and click an account"; "Select and click an account to receive from" = "Select and click an account to receive from"; "Select and click an account to view it’s transaction history" = "Select and click an account to view it’s transaction history"; "Select and click an address" = "Select and click an address"; "Send" = "Отправить"; "Send Payment" = "править платеж"; "Send To Address In Contacts" = "Send To Address In Contacts"; "Send authorized payment?" = "Отправить авторизованный платеж?"; "Sending" = "Отправление"; "Sending payment to a reusable address might take longer to show up then a normal transaction with the blockchain.info API. You might have to wait until at least 1 confirmation for the transaction to show up. This is due to the limitations of the blockchain.info API. For reusable address payments to show up faster, configure your app to use the Insight API in advance settings." = "Отправка платежа на многоразовый адрес может занять больше времени, чем отправка обычной транзакции с помощью API-интерфейса blockchain.info. Возможно, Вам придется подождать, пока не появится хотя бы одно подтверждение транзакции. Это связано с ограничениями API-интерфейса blockchain.info. Чтобы многоразовые адресные платежи отображались быстрее, настройте приложение для использования API Insight."; "Sent %@ to %@" = "Отправлено %@ получателю %@"; "Set Transaction Fee in %@" = "Установить комиссию по транзакции %@"; "Settings" = "Настройки"; "Some funds may be pending confirmation and cannot be spent yet. (Check your account history) Account only has a spendable balance of %@" = "Некоторые средства могут находиться в ожидании подтверждения и еще не могут быть потрачены. (Проверьте историю своей учетной записи) Учетная запись имеет только отложенный остаток %@"; "Some people have compared bitcoin addresses to a bank routing number. It is a good analogy, however bitcoin addresses are public. So if you reuse the same bitcoin address for multiple payments like you would a routing number, people will be able to figure out how much bitcoin you have. Thus it is recommended that you only use one address per payment.\nThis causes usability issues making the user use a new address whenever receiving a payment is cumbersome.\nStealth/reusable addresses provides a better solution. When you give a sender a reusable address, the sender will derive a one time regular bitcoin address from the reusable address. Then the sender will send a payment to that regular bitcoin address. Now you can give many people just one reusable address and have them all send you payments without letting other people know how much bitcoin you have.\nA reusable address looks like this vJmxthatTBXibYe9aZavx18iAT9gyiJETGkhwPX2WbHQGuzX83YvQXynD2t8yHU4Xjfonu5x9m6B4yxquytFP1c2CRbVR9mecxesvE. A reusable address is a lot longer then a regular bitcoin address, it is 102 characters in length.\nReusable addresses are great, however there are no other mobile bitcoin wallets but ArcBit that supports reusable addresses for now. Which is why ArcBit supports receiving payments from both regular bitcoin addresses and reusable addresses.\nFor each account, you have one reusable address. You can find it on your Receive screen. Swipe all the way to right on the QRCode on your Receive screen and you will find a reusable address." = "Some people have compared bitcoin addresses to a bank routing number. It is a good analogy, however bitcoin addresses are public. So if you reuse the same bitcoin address for multiple payments like you would a routing number, people will be able to figure out how much bitcoin you have. Thus it is recommended that you only use one address per payment.\nThis causes usability issues making the user use a new address whenever receiving a payment is cumbersome.\nStealth/reusable addresses provides a better solution. When you give a sender a reusable address, the sender will derive a one time regular bitcoin address from the reusable address. Then the sender will send a payment to that regular bitcoin address. Now you can give many people just one reusable address and have them all send you payments without letting other people know how much bitcoin you have.\nA reusable address looks like this vJmxthatTBXibYe9aZavx18iAT9gyiJETGkhwPX2WbHQGuzX83YvQXynD2t8yHU4Xjfonu5x9m6B4yxquytFP1c2CRbVR9mecxesvE. A reusable address is a lot longer then a regular bitcoin address, it is 102 characters in length.\nReusable addresses are great, however there are no other mobile bitcoin wallets but ArcBit that supports reusable addresses for now. Which is why ArcBit supports receiving payments from both regular bitcoin addresses and reusable addresses.\nFor each account, you have one reusable address. You can find it on your Receive screen. Swipe all the way to right on the QRCode on your Receive screen and you will find a reusable address."; "Spending from a cold wallet account" = "Трата из учетной записи холодного кошелька"; "Start fresh" = "Начать свежие"; "Start/Restore Another Wallet" = "Start/Restore Another Wallet"; "Starting Change address ID:" = "Запуск Изменить адрес ID:"; "Starting Receiving Address ID:" = "Начало получения ID адресов:"; "Step 1: Scan transaction to authorize" = "Шаг 1: Сканируйте транзакцию для авторизации"; "Step 2: Input 12 word backup passphrase" = "Шаг 2: Введите 12 слов Вашей восстановительный фразы-пароля"; "Step 3: Pass authorized transaction data" = "Шаг 3: Передайте авторизированные данные транзакции"; "Steps" = "Steps"; "Success" = "Успешно"; "Swipe right on an address" = "Swipe right on an address"; "Swipe to the right on the QR Code Image until you see the reusable address" = "Swipe to the right on the QR Code Image until you see the reusable address"; "Temporarily import account private key" = "Временно импортируйте закрытый ключ учетной записи"; "Temporarily import private key" = "Временно импортируйте закрытый ключа"; "The cold wallet feature will allow you to create accounts which offer better security compared to normal online wallets. You will need 2 devices to use this feature. Your normal day to day device that is connected to the internet and a secondary device that is not connected to the internet (Your secondary device would need to be online once to download the ArcBit app. Afterwards keep the secondary device offline for maximum security). This feature allows you to authorize bitcoin payments from an offline device so that the keys to your bitcoins will never need to be stored on your online device. Follow the step by step instructions by clicking the info buttons within the below sections." = "Функция «холодного кошелька» позволит Вам создавать учетные записи, которые обеспечивают лучшую безопасность, чем обычные онлайн-кошельки. Для использования этой функции Вам понадобятся 2 устройства. Ваше обычное ежедневное устройство, подключенное к Интернету, и второе устройство, которое не подключено к Интернету (для загрузки приложения ArcBit необходимо единожды подключиться к Интернету, а затем отключить устройство в оффлайн режим для максимальной безопасности). Эта функция позволяет Вам авторизовывать биткоин-платежи с оффлайн устройства, так что Ваши ключи от биткоинов не будут храниться на Вашем онлайн-устройстве. Следуйте пошаговым инструкциям, кликая на информационные кнопки в нижеследующих разделах."; "The cold wallet feature will allow you to create accounts which offer better security compared to normal online wallets. You will need 2 devices to use this feature. Your normal day to day device that is connected to the internet and a secondary device that is not connected to the internet (Your secondary device would need to be online once to download the ArcBit app. Afterwards keep the secondary device offline for maximum security). This feature allows you to authorize bitcoin payments from an offline device so that the keys to your bitcoins will never need to be stored on your online device. You can enable the cold wallet feature by going into advanced settings." = "The cold wallet feature will allow you to create accounts which offer better security compared to normal online wallets. You will need 2 devices to use this feature. Your normal day to day device that is connected to the internet and a secondary device that is not connected to the internet (Your secondary device would need to be online once to download the ArcBit app. Afterwards keep the secondary device offline for maximum security). This feature allows you to authorize bitcoin payments from an offline device so that the keys to your bitcoins will never need to be stored on your online device. You can enable the cold wallet feature by going into advanced settings."; "This account type can't see reusable address payments" = "Данный тип учетной записи не может просматривать платежи на многоразовые адреса"; "This feature allows you to manually input a transaction ID and see if the corresponding transaction contains a reusable address payment to your reusable address. If so, then the funds will be added to your wallet. Normally the app will discover reusable address payments automatically for you, but if you believe a payment is missing you can use this feature." = "Эта функция позволяет вам вручную ввести ID транзакции и посмотреть, содержит ли соответствующая транзакция платеж на ваш многразовый адрес. Если это так, то средства будут добавлены в ваш кошелек. Обычно приложение автоматически обнаруживает переадресацию платежей, но если Вы считаете, что платеж отсутствует, Вы можете использовать эту функцию."; "To:" = "Кому:"; "Today" = "Cегодня"; "Toggle Automatic Transaction Fee" = "Toggle Automatic Transaction Fee"; "Toggle ‘Enable Transaction Fee’" = "Toggle ‘Enable Transaction Fee’"; "Toggle ’Enable advanced mode’" = "Toggle ’Enable advanced mode’"; "Total:" = "Всего:"; "Transaction %@ already accounted for." = "Транзакция %@ уже учтена."; "Transaction %@ does not belong to this account." = "Транзакция %@ не принадлежит данной учетной записи."; "Transaction Fee" = "Комиссия по транзакции"; "Transaction ID" = "ID транзакции"; "Transaction ID: %@" = "ID транзакции: %@"; "Transaction authorized" = "Транзакция авторизована"; "Transaction confirmations" = "Transaction confirmations"; "Transaction fees impact how quickly the Bitcoin network will confirm your transactions. Higher fees means faster confirmation times. Default fee behavior can be configured in settings." = "Комиссия по транзакции влияет на то, как быстро сеть Биткоин подтвердит ваши транзакции. Более высокая комиссия дает быстрое подтверждение по времени. Комиссия по умолчанию может быть изменена в настройках."; "Transaction is not a reusable address transaction." = "Транзакция не является транзакцией на многоразовый адрес."; "Transaction needs to be authorized by an offline and air gap device. Send transaction to an offline device for authorization?" = "Транзакция должна быть авторизована онлайн и физически изолированным устройством. Отправить транзакцию оффлайн устройству для авторизации?"; "Transaction needs to be passed back to your online device in order for the payment to be sent" = "Чтобы отправить платеж, транзакция должна быть возвращена на Ваше онлайн-устройство."; "Try Again" = "Попробуйте еще раз"; "Try our new cold wallet feature!" = "Попробуйте нашу новую функцию холодного кошелька!"; "URL does not contain an address." = "URL-адрес не содержит адреса."; "Unable to get dynamic fees. Falling back on fixed transaction fee. (fee can be configured on review payment)" = "Невозможно получить динамическую комиссию. Возврат к фиксированной комиссии по транзакции. (Плата может быть настроена за просмотр)"; "Unarchive Account" = "Разархивированная учетная запись"; "Unarchive address" = "Разархивированный адрес"; "Unarchived address" = "Разархивированный адрес"; "Unconfirmed" = "Не подтверждено"; "Unencrypted" = "Не зашифровано"; "Use ArcBit on your browser to complement the mobile app. The web wallet has all the features that the mobile wallet has plus more!" = "Используйте ArcBit в своем браузере, чтобы дополнить мобильное приложение. Веб-кошелек имеет все функции, как и у мобильного кошелька, плюс дополнительные возможности!"; "Use all funds" = "Использовать все средства"; "View Account Address" = "View Account Address"; "View Account Address In Web" = "View Account Address In Web"; "View Account Addresses" = "View Account Addresses"; "View Account Private Key" = "View Account Private Key"; "View Account Public Key" = "View Account Public Key"; "View Achievements" = "View Achievements"; "View Addresses" = "Показать адреса"; "View ArcBit Brain Wallet Details" = "Показать детали ArcBit мыслекошелька"; "View ArcBit Web Wallet Details" = "Показать детали ArcBit Веб-кошелька"; "View History" = "View History"; "View Private Key" = "View Private Key"; "View Transaction In Web" = "View Transaction In Web"; "View account private key QR code" = "Показать QR код закрытого ключа учетной записи"; "View account public key QR code" = "Показать QR код открытого ключа учетной записи"; "View address QR code" = "Показать QR код адреса"; "View address in web" = "Показать адрес в Веб"; "View in web" = "Показать в Веб"; "View private key QR code" = "Показать закрытый ключ QR код"; "Visit our home page" = "Посетите нашу домашнюю страницу"; "Wallet backup passphrase" = "Восстановительная фраза-пароль кошелька"; "Warning" = "Предупреждение"; "Welcome to ArcBit, a user only controlled Bitcoin wallet. Start using the app now by depositing your Bitcoins here." = "Добро пожаловать в ArcBit - Биткоин-кошелек, контролируемый только пользователем. Начните использовать приложение сейчас, разместив свои биткоины здесь"; "Welcome!" = "Добро пожаловать!"; "What are Account/Extended Keys?" = "What are Account/Extended Keys?"; "What are accounts?" = "What are accounts?"; "What are reusable addresses?" = "What are reusable addresses?"; "What are the benefits and advantages of Bitcoin?" = "What are the benefits and advantages of Bitcoin?"; "What are transaction confirmations?" = "What are transaction confirmations?"; "What is ArcBit's cold wallet feature?" = "What is ArcBit's cold wallet feature?"; "What is Bitcoin?" = "What is Bitcoin?"; "What is a bitcoin wallet?" = "What is a bitcoin wallet?"; "What makes ArcBit different from other bitcoin wallets?" = "What makes ArcBit different from other bitcoin wallets?"; "With an ArcBit cold wallet feature, you can create wallets and make payments offline without exposing your private keys to an internet connected device. This feature is great for storing large amounts of bitcoin or for the security conscious minded. Check out this feature in the cold wallet section in the side menu." = "Благодаря функции холодного хранения ArcBit Вы можете создавать кошельки и совершать платежи в оффлайн режиме, не используя свои личные ключи на устройстве, подключенному к Интернету. Эта функция отлично подходит для хранения большого количества биткоинов и по причинам безопасности. Вы можете найти эту функцию в секции холодного кошелька в боковом меню."; "Write down backup passphrase" = "Write down backup passphrase"; "Write down or memorize your 12 word wallet backup passphrase. You can view it by clicking \"Show backup passphrase\" in Settings. Your wallet backup passphrase is needed to recover your bitcoins." = "Запишите или запомните кодовую фразу из 12 слов, которая может быть найдена в настройках. Восстановительная фраза-пароль из 12 слов необходима для восстановления ваших биткоинов."; "Write down the 12 word passphrase below and keep it safe. This passphrase alone can restore your entire wallets\' bitcoins (excluding imports)." = "Запишите приведенную ниже кодовую фразу-пароль из 12 слов и сохраните ее в безопасности. Эта кодовая фраза может восстановить все ваши кошельки и биткоины (включая импорты)."; "Write down the 12 word passphrase below and keep it safe. This passphrase alone can restore your entire wallets\' bitcoins." = "Запишите приведенную ниже кодовую фразу-пароль из 12 слов и сохраните ее в безопасности. Эта кодовая фраза может восстановить все ваши кошельки и биткоины."; "Yes" = "Да"; "You are making a payment to a reusable address. Make sure that the receiver can see the payment made to them. (All ArcBit reusable addresses are compatible with other ArcBit wallets)" = "Вы совершаете платеж на адрес многоразового использования. Убедитесь, что получатель может видеть отправленные ему платежи. (Все многоразовые адреса ArcBit совместимы с другими кошельками ArcBit)"; "You have %@, but %@ is needed. (This includes the transactions fee)" = " вас есть %@, но необходимо %@. (Сюда уже включена комиссия по транзакции)"; "You must exit and kill this app in order for this to take effect." = "Вы должны выйти и закрыть это приложение, чтобы изменения вступили в силу."; "Your current wallet will be deleted. Your can restore your current wallet later with the wallet passphrase, but any imported accounts or addresses created in advanced mode cannot be recovered. Do you wish to continue?" = "Ваш текущий кошелек будет удален. Вы можете восстановить текущий кошелек с помощью восстановительной фразы-пароля, но любые импортированные учетные записи или адреса, созданные в расширенном режиме, не могут быть восстановлены. Вы хотите продолжить?"; "Your iCloud backup was last saved on %@. Do you want to restore your wallet from iCloud or backup your local wallet to iCloud?" = "Ваша резервная копия iCloud была сохранена в последний раз на% @. Вы хотите восстановить свой кошелек из iCloud или создать резервную копию своего кошелька в iCloud?"; "Your new transaction fee is too high" = "Ваша новая комиссия по транзакции слишком высокая"; "Your wallet is now restored" = "Ваш кошелек восстановлен"; "\nAllow camera access in\n Settings->Privacy->Camera->%@" = "\nРазрешить доступ к камере в\n настройки->Конфиденциальность->камера->%@"; "\tArcBit Web Wallet is a Chrome extension. It has all the features of the mobile wallet plus more. Highlights include the ability to create multiple wallets instead of just one, and a new non-cumbersome way to generate wallets, store and spend bitcoins all from cold storage! ArcBit's new way to manage your cold storage bitcoins also offers a more compelling reason to use ArcBit's watch account feature. Now you can safely watch the balance of your cold storage bitcoins by enabling advance mode in ArcBit and importing your cold storage account public keys.\n\tUse ArcBit Web Wallet in whatever way you wish. You can create a new wallet, or you can input your current 12 word backup passphrase to manage the same bitcoins across different devices. Check out the ArcBit Web Wallet in the Chrome Web Store for more details!\n" = "\tArcBit веб-кошелек - это расширение для Chrome. Он поддерживает все функции мобильного кошелька, а также многое другое. Из основного - возможность создания нескольких кошельков вместо одного, а также новый не громоздкий способ генерации кошельков, хранение и расчёты биткоинами из холодного хранения! Новый способ управления Вашими биткоинами с использованием холодного хранения ArcBit также предлагает более веские основания использовать функцию учетной записи ArcBit. Теперь Вы можете спокойно следить за балансом ваших биткоинов с холодным хранением, включив опционный режим в ArcBit и импортируя открытые ключи учетной записи холодного хранения.\n\tИспользуйте ArcBit веб-кошелек по-разному, как бы Вы хотели: Вы можете создать новый кошелек, или Вы можете ввести свою текущую восстановительную фразу-пароль, которая состоит из 12 слов для управления одними и теми же биткоинами на разных устройствах. Узнайте больше о веб-кошельке ArcBit в интернет-магазине Chrome.\n"; "\tWith the Arcbit Brain Wallet you can safely spend your bitcoins without ever having your private keys be exposed to the internet. It can be use in conjunction with your Arcbit Wallet or as a stand alone wallet.\n" = "\tС Arcbit-мыслекошельком Вы можете безопасно тратить свои биткоины, не используя при этом Ваши личные ключи в Интернете. Его можно использовать в сочетании с вашим Arcbit-кошельком или оффлайн-кошельком.\n"; "iCloud Error: %@" = "Ошибка iCloud: %@"; "iCloud backup found" = "Найдена резервная копия iCloud"; "iCloud backup not found" = "Резервное копирование iCloud не найдено"; "iCloud backup will be lost. Are you sure you want to backup your local wallet to iCloud?" = "Резервное копирование iCloud будет потеряно. Вы действительно хотите сделать резервную копию своего местного кошелька в iCloud?"; "Wallet backup passphrase will be shown" = "Восстановительная фраза-пароль кошелька будет отображена"; "Write down or memorize your wallet backup passphrase. If you lose your backup passphrase, your wallet cannot be recovered." = "Запишите или запишите свою кодовую фразу. Если вы потеряете ключевую фразу для резервного копирования, ваш кошелек не может быть восстановлен."; "I understand" = "я понимаю"; "iCloud support for ArcBit discontinued" = "Поддержка iCloud для ArcBit прекращена"; "iCloud support for ArcBit is being discontinued. If your backup passphrase has not been backed up already, please do so." = "Поддержка iCloud для ArcBit прекращается. Если ваша кодовая фраза резервного копирования еще не была скопирована, сделайте это."; "Reusable address payments are disabled until further notice." = "Повторные платежи адресов отключены до дальнейшего уведомления."; ================================================ FILE: ArcBit/ru.lproj/Main.strings ================================================ /* Class = "UILabel"; text = "BTC"; ObjectID = "3XA-yE-mb3"; */ "3XA-yE-mb3.text" = "BTC"; /* Class = "UINavigationItem"; title = "Restore Wallet"; ObjectID = "4al-pX-1Gn"; */ "4al-pX-1Gn.title" = "Restore Wallet"; /* Class = "UITabBarItem"; title = "Send"; ObjectID = "4mb-2o-3A1"; */ "4mb-2o-3A1.title" = "Send"; /* Class = "UITabBarItem"; title = "Receive"; ObjectID = "4vC-pM-f1o"; */ "4vC-pM-f1o.title" = "Receive"; /* Class = "UILabel"; text = "Amount:"; ObjectID = "59b-yN-GFc"; */ "59b-yN-GFc.text" = "Amount:"; /* Class = "UILabel"; text = "Account Name"; ObjectID = "5EJ-8r-ive"; */ "5EJ-8r-ive.text" = "Account Name"; /* Class = "UILabel"; text = "Master Seed Hex"; ObjectID = "6BY-dx-fLW"; */ "6BY-dx-fLW.text" = "Master Seed Hex"; /* Class = "UITextField"; placeholder = "address"; ObjectID = "6V1-g4-Kqz"; */ "6V1-g4-Kqz.placeholder" = "address"; /* Class = "UILabel"; text = "Label"; ObjectID = "AHj-6Y-pf6"; */ "AHj-6Y-pf6.text" = "Label"; /* Class = "UINavigationItem"; title = "Root View Controller"; ObjectID = "AUN-mh-2Qy"; */ "AUN-mh-2Qy.title" = "Root View Controller"; /* Class = "UILabel"; text = "Wallet backup passphrase"; ObjectID = "C32-6S-BC5"; */ "C32-6S-BC5.text" = "Wallet backup passphrase"; /* Class = "UILabel"; text = "USD"; ObjectID = "DlS-FL-u3H"; */ "DlS-FL-u3H.text" = "USD"; /* Class = "UILabel"; text = "Enter this wallet's backup phrase to wipe it and start/restore another."; ObjectID = "End-6K-7BJ"; */ "End-6K-7BJ.text" = "Enter this wallet's backup phrase to wipe it and start/restore another."; /* Class = "UILabel"; text = "Account Name"; ObjectID = "GKZ-gr-jad"; */ "GKZ-gr-jad.text" = "Account Name"; /* Class = "UILabel"; text = "description"; ObjectID = "HDn-9v-o1a"; */ "HDn-9v-o1a.text" = "description"; /* Class = "UILabel"; text = "Label"; ObjectID = "JHD-rx-W8M"; */ "JHD-rx-W8M.text" = "Label"; /* Class = "UIButton"; normalTitle = "Contacts"; ObjectID = "JvL-Cg-kNK"; */ "JvL-Cg-kNK.normalTitle" = "Contacts"; /* Class = "UITabBarItem"; title = "Send"; ObjectID = "KIz-U8-UaX"; */ "KIz-U8-UaX.title" = "Send"; /* Class = "UIButton"; normalTitle = "Button"; ObjectID = "Koj-wy-VOb"; */ "Koj-wy-VOb.normalTitle" = "Button"; /* Class = "UILabel"; text = "100 Confimations"; ObjectID = "Lf7-5m-ufO"; */ "Lf7-5m-ufO.text" = "100 Confimations"; /* Class = "UITextView"; text = "f23324e18c237876f96d2064abbc26399cc3cd34d503cc6f2b00a9403b003dcad2cf7642e4e3311cf2e5de32307d88b116ac743f0b02fc4c2afe0e30dcd843f6"; ObjectID = "N96-Pu-vSQ"; */ "N96-Pu-vSQ.text" = "f23324e18c237876f96d2064abbc26399cc3cd34d503cc6f2b00a9403b003dcad2cf7642e4e3311cf2e5de32307d88b116ac743f0b02fc4c2afe0e30dcd843f6"; /* Class = "UILabel"; text = "Receive From:"; ObjectID = "OX8-v3-aou"; */ "OX8-v3-aou.text" = "Receive From:"; /* Class = "UILabel"; text = "Label"; ObjectID = "P5j-13-Jpg"; */ "P5j-13-Jpg.text" = "Label"; /* Class = "UILabel"; text = "Label"; ObjectID = "P8X-aW-lhi"; */ "P8X-aW-lhi.text" = "Label"; /* Class = "UILabel"; text = "date"; ObjectID = "RAj-d0-yzE"; */ "RAj-d0-yzE.text" = "date"; /* Class = "UILabel"; text = "From:"; ObjectID = "VQ0-4Z-YAo"; */ "VQ0-4Z-YAo.text" = "From:"; /* Class = "UINavigationItem"; title = "Manage Accounts"; ObjectID = "WPt-OD-Zry"; */ "WPt-OD-Zry.title" = "Manage Accounts"; /* Class = "UITextView"; text = "Loading..."; ObjectID = "YlR-nU-l4e"; */ "YlR-nU-l4e.text" = "Loading..."; /* Class = "UIButton"; normalTitle = "Review Payment"; ObjectID = "b97-9w-wKT"; */ "b97-9w-wKT.normalTitle" = "Review Payment"; /* Class = "UINavigationItem"; title = "Send"; ObjectID = "bCV-xP-8V6"; */ "bCV-xP-8V6.title" = "Send"; /* Class = "UILabel"; text = "Account Name"; ObjectID = "cmA-Xp-Fwi"; */ "cmA-Xp-Fwi.text" = "Account Name"; /* Class = "UILabel"; text = "Write down the 12 word passphrase below and keep it safe. You can restore your wallet with this single passphrase."; ObjectID = "evC-L0-0Dc"; */ "evC-L0-0Dc.text" = "Write down the 12 word passphrase below and keep it safe. You can restore your wallet with this single passphrase."; /* Class = "UITabBarItem"; title = "Receive"; ObjectID = "hWO-u0-HoS"; */ "hWO-u0-HoS.title" = "Receive"; /* Class = "UILabel"; text = "Label"; ObjectID = "i0i-6T-FsV"; */ "i0i-6T-FsV.text" = "Label"; /* Class = "UIButton"; normalTitle = "Scan QR"; ObjectID = "i9k-eB-RT8"; */ "i9k-eB-RT8.normalTitle" = "Scan QR"; /* Class = "UINavigationItem"; title = "History"; ObjectID = "kF7-DP-jfw"; */ "kF7-DP-jfw.title" = "History"; /* Class = "UINavigationItem"; title = "Help"; ObjectID = "m13-Co-pa3"; */ "m13-Co-pa3.title" = "Help"; /* Class = "UINavigationItem"; title = "Contacts"; ObjectID = "ob3-Xc-rR5"; */ "ob3-Xc-rR5.title" = "Contacts"; /* Class = "UITextView"; text = "nice muse brush descend pattern focus bleed college sneak calm leap flight"; ObjectID = "p18-DP-CtY"; */ "p18-DP-CtY.text" = "nice muse brush descend pattern focus bleed college sneak calm leap flight"; /* Class = "UIButton"; normalTitle = "Button"; ObjectID = "q2V-qX-91M"; */ "q2V-qX-91M.normalTitle" = "Button"; /* Class = "UILabel"; text = "Account Name"; ObjectID = "qKO-FH-M15"; */ "qKO-FH-M15.text" = "Account Name"; /* Class = "UINavigationItem"; title = "Receive"; ObjectID = "wZG-Zf-k4F"; */ "wZG-Zf-k4F.title" = "Receive"; /* Class = "UILabel"; text = "The master seed hex is derived from the passphrase. If you want to import Bitcoins into another wallet, and if that other wallets don't use BIP39, then it's possible to import the Bitcoins using the master seed hex as long as they support BIP44."; ObjectID = "wjz-SH-FCO"; */ "wjz-SH-FCO.text" = "The master seed hex is derived from the passphrase. If you want to import Bitcoins into another wallet, and if that other wallets don't use BIP39, then it's possible to import the Bitcoins using the master seed hex as long as they support BIP44."; /* Class = "UILabel"; text = "To:"; ObjectID = "xuQ-JE-0IT"; */ "xuQ-JE-0IT.text" = "To:"; /* Class = "UILabel"; text = "From:"; ObjectID = "yUe-8I-uLU"; */ "yUe-8I-uLU.text" = "From:"; /* Class = "UINavigationItem"; title = "Passphrase"; ObjectID = "zdP-16-weC"; */ "zdP-16-weC.title" = "Passphrase"; ================================================ FILE: ArcBit/utils/TLColors.swift ================================================ // // TLColors.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit class TLColors { struct Static { static var color:UIColor? = nil } class func mainAppColor() -> UIColor { if Static.color == nil { let r = 36.0/255.0 let g = 171/255.0 let b = 220/255.0 Static.color = UIColor(red: CGFloat(r), green:CGFloat(g), blue:CGFloat(b), alpha:1) } return Static.color! } class func mainAppOppositeColor() -> UIColor { return UIColor.white } class func evenTableViewCellColor() -> UIColor { return UIColor.white } class func oddTableViewCellColor() -> UIColor { return UIColor.white } } ================================================ FILE: ArcBit/utils/TLHUDWrapper.swift ================================================ // // TLHUDWrapper.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit class TLHUDWrapper { struct STATIC_MEMBERS { static var LOADING_BACKGROUND_VIEW_TAG = 618033 static var BACKGROUND_VIEW_ALPHA = 0.7 } class func showHUDAddedTo(_ view:UIView, labelText:String, animated:Bool) -> () { let subView = UIView(frame: view.frame) subView.backgroundColor = UIColor.black subView.alpha = CGFloat(STATIC_MEMBERS.BACKGROUND_VIEW_ALPHA) subView.tag = STATIC_MEMBERS.LOADING_BACKGROUND_VIEW_TAG AppDelegate.instance().window!.addSubview(subView) let hud = MBProgressHUD.showAdded(to: AppDelegate.instance().window, animated:animated) hud?.labelText = labelText } class func hideHUDForView(_ view:UIView, animated:(Bool)) -> () { MBProgressHUD.hide(for: AppDelegate.instance().window, animated:true) for subview in AppDelegate.instance().window!.subviews { if (subview.tag == STATIC_MEMBERS.LOADING_BACKGROUND_VIEW_TAG) { subview.removeFromSuperview() break } } } } ================================================ FILE: ArcBit/utils/TLMacros.swift ================================================ // // TLMacros.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation func DLog(_ message: String, function: AnyObject) { #if DEBUG if(function is NSObject) { let obj = function as! NSObject NSLog(String(format: message, obj))} else { NSLog(String(format: message, "")) } #endif } func DLog(_ message: String) { #if DEBUG NSLog("%@", message) #endif } ================================================ FILE: ArcBit/utils/TLNotificationEvents.swift ================================================ // // TLNotificationEvents.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation class TLNotificationEvents { class func EVENT_WALLET_PAYLOAD_UPDATED() -> String { return "event.wallet.payload.updated"} class func EVENT_ACCOUNT_SELECTED() -> String { return "event.account.selected"} class func EVENT_ADDRESS_SELECTED() -> String { return "event.address.selected"} class func EVENT_PREFERENCES_FIAT_DISPLAY_CHANGED() -> String { return "pref.fiatcurrencychanged"} class func EVENT_PREFERENCES_BITCOIN_DISPLAY_CHANGED() -> String { return "pref.bitcoindisplaychanged"} class func EVENT_DISPLAY_LOCAL_CURRENCY_TOGGLED() -> String { return "pref.displaylocalcurrencychanged"} class func EVENT_ADVANCE_MODE_TOGGLED() -> String { return "pref.advancemode"} class func EVENT_FETCHED_ADDRESS() -> String { return "api.address"} class func EVENT_FETCHED_ADDRESSES_DATA() -> String { return "api.multiaddress"} class func EVENT_NEW_UNCONFIRMED_TRANSACTION() -> String { return "api.newunconfirmedtx"} class func EVENT_NEW_BLOCK() -> String { return "api.newblock"} class func EVENT_TRANSACTION_LISTENER_OPEN() -> String { return "api.transaction.listener.open"} class func EVENT_TRANSACTION_LISTENER_CLOSE() -> String { return "api.transaction.listener.close"} class func EVENT_STEALTH_PAYMENT_LISTENER_OPEN() -> String { return "api.stealth.payment.listener.open"} class func EVENT_STEALTH_PAYMENT_LISTENER_CLOSE() -> String { return "api.stealth.payment.listener.close"} class func EVENT_RECEIVED_STEALTH_CHALLENGE() -> String { return "api.stealth.challenge"} class func EVENT_RECEIVED_STEALTH_ADDRESS_SUBSCRIPTION() -> String { return "api.stealth.address.subscription"} class func EVENT_RECEIVED_STEALTH_PAYMENT() -> String { return "api.stealth.payment"} class func EVENT_EXCHANGE_RATE_UPDATED() -> String { return "api.updated.exchangerate"} class func EVENT_MODEL_UPDATED_NEW_UNCONFIRMED_TRANSACTION() -> String { return "model.updated.newunconfirmedtx"} class func EVENT_MODEL_UPDATED_NEW_BLOCK() -> String { return "model.updated.newblock"} class func EVENT_NEW_ADDRESS_GENERATED() -> String { return "app.newaddressgenerated"} class func EVENT_UPDATED_RECEIVING_ADDRESSES() -> String { return "app.updatedreceivingaddresses"} class func EVENT_VIEW_QR_CODE_PRIVATE_KEY() -> String { return "trigger.feature.qrcode.private.key"} class func EVENT_VIEW_QR_CODE_ADDRESS() -> String { return "trigger.feature.qrcode.address"} class func EVENT_VIEW_QR_CODE_EXTENDED_PRIVATE_KEY() -> String { return "trigger.feature.qrcode.extended.private.key"} class func EVENT_VIEW_QR_CODE_EXTENDED_PUBLIC_KEY() -> String { return "trigger.feature.qrcode.extended.public.key"} class func EVENT_VIEW_QR_CODE_ACCOUNT_ADDRESSES() -> String { return "trigger.feature.qrcode.private.key"} class func EVENT_VIEW_QR_CODE_ACCOUNT_ADDRESSES_WEBVIEW() -> String { return "trigger.feature.qrcode.private.key"} class func EVENT_ENTER_MNEMONIC_VIEWCONTROLLER_DISMISSED() -> String {return "EnterMnemonicViewControllerDismissed"} class func EVENT_SEND_SCREEN_LOADING() -> String { return "event.feature.send.screen.loading"} class func EVENT_VIEW_SEND_SCREEN() -> String { return "event.view.send.screen"} class func EVENT_VIEW_RECEIVE_SCREEN() -> String { return "event.view.receive.screen"} class func EVENT_VIEW_ACCOUNTS_SCREEN() -> String { return "event.view.accounts!.screen"} class func EVENT_VIEW_MANAGE_ACCOUNTS_SCREEN() -> String { return "event.view.manageaccounts.screen"} class func EVENT_VIEW_HELP_SCREEN() -> String { return "event.view.help.screen"} class func EVENT_VIEW_COLD_WALLET_SCREEN() -> String { return "event.view.cold.wallet.screen"} class func EVENT_VIEW_SETTINGS_SCREEN() -> String { return "event.view.settings.screen"} class func EVENT_HAMBURGER_MENU_OPENED() -> String { return "event.feature.hamburger.menu.opened"} class func EVENT_HAMBURGER_MENU_CLOSED() -> String { return "event.feature.hamburger.menu.closed"} class func EVENT_SEND_PAYMENT () -> String { return "event.send.payment"} class func EVENT_RECEIVE_PAYMENT () -> String { return "event.receive.payment"} class func EVENT_RECEIVE_PAYMENT_FROM_STEALTH_ADDRESS () -> String { return "event.receive.payment.from.stealth.address"} class func EVENT_VIEW_HISTORY () -> String { return "event.view.history.screen"} class func EVENT_CREATE_NEW_ACCOUNT() -> String { return "event.create.new.account"} class func EVENT_EDIT_ACCOUNT_NAME() -> String { return "event.edit.account.name"} class func EVENT_ARCHIVE_ACCOUNT () -> String { return "event.archive.account"} class func EVENT_ENABLE_PIN_CODE () -> String { return "event.enable.pin.code"} class func EVENT_BACKUP_PASSPHRASE () -> String { return "event.backup.passphrase"} class func EVENT_RESTORE_WALLET() -> String { return "event.restorewallet"} class func EVENT_ADD_TO_ADDRESS_BOOK () -> String { return "event.add.to.address.book"} class func EVENT_EDIT_ENTRY_ADDRESS_BOOK () -> String { return "event.edit.entry.address.book"} class func EVENT_DELETE_ENTRY_ADDRESS_BOOK () -> String { return "event.delete.entry.address.book"} class func EVENT_SEND_TO_ADDRESS_IN_ADDRESS_BOOK () -> String { return "event.send.to.address.in.address.book"} class func EVENT_TAG_TRANSACTION () -> String { return "event.label.trasaction"} class func EVENT_TOGGLE_AUTOMATIC_TX_FEE () -> String { return "event.toggle.automatic.transaction.fee"} class func EVENT_CHANGE_AUTOMATIC_TX_FEE () -> String { return "event.change.automatic.transaction.fee"} class func EVENT_VIEW_ACCOUNT_ADDRESSES() -> String { return "event.view.account.addresses"} class func EVENT_VIEW_ACCOUNT_ADDRESS() -> String { return "event.view.account.address"} class func EVENT_VIEW_ACCOUNT_ADDRESS_IN_WEB() -> String { return "event.view.account.address.web"} class func EVENT_VIEW_TRANSACTION_IN_WEB() -> String { return "event.view.transaction.web"} class func EVENT_ENABLE_ADVANCE_MODE() -> String { return "event.view.enable.advance.mode"} class func EVENT_IMPORT_COLD_WALLET_ACCOUNT() -> String { return "event.import.cold.wallet.account"} class func EVENT_IMPORT_ACCOUNT() -> String { return "event.import.account"} class func EVENT_IMPORT_WATCH_ONLY_ACCOUNT() -> String { return "event.import.watch.only.account"} class func EVENT_IMPORT_PRIVATE_KEY() -> String { return "event.import.private.key"} class func EVENT_IMPORT_WATCH_ONLY_ADDRESS() -> String { return "event.import.watch.only.addresss"} class func EVENT_CHANGE_BLOCKEXPLORER_TYPE() -> String { return "event.change.blockexplorer.type"} class func EVENT_VIEW_EXTENDED_PUBLIC_KEY() -> String { return "event.view.extended.public.key"} class func EVENT_VIEW_EXTENDED_PRIVATE_KEY() -> String { return "event.view.extended.private.key"} class func EVENT_VIEW_ACCOUNT_PRIVATE_KEY() -> String { return "event.view.account.private.key"} } ================================================ FILE: ArcBit/utils/TLPrompts.swift ================================================ // // TLPrompts.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit class TLPrompts { typealias EnterPINSuccess = () -> () typealias EnterPINFailure = (Bool) -> () typealias Failure = (Bool) -> () typealias UserInputCallback = (String!) -> () class func promptAlertController(_ vc: UIViewController, title: String, message: String, okText: String, cancelTx: String, success: @escaping TLWalletUtils.Success , failure: @escaping Failure) -> () { UIAlertController.showAlert(in: vc, withTitle: title, message: message, cancelButtonTitle: cancelTx, destructiveButtonTitle: nil, otherButtonTitles: [okText], tap: {(alertView, action, buttonIndex) in if (buttonIndex == alertView?.firstOtherButtonIndex) { success() } else if (buttonIndex == alertView?.cancelButtonIndex) { failure(true) } }) } class func promtForInputText(_ vc:UIViewController, title: String, message: String, textFieldPlaceholder: String?, success: @escaping UserInputCallback , failure: @escaping Failure) -> () { UIAlertController.showAlert(in: vc, withTitle: title, message: message, preferredStyle: .alert, cancelButtonTitle: TLDisplayStrings.CANCEL_STRING(), destructiveButtonTitle: nil, otherButtonTitles: [TLDisplayStrings.OK_STRING()], preShow: {(controller) in func addPromptTextField(_ textField: UITextField!){ textField.placeholder = textFieldPlaceholder } controller!.addTextField(configurationHandler: addPromptTextField) } , tap: {(alertView, action, buttonIndex) in if (buttonIndex == alertView!.firstOtherButtonIndex) { let inputedText = (alertView!.textFields![0] ).text success(inputedText) } else if (buttonIndex == alertView!.cancelButtonIndex) { failure(true) } }) } class func promtForOKCancel(_ vc: UIViewController, title: String, message: String, success: @escaping TLWalletUtils.Success , failure: @escaping Failure) -> () { UIAlertController.showAlert(in: vc, withTitle: title, message: message, cancelButtonTitle: TLDisplayStrings.CANCEL_STRING(), destructiveButtonTitle: nil, otherButtonTitles: [TLDisplayStrings.OK_STRING()], tap: {(alertView, action, buttonIndex) in if (buttonIndex == alertView!.firstOtherButtonIndex) { success() } else if (buttonIndex == alertView!.cancelButtonIndex) { failure(true) } }) } class func promptWithOneButton(_ vc: UIViewController, title: String, message: String, buttonText: String, success: @escaping TLWalletUtils.Success) -> () { UIAlertController.showAlert(in: vc, withTitle: title, message: message, cancelButtonTitle: buttonText, destructiveButtonTitle: nil, otherButtonTitles: [], tap: {(alertView, action, buttonIndex) in success() }) } class func promtForOK(_ vc:UIViewController, title: String, message: String, success: @escaping TLWalletUtils.Success) -> () { UIAlertController.showAlert(in: vc, withTitle: title, message: message, cancelButtonTitle: nil, destructiveButtonTitle: nil, otherButtonTitles: [TLDisplayStrings.OK_STRING()], tap: {(alertView, action, buttonIndex) in success() }) } class func promptForTempararyImportPrivateKey(_ viewController: UIViewController, success: @escaping TLWalletUtils.SuccessWithString, error: @escaping TLWalletUtils.ErrorWithString) -> () { UIAlertController.showAlert(in: viewController, withTitle: TLDisplayStrings.PRIVATE_KEY_MISSING_STRING(), message: TLDisplayStrings.DO_YOU_WANT_TO_TEMPORARY_IMPORT_YOUR_PRIVATE_KEY_STRING(), cancelButtonTitle: TLDisplayStrings.NO_STRING(), destructiveButtonTitle: nil, otherButtonTitles: [TLDisplayStrings.YES_STRING()], tap: {(alertView, action, buttonIndex) in if (buttonIndex == alertView?.firstOtherButtonIndex) { UIAlertController.showAlert(in: viewController, withTitle: "", message:"", preferredStyle: .actionSheet, cancelButtonTitle: TLDisplayStrings.CANCEL_STRING(), destructiveButtonTitle: nil, otherButtonTitles: [TLDisplayStrings.IMPORT_WITH_QR_CODE_STRING(), TLDisplayStrings.IMPORT_WITH_TEXT_INPUT_STRING()], tap: {(actionSheet, action, buttonIndex) in if (buttonIndex == (actionSheet?.firstOtherButtonIndex)! + 0) { AppDelegate.instance().showPrivateKeyReaderController(viewController, success: { (data: NSDictionary) in let encryptedPrivateKey = data.object(forKey: "encryptedPrivateKey") as? String if encryptedPrivateKey == nil { success(data.object(forKey: "privateKey") as? String) } }, error: { (data: String?) in error(data) }) } else if (buttonIndex == (actionSheet?.firstOtherButtonIndex)! + 1) { TLPrompts.promtForInputText(viewController, title: TLDisplayStrings.TEMPORARY_IMPORT_PRIVATE_KEY_STRING(), message: "", textFieldPlaceholder: nil, success: { (inputText: String!) in success(inputText) }, failure: { (cancelled: Bool) in }) } else if (buttonIndex == actionSheet?.cancelButtonIndex) { } }) } else if (buttonIndex == alertView?.cancelButtonIndex) { error("") } }) } class func promptForTempararyImportExtendedPrivateKey(_ viewController: UIViewController, success: @escaping TLWalletUtils.SuccessWithString, error: @escaping TLWalletUtils.ErrorWithString) -> () { UIAlertController.showAlert(in: viewController, withTitle: TLDisplayStrings.ACCOUNT_PRIVATE_KEY_MISSING_STRING(), message: TLDisplayStrings.ASK_TEMPORARY_IMPORT_ACCOUNT_PRIVATE_KEY_STRING(), cancelButtonTitle: TLDisplayStrings.NO_STRING(), destructiveButtonTitle: nil, otherButtonTitles: [TLDisplayStrings.YES_STRING()], tap: {(alertView, action, buttonIndex) in if (buttonIndex == alertView?.firstOtherButtonIndex) { UIAlertController.showAlert(in: viewController, withTitle: "", message:"", preferredStyle: .actionSheet, cancelButtonTitle: TLDisplayStrings.CANCEL_STRING(), destructiveButtonTitle: nil, otherButtonTitles: [TLDisplayStrings.IMPORT_WITH_QR_CODE_STRING(), TLDisplayStrings.IMPORT_WITH_TEXT_INPUT_STRING()], tap: {(actionSheet, action, buttonIndex) in if (buttonIndex == (actionSheet?.firstOtherButtonIndex)! + 0) { AppDelegate.instance().showExtendedPrivateKeyReaderController(viewController, success: { (data: String!) in success(data) }, error: { (data: String?) in error(data) }) } else if (buttonIndex == (actionSheet?.firstOtherButtonIndex)! + 1) { TLPrompts.promtForInputText(viewController, title:TLDisplayStrings.TEMPORARY_IMPORT_ACCOUNT_PRIVATE_KEY_STRING(), message: TLDisplayStrings.INPUT_ACCOUNT_PRIVATE_KEY_STRING(), textFieldPlaceholder: nil, success: { (inputText: String!) in success(inputText) }, failure: { (cancelled: Bool) in }) } else if (buttonIndex == actionSheet?.cancelButtonIndex) { } }) } else if (buttonIndex == alertView?.cancelButtonIndex) { error("") } }) } class func promptForEncryptedPrivKeyPassword(_ vc:UIViewController, view: UIView, encryptedPrivKey: String, success: @escaping UserInputCallback, failure: @escaping Failure) -> () { UIAlertController.showAlert(in: vc, withTitle: TLDisplayStrings.ENTER_PASSWORD_FOR_ENCRYPTED_PRIVATE_KEY_STRING(), message: "", preferredStyle: .alert, cancelButtonTitle: TLDisplayStrings.CANCEL_STRING(), destructiveButtonTitle: nil, otherButtonTitles: [TLDisplayStrings.OK_STRING()], preShow: {(controller) in func addPromptTextField(_ textField: UITextField!){ textField.placeholder = TLDisplayStrings.PASSWORD_STRING() } controller!.addTextField(configurationHandler: addPromptTextField) }, tap: {(alertView, action, buttonIndex) in if (buttonIndex == alertView!.firstOtherButtonIndex) { TLHUDWrapper.showHUDAddedTo(view, labelText: TLDisplayStrings.DECRYPTING_PRIVATE_KEY_STRING(), animated: true) let password = (alertView!.textFields![0] ).text DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.high).async { let privKey = TLCoreBitcoinWrapper.privateKeyFromEncryptedPrivateKey(encryptedPrivKey, password: password!, isTestnet: AppDelegate.instance().appWallet.walletConfig.isTestnet) DispatchQueue.main.async { TLHUDWrapper.hideHUDForView(view, animated: true) if (privKey != nil) { success(privKey) } else { UIAlertController.showAlert(in: vc, withTitle: TLDisplayStrings.INVALID_PASSPHRASE_STRING(), message: "", cancelButtonTitle: TLDisplayStrings.CANCEL_STRING(), destructiveButtonTitle: nil, otherButtonTitles: [TLDisplayStrings.RETRY_STRING()], tap: {(alertView, action, buttonIndex) in if (buttonIndex == alertView!.firstOtherButtonIndex) { self.promptForEncryptedPrivKeyPassword(vc, view:view, encryptedPrivKey: encryptedPrivKey, success: success, failure: failure) } else if (buttonIndex == alertView!.cancelButtonIndex) { failure(true) } }) } } } } else if (buttonIndex == alertView!.cancelButtonIndex) { failure(true) } }) } class func promptSuccessMessage(_ title: String?, message: String) -> () { let av = UIAlertView(title: title ?? "", message: message, delegate: nil, cancelButtonTitle: nil, otherButtonTitles: TLDisplayStrings.OK_STRING()) av.show() } class func promptErrorMessage(_ title: String, message: String) -> () { let av = UIAlertView(title: title, message: message, delegate: nil, cancelButtonTitle: nil, otherButtonTitles: TLDisplayStrings.OK_STRING()) av.show() } } ================================================ FILE: ArcBit/utils/TLQRImageModal.swift ================================================ // // TLQRImageModal.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit @objc class TLQRImageModal:NSObject { fileprivate let alertView:CustomIOS7AlertView let QRcodeDisplayData:String init(data:NSString, buttonCopyText:(String), vc:(UIViewController)) { QRcodeDisplayData = data as String let QR_CODE_IMAGE_DIMENSION_IPHONE = CGFloat(300.0) let QR_CODE_IMAGE_DIMENSION_IPAD = CGFloat(450.0) let DATA_LABEL_HEIGHT_IPHONE = CGFloat(60.0) let DATA_LABEL_HEIGHT_IPAD = CGFloat(120.0) let QRCodeImageDimension:CGFloat let dataLabelHeight:CGFloat if (UIDevice.current.userInterfaceIdiom == .phone) { QRCodeImageDimension = QR_CODE_IMAGE_DIMENSION_IPHONE dataLabelHeight = DATA_LABEL_HEIGHT_IPHONE } else { QRCodeImageDimension = QR_CODE_IMAGE_DIMENSION_IPAD dataLabelHeight = DATA_LABEL_HEIGHT_IPAD } let containerView = UIView(frame:CGRect(x: 0, y: 0, width: QRCodeImageDimension, height: QRCodeImageDimension+dataLabelHeight)) let QRCodeImage = TLWalletUtils.getQRCodeImage(data as String, imageDimension:Int(QRCodeImageDimension)) let qrCodeImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: QRCodeImageDimension, height: QRCodeImageDimension)) qrCodeImageView.image = QRCodeImage containerView.addSubview(qrCodeImageView) alertView = CustomIOS7AlertView() let titleLabel = UITextView(frame:CGRect(x: 0, y: QRCodeImageDimension, width: QRCodeImageDimension, height: dataLabelHeight)) titleLabel.text = data as String titleLabel.textAlignment = .center titleLabel.isUserInteractionEnabled = false titleLabel.backgroundColor = TLColors.mainAppColor() titleLabel.textColor = TLColors.mainAppOppositeColor() titleLabel.layer.cornerRadius = 5.0 containerView.addSubview(titleLabel) alertView.containerView = containerView alertView.buttonTitles = [buttonCopyText, TLDisplayStrings.DISMISS_STRING()] //TODO: refactor, find cleaner way to do this if (vc is TLManageAccountsViewController) { alertView.delegate = vc as! TLManageAccountsViewController } else if (vc is TLAddressListViewController) { alertView.delegate = vc as! TLAddressListViewController } else if (vc is TLCreateColdWalletViewController) { alertView.delegate = vc as! TLCreateColdWalletViewController } else if (vc is TLAuthorizeColdWalletPaymentViewController) { alertView.delegate = vc as! TLAuthorizeColdWalletPaymentViewController } else if (vc is TLBrainWalletViewController) { alertView.delegate = vc as! TLBrainWalletViewController } else if (vc is TLReviewPaymentViewController) { alertView.delegate = vc as! TLReviewPaymentViewController } } func show() -> () { alertView.show() NotificationCenter.default.addObserver(self, selector:#selector(TLQRImageModal.dismissDialog(_:)), name:NSNotification.Name.UIApplicationDidEnterBackground, object:nil) } func dismissDialog(_ notification: Notification) -> () { for subview in alertView.dialogView.subviews { if (subview is UIButton) { let button = subview as! UIButton if (button.titleLabel!.text == TLDisplayStrings.DISMISS_STRING()) { button.sendActions(for: .touchUpInside) } } } } deinit { NotificationCenter.default.removeObserver(self, name:NSNotification.Name.UIApplicationDidEnterBackground, object:nil) } } ================================================ FILE: ArcBit/utils/TLStringLocalized.swift ================================================ // // TLMacros.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation extension String { var localized: String { return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "") } func localizedWithComment(_ comment:String) -> String { return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: comment) } } ================================================ FILE: ArcBit/utils/TLUpdateAppData.swift ================================================ // // TLUpdateAppData.swift // ArcBit // // Created by Tim Lee on 8/20/15. // Copyright (c) 2015 ArcBit. All rights reserved. // import Foundation class TLUpdateAppData { struct STATIC_MEMBERS { static var instance:TLUpdateAppData? } var beforeUpdatedAppVersion: String? = nil class func instance() -> (TLUpdateAppData) { if(STATIC_MEMBERS.instance == nil) { STATIC_MEMBERS.instance = TLUpdateAppData() } return STATIC_MEMBERS.instance! } } ================================================ FILE: ArcBit/utils/TLUtils.swift ================================================ // // TLUtils.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation class TLUtils { class func getiOSVersion() -> Int { let versionArray = UIDevice.current.systemVersion.components(separatedBy: ".") return (versionArray[0] as NSString).integerValue } //TODO: better way class func isIPhone5() -> Bool { return UIScreen.main.bounds.size.height == 568 } class func isIPhone4() -> Bool { return UIScreen.main.bounds.size.height == 480 } class func defaultAppName() -> String { return "ArcBit" } class func daysSinceDate(_ date: Date) -> Int { let nowDate:Date = Date() let calendar: Calendar = Calendar.current let dateComponents = calendar.dateComponents([Calendar.Component.day], from: calendar.startOfDay(for: date), to: calendar.startOfDay(for: nowDate)) return dateComponents.day! } class func dictionaryToJSONString(_ prettyPrint: Bool, dict: NSDictionary) -> String { let jsonData: Data? do { jsonData = try JSONSerialization.data(withJSONObject: dict, options: (prettyPrint ? JSONSerialization.WritingOptions.prettyPrinted : JSONSerialization.WritingOptions(rawValue: 0)) as JSONSerialization.WritingOptions) } catch _ as NSError { jsonData = nil } assert(jsonData != nil, "jsonData not valid") return NSString(data: jsonData!, encoding: String.Encoding.utf8.rawValue)! as String } class func JSONStringToDictionary(_ jsonString: String) -> NSDictionary { var error: NSError? = nil let jsonData = jsonString.data(using: String.Encoding.utf8) let jsonDict:Any? do { jsonDict = try JSONSerialization.jsonObject(with: jsonData!, options: JSONSerialization.ReadingOptions.mutableContainers) } catch let error1 as NSError { error = error1 jsonDict = nil } assert(error == nil, "Invalid JSON string") return jsonDict as! NSDictionary } } ================================================ FILE: ArcBit/utils/TransitionDelegate.swift ================================================ // // TransitionDelegate.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import UIKit class TransitionDelegate: NSObject, UIViewControllerTransitioningDelegate { func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { return nil; } func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return nil; } } ================================================ FILE: ArcBit/utils/UINavigationController+StatusBarStyle.swift ================================================ // // UINavigationController+StatusBarStyle.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit extension UINavigationController { // work around to get status bar text color to be white: http://stackoverflow.com/a/21433339/299648 open override var preferredStatusBarStyle : UIStatusBarStyle { return UIStatusBarStyle.lightContent } } ================================================ FILE: ArcBit/utils/UIView+FormScroll.swift ================================================ // // UIView+FormScroll.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit extension UIView{ public func scrollToY(_ y:Float) -> () { UIView.beginAnimations("registerScroll", context:nil) UIView.setAnimationCurve(.easeInOut) UIView.setAnimationDuration(0.4) transform = CGAffineTransform(translationX: 0, y: CGFloat(y)) UIView.commitAnimations() } public func scrollToView(_ view:UIView) -> () { let OFFSET_Y = CGFloat(70.0) let theFrame = view.frame var y = theFrame.origin.y - 15 y -= (y/1.7) - 60 if (-y+OFFSET_Y < 0) { scrollToY(Float(-y+OFFSET_Y)) } else { scrollToY(Float(-y)) } } public func scrollElement(_ view:UIView, toPoint y:Float) -> () { let theFrame = view.frame let orig_y = theFrame.origin.y let diff = y - Float(orig_y) if (diff < 0) { scrollToY(diff) } else { scrollToY(0) } } } ================================================ FILE: ArcBit/utils/UIViewController+Extras.swift ================================================ // // UIViewController+Extras.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit extension UIViewController { public func showSendView() -> () { slidingViewController().topViewController = storyboard!.instantiateViewController(withIdentifier: "SendNav") } } ================================================ FILE: ArcBit/utils/UIViewController+Style.swift ================================================ // // UIViewController+Style.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit extension UIViewController { public func setLogoImageView() -> () { let image = UIImage(named:"360X80logo.png") let imageView = UIImageView(frame:CGRect(x: 0, y: 0, width: 180, height: 40)) imageView.contentMode = UIViewContentMode.scaleToFill imageView.contentMode = UIViewContentMode.scaleAspectFit imageView.image = image imageView.backgroundColor = TLColors.mainAppColor() navigationItem.titleView = imageView // Add dummy rightBarButtonItem so that self.navigationItem.titleView does not extend to far to the right let dummyBarButtonItem = UIBarButtonItem(title: "", style: UIBarButtonItemStyle.plain, target: self, action: nil) navigationItem.rightBarButtonItem = dummyBarButtonItem // work around to hide rightBarButtonItem navigationItem.rightBarButtonItem!.tintColor = TLColors.mainAppColor() } public func setColors() -> () { if(navigationController != nil) { navigationController!.navigationBar.fixedHeightWhenStatusBarHidden = true navigationController!.navigationBar.barTintColor = TLColors.mainAppColor() navigationController!.navigationBar.tintColor = TLColors.mainAppOppositeColor() navigationController!.navigationBar.isTranslucent = false navigationController!.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: TLColors.mainAppOppositeColor()] navigationController!.navigationItem.rightBarButtonItem = nil } if self.slidingViewController() != nil { slidingViewController().topViewController.view.layer.shadowOpacity = 0.75 slidingViewController().topViewController.view.layer.shadowRadius = 10.0 slidingViewController().topViewController.view.layer.shadowColor = UIColor.black.cgColor } } public func setNavigationBarColors(_ navigationBar:UINavigationBar) -> () { navigationBar.barTintColor = TLColors.mainAppColor() navigationBar.tintColor = TLColors.mainAppOppositeColor() navigationBar.isTranslucent = false navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: TLColors.mainAppOppositeColor()] navigationBar.barTintColor = TLColors.mainAppColor() view.backgroundColor = TLColors.mainAppColor() } } ================================================ FILE: ArcBit/viewControllers/TLAccountTableViewCell.swift ================================================ // // TLAccountTableViewCell.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit @objc(TLAccountTableViewCell) class TLAccountTableViewCell:UITableViewCell { required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } @IBOutlet var accountNameLabel : UILabel? @IBOutlet var accountBalanceButton : UIButton? override func awakeFromNib() { super.awakeFromNib() accountBalanceButton!.backgroundColor = TLColors.mainAppColor() self.accountBalanceButton!.setTitleColor(TLColors.mainAppOppositeColor(), for:UIControlState()) self.accountBalanceButton!.titleLabel!.adjustsFontSizeToFitWidth = true } override init(style: UITableViewCellStyle, reuseIdentifier: String?) { super.init(style: UITableViewCellStyle.subtitle, reuseIdentifier: reuseIdentifier) } override func setSelected(_ selected:Bool, animated:Bool) -> () { super.setSelected(selected, animated:animated) } @IBAction fileprivate func accountBalanceButtonClicked(_ sender:UIButton) { TLPreferences.setDisplayLocalCurrency(!TLPreferences.isDisplayLocalCurrency()) TLPreferences.setInAppSettingsKitDisplayLocalCurrency(TLPreferences.isDisplayLocalCurrency()) } } ================================================ FILE: ArcBit/viewControllers/TLAccountsViewController.swift ================================================ // // TLAccountsViewController.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import UIKit @objc(TLAccountsViewController) class TLAccountsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } @IBOutlet fileprivate var accountsTableView: UITableView? fileprivate var numberOfSections = 0 fileprivate var accountListSection = 0 fileprivate var coldWalletAccountSection = 0 fileprivate var importedAccountSection = 0 fileprivate var importedWatchAccountSection = 0 fileprivate var importedAddressSection = 0 fileprivate var importedWatchAddressSection = 0 fileprivate var accountRefreshControl: UIRefreshControl? override func viewDidLoad() { super.viewDidLoad() setColors() accountListSection = 0 self.accountsTableView!.delegate = self self.accountsTableView!.dataSource = self self.accountsTableView!.tableFooterView = UIView(frame: CGRect.zero) NotificationCenter.default.addObserver(self, selector: #selector(TLAccountsViewController.refreshWalletAccountsNotification(_:)), name: NSNotification.Name(rawValue: TLNotificationEvents.EVENT_DISPLAY_LOCAL_CURRENCY_TOGGLED()), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(TLAccountsViewController.refreshWalletAccountsNotification(_:)), name: NSNotification.Name(rawValue: TLNotificationEvents.EVENT_FETCHED_ADDRESSES_DATA()), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(TLAccountsViewController.accountsTableViewReloadDataWrapper(_:)), name: NSNotification.Name(rawValue: TLNotificationEvents.EVENT_ADVANCE_MODE_TOGGLED()), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(TLAccountsViewController.accountsTableViewReloadDataWrapper(_:)), name: NSNotification.Name(rawValue: TLNotificationEvents.EVENT_EXCHANGE_RATE_UPDATED()), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(TLAccountsViewController.accountsTableViewReloadDataWrapper(_:)), name: NSNotification.Name(rawValue: TLNotificationEvents.EVENT_MODEL_UPDATED_NEW_UNCONFIRMED_TRANSACTION()), object: nil) accountRefreshControl = UIRefreshControl() accountRefreshControl!.addTarget(self, action: #selector(TLAccountsViewController.refresh(_:)), for: .valueChanged) self.accountsTableView!.addSubview(accountRefreshControl!) self.checkToRecoverAccounts() self.refreshWalletAccounts(false) } func refresh(_ refreshControl: UIRefreshControl) { self.refreshWalletAccounts(true) accountRefreshControl!.endRefreshing() } override func viewWillAppear(_ animated: Bool) { self.refreshWalletAccounts(false) } override func viewDidAppear(_ animated: Bool) -> () { NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_VIEW_ACCOUNTS_SCREEN()), object: nil, userInfo: nil) } fileprivate func checkToRecoverAccounts() { if (AppDelegate.instance().aAccountNeedsRecovering()) { TLHUDWrapper.showHUDAddedTo(self.slidingViewController().topViewController!.view, labelText: TLDisplayStrings.RESTORING_WALLET_STRING(), animated: true) DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.high).async { AppDelegate.instance().checkToRecoverAccounts() DispatchQueue.main.async { self.refreshWalletAccounts(false) TLHUDWrapper.hideHUDForView(self.view, animated: true) } } } } fileprivate func refreshColdWalletAccounts(_ fetchDataAgain: Bool) { for i in stride(from: 0, to: AppDelegate.instance().coldWalletAccounts!.getNumberOfAccounts(), by: 1) { let accountObject = AppDelegate.instance().coldWalletAccounts!.getAccountObjectForIdx(i) let indexPath = IndexPath(row: i, section:coldWalletAccountSection) if (!accountObject.hasFetchedAccountData() || fetchDataAgain) { let cell = self.accountsTableView!.cellForRow(at: indexPath) as? TLAccountTableViewCell if cell != nil { (cell!.accessoryView as! UIActivityIndicatorView).isHidden = false cell!.accountBalanceButton!.isHidden = true (cell!.accessoryView as! UIActivityIndicatorView).startAnimating() } AppDelegate.instance().pendingOperations.addSetUpAccountOperation(accountObject, fetchDataAgain: fetchDataAgain, success: { if cell != nil { (cell!.accessoryView as! UIActivityIndicatorView).stopAnimating() (cell!.accessoryView as! UIActivityIndicatorView).isHidden = true cell!.accountBalanceButton!.isHidden = false if accountObject.downloadState == .downloaded { let balance = TLCurrencyFormat.getProperAmount(accountObject.getBalance()) cell!.accountBalanceButton!.setTitle(balance as String, for: UIControlState()) } cell!.accountBalanceButton!.isHidden = false } }) } else { if let cell = self.accountsTableView!.cellForRow(at: indexPath) as? TLAccountTableViewCell { cell.accountNameLabel!.text = accountObject.getAccountName() let balance = TLCurrencyFormat.getProperAmount(accountObject.getBalance()) cell.accountBalanceButton!.setTitle(balance as String, for: UIControlState()) } } } } fileprivate func refreshImportedAccounts(_ fetchDataAgain: Bool) { for i in stride(from: 0, to: AppDelegate.instance().importedAccounts!.getNumberOfAccounts(), by: 1) { let accountObject = AppDelegate.instance().importedAccounts!.getAccountObjectForIdx(i) let indexPath = IndexPath(row: i, section: importedAccountSection) if (!accountObject.hasFetchedAccountData() || fetchDataAgain) { let cell = self.accountsTableView!.cellForRow(at: indexPath) as? TLAccountTableViewCell if cell != nil { (cell!.accessoryView as! UIActivityIndicatorView).isHidden = false cell!.accountBalanceButton!.isHidden = true (cell!.accessoryView as! UIActivityIndicatorView).startAnimating() } AppDelegate.instance().pendingOperations.addSetUpAccountOperation(accountObject, fetchDataAgain: fetchDataAgain, success: { if cell != nil { (cell!.accessoryView as! UIActivityIndicatorView).stopAnimating() (cell!.accessoryView as! UIActivityIndicatorView).isHidden = true cell!.accountBalanceButton!.isHidden = false if accountObject.downloadState == .downloaded { let balance = TLCurrencyFormat.getProperAmount(accountObject.getBalance()) cell!.accountBalanceButton!.setTitle(balance as String, for: UIControlState()) } cell!.accountBalanceButton!.isHidden = false } }) } else { if let cell = self.accountsTableView!.cellForRow(at: indexPath) as? TLAccountTableViewCell { cell.accountNameLabel!.text = accountObject.getAccountName() let balance = TLCurrencyFormat.getProperAmount(accountObject.getBalance()) cell.accountBalanceButton!.setTitle(balance as String, for: UIControlState()) } } } } fileprivate func refreshImportedWatchAccounts(_ fetchDataAgain: Bool) { for i in stride(from: 0, to: AppDelegate.instance().importedWatchAccounts!.getNumberOfAccounts(), by: 1) { let accountObject = AppDelegate.instance().importedWatchAccounts!.getAccountObjectForIdx(i) let indexPath = IndexPath(row: i, section:importedWatchAccountSection) if (!accountObject.hasFetchedAccountData() || fetchDataAgain) { let cell = self.accountsTableView!.cellForRow(at: indexPath) as? TLAccountTableViewCell if cell != nil { (cell!.accessoryView as! UIActivityIndicatorView).isHidden = false cell!.accountBalanceButton!.isHidden = true (cell!.accessoryView as! UIActivityIndicatorView).startAnimating() } AppDelegate.instance().pendingOperations.addSetUpAccountOperation(accountObject, fetchDataAgain: fetchDataAgain, success: { if cell != nil { (cell!.accessoryView as! UIActivityIndicatorView).stopAnimating() (cell!.accessoryView as! UIActivityIndicatorView).isHidden = true cell!.accountBalanceButton!.isHidden = false if accountObject.downloadState == .downloaded { let balance = TLCurrencyFormat.getProperAmount(accountObject.getBalance()) cell!.accountBalanceButton!.setTitle(balance as String, for: UIControlState()) } cell!.accountBalanceButton!.isHidden = false } }) } else { if let cell = self.accountsTableView!.cellForRow(at: indexPath) as? TLAccountTableViewCell { cell.accountNameLabel!.text = accountObject.getAccountName() let balance = TLCurrencyFormat.getProperAmount(accountObject.getBalance()) cell.accountBalanceButton!.setTitle(balance as String, for: UIControlState()) } } } } fileprivate func refreshImportedAddressBalances(_ fetchDataAgain: Bool) { if (AppDelegate.instance().importedAddresses!.getCount() > 0 && (!AppDelegate.instance().importedAddresses!.hasFetchedAddressesData() || fetchDataAgain)) { for i in stride(from: 0, to: AppDelegate.instance().importedAddresses!.getCount(), by: 1) { let indexPath = IndexPath(row: i, section: importedAddressSection) if let cell = self.accountsTableView!.cellForRow(at: indexPath) as? TLAccountTableViewCell { (cell.accessoryView as! UIActivityIndicatorView).isHidden = false cell.accountBalanceButton!.isHidden = true (cell.accessoryView as! UIActivityIndicatorView).startAnimating() } } AppDelegate.instance().pendingOperations.addSetUpImportedAddressesOperation(AppDelegate.instance().importedAddresses!, fetchDataAgain: fetchDataAgain, success: { for i in stride(from: 0, to: AppDelegate.instance().importedAddresses!.getCount(), by: 1) { let indexPath = IndexPath(row: i, section: self.importedAddressSection) if let cell = self.accountsTableView!.cellForRow(at: indexPath) as? TLAccountTableViewCell { (cell.accessoryView as! UIActivityIndicatorView).stopAnimating() (cell.accessoryView as! UIActivityIndicatorView).isHidden = true if AppDelegate.instance().importedAddresses!.downloadState == .downloaded { let importAddressObject = AppDelegate.instance().importedAddresses!.getAddressObjectAtIdx(i) let balance = TLCurrencyFormat.getProperAmount(importAddressObject.getBalance()!) cell.accountBalanceButton!.setTitle(balance as String, for: UIControlState()) } cell.accountBalanceButton!.isHidden = false } } }) } } fileprivate func refreshImportedWatchAddressBalances(_ fetchDataAgain: Bool) { if (AppDelegate.instance().importedWatchAddresses!.getCount() > 0 && (!AppDelegate.instance().importedWatchAddresses!.hasFetchedAddressesData() || fetchDataAgain)) { for i in stride(from: 0, to: AppDelegate.instance().importedWatchAddresses!.getCount(), by: 1) { let indexPath = IndexPath(row: i, section: importedWatchAddressSection) if let cell = self.accountsTableView!.cellForRow(at: indexPath) as? TLAccountTableViewCell { (cell.accessoryView as! UIActivityIndicatorView).isHidden = false cell.accountBalanceButton!.isHidden = true (cell.accessoryView as! UIActivityIndicatorView).startAnimating() } } AppDelegate.instance().pendingOperations.addSetUpImportedAddressesOperation(AppDelegate.instance().importedWatchAddresses!, fetchDataAgain: fetchDataAgain, success: { for i in stride(from: 0, to: AppDelegate.instance().importedWatchAddresses!.getCount(), by: 1) { let indexPath = IndexPath(row: i, section: self.importedWatchAddressSection) if let cell = self.accountsTableView!.cellForRow(at: indexPath) as? TLAccountTableViewCell { (cell.accessoryView as! UIActivityIndicatorView).stopAnimating() (cell.accessoryView as! UIActivityIndicatorView).isHidden = true if AppDelegate.instance().importedWatchAddresses!.downloadState == .downloaded { let importAddressObject = AppDelegate.instance().importedWatchAddresses!.getAddressObjectAtIdx(i) let balance = TLCurrencyFormat.getProperAmount(importAddressObject.getBalance()!) cell.accountBalanceButton!.setTitle(balance as String, for: UIControlState()) } cell.accountBalanceButton!.isHidden = false } } }) } } fileprivate func refreshAccountBalances(_ fetchDataAgain: Bool) { for i in stride(from: 0, to: AppDelegate.instance().accounts!.getNumberOfAccounts(), by: 1) { let accountObject = AppDelegate.instance().accounts!.getAccountObjectForIdx(i) let indexPath = IndexPath(row: i, section: accountListSection) if (!accountObject.hasFetchedAccountData() || fetchDataAgain) { let cell = self.accountsTableView!.cellForRow(at: indexPath) as? TLAccountTableViewCell if cell != nil { (cell!.accessoryView as! UIActivityIndicatorView).isHidden = false cell!.accountBalanceButton!.isHidden = true (cell!.accessoryView as! UIActivityIndicatorView).startAnimating() } AppDelegate.instance().pendingOperations.addSetUpAccountOperation(accountObject, fetchDataAgain: fetchDataAgain, success: { if cell != nil { (cell!.accessoryView as! UIActivityIndicatorView).stopAnimating() (cell!.accessoryView as! UIActivityIndicatorView).isHidden = true cell!.accountBalanceButton!.isHidden = false if accountObject.downloadState == .downloaded { let balance = TLCurrencyFormat.getProperAmount(accountObject.getBalance()) cell!.accountBalanceButton!.setTitle(balance as String, for: UIControlState()) } cell!.accountBalanceButton!.isHidden = false } }) } else { if let cell = self.accountsTableView!.cellForRow(at: indexPath) as? TLAccountTableViewCell { cell.accountNameLabel!.text = accountObject.getAccountName() let balance = TLCurrencyFormat.getProperAmount(accountObject.getBalance()) cell.accountBalanceButton!.setTitle(balance as String, for: UIControlState()) } } } } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } func refreshWalletAccountsNotification(_ notification: Notification) { self.refreshWalletAccounts(false) } fileprivate func refreshWalletAccounts(_ fetchDataAgain: Bool) { self._accountsTableViewReloadDataWrapper() self.refreshAccountBalances(fetchDataAgain) if TLPreferences.enabledColdWallet() { self.refreshColdWalletAccounts(fetchDataAgain) } if (TLPreferences.enabledAdvancedMode()) { self.refreshImportedAccounts(fetchDataAgain) self.refreshImportedWatchAccounts(fetchDataAgain) self.refreshImportedAddressBalances(fetchDataAgain) self.refreshImportedWatchAddressBalances(fetchDataAgain) } } fileprivate func setUpCellAccounts(_ accountObject: TLAccountObject, cell: TLAccountTableViewCell, cellForRowAtIndexPath indexPath: IndexPath) { cell.accountNameLabel!.text = accountObject.getAccountName() if (accountObject.hasFetchedAccountData()) { (cell.accessoryView as! UIActivityIndicatorView).isHidden = true (cell.accessoryView as! UIActivityIndicatorView).stopAnimating() let balance = TLCurrencyFormat.getProperAmount(accountObject.getBalance()) cell.accountBalanceButton!.setTitle(balance as String, for: UIControlState()) cell.accountBalanceButton!.isHidden = false } else { (cell.accessoryView as! UIActivityIndicatorView).isHidden = false (cell.accessoryView as! UIActivityIndicatorView).startAnimating() AppDelegate.instance().pendingOperations.addSetUpAccountOperation(accountObject, fetchDataAgain: false, success: { (cell.accessoryView as! UIActivityIndicatorView).stopAnimating() (cell.accessoryView as! UIActivityIndicatorView).isHidden = true if accountObject.downloadState == .downloaded { let balance = TLCurrencyFormat.getProperAmount(accountObject.getBalance()) cell.accountBalanceButton!.setTitle(balance as String, for: UIControlState()) cell.accountBalanceButton!.isHidden = false } }) } } fileprivate func setUpCellImportedAddresses(_ importedAddressObject: TLImportedAddress, cell: TLAccountTableViewCell, cellForRowAtIndexPath indexPath: IndexPath) { let label = importedAddressObject.getLabel() cell.accountNameLabel!.text = label if (importedAddressObject.hasFetchedAccountData()) { (cell.accessoryView as! UIActivityIndicatorView).isHidden = true (cell.accessoryView as! UIActivityIndicatorView).stopAnimating() let balance = TLCurrencyFormat.getProperAmount(importedAddressObject.getBalance()!) cell.accountBalanceButton!.setTitle(balance as String, for: UIControlState()) cell.accountBalanceButton!.isHidden = false } } func _accountsTableViewReloadDataWrapper() { numberOfSections = 1 var sectionCounter = 1 if TLPreferences.enabledColdWallet() { if (AppDelegate.instance().coldWalletAccounts!.getNumberOfAccounts() > 0) { coldWalletAccountSection = sectionCounter sectionCounter += 1 numberOfSections += 1 } else { coldWalletAccountSection = NSIntegerMax } } if (TLPreferences.enabledAdvancedMode()) { if (AppDelegate.instance().importedAccounts!.getNumberOfAccounts() > 0) { importedAccountSection = sectionCounter sectionCounter += 1 numberOfSections += 1 } else { importedAccountSection = NSIntegerMax } if (AppDelegate.instance().importedWatchAccounts!.getNumberOfAccounts() > 0) { importedWatchAccountSection = sectionCounter sectionCounter += 1 numberOfSections += 1 } else { importedWatchAccountSection = NSIntegerMax } if (AppDelegate.instance().importedAddresses!.getCount() > 0) { importedAddressSection = sectionCounter sectionCounter += 1 numberOfSections += 1 } else { importedAddressSection = NSIntegerMax } if (AppDelegate.instance().importedWatchAddresses!.getCount() > 0) { importedWatchAddressSection = sectionCounter sectionCounter += 1 numberOfSections += 1 } else { importedWatchAddressSection = NSIntegerMax } } self.accountsTableView!.reloadData()} func accountsTableViewReloadDataWrapper(_ notification: Notification) { _accountsTableViewReloadDataWrapper() } fileprivate func doSelectFrom(_ sendFromType: TLSendFromType, sendFromIndex: NSInteger) { var viewControllersIdx = self.navigationController!.viewControllers.count - 2 // make sure viewControllersIdx not negative if (self.navigationController!.viewControllers.count < 2) { viewControllersIdx = 0 } self.navigationController!.popToViewController((self.navigationController!.viewControllers as NSArray).object(at: viewControllersIdx) as! UIViewController, animated: true) let selectedDict = ["sendFromType": sendFromType.rawValue, "sendFromIndex": sendFromIndex] NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_ACCOUNT_SELECTED()), object: selectedDict, userInfo: nil) } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { // hard code height here to prevent cell auto-resizing return 74 } func numberOfSections(in tableView: UITableView) -> Int { return numberOfSections } func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { if (TLPreferences.enabledAdvancedMode()) { if (section == accountListSection) { return TLDisplayStrings.ACCOUNTS_STRING() } else if (section == coldWalletAccountSection) { return TLDisplayStrings.COLD_WALLET_ACCOUNTS_STRING() } else if (section == importedAccountSection) { return TLDisplayStrings.IMPORTED_ACCOUNTS_STRING() } else if (section == importedWatchAccountSection) { return TLDisplayStrings.IMPORTED_WATCH_ACCOUNTS_STRING() } else if (section == importedAddressSection) { return TLDisplayStrings.IMPORTED_ADDRESSES_STRING() } else if (section == importedWatchAddressSection) { return TLDisplayStrings.IMPORTED_WATCH_ADDRESSES_STRING() } else { } } else { if (section == accountListSection) { return TLDisplayStrings.ACCOUNTS_STRING() } else { } } return nil } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if (TLPreferences.enabledColdWallet() && section == coldWalletAccountSection) { return AppDelegate.instance().coldWalletAccounts!.getNumberOfAccounts() } if (TLPreferences.enabledAdvancedMode()) { if (section == accountListSection) { return AppDelegate.instance().accounts!.getNumberOfAccounts() } else if (section == importedAccountSection) { return AppDelegate.instance().importedAccounts!.getNumberOfAccounts() } else if (section == importedWatchAccountSection) { return AppDelegate.instance().importedWatchAccounts!.getNumberOfAccounts() } else if (section == importedAddressSection) { return AppDelegate.instance().importedAddresses!.getCount() } else if (section == importedWatchAddressSection) { return AppDelegate.instance().importedWatchAddresses!.getCount() } else { } } else { if (section == accountListSection) { return AppDelegate.instance().accounts!.getNumberOfAccounts() } else { } } return 0 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let MyIdentifier = "AccountCellIdentifier" var cell = tableView.dequeueReusableCell(withIdentifier: MyIdentifier) as! TLAccountTableViewCell? if (cell == nil) { cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: MyIdentifier) as? TLAccountTableViewCell } let activityView = UIActivityIndicatorView(activityIndicatorStyle: .gray) cell!.accessoryView = (activityView) cell!.accountBalanceButton!.titleLabel!.adjustsFontSizeToFitWidth = true if (TLPreferences.enabledColdWallet() && (indexPath as NSIndexPath).section == coldWalletAccountSection) { let accountObject = AppDelegate.instance().coldWalletAccounts!.getAccountObjectForIdx((indexPath as NSIndexPath).row) self.setUpCellAccounts(accountObject, cell: cell!, cellForRowAtIndexPath: indexPath) } if (TLPreferences.enabledAdvancedMode()) { if ((indexPath as NSIndexPath).section == accountListSection) { let accountObject = AppDelegate.instance().accounts!.getAccountObjectForIdx((indexPath as NSIndexPath).row) self.setUpCellAccounts(accountObject, cell: cell!, cellForRowAtIndexPath: indexPath) } else if ((indexPath as NSIndexPath).section == importedAccountSection) { let accountObject = AppDelegate.instance().importedAccounts!.getAccountObjectForIdx((indexPath as NSIndexPath).row) self.setUpCellAccounts(accountObject, cell: cell!, cellForRowAtIndexPath: indexPath) } else if ((indexPath as NSIndexPath).section == importedWatchAccountSection) { let accountObject = AppDelegate.instance().importedWatchAccounts!.getAccountObjectForIdx((indexPath as NSIndexPath).row) self.setUpCellAccounts(accountObject, cell: cell!, cellForRowAtIndexPath: indexPath) } else if ((indexPath as NSIndexPath).section == importedAddressSection) { let importedAddressObject = AppDelegate.instance().importedAddresses!.getAddressObjectAtIdx((indexPath as NSIndexPath).row) self.setUpCellImportedAddresses(importedAddressObject, cell: cell!, cellForRowAtIndexPath: indexPath) } else if ((indexPath as NSIndexPath).section == importedWatchAddressSection) { let importedAddressObject = AppDelegate.instance().importedWatchAddresses!.getAddressObjectAtIdx((indexPath as NSIndexPath).row) self.setUpCellImportedAddresses(importedAddressObject, cell: cell!, cellForRowAtIndexPath: indexPath) } else { } } else { if ((indexPath as NSIndexPath).section == accountListSection) { let accountObject = AppDelegate.instance().accounts!.getAccountObjectForIdx((indexPath as NSIndexPath).row) self.setUpCellAccounts(accountObject, cell: cell!, cellForRowAtIndexPath: indexPath) } else { } } if ((indexPath as NSIndexPath).row % 2 == 0) { cell!.backgroundColor = TLColors.evenTableViewCellColor() } else { cell!.backgroundColor = TLColors.oddTableViewCellColor() } return cell! } func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? { if (TLPreferences.enabledColdWallet() && (indexPath as NSIndexPath).section == coldWalletAccountSection) { self.doSelectFrom(.coldWalletAccount, sendFromIndex: (indexPath as NSIndexPath).row) return nil } if (TLPreferences.enabledAdvancedMode()) { if ((indexPath as NSIndexPath).section == accountListSection) { let accountObject = AppDelegate.instance().accounts!.getAccountObjectForIdx((indexPath as NSIndexPath).row) if (accountObject.hasFetchedAccountData()) { self.doSelectFrom(.hdWallet, sendFromIndex: (indexPath as NSIndexPath).row) } return nil } else if ((indexPath as NSIndexPath).section == importedAccountSection) { self.doSelectFrom(.importedAccount, sendFromIndex: (indexPath as NSIndexPath).row) return nil } else if ((indexPath as NSIndexPath).section == importedWatchAccountSection) { self.doSelectFrom(.importedWatchAccount, sendFromIndex: (indexPath as NSIndexPath).row) return nil } else if ((indexPath as NSIndexPath).section == importedAddressSection) { self.doSelectFrom(.importedAddress, sendFromIndex: (indexPath as NSIndexPath).row) return nil } else if ((indexPath as NSIndexPath).section == importedWatchAddressSection) { self.doSelectFrom(.importedWatchAddress, sendFromIndex: (indexPath as NSIndexPath).row) return nil } else { } } else { if ((indexPath as NSIndexPath).section == accountListSection) { let accountObject = AppDelegate.instance().accounts!.getAccountObjectForIdx((indexPath as NSIndexPath).row) if (accountObject.hasFetchedAccountData()) { self.doSelectFrom(.hdWallet, sendFromIndex: (indexPath as NSIndexPath).row) } } else { } } return nil } deinit { NotificationCenter.default.removeObserver(self) } } ================================================ FILE: ArcBit/viewControllers/TLAchievementsViewController.swift ================================================ // // TLAchievementsViewController.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit @objc(TLAchievementsViewController) class TLAchievementsViewController : UIViewController, UITableViewDataSource, UITableViewDelegate { var eventActionArray:NSArray? var advanceeventActionArray:NSArray? @IBOutlet fileprivate var howToInstructionsTableView: UITableView? override func viewDidLoad() { super.viewDidLoad() setColors() eventActionArray = TLHelpDoc.getEventsArray() advanceeventActionArray = TLHelpDoc.getAdvanceEventsArray() self.howToInstructionsTableView!.delegate = self self.howToInstructionsTableView!.dataSource = self self.howToInstructionsTableView!.tableFooterView = UIView(frame:CGRect.zero) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } func numberOfSections(in tableView:UITableView) -> Int { if (TLPreferences.enabledAdvancedMode()) { return 2 } else { return 1 } } func tableView(_ tableView:UITableView, titleForHeaderInSection section:Int) -> String? { if(section == 0) { return TLDisplayStrings.ACHIEVEMENT_LIST_STRING() } else { return TLDisplayStrings.ADVANCE_ACHIEVEMENT_LIST_STRING() } } func tableView(_ tableView: UITableView, numberOfRowsInSection section:Int) -> Int { if(section == 0) { return eventActionArray!.count } else { return advanceeventActionArray!.count } } func tableView(_ tableView: UITableView, cellForRowAt indexPath:IndexPath) -> UITableViewCell{ let MyIdentifier = "AchievementCellIdentifier" var cell = tableView.dequeueReusableCell(withIdentifier: MyIdentifier) if (cell == nil) { cell = UITableViewCell(style:UITableViewCellStyle.default, reuseIdentifier:MyIdentifier) } cell!.textLabel!.numberOfLines = 0 if((indexPath as NSIndexPath).section == 0) { let event = eventActionArray!.object(at: (indexPath as NSIndexPath).row) as! String if (TLAchievements.instance().hasDoneAction(event)) { cell!.accessoryType = UITableViewCellAccessoryType.checkmark } else { cell!.accessoryType = UITableViewCellAccessoryType.none } cell!.textLabel!.text = TLHelpDoc.getActionEventToHowToActionTitleDict().object(forKey: event) as? String } else { let event = advanceeventActionArray!.object(at: (indexPath as NSIndexPath).row) as! String if (TLAchievements.instance().hasDoneAction(event)) { cell!.accessoryType = UITableViewCellAccessoryType.checkmark } else { cell!.accessoryType = UITableViewCellAccessoryType.none } cell!.textLabel!.text = TLHelpDoc.getActionEventToHowToActionTitleDict().object(forKey: event) as? String } if ((indexPath as NSIndexPath).row % 2 == 0) { cell!.backgroundColor = TLColors.evenTableViewCellColor() } else { cell!.backgroundColor = TLColors.oddTableViewCellColor() } return cell! } func tableView(_ tableView:UITableView, willSelectRowAt indexPath:IndexPath) -> IndexPath? { return nil } } ================================================ FILE: ArcBit/viewControllers/TLAddressBookViewController.swift ================================================ // // TLAddressBookViewController.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit @objc(TLAddressBookViewController) class TLAddressBookViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { @IBOutlet fileprivate var navigationBar: UINavigationBar? @IBOutlet fileprivate var addressBookTableView: UITableView? fileprivate var addressBook: NSArray? required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } override var preferredStatusBarStyle : (UIStatusBarStyle) { return UIStatusBarStyle.lightContent } override func viewDidLoad() { super.viewDidLoad() self.setNavigationBarColors(self.navigationBar!) self.navigationBar?.topItem?.title = TLDisplayStrings.CONTACTS_STRING() addressBook = AppDelegate.instance().appWallet.getAddressBook() self.addressBookTableView!.delegate = self self.addressBookTableView!.dataSource = self self.addressBookTableView!.tableFooterView = UIView(frame: CGRect.zero) } override func viewWillAppear(_ animated: Bool) { // TODO: better way if AppDelegate.instance().scannedAddressBookAddress != nil { self.processAddressBookAddress(AppDelegate.instance().scannedAddressBookAddress!) AppDelegate.instance().scannedAddressBookAddress = nil } } fileprivate func promptAddToAddressBookActionSheet() -> () { UIAlertController.showAlert(in: self, withTitle: TLDisplayStrings.CREATE_NEW_CONTACT_STRING(), message:"", preferredStyle: .actionSheet, cancelButtonTitle: TLDisplayStrings.CANCEL_STRING(), destructiveButtonTitle: nil, otherButtonTitles: [TLDisplayStrings.IMPORT_WITH_QR_CODE_STRING(), TLDisplayStrings.IMPORT_WITH_TEXT_INPUT_STRING()], tap: {(actionSheet, action, buttonIndex) in if (buttonIndex == actionSheet?.firstOtherButtonIndex) { AppDelegate.instance().showAddressReaderControllerFromViewController(self, success: { (data: String!) in AppDelegate.instance().scannedAddressBookAddress = data }, error: { (data: String?) in }) } else if (buttonIndex == (actionSheet?.firstOtherButtonIndex)! + 1) { TLPrompts.promtForInputText(self, title: TLDisplayStrings.INPUT_ADDRESS_STRING(), message: "", textFieldPlaceholder: TLDisplayStrings.ADDRESS_STRING(), success: {(inputText: String!) in self.processAddressBookAddress(inputText) }, failure: { (isCanceled: Bool) in }) } else if (buttonIndex == actionSheet?.cancelButtonIndex) { } }) } fileprivate func processAddressBookAddress(_ address: String) -> () { if (TLCoreBitcoinWrapper.isValidAddress(address, isTestnet: AppDelegate.instance().appWallet.walletConfig.isTestnet)) { if (TLCoreBitcoinWrapper.isAddressVersion0(address, isTestnet: AppDelegate.instance().appWallet.walletConfig.isTestnet)) { if (TLWalletUtils.ENABLE_STEALTH_ADDRESS() && TLSuggestions.instance().enabledSuggestDontAddNormalAddressToAddressBook()) { TLPrompts.promtForOKCancel(self, title: TLDisplayStrings.WARNING_STRING(), message: TLDisplayStrings.ADD_ADDRESS_TO_CONTACT_WARNING_DESC_STRING(), success: { () in self.promptForLabel(address) TLSuggestions.instance().setEnableSuggestDontAddNormalAddressToAddressBook(false) }, failure: { (isCanceled: Bool) in }) } else { self.promptForLabel(address) } } else { self.promptForLabel(address) } } else { TLPrompts.promptErrorMessage(TLDisplayStrings.INVALID_ADDRESS_STRING(), message: "") } } fileprivate func promptForLabel(_ address: String) -> () { TLPrompts.promtForInputText(self, title: TLDisplayStrings.EDIT_CONTACTS_ENTRY_STRING(), message: "", textFieldPlaceholder: TLDisplayStrings.LABEL_STRING(), success: { (inputText: String!) in AppDelegate.instance().appWallet.addAddressBookEntry(address, label: inputText) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_ADD_TO_ADDRESS_BOOK()), object: nil, userInfo: nil) self.addressBookTableView!.reloadData() }, failure: { (isCanceled: Bool) in }) } @IBAction fileprivate func addAddressBookEntryButtonClicked(_ sender: UIButton) -> () { self.promptAddToAddressBookActionSheet() } @IBAction fileprivate func cancelButtonClicked(_ sender: UIButton) -> () { dismiss(animated: true, completion: nil) } func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { return true } func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) -> () { if (editingStyle == UITableViewCellEditingStyle.delete) { } else if (editingStyle == UITableViewCellEditingStyle.none) { } } func numberOfSections(in tableView: UITableView) -> (Int) { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return addressBook!.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let MyIdentifier = "AddressBookCellIdentifier" var cell = tableView.dequeueReusableCell(withIdentifier: MyIdentifier) if (cell == nil) { cell = UITableViewCell(style: UITableViewCellStyle.subtitle, reuseIdentifier: MyIdentifier) } cell!.textLabel!.text = (addressBook!.object(at: (indexPath as NSIndexPath).row) as! NSDictionary).object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_LABEL) as? String cell!.detailTextLabel!.text = (addressBook!.object(at: (indexPath as NSIndexPath).row) as! NSDictionary).object(forKey: TLWalletJSONKeys.STATIC_MEMBERS.WALLET_PAYLOAD_KEY_ADDRESS) as? String cell!.detailTextLabel!.numberOfLines = 0 if ((indexPath as NSIndexPath).row % 2 == 0) { cell!.backgroundColor = TLColors.evenTableViewCellColor() } else { cell!.backgroundColor = TLColors.oddTableViewCellColor() } return cell! } func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? { let cell = self.addressBookTableView!.cellForRow(at: indexPath) self.dismiss(animated: true, completion: nil) let address = cell!.detailTextLabel!.text NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_ADDRESS_SELECTED()), object: address, userInfo: nil) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_SEND_TO_ADDRESS_IN_ADDRESS_BOOK()), object: nil, userInfo: nil) return nil } func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? { let moreAction = UITableViewRowAction(style:UITableViewRowActionStyle.default, title: TLDisplayStrings.EDIT_STRING(), handler: { (action: UITableViewRowAction, indexPath: IndexPath) in tableView.isEditing = false TLPrompts.promtForInputText(self, title: TLDisplayStrings.EDIT_CONTACTS_ENTRY_STRING(), message: "", textFieldPlaceholder: TLDisplayStrings.ADDRESS_STRING(), success: { (inputText: String!) in AppDelegate.instance().appWallet.editAddressBookEntry((indexPath as NSIndexPath).row, label: inputText) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_EDIT_ENTRY_ADDRESS_BOOK()), object: nil, userInfo: nil) tableView.reloadData() }, failure: { (isCancelled: Bool) in }) }) moreAction.backgroundColor = UIColor.lightGray let deleteAction = UITableViewRowAction(style:UITableViewRowActionStyle.default, title: TLDisplayStrings.DELETE_STRING(), handler: { (action: UITableViewRowAction, indexPath: IndexPath) in tableView.isEditing = false TLPrompts.promtForOKCancel(self, title: TLDisplayStrings.DELETE_ADDRESS_STRING(), message: TLDisplayStrings.ARE_YOU_SURE_YOU_WANT_TO_DELETE_THIS_ACCOUNT_STRING(), success: { () in AppDelegate.instance().appWallet.deleteAddressBookEntry((indexPath as NSIndexPath).row) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_DELETE_ENTRY_ADDRESS_BOOK()), object: nil, userInfo: nil) tableView.reloadData() }, failure: { (isCancelled: Bool) in }) }) return [deleteAction, moreAction] } } ================================================ FILE: ArcBit/viewControllers/TLAddressListViewController.swift ================================================ // // TLAddressListViewController.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit @objc(TLAddressListViewController) class TLAddressListViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, CustomIOS7AlertViewDelegate { struct STATIC_MEMBERS { static let kStealthAddressSection = "kStealthAddressSection" static let kActiveMainSection = "kActiveMainSection" static let kArchivedMainSection = "kArchivedMainSection" static let kActiveChangeSection = "kActiveChangeSection" static let kArchivedChangeSection = "kArchivedChangeSection" } var accountObject: TLAccountObject? fileprivate var QRImageModal: TLQRImageModal? fileprivate lazy var sectionArray = Array() var showBalances: Bool = false let TL_STRING_NO_STEALTH_PAYMENT_ADDRESSES_INFO = TLDisplayStrings.CANT_SEE_REUSABLE_ADDRESS_PAYMENTS_STRING() let TL_STRING_NONE_CURRENTLY = TLDisplayStrings.NONE_CURRENTLY_STRING() @IBOutlet fileprivate var addressListTableView: UITableView? required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } override func viewDidLoad() { super.viewDidLoad() setColors() NotificationCenter.default.addObserver(self, selector: #selector(TLAddressListViewController.reloadAddressListTableView(_:)), name: NSNotification.Name(rawValue: TLNotificationEvents.EVENT_DISPLAY_LOCAL_CURRENCY_TOGGLED()), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(TLAddressListViewController.reloadAddressListTableView(_:)), name: NSNotification.Name(rawValue: TLNotificationEvents.EVENT_EXCHANGE_RATE_UPDATED()), object: nil) if TLWalletUtils.ENABLE_STEALTH_ADDRESS() { self.sectionArray = [STATIC_MEMBERS.kStealthAddressSection, STATIC_MEMBERS.kActiveMainSection, STATIC_MEMBERS.kArchivedMainSection, STATIC_MEMBERS.kActiveChangeSection, STATIC_MEMBERS.kArchivedChangeSection] } else { self.sectionArray = [STATIC_MEMBERS.kActiveMainSection, STATIC_MEMBERS.kArchivedMainSection, STATIC_MEMBERS.kActiveChangeSection, STATIC_MEMBERS.kArchivedChangeSection] } self.addressListTableView!.delegate = self self.addressListTableView!.dataSource = self self.addressListTableView!.tableFooterView = UIView(frame: CGRect.zero) } override func viewDidAppear(_ animated: Bool) -> () { NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_VIEW_ACCOUNT_ADDRESSES()), object: nil, userInfo: nil) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } func reloadAddressListTableView(_ notification: Notification) -> () { self.addressListTableView!.reloadData() } fileprivate func promptAddressActionSheet(_ address: String, addressType: TLAddressType, title: String, addressNtxs: Int) -> () { let otherButtonTitles:[String] if (TLPreferences.enabledAdvancedMode()) { otherButtonTitles = [TLDisplayStrings.VIEW_IN_WEB_STRING(), TLDisplayStrings.VIEW_ADDRESS_QR_CODE_STRING(), TLDisplayStrings.VIEW_PRIVATE_KEY_QR_CODE_STRING()] } else { otherButtonTitles = [TLDisplayStrings.VIEW_IN_WEB_STRING(), TLDisplayStrings.VIEW_ADDRESS_QR_CODE_STRING()] } UIAlertController.showAlert(in: self, withTitle: title, message:"", preferredStyle: .actionSheet, cancelButtonTitle: TLDisplayStrings.CANCEL_STRING(), destructiveButtonTitle: nil, otherButtonTitles: otherButtonTitles as [AnyObject], tap: {(actionSheet, action, buttonIndex) in if (buttonIndex == actionSheet?.firstOtherButtonIndex) { TLBlockExplorerAPI.instance().openWebViewForAddress(address) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_VIEW_ACCOUNT_ADDRESS_IN_WEB()), object: nil, userInfo: nil) } else if (buttonIndex == (actionSheet?.firstOtherButtonIndex)! + 1) { if (TLSuggestions.instance().enabledSuggestDontManageIndividualAccountAddress()) { TLPrompts.promtForOK(self, title:TLDisplayStrings.WARNING_STRING(), message: TLDisplayStrings.DONT_MANAGE_INDIVIDUAL_ACCOUNT_ADDRESS_WARNING_DESC_STRING(), success: { () in self.showAddressQRCode(address) TLSuggestions.instance().setEnableSuggestDontManageIndividualAccountAddress(false) }) } else { self.showAddressQRCode(address) } } else if (buttonIndex == (actionSheet?.firstOtherButtonIndex)! + 2) { if (TLSuggestions.instance().enabledSuggestDontManageIndividualAccountPrivateKeys()) { TLPrompts.promtForOK(self,title:TLDisplayStrings.WARNING_STRING(), message: TLDisplayStrings.DONT_MANAGE_INDIVIDUAL_ACCOUNT_PRIVATE_KEY_WARNING_DESC_STRING(), success: { () in self.showPrivateKeyQRCode(address, addressType: addressType) TLSuggestions.instance().setEnableSuggestDontManageIndividualAccountPrivateKeys(false) }) } else { self.showPrivateKeyQRCode(address, addressType: addressType) } } else if (buttonIndex == actionSheet?.cancelButtonIndex) { } }) } fileprivate func showAddressQRCode(_ address: String) -> () { self.QRImageModal = TLQRImageModal(data: address as NSString, buttonCopyText: TLDisplayStrings.COPY_TO_CLIPBOARD_STRING(), vc: self) self.QRImageModal!.show() NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_VIEW_ACCOUNT_ADDRESS()), object: nil, userInfo: nil) } fileprivate func showPrivateKeyQRCode(_ address: String, addressType: TLAddressType) -> () { if (self.accountObject!.isWatchOnly() && !self.accountObject!.hasSetExtendedPrivateKeyInMemory() && (addressType == .main || addressType == .change)) { TLPrompts.promptForTempararyImportExtendedPrivateKey(self, success: { (data: String!) -> () in if (!TLHDWalletWrapper.isValidExtendedPrivateKey(data)) { TLPrompts.promptErrorMessage(TLDisplayStrings.ERROR_STRING(), message: TLDisplayStrings.INVALID_ACCOUNT_PRIVATE_KEY_STRING()) } else { let success = self.accountObject!.setExtendedPrivateKeyInMemory(data) if (!success) { TLPrompts.promptErrorMessage(TLDisplayStrings.ERROR_STRING(), message: TLDisplayStrings.ACCOUNT_PRIVATE_KEY_DOES_NOT_MATCH_STRING()) } else { self.showPrivateKeyQRCodeFinal(address, addressType: addressType) } } }, error: { (data: String?) in }) } else if self.accountObject!.isColdWalletAccount() { TLPrompts.promptErrorMessage("", message: TLDisplayStrings.COLD_WALLET_PRIVATE_KEYS_ARE_NOT_STORED_HERE_STRING()) } else { self.showPrivateKeyQRCodeFinal(address, addressType: addressType) } } fileprivate func showPrivateKeyQRCodeFinal(_ address: String, addressType: TLAddressType) { let privateKey:String if (addressType == .stealth) { privateKey = self.accountObject!.stealthWallet!.getPaymentAddressPrivateKey(address)! } else if (addressType == .main) { privateKey = self.accountObject!.getMainPrivateKey(address) } else { privateKey = self.accountObject!.getChangePrivateKey(address) } self.QRImageModal = TLQRImageModal(data: privateKey as NSString, buttonCopyText: TLDisplayStrings.COPY_TO_CLIPBOARD_STRING(), vc: self) self.QRImageModal!.show() NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_VIEW_ACCOUNT_PRIVATE_KEY()), object: nil, userInfo: nil) } func numberOfSections(in tableView: UITableView) -> Int { return self.sectionArray.count } func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { let sectionType = self.sectionArray[section] if sectionType == STATIC_MEMBERS.kStealthAddressSection { //there are no archived stealth payment addresses, because old payment addresses are deleted return TLDisplayStrings.REUSABLE_ADDRESS_PAYMENT_ADDRESSES_STRING() } else if sectionType == STATIC_MEMBERS.kActiveMainSection { return TLDisplayStrings.ACTIVE_MAIN_ADDRESSES_STRING() } else if sectionType == STATIC_MEMBERS.kArchivedMainSection { return TLDisplayStrings.ARCHIVED_MAIN_ADDRESSES_STRING() } else if sectionType == STATIC_MEMBERS.kActiveChangeSection { return TLDisplayStrings.ACTIVE_CHANGE_ADDRESSES_STRING() } else if sectionType == STATIC_MEMBERS.kArchivedChangeSection { return TLDisplayStrings.ARCHIVED_CHANGE_ADDRESSES_STRING() } else { return nil } } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { let count:Int let sectionType = self.sectionArray[section] if sectionType == STATIC_MEMBERS.kStealthAddressSection { if TLWalletUtils.ENABLE_STEALTH_ADDRESS() && self.accountObject!.getAccountType() != .importedWatch && self.accountObject!.getAccountType() != .coldWallet { count = self.accountObject!.stealthWallet!.getStealthAddressPaymentsCount() } else { count = 0 } } else if sectionType == STATIC_MEMBERS.kActiveMainSection { count = self.accountObject!.getMainActiveAddressesCount() } else if sectionType == STATIC_MEMBERS.kArchivedMainSection { count = self.accountObject!.getMainArchivedAddressesCount() } else if sectionType == STATIC_MEMBERS.kActiveChangeSection { count = self.accountObject!.getChangeActiveAddressesCount() } else if sectionType == STATIC_MEMBERS.kArchivedChangeSection { count = self.accountObject!.getChangeArchivedAddressesCount() } else { count = 0 } return count == 0 ? 1 : count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let MyIdentifier = "AddressCellIdentifier" var cell = tableView.dequeueReusableCell(withIdentifier: MyIdentifier) as! TLAddressTableViewCell? if (cell == nil) { cell = UITableViewCell(style: UITableViewCellStyle.subtitle, reuseIdentifier: MyIdentifier) as? TLAddressTableViewCell } let sectionType = self.sectionArray[(indexPath as NSIndexPath).section] if sectionType == STATIC_MEMBERS.kStealthAddressSection || sectionType == STATIC_MEMBERS.kActiveMainSection || sectionType == STATIC_MEMBERS.kActiveChangeSection { cell!.textLabel!.isHidden = true cell!.amountButton!.isHidden = false cell!.addressLabel!.isHidden = false cell!.addressLabel!.adjustsFontSizeToFitWidth = true var address = "" var balance = "" if sectionType == STATIC_MEMBERS.kStealthAddressSection { if TLWalletUtils.ENABLE_STEALTH_ADDRESS() && self.accountObject!.getAccountType() != .importedWatch && self.accountObject!.getAccountType() != .coldWallet { if (self.accountObject!.stealthWallet!.getStealthAddressPaymentsCount() == 0) { address = TL_STRING_NONE_CURRENTLY } else { let idx = self.accountObject!.stealthWallet!.getStealthAddressPaymentsCount() - 1 - (indexPath as NSIndexPath).row address = self.accountObject!.stealthWallet!.getPaymentAddressForIndex(idx) } } else { cell!.textLabel!.isHidden = false cell!.addressLabel!.isHidden = true cell!.textLabel!.numberOfLines = 0 cell!.textLabel!.text = TL_STRING_NO_STEALTH_PAYMENT_ADDRESSES_INFO address = TL_STRING_NO_STEALTH_PAYMENT_ADDRESSES_INFO } } else if sectionType == STATIC_MEMBERS.kActiveMainSection { if (self.accountObject!.getMainActiveAddressesCount() == 0) { address = TL_STRING_NONE_CURRENTLY } else { let idx = self.accountObject!.getMainActiveAddressesCount() - 1 - (indexPath as NSIndexPath).row address = self.accountObject!.getMainActiveAddress(idx) } } else if sectionType == STATIC_MEMBERS.kActiveChangeSection { if (self.accountObject!.getChangeActiveAddressesCount() == 0) { address = TL_STRING_NONE_CURRENTLY } else { let idx = self.accountObject!.getChangeActiveAddressesCount() - 1 - (indexPath as NSIndexPath).row address = self.accountObject!.getChangeActiveAddress(idx) } } cell!.addressLabel!.text = address if (self.showBalances && address != TL_STRING_NONE_CURRENTLY && address != TL_STRING_NO_STEALTH_PAYMENT_ADDRESSES_INFO && (sectionType == STATIC_MEMBERS.kStealthAddressSection || sectionType == STATIC_MEMBERS.kActiveMainSection || sectionType == STATIC_MEMBERS.kActiveChangeSection)) { // only show balances of active addresses cell!.amountButton!.isHidden = false balance = TLCurrencyFormat.getProperAmount(self.accountObject!.getAddressBalance(address)) as String cell!.amountButton!.setTitle(balance, for: UIControlState()) } else { cell!.amountButton!.isHidden = true } } else { cell!.textLabel!.isHidden = false cell!.amountButton!.isHidden = true cell!.addressLabel!.isHidden = true cell!.textLabel!.adjustsFontSizeToFitWidth = true var address = "" if sectionType == STATIC_MEMBERS.kArchivedMainSection { if (self.accountObject!.getMainArchivedAddressesCount() == 0) { address = TL_STRING_NONE_CURRENTLY } else { let idx = self.accountObject!.getMainArchivedAddressesCount() - 1 - (indexPath as NSIndexPath).row address = self.accountObject!.getMainArchivedAddress(idx) } } else if sectionType == STATIC_MEMBERS.kArchivedChangeSection { if (self.accountObject!.getChangeArchivedAddressesCount() == 0) { address = TL_STRING_NONE_CURRENTLY } else { let idx = self.accountObject!.getChangeArchivedAddressesCount() - 1 - (indexPath as NSIndexPath).row address = self.accountObject!.getChangeArchivedAddress(idx) } } cell!.textLabel!.text = address } if ((indexPath as NSIndexPath).row % 2 == 0) { cell!.backgroundColor = TLColors.evenTableViewCellColor() } else { cell!.backgroundColor = TLColors.oddTableViewCellColor() } return cell! } func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? { var address = "" let cell = tableView.cellForRow(at: indexPath) as! TLAddressTableViewCell let sectionType = self.sectionArray[(indexPath as NSIndexPath).section] if sectionType == STATIC_MEMBERS.kStealthAddressSection || sectionType == STATIC_MEMBERS.kActiveMainSection || sectionType == STATIC_MEMBERS.kActiveChangeSection { address = cell.addressLabel!.text! } else { address = cell.textLabel!.text! } if address == TL_STRING_NONE_CURRENTLY || address == TL_STRING_NO_STEALTH_PAYMENT_ADDRESSES_INFO { return nil } let addressType:TLAddressType let title:String if sectionType == STATIC_MEMBERS.kStealthAddressSection { addressType = .stealth title = String(format: TLDisplayStrings.PAYMENT_INDEX_X_STRING(), self.accountObject!.stealthWallet!.getStealthAddressPaymentsCount() - (indexPath as NSIndexPath).row) } else if sectionType == STATIC_MEMBERS.kActiveMainSection || sectionType == STATIC_MEMBERS.kActiveChangeSection { addressType = .main title = String(format: TLDisplayStrings.ADDRESS_ID_X_STRING_STRING(), self.accountObject!.getAddressHDIndex(address)) } else { addressType = .change title = String(format: TLDisplayStrings.ADDRESS_ID_X_STRING_STRING(), self.accountObject!.getAddressHDIndex(address)) } var nTxs = 0 if (sectionType == STATIC_MEMBERS.kActiveMainSection || sectionType == STATIC_MEMBERS.kActiveChangeSection) { nTxs = self.accountObject!.getNumberOfTransactionsForAddress(address) } promptAddressActionSheet(address, addressType: addressType, title: title, addressNtxs: nTxs) return nil } func customIOS7dialogButtonTouchUp(inside alertView: CustomIOS7AlertView, clickedButtonAt buttonIndex: Int) { if (buttonIndex == 0) { iToast.makeText(TLDisplayStrings.COPY_TO_CLIPBOARD_STRING()).setGravity(iToastGravityCenter).setDuration(1000).show() let pasteboard = UIPasteboard.general pasteboard.string = self.QRImageModal!.QRcodeDisplayData } alertView.close() } deinit { NotificationCenter.default.removeObserver(self) } } ================================================ FILE: ArcBit/viewControllers/TLAddressTableViewCell.swift ================================================ // // TLAddressTableViewCell.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit @objc(TLAddressTableViewCell) class TLAddressTableViewCell:UITableViewCell { required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } @IBOutlet var addressLabel : UILabel? @IBOutlet var numberOfTransactionsCountLabel : UILabel? @IBOutlet var amountButton : UIButton? override func awakeFromNib() { super.awakeFromNib() self.amountButton!.backgroundColor = TLColors.mainAppColor() self.amountButton!.setTitleColor(TLColors.mainAppOppositeColor(), for:UIControlState()) self.amountButton!.titleLabel!.adjustsFontSizeToFitWidth = true } override func setSelected(_ selected:Bool, animated:Bool) -> () { super.setSelected(selected, animated:animated) } @IBAction fileprivate func amountButtonClicked(_ sender:UIButton) { TLPreferences.setDisplayLocalCurrency(!TLPreferences.isDisplayLocalCurrency()) TLPreferences.setInAppSettingsKitDisplayLocalCurrency(TLPreferences.isDisplayLocalCurrency()) } } ================================================ FILE: ArcBit/viewControllers/TLAuthorizeColdWalletPaymentViewController.swift ================================================ // // TLAuthorizeColdWalletPaymentViewController.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit @objc(TLAuthorizeColdWalletPaymentViewController) class TLAuthorizeColdWalletPaymentViewController : UIViewController, UITableViewDataSource, UITableViewDelegate, UITextViewDelegate, TLScanUnsignedTxTableViewCellDelegate, TLInputColdWalletKeyTableViewCellDelegate, TLPassSignedTxTableViewCellDelegate, CustomIOS7AlertViewDelegate { struct STATIC_MEMBERS { static let kInstuctionsSection = "kInstuctionsSection" static let kSpendColdWalletSection = "kSpendColdWalletSection" static let kScanUnsignedTxRow = "kScanUnsignedTxRow" static let kInputKeyRow = "kInputKeyRow" static let kPassSignedTxRow = "kPassSignedTxRow" } @IBOutlet fileprivate var tableView: UITableView? fileprivate var QRImageModal: TLQRImageModal? fileprivate var sectionArray: Array? fileprivate var instructionsRowArray: Array? fileprivate var spendColdWalletRowArray: Array? fileprivate var tapGesture: UITapGestureRecognizer? fileprivate var scanUnsignedTxTableViewCell: TLScanUnsignedTxTableViewCell? fileprivate var inputColdWalletKeyTableViewCell: TLInputColdWalletKeyTableViewCell? fileprivate var passSignedTxTableViewCell: TLPassSignedTxTableViewCell? private var scannedUnsignedTxAirGapDataPartsDict = [Int:String]() private var totalExpectedParts:Int = 0 private var scannedUnsignedTxAirGapData:String? = nil private var airGapDataBase64PartsArray: Array? private var savedAirGapDataBase64PartsArray: Array? override func viewDidLoad() { super.viewDidLoad() setColors() NotificationCenter.default.addObserver(self ,selector:#selector(TLCreateColdWalletViewController.keyboardWillShow(_:)), name:NSNotification.Name.UIKeyboardWillShow, object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLCreateColdWalletViewController.keyboardWillHide(_:)), name:NSNotification.Name.UIKeyboardWillHide, object:nil) self.tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard)) self.view.addGestureRecognizer(self.tapGesture!) self.sectionArray = [STATIC_MEMBERS.kInstuctionsSection, STATIC_MEMBERS.kSpendColdWalletSection] self.instructionsRowArray = [] self.spendColdWalletRowArray = [STATIC_MEMBERS.kScanUnsignedTxRow, STATIC_MEMBERS.kInputKeyRow, STATIC_MEMBERS.kPassSignedTxRow] self.tableView!.delegate = self self.tableView!.dataSource = self self.tableView!.tableFooterView = UIView(frame:CGRect.zero) } func dismissKeyboard() { self.inputColdWalletKeyTableViewCell?.keyInputTextView.resignFirstResponder() } func didClickScanUnsignedTxInfoButton(_ cell: TLScanUnsignedTxTableViewCell) { dismissKeyboard() let msg = TLDisplayStrings.SCAN_UNSIGNED_TX_INFO_STRING() TLPrompts.promtForOK(self, title:"", message: msg, success: { () in }) } func didClickInputColdWalletKeyInfoButton(_ cell: TLInputColdWalletKeyTableViewCell) { dismissKeyboard() let msg = TLDisplayStrings.INPUT_COLD_WALLET_KEY_INFO_STRING() TLPrompts.promtForOK(self, title:"", message: msg, success: { () in }) } func didClickPassSignedTxInfoButton(_ cell: TLPassSignedTxTableViewCell) { dismissKeyboard() let msg = TLDisplayStrings.PASS_SIGNED_TX_INFO_STRING() TLPrompts.promtForOK(self, title:"", message: msg, success: { () in }) } func checkToCreateSignedTx() { let keyText = self.inputColdWalletKeyTableViewCell?.keyInputTextView.text if keyText == nil || keyText!.isEmpty { self.inputColdWalletKeyTableViewCell?.statusLabel.text = TLDisplayStrings.INCOMPLETE_STRING() self.inputColdWalletKeyTableViewCell?.setstatusLabel(false) self.passSignedTxTableViewCell?.passButton.isEnabled = false self.passSignedTxTableViewCell?.passButton.alpha = 0.5 return } if !TLHDWalletWrapper.phraseIsValid(keyText!) && !TLHDWalletWrapper.isValidExtendedPrivateKey(keyText!) { self.inputColdWalletKeyTableViewCell?.statusLabel.text = TLDisplayStrings.INVALID_PASSPHRASE_STRING() self.inputColdWalletKeyTableViewCell?.setstatusLabel(false) self.passSignedTxTableViewCell?.passButton.isEnabled = false self.passSignedTxTableViewCell?.passButton.alpha = 0.5 return } if self.scannedUnsignedTxAirGapData == nil { self.inputColdWalletKeyTableViewCell?.statusLabel.text = TLDisplayStrings.COMPLETE_STEP_1_STRING() self.inputColdWalletKeyTableViewCell?.setstatusLabel(false) self.passSignedTxTableViewCell?.passButton.isEnabled = false self.passSignedTxTableViewCell?.passButton.alpha = 0.5 return } do { let serializedSignedAipGapData = try TLColdWallet.createSerializedSignedTxAipGapData(self.scannedUnsignedTxAirGapData!, mnemonicOrExtendedPrivateKey: keyText!, isTestnet: AppDelegate.instance().appWallet.walletConfig.isTestnet) self.airGapDataBase64PartsArray = TLColdWallet.splitStringToArray(serializedSignedAipGapData!) self.savedAirGapDataBase64PartsArray = TLColdWallet.splitStringToArray(serializedSignedAipGapData!) self.inputColdWalletKeyTableViewCell?.statusLabel.text = TLDisplayStrings.COMPLETE_STRING() self.inputColdWalletKeyTableViewCell?.setstatusLabel(true) self.passSignedTxTableViewCell?.passButton.isEnabled = true self.passSignedTxTableViewCell?.passButton.alpha = 1.0 } catch TLColdWallet.TLColdWalletError.InvalidScannedData(let error) { //shouldn't happen, if user scanned correct QR codes self.airGapDataBase64PartsArray = nil self.savedAirGapDataBase64PartsArray = nil self.scanUnsignedTxTableViewCell?.setInvalidScannedData() self.passSignedTxTableViewCell?.passButton.isEnabled = false self.passSignedTxTableViewCell?.passButton.alpha = 0.5 } catch TLColdWallet.TLColdWalletError.InvalidKey(let error) { self.airGapDataBase64PartsArray = nil self.savedAirGapDataBase64PartsArray = nil self.inputColdWalletKeyTableViewCell?.statusLabel.text = TLDisplayStrings.INVALID_PASSPHRASE_STRING() self.inputColdWalletKeyTableViewCell?.setstatusLabel(false) self.passSignedTxTableViewCell?.passButton.isEnabled = false self.passSignedTxTableViewCell?.passButton.alpha = 0.5 } catch TLColdWallet.TLColdWalletError.MisMatchExtendedPublicKey(let error) { self.airGapDataBase64PartsArray = nil self.savedAirGapDataBase64PartsArray = nil self.inputColdWalletKeyTableViewCell?.statusLabel.text = TLDisplayStrings.PASSPHRASE_DOES_NOT_MATCH_THE_TRANSACTION_STRING() self.inputColdWalletKeyTableViewCell?.setstatusLabel(false) self.passSignedTxTableViewCell?.passButton.isEnabled = false self.passSignedTxTableViewCell?.passButton.alpha = 0.5 } catch { } } func didClickScanButton(_ cell: TLScanUnsignedTxTableViewCell) { dismissKeyboard() scanUnsignedTx(success: { () in if self.totalExpectedParts != 0 && self.scannedUnsignedTxAirGapDataPartsDict.count == self.totalExpectedParts { self.scannedUnsignedTxAirGapData = "" for i in stride(from: 1, through: self.totalExpectedParts, by: 1) { let dataPart = self.scannedUnsignedTxAirGapDataPartsDict[i] self.scannedUnsignedTxAirGapData = self.scannedUnsignedTxAirGapData! + dataPart! } self.scannedUnsignedTxAirGapDataPartsDict = [Int:String]() self.checkToCreateSignedTx() } }, error: { () in }) } func scanUnsignedTx(success: @escaping (TLWalletUtils.Success), error: @escaping (TLWalletUtils.Error)) { AppDelegate.instance().showColdWalletSpendReaderControllerFromViewController(self, success: { (data: String!) in let ret = TLColdWallet.parseScannedPart(data) let dataPart = ret.0 let partNumber = ret.1 let totalParts = ret.2 self.totalExpectedParts = totalParts self.scannedUnsignedTxAirGapDataPartsDict[partNumber] = dataPart self.scanUnsignedTxTableViewCell?.setstatusLabel(self.scannedUnsignedTxAirGapDataPartsDict.count, totalParts: totalParts) success() }, error: { (data: String?) in error() }) } func showNextSignedTxPartQRCode() { if self.airGapDataBase64PartsArray == nil { return } let nextAipGapDataPart = self.airGapDataBase64PartsArray![0] self.airGapDataBase64PartsArray!.remove(at: 0) self.QRImageModal = TLQRImageModal(data: nextAipGapDataPart as NSString, buttonCopyText: TLDisplayStrings.NEXT_STRING(), vc: self) self.QRImageModal!.show() } func didClickPassButton(_ cell: TLPassSignedTxTableViewCell) { dismissKeyboard() if self.airGapDataBase64PartsArray == nil { return } TLPrompts.promtForOKCancel(self, title: TLDisplayStrings.TRANSACTION_AUTHORIZED_STRING(), message: "Transaction needs to be passed back to your online device in order for the payment to be sent", success: { () in self.showNextSignedTxPartQRCode() }, failure: { (isCancelled: Bool) in }) } func textViewDidChange(_ textView: UITextView) { if textView == self.inputColdWalletKeyTableViewCell?.keyInputTextView { self.checkToCreateSignedTx() } } func numberOfSections(in tableView:UITableView) -> Int { return self.sectionArray!.count } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { let section = self.sectionArray![(indexPath as NSIndexPath).section] if(section == STATIC_MEMBERS.kInstuctionsSection) { return 100 } else if(section == STATIC_MEMBERS.kSpendColdWalletSection) { let row = self.spendColdWalletRowArray![(indexPath as NSIndexPath).row] if row == STATIC_MEMBERS.kScanUnsignedTxRow { return TLScanUnsignedTxTableViewCell.cellHeight() } else if row == STATIC_MEMBERS.kInputKeyRow { return TLInputColdWalletKeyTableViewCell.cellHeight() } else if row == STATIC_MEMBERS.kPassSignedTxRow { return TLPassSignedTxTableViewCell.cellHeight() } } return 0 } func tableView(_ tableView:UITableView, titleForHeaderInSection section:Int) -> String? { let section = self.sectionArray![section] if(section == STATIC_MEMBERS.kInstuctionsSection) { return "" } else if(section == STATIC_MEMBERS.kSpendColdWalletSection) { return "" } return "" } func tableView(_ tableView: UITableView, numberOfRowsInSection section:Int) -> Int { let section = self.sectionArray![section] if (section == STATIC_MEMBERS.kInstuctionsSection) { return self.instructionsRowArray!.count } else if(section == STATIC_MEMBERS.kSpendColdWalletSection) { return self.spendColdWalletRowArray!.count } return 0 } func tableView(_ tableView: UITableView, cellForRowAt indexPath:IndexPath) -> UITableViewCell{ let section = self.sectionArray![(indexPath as NSIndexPath).section]; if (section == STATIC_MEMBERS.kInstuctionsSection) { let MyIdentifier = "InstructionsCellIdentifier" var cell = tableView.dequeueReusableCell(withIdentifier: MyIdentifier) if (cell == nil) { cell = UITableViewCell(style:UITableViewCellStyle.default, reuseIdentifier:MyIdentifier) } return cell! } else if(section == STATIC_MEMBERS.kSpendColdWalletSection) { let row = self.spendColdWalletRowArray![(indexPath as NSIndexPath).row]; self.spendColdWalletRowArray = [STATIC_MEMBERS.kScanUnsignedTxRow, STATIC_MEMBERS.kInputKeyRow, STATIC_MEMBERS.kPassSignedTxRow] if row == STATIC_MEMBERS.kScanUnsignedTxRow { let MyIdentifier = "ScanUnsignedTxCellIdentifier" var cell = tableView.dequeueReusableCell(withIdentifier: MyIdentifier) as! TLScanUnsignedTxTableViewCell? if (cell == nil) { cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: MyIdentifier) as? TLScanUnsignedTxTableViewCell } cell?.delegate = self self.scanUnsignedTxTableViewCell = cell return cell! } else if row == STATIC_MEMBERS.kInputKeyRow { let MyIdentifier = "InputColdWalletKeyCellIdentifier" var cell = tableView.dequeueReusableCell(withIdentifier: MyIdentifier) as! TLInputColdWalletKeyTableViewCell? if (cell == nil) { cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: MyIdentifier) as? TLInputColdWalletKeyTableViewCell } cell?.delegate = self cell?.keyInputTextView.delegate = self self.inputColdWalletKeyTableViewCell = cell return cell! } else if row == STATIC_MEMBERS.kPassSignedTxRow { let MyIdentifier = "PassSignedTxCellIdentifier" var cell = tableView.dequeueReusableCell(withIdentifier: MyIdentifier) as! TLPassSignedTxTableViewCell? if (cell == nil) { cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: MyIdentifier) as? TLPassSignedTxTableViewCell } cell?.delegate = self self.passSignedTxTableViewCell = cell return cell! } } return UITableViewCell(style:UITableViewCellStyle.default, reuseIdentifier:"DefaultCellIdentifier") } func keyboardWillShow(_ sender: Notification) { let kbSize = ((sender as NSNotification).userInfo![UIKeyboardFrameEndUserInfoKey]! as AnyObject).cgRectValue!.size let duration = ((sender as NSNotification).userInfo![UIKeyboardAnimationDurationUserInfoKey]! as AnyObject).doubleValue! let height = UIDeviceOrientationIsPortrait(UIDevice.current.orientation) ? kbSize.height : kbSize.width; UIView.animate(withDuration: duration, delay: 1.0, options: UIViewAnimationOptions(), animations: { var edgeInsets = self.tableView!.contentInset; edgeInsets.bottom = height; self.tableView!.contentInset = edgeInsets; edgeInsets = self.tableView!.scrollIndicatorInsets; edgeInsets.bottom = height; self.tableView!.scrollIndicatorInsets = edgeInsets; }, completion: { finished in }) } func keyboardWillHide(_ sender: Notification) { let duration = ((sender as NSNotification).userInfo![UIKeyboardAnimationDurationUserInfoKey]! as AnyObject).doubleValue! UIView.animate(withDuration: duration, delay: 1.0, options: UIViewAnimationOptions(), animations: { var edgeInsets = self.tableView!.contentInset; edgeInsets.bottom = 0; self.tableView!.contentInset = edgeInsets; edgeInsets = self.tableView!.scrollIndicatorInsets; edgeInsets.bottom = 0; self.tableView!.scrollIndicatorInsets = edgeInsets; }, completion: { finished in }) } func customIOS7dialogButtonTouchUp(inside alertView: CustomIOS7AlertView, clickedButtonAt buttonIndex: Int) { if (buttonIndex == 0) { if self.airGapDataBase64PartsArray == nil { return } if self.airGapDataBase64PartsArray!.count > 0 { self.showNextSignedTxPartQRCode() } else { TLPrompts.promtForOK(self, title: TLDisplayStrings.FINISHED_PASSING_TRANSACTION_DATA_STRING(), message: TLDisplayStrings.FINISHED_PASSING_TRANSACTION_DATA_DESC_STRING(), success: { () in self.airGapDataBase64PartsArray = self.savedAirGapDataBase64PartsArray //for part in self.savedAirGapDataBase64PartsArray! { // self.airGapDataBase64PartsArray!.append(part.copy() as! String) //} }) } } else { self.airGapDataBase64PartsArray = self.savedAirGapDataBase64PartsArray } alertView.close() } } ================================================ FILE: ArcBit/viewControllers/TLBrainWalletViewController.swift ================================================ // // TLBrainWalletViewController.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit @objc(TLBrainWalletViewController) class TLBrainWalletViewController : UIViewController, UITableViewDataSource, UITableViewDelegate, UITextViewDelegate, UITextFieldDelegate, TLAdvancedNewWalletTableViewCellDelegate, TLColdWalletSelectKeyTypeTableViewCellDelegate, CustomIOS7AlertViewDelegate { struct STATIC_MEMBERS { static let kInstuctionsSection = "kInstuctionsSection" static let kCreateNewWalletSection = "kCreateNewWalletSection" static let kSelectWhichKeyRow = "kSelectWhichKeyRow" static let kAdvancedNewWalletRow = "kAdvancedNewWalletRow" } @IBOutlet fileprivate var tableView: UITableView? fileprivate var QRImageModal: TLQRImageModal? fileprivate var mnemonicPassphrase: String? // private var shouldShowMore: Bool = false fileprivate var masterHex: String? fileprivate var extendedKeyIdx: UInt? fileprivate var extendedPublicKey: String? fileprivate var extendedPrivateKey: String? fileprivate var sectionArray: Array? fileprivate var instructionsRowArray: Array? fileprivate var createNewWalletRowArray: Array? fileprivate var advancedNewWalletTableViewCell: TLAdvancedNewWalletTableViewCell? fileprivate var tapGesture: UITapGestureRecognizer? fileprivate lazy var coldWalletKeyType: TLColdWalletKeyType = .mnemonic override func viewDidLoad() { super.viewDidLoad() setColors() NotificationCenter.default.addObserver(self ,selector:#selector(TLBrainWalletViewController.keyboardWillShow(_:)), name:NSNotification.Name.UIKeyboardWillShow, object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLBrainWalletViewController.keyboardWillHide(_:)), name:NSNotification.Name.UIKeyboardWillHide, object:nil) self.tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard)) self.view.addGestureRecognizer(self.tapGesture!) self.extendedKeyIdx = 0 self.sectionArray = [STATIC_MEMBERS.kInstuctionsSection, STATIC_MEMBERS.kCreateNewWalletSection] self.instructionsRowArray = [] self.createNewWalletRowArray = [STATIC_MEMBERS.kSelectWhichKeyRow, STATIC_MEMBERS.kAdvancedNewWalletRow] self.tableView!.delegate = self self.tableView!.dataSource = self self.tableView!.tableFooterView = UIView(frame:CGRect.zero) } func dismissKeyboard() { self.advancedNewWalletTableViewCell?.mnemonicTextView.resignFirstResponder() self.advancedNewWalletTableViewCell?.accountIDTextField.resignFirstResponder() self.advancedNewWalletTableViewCell?.accountPublicKeyTextView.resignFirstResponder() self.advancedNewWalletTableViewCell?.accountPrivateKeyTextView.resignFirstResponder() self.advancedNewWalletTableViewCell?.startingAddressIDTextField.resignFirstResponder() self.advancedNewWalletTableViewCell?.startingChangeAddressIDTextField.resignFirstResponder() } func didSelectColdWalletKeyType(_ cell: TLColdWalletSelectKeyTypeTableViewCell, keyType: TLColdWalletKeyType) { self.coldWalletKeyType = keyType self.tableView?.reloadData() } func didAdvancedNewWalletClickShowQRCodeButton(_ cell: TLAdvancedNewWalletTableViewCell, data: String) { dismissKeyboard() self.QRImageModal = TLQRImageModal(data: data as NSString, buttonCopyText: TLDisplayStrings.COPY_TO_CLIPBOARD_STRING(), vc: self) self.QRImageModal!.show() } func textFieldDidBeginEditing(_ textField: UITextField) { // var frame:CGRect = CGRectMake(textField.frame.origin.x, textField.frame.origin.y, textField.frame.size.width, textField.frame.size.height) // if textField == self.newWalletTableViewCell?.accountIDTextField { // frame.origin.y += 100 // } else { // frame.origin.y += 50 // } // self.tableView!.scrollRectToVisible(self.tableView!.convertRect(frame, fromView:textField.superview), animated:true) self.tableView!.scrollRectToVisible(self.tableView!.convert(textField.frame, from:textField.superview), animated:true) } func textViewDidBeginEditing(_ textView: UITextView) { let frame = CGRect(x: textView.frame.origin.x, y: textView.frame.origin.y+50, width: textView.frame.size.width, height: textView.frame.size.height) self.tableView!.scrollRectToVisible(self.tableView!.convert(frame, from:textView.superview), animated:true) } func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { if textField == self.advancedNewWalletTableViewCell?.accountIDTextField { let nsString = textField.text! as NSString let newString = nsString.replacingCharacters(in: range, with: string) var HDAccountID:Int = 0 if let accountID = Int(newString) { HDAccountID = accountID } if self.coldWalletKeyType != .mnemonic { return true } self.advancedNewWalletTableViewCell?.didUpdateMnemonic(self.advancedNewWalletTableViewCell!.mnemonicTextView.text, accountID: HDAccountID) } else if textField == self.advancedNewWalletTableViewCell?.startingAddressIDTextField { let nsString = textField.text! as NSString let newString = nsString.replacingCharacters(in: range, with: string) var addressID:Int = 0 if let addrID = Int(newString) { addressID = addrID } self.advancedNewWalletTableViewCell?.updateAddressFieldsWithStartingAddressID(addressID) } else if textField == self.advancedNewWalletTableViewCell?.startingChangeAddressIDTextField { let nsString = textField.text! as NSString let newString = nsString.replacingCharacters(in: range, with: string) var addressID:Int = 0 if let addrID = Int(newString) { addressID = addrID } self.advancedNewWalletTableViewCell?.updateChangeAddressFieldsWithStartingAddressID(addressID) } return true } func textViewDidChange(_ textView: UITextView) { if textView == self.advancedNewWalletTableViewCell?.mnemonicTextView { self.advancedNewWalletTableViewCell?.didUpdateMnemonic(textView.text!) } else if textView == self.advancedNewWalletTableViewCell?.accountPublicKeyTextView { self.advancedNewWalletTableViewCell?.didUpdateAccountPublicKey(textView.text!) } else if textView == self.advancedNewWalletTableViewCell?.accountPrivateKeyTextView { self.advancedNewWalletTableViewCell?.didUpdateAccountPrivateKey(textView.text!) } } func keyboardWillShow(_ sender: Notification) { let kbSize = ((sender as NSNotification).userInfo![UIKeyboardFrameEndUserInfoKey]! as AnyObject).cgRectValue!.size let duration = ((sender as NSNotification).userInfo![UIKeyboardAnimationDurationUserInfoKey]! as AnyObject).doubleValue! let height = UIDeviceOrientationIsPortrait(UIDevice.current.orientation) ? kbSize.height : kbSize.width; UIView.animate(withDuration: duration, delay: 1.0, options: UIViewAnimationOptions(), animations: { var edgeInsets = self.tableView!.contentInset; edgeInsets.bottom = height; self.tableView!.contentInset = edgeInsets; edgeInsets = self.tableView!.scrollIndicatorInsets; edgeInsets.bottom = height; self.tableView!.scrollIndicatorInsets = edgeInsets; }, completion: { finished in }) } func keyboardWillHide(_ sender: Notification) { let duration = ((sender as NSNotification).userInfo![UIKeyboardAnimationDurationUserInfoKey]! as AnyObject).doubleValue! UIView.animate(withDuration: duration, delay: 1.0, options: UIViewAnimationOptions(), animations: { var edgeInsets = self.tableView!.contentInset; edgeInsets.bottom = 0; self.tableView!.contentInset = edgeInsets; edgeInsets = self.tableView!.scrollIndicatorInsets; edgeInsets.bottom = 0; self.tableView!.scrollIndicatorInsets = edgeInsets; }, completion: { finished in }) } func numberOfSections(in tableView:UITableView) -> Int { return self.sectionArray!.count } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { let section = self.sectionArray![(indexPath as NSIndexPath).section] if(section == STATIC_MEMBERS.kInstuctionsSection) { return 100 } else if(section == STATIC_MEMBERS.kCreateNewWalletSection) { let row = self.createNewWalletRowArray![(indexPath as NSIndexPath).row] if row == STATIC_MEMBERS.kSelectWhichKeyRow { return TLColdWalletSelectKeyTypeTableViewCell.cellHeight() } else if row == STATIC_MEMBERS.kAdvancedNewWalletRow { return TLAdvancedNewWalletTableViewCell.cellHeight() } } return 0 } func tableView(_ tableView:UITableView, titleForHeaderInSection section:Int) -> String? { let section = self.sectionArray![section] if(section == STATIC_MEMBERS.kInstuctionsSection) { return "" } else if(section == STATIC_MEMBERS.kCreateNewWalletSection) { return "" } return "" } func tableView(_ tableView: UITableView, numberOfRowsInSection section:Int) -> Int { let section = self.sectionArray![section] if (section == STATIC_MEMBERS.kInstuctionsSection) { return self.instructionsRowArray!.count } else if(section == STATIC_MEMBERS.kCreateNewWalletSection) { return self.createNewWalletRowArray!.count } return 0 } func tableView(_ tableView: UITableView, cellForRowAt indexPath:IndexPath) -> UITableViewCell { let section = self.sectionArray![(indexPath as NSIndexPath).section]; if (section == STATIC_MEMBERS.kInstuctionsSection) { let MyIdentifier = "InstructionsCellIdentifier" var cell = tableView.dequeueReusableCell(withIdentifier: MyIdentifier) if (cell == nil) { cell = UITableViewCell(style:UITableViewCellStyle.default, reuseIdentifier:MyIdentifier) } return cell! } else if(section == STATIC_MEMBERS.kCreateNewWalletSection) { let row = self.createNewWalletRowArray![(indexPath as NSIndexPath).row]; if row == STATIC_MEMBERS.kSelectWhichKeyRow { let MyIdentifier = "ColdWalletSelectKeyTypeCellIdentifier" var cell = tableView.dequeueReusableCell(withIdentifier: MyIdentifier) as! TLColdWalletSelectKeyTypeTableViewCell? if (cell == nil) { cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: MyIdentifier) as? TLColdWalletSelectKeyTypeTableViewCell } cell?.delegate = self return cell! } else if row == STATIC_MEMBERS.kAdvancedNewWalletRow { let MyIdentifier = "AdvancedNewWalletCellIdentifier" var cell = tableView.dequeueReusableCell(withIdentifier: MyIdentifier) as! TLAdvancedNewWalletTableViewCell? if (cell == nil) { cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: MyIdentifier) as? TLAdvancedNewWalletTableViewCell } cell?.updateCellWithColdWalletKeyType(self.coldWalletKeyType) cell?.delegate = self cell?.mnemonicTextView.delegate = self cell?.accountIDTextField.delegate = self cell?.accountPublicKeyTextView.delegate = self cell?.accountPrivateKeyTextView.delegate = self cell?.startingAddressIDTextField.delegate = self cell?.startingChangeAddressIDTextField.delegate = self self.advancedNewWalletTableViewCell = cell return cell! } } return UITableViewCell(style:UITableViewCellStyle.default, reuseIdentifier:"DefaultCellIdentifier") } func tableView(_ tableView:UITableView, willSelectRowAt indexPath:IndexPath) -> IndexPath? { return nil } func customIOS7dialogButtonTouchUp(inside alertView: CustomIOS7AlertView, clickedButtonAt buttonIndex: Int) { if (buttonIndex == 0) { iToast.makeText(TLDisplayStrings.COPY_TO_CLIPBOARD_STRING()).setGravity(iToastGravityCenter).setDuration(1000).show() let pasteboard = UIPasteboard.general pasteboard.string = self.QRImageModal!.QRcodeDisplayData } alertView.close() } } ================================================ FILE: ArcBit/viewControllers/TLColdWalletViewController.swift ================================================ // // TLColdWalletViewController.m // ArcBit // // Created by Tim Lee on 3/18/15. // Copyright (c) 2015 ArcBit. All rights reserved. // // // TLColdWalletViewController.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit @objc(TLColdWalletViewController) class TLColdWalletViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, MFMailComposeViewControllerDelegate { struct STATIC_MEMBERS { static let kColdWalletSection = "kColdWalletSection" static let kColdWalletOverViewRow = "kColdWalletOverViewRow" static let kColdWalletCreateRow = "kColdWalletCreateRow" static let kColdWalletSpendtRow = "kColdWalletSpendtRow" static let kSeeHDWalletDataSection = "kSeeHDWalletDataSection" static let kSeeHDWalletDataRow = "kSeeHDWalletDataRow" } fileprivate var sectionArray: Array? fileprivate var coldWalletRowArray: Array? fileprivate var seeHDWalletDataRowArray: Array? required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } @IBOutlet fileprivate var coldWalletTableView:UITableView? override func viewDidLoad() { super.viewDidLoad() setColors() setLogoImageView() self.navigationController!.view.addGestureRecognizer(self.slidingViewController().panGesture) if (TLPreferences.enabledAdvancedMode()) { self.sectionArray = [STATIC_MEMBERS.kColdWalletSection, STATIC_MEMBERS.kSeeHDWalletDataSection] } else { self.sectionArray = [STATIC_MEMBERS.kColdWalletSection] } self.coldWalletRowArray = [STATIC_MEMBERS.kColdWalletOverViewRow, STATIC_MEMBERS.kColdWalletCreateRow, STATIC_MEMBERS.kColdWalletSpendtRow] self.seeHDWalletDataRowArray = [STATIC_MEMBERS.kSeeHDWalletDataRow] self.coldWalletTableView!.delegate = self self.coldWalletTableView!.dataSource = self self.coldWalletTableView!.tableFooterView = UIView(frame:CGRect.zero) } override func viewDidAppear(_ animated:Bool) -> () { NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_VIEW_COLD_WALLET_SCREEN()), object:nil) } override func prepare(for segue: UIStoryboardSegue, sender:Any!) -> () { if (segue.identifier == "SegueCreateColdWallet") { let vc = segue.destination vc.navigationItem.title = TLDisplayStrings.CREATE_COLD_WALLET_STRING() } else if (segue.identifier == "SegueSpendColdWallet") { let vc = segue.destination vc.navigationItem.title = TLDisplayStrings.AUTHORIZE_PAYMENT_STRING() } else if (segue.identifier == "SegueSpendColdWallet") { let vc = segue.destination vc.navigationItem.title = "" } } @IBAction fileprivate func menuButtonClicked(_ sender:UIButton) -> () { self.slidingViewController().anchorTopViewToRight(animated: true) } func tableView(_ tableView: UITableView, numberOfRowsInSection section:Int) -> Int { let sectionType = self.sectionArray![section] if(sectionType == STATIC_MEMBERS.kColdWalletSection) { return self.coldWalletRowArray!.count } else if(sectionType == STATIC_MEMBERS.kSeeHDWalletDataSection) { return self.seeHDWalletDataRowArray!.count } return 0 } func numberOfSections(in tableView:UITableView) -> Int { return self.sectionArray!.count } func tableView(_ tableView:UITableView, titleForHeaderInSection section:Int) -> String? { let sectionType = self.sectionArray![section] if(sectionType == STATIC_MEMBERS.kColdWalletSection) { return TLDisplayStrings.COLD_WALLET_STRING() } else if(sectionType == STATIC_MEMBERS.kSeeHDWalletDataSection) { return TLDisplayStrings.INTERNAL_WALLET_DATA_STRING() } return "" } func tableView(_ tableView: UITableView, cellForRowAt indexPath:IndexPath) -> UITableViewCell{ let MyIdentifier = "ColdWalletCellIdentifier" var cell = tableView.dequeueReusableCell(withIdentifier: MyIdentifier) if (cell == nil) { cell = UITableViewCell(style:UITableViewCellStyle.default, reuseIdentifier:MyIdentifier) } let sectionType = self.sectionArray![(indexPath as NSIndexPath).section] if(sectionType == STATIC_MEMBERS.kColdWalletSection) { cell!.textLabel!.font = cell!.textLabel!.font.withSize(15) let row = self.coldWalletRowArray![(indexPath as NSIndexPath).row] if row == STATIC_MEMBERS.kColdWalletOverViewRow { cell!.textLabel!.text = TLDisplayStrings.COLD_WALLET_OVERVIEW_STRING() } else if row == STATIC_MEMBERS.kColdWalletCreateRow { cell!.textLabel!.text = TLDisplayStrings.CREATE_COLD_WALLET_STRING() } else if row == STATIC_MEMBERS.kColdWalletSpendtRow { cell!.textLabel!.text = TLDisplayStrings.AUTHORIZE_COLD_WALLET_ACCOUNT_PAYMENT_STRING() } } else if(sectionType == STATIC_MEMBERS.kSeeHDWalletDataSection) { let row = self.seeHDWalletDataRowArray![(indexPath as NSIndexPath).row] if row == STATIC_MEMBERS.kSeeHDWalletDataRow { cell!.textLabel!.text = TLDisplayStrings.INTERNAL_WALLET_DATA_STRING() } } return cell! } func tableView(_ tableView:UITableView, willSelectRowAt indexPath:IndexPath) -> IndexPath? { let sectionType = self.sectionArray![(indexPath as NSIndexPath).section] if(sectionType == STATIC_MEMBERS.kColdWalletSection) { let row = self.coldWalletRowArray![(indexPath as NSIndexPath).row] if row == STATIC_MEMBERS.kColdWalletOverViewRow { let msg = TLDisplayStrings.COLD_WALLET_OVERVIEW_DESC_STRING() TLPrompts.promtForOK(self, title:"", message: msg, success: { () in }) } else if row == STATIC_MEMBERS.kColdWalletCreateRow { performSegue(withIdentifier: "SegueCreateColdWallet", sender:self) } else if row == STATIC_MEMBERS.kColdWalletSpendtRow { performSegue(withIdentifier: "SegueSpendColdWallet", sender:self) } } else if(sectionType == STATIC_MEMBERS.kSeeHDWalletDataSection){ let row = self.seeHDWalletDataRowArray![(indexPath as NSIndexPath).row] if row == STATIC_MEMBERS.kSeeHDWalletDataRow { performSegue(withIdentifier: "SegueSeeWalletData", sender:self) } } return nil } } ================================================ FILE: ArcBit/viewControllers/TLCreateColdWalletViewController.swift ================================================ // // TLCreateColdWalletViewController.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit @objc(TLCreateColdWalletViewController) class TLCreateColdWalletViewController : UIViewController, UITableViewDataSource, UITableViewDelegate, UITextViewDelegate, UITextFieldDelegate, TLNewWalletTableViewCellDelegate, CustomIOS7AlertViewDelegate { struct STATIC_MEMBERS { static let kInstuctionsSection = "kInstuctionsSection" static let kCreateNewWalletSection = "kCreateNewWalletSection" static let kSimpleNewWalletRow = "kSimpleNewWalletRow" } @IBOutlet fileprivate var tableView: UITableView? fileprivate var QRImageModal: TLQRImageModal? fileprivate var mnemonicPassphrase: String? fileprivate var masterHex: String? fileprivate var extendedKeyIdx: UInt? fileprivate var extendedPublicKey: String? fileprivate var extendedPrivateKey: String? fileprivate var sectionArray: Array? fileprivate var instructionsRowArray: Array? fileprivate var createNewWalletRowArray: Array? fileprivate var newWalletTableViewCell: TLNewWalletTableViewCell? fileprivate var tapGesture: UITapGestureRecognizer? override func viewDidLoad() { super.viewDidLoad() setColors() NotificationCenter.default.addObserver(self ,selector:#selector(TLCreateColdWalletViewController.keyboardWillShow(_:)), name:NSNotification.Name.UIKeyboardWillShow, object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLCreateColdWalletViewController.keyboardWillHide(_:)), name:NSNotification.Name.UIKeyboardWillHide, object:nil) self.tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard)) self.view.addGestureRecognizer(self.tapGesture!) self.extendedKeyIdx = 0 self.sectionArray = [STATIC_MEMBERS.kInstuctionsSection, STATIC_MEMBERS.kCreateNewWalletSection] self.instructionsRowArray = [] self.createNewWalletRowArray = [STATIC_MEMBERS.kSimpleNewWalletRow] self.tableView!.delegate = self self.tableView!.dataSource = self self.tableView!.tableFooterView = UIView(frame:CGRect.zero) } func dismissKeyboard() { self.newWalletTableViewCell?.accountIDTextField.resignFirstResponder() self.newWalletTableViewCell?.mnemonicTextView.resignFirstResponder() self.newWalletTableViewCell?.accountPublicKeyTextView.resignFirstResponder() } func didClickShowQRCodeButton(_ cell: TLNewWalletTableViewCell, data: String) { dismissKeyboard() self.QRImageModal = TLQRImageModal(data: data as NSString, buttonCopyText: TLDisplayStrings.COPY_TO_CLIPBOARD_STRING(), vc: self) self.QRImageModal!.show() } func didClickMnemonicInfoButton(_ cell: TLNewWalletTableViewCell) { dismissKeyboard() let msg = TLDisplayStrings.MNEMONIC_INFO_STRING() TLPrompts.promtForOK(self, title:"", message: msg, success: { () in }) } func didClickAccountInfoButton(_ cell: TLNewWalletTableViewCell) { dismissKeyboard() let msg = TLDisplayStrings.ACCOUNT_ID_INFO_STRING() TLPrompts.promtForOK(self, title:"", message: msg, success: { () in }) } func textFieldDidBeginEditing(_ textField: UITextField) { // var frame:CGRect = CGRectMake(textField.frame.origin.x, textField.frame.origin.y, textField.frame.size.width, textField.frame.size.height) // if textField == self.newWalletTableViewCell?.accountIDTextField { // frame.origin.y += 100 // } else { // frame.origin.y += 50 // } // self.tableView!.scrollRectToVisible(self.tableView!.convertRect(frame, fromView:textField.superview), animated:true) self.tableView!.scrollRectToVisible(self.tableView!.convert(textField.frame, from:textField.superview), animated:true) } func textViewDidBeginEditing(_ textView: UITextView) { let frame = CGRect(x: textView.frame.origin.x, y: textView.frame.origin.y+50, width: textView.frame.size.width, height: textView.frame.size.height) self.tableView!.scrollRectToVisible(self.tableView!.convert(frame, from:textView.superview), animated:true) } func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { if textField == self.newWalletTableViewCell?.accountIDTextField { if textField.text!.characters.count == 1 && string.isEmpty { self.newWalletTableViewCell?.updateAccountPublicKeyTextView(nil) return true } let nsString = textField.text! as NSString let newString = nsString.replacingCharacters(in: range, with: string) if let accountID = UInt(newString) { let mnemonicPassphrase = self.newWalletTableViewCell?.mnemonicTextView.text if mnemonicPassphrase != nil && TLHDWalletWrapper.phraseIsValid(mnemonicPassphrase!) { let masterHex = TLHDWalletWrapper.getMasterHex(mnemonicPassphrase!) let extendedPublicKey = TLHDWalletWrapper.getExtendPubKeyFromMasterHex(masterHex, accountIdx: accountID) self.newWalletTableViewCell?.accountPublicKeyTextView.text = extendedPublicKey self.newWalletTableViewCell?.updateAccountPublicKeyTextView(extendedPublicKey) } else { self.newWalletTableViewCell?.updateAccountPublicKeyTextView(nil) } } else { self.newWalletTableViewCell?.updateAccountPublicKeyTextView(nil) } } return true } func textViewDidChange(_ textView: UITextView) { if textView == self.newWalletTableViewCell?.mnemonicTextView { let mnemonicPassphrase = textView.text! if TLHDWalletWrapper.phraseIsValid(mnemonicPassphrase) { self.newWalletTableViewCell?.didUpdateMnemonic(textView.text!) } else { self.newWalletTableViewCell?.updateAccountPublicKeyTextView(nil) } } else if textView == self.newWalletTableViewCell?.accountPublicKeyTextView { let accountPublicKey = self.newWalletTableViewCell?.accountPublicKeyTextView.text if accountPublicKey != nil && !accountPublicKey!.isEmpty && TLHDWalletWrapper.isValidExtendedPublicKey(accountPublicKey!) { self.newWalletTableViewCell?.showAccountPublicKeyQRButton.isEnabled = true self.newWalletTableViewCell?.showAccountPublicKeyQRButton.alpha = 1 } else { self.newWalletTableViewCell?.showAccountPublicKeyQRButton.isEnabled = false self.newWalletTableViewCell?.showAccountPublicKeyQRButton.alpha = 0.5 } } } func numberOfSections(in tableView:UITableView) -> Int { return self.sectionArray!.count } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { let section = self.sectionArray![(indexPath as NSIndexPath).section] if(section == STATIC_MEMBERS.kInstuctionsSection) { return 100 } else if(section == STATIC_MEMBERS.kCreateNewWalletSection) { let row = self.createNewWalletRowArray![(indexPath as NSIndexPath).row] if row == STATIC_MEMBERS.kSimpleNewWalletRow { return TLNewWalletTableViewCell.cellHeight() } } return 0 } func tableView(_ tableView:UITableView, titleForHeaderInSection section:Int) -> String? { let section = self.sectionArray![section] if(section == STATIC_MEMBERS.kInstuctionsSection) { return "" } else if(section == STATIC_MEMBERS.kCreateNewWalletSection) { return "" } return "" } func tableView(_ tableView: UITableView, numberOfRowsInSection section:Int) -> Int { let section = self.sectionArray![section] if (section == STATIC_MEMBERS.kInstuctionsSection) { return self.instructionsRowArray!.count } else if(section == STATIC_MEMBERS.kCreateNewWalletSection) { return self.createNewWalletRowArray!.count } return 0 } func tableView(_ tableView: UITableView, cellForRowAt indexPath:IndexPath) -> UITableViewCell { let section = self.sectionArray![(indexPath as NSIndexPath).section]; if (section == STATIC_MEMBERS.kInstuctionsSection) { let MyIdentifier = "InstructionsCellIdentifier" var cell = tableView.dequeueReusableCell(withIdentifier: MyIdentifier) if (cell == nil) { cell = UITableViewCell(style:UITableViewCellStyle.default, reuseIdentifier:MyIdentifier) } return cell! } else if(section == STATIC_MEMBERS.kCreateNewWalletSection) { let row = self.createNewWalletRowArray![(indexPath as NSIndexPath).row]; if row == STATIC_MEMBERS.kSimpleNewWalletRow { let MyIdentifier = "NewWalletCellIdentifier" var cell = tableView.dequeueReusableCell(withIdentifier: MyIdentifier) as! TLNewWalletTableViewCell? if (cell == nil) { cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: MyIdentifier) as? TLNewWalletTableViewCell } cell?.delegate = self cell?.mnemonicTextView.delegate = self cell?.accountIDTextField.delegate = self cell?.accountPublicKeyTextView.delegate = self self.newWalletTableViewCell = cell return cell! } } return UITableViewCell(style:UITableViewCellStyle.default, reuseIdentifier:"DefaultCellIdentifier") } func keyboardWillShow(_ sender: Notification) { let kbSize = ((sender as NSNotification).userInfo![UIKeyboardFrameEndUserInfoKey]! as AnyObject).cgRectValue!.size let duration = ((sender as NSNotification).userInfo![UIKeyboardAnimationDurationUserInfoKey]! as AnyObject).doubleValue! let height = UIDeviceOrientationIsPortrait(UIDevice.current.orientation) ? kbSize.height : kbSize.width; UIView.animate(withDuration: duration, delay: 1.0, options: UIViewAnimationOptions(), animations: { var edgeInsets = self.tableView!.contentInset; edgeInsets.bottom = height; self.tableView!.contentInset = edgeInsets; edgeInsets = self.tableView!.scrollIndicatorInsets; edgeInsets.bottom = height; self.tableView!.scrollIndicatorInsets = edgeInsets; }, completion: { finished in }) } func keyboardWillHide(_ sender: Notification) { let duration = ((sender as NSNotification).userInfo![UIKeyboardAnimationDurationUserInfoKey]! as AnyObject).doubleValue! UIView.animate(withDuration: duration, delay: 1.0, options: UIViewAnimationOptions(), animations: { var edgeInsets = self.tableView!.contentInset; edgeInsets.bottom = 0; self.tableView!.contentInset = edgeInsets; edgeInsets = self.tableView!.scrollIndicatorInsets; edgeInsets.bottom = 0; self.tableView!.scrollIndicatorInsets = edgeInsets; }, completion: { finished in }) } func customIOS7dialogButtonTouchUp(inside alertView: CustomIOS7AlertView, clickedButtonAt buttonIndex: Int) { if (buttonIndex == 0) { iToast.makeText(TLDisplayStrings.COPY_TO_CLIPBOARD_STRING()).setGravity(iToastGravityCenter).setDuration(1000).show() let pasteboard = UIPasteboard.general pasteboard.string = self.QRImageModal!.QRcodeDisplayData } alertView.close() } } ================================================ FILE: ArcBit/viewControllers/TLHelpViewController.swift ================================================ // // TLHelpViewController.m // ArcBit // // Created by Tim Lee on 3/18/15. // Copyright (c) 2015 ArcBit. All rights reserved. // // // TLHelpViewController.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit @objc(TLHelpViewController) class TLHelpViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } struct STATIC_MEMBERS { static let kAchievementsSection = "kAchievementsSection" static let kFAQSection = "kFAQSection" static let kHowToSection = "kHowToSection" static let kAdvancedFAQSection = "kAdvancedFAQSection" static let kAdvancedHowToFAQSection = "kAdvancedHowToFAQSection" } @IBOutlet fileprivate var howToInstructionsTableView:UITableView? fileprivate var eventActionArray:NSArray? fileprivate var eventAdvanceActionArray:NSArray? fileprivate var FAQArray:NSArray? fileprivate var advancedFAQArray:NSArray? fileprivate var instructions:NSArray? fileprivate var action:NSString? fileprivate var FAQText:NSString? fileprivate var sectionArray: Array? override func viewDidLoad() { super.viewDidLoad() setColors() setLogoImageView() eventActionArray = TLHelpDoc.getEventsArray() eventAdvanceActionArray = TLHelpDoc.getAdvanceEventsArray() FAQArray = TLHelpDoc.getFAQArray() advancedFAQArray = TLHelpDoc.getAdvanceFAQArray() if (TLPreferences.enabledAdvancedMode()) { //self.sectionArray = [STATIC_MEMBERS.kAchievementsSection, STATIC_MEMBERS.kFAQSection, STATIC_MEMBERS.kHowToSection, STATIC_MEMBERS.kAdvancedFAQSection, STATIC_MEMBERS.kAdvancedHowToFAQSection] self.sectionArray = [STATIC_MEMBERS.kFAQSection, STATIC_MEMBERS.kHowToSection, STATIC_MEMBERS.kAdvancedFAQSection, STATIC_MEMBERS.kAdvancedHowToFAQSection] } else { //self.sectionArray = [STATIC_MEMBERS.kAchievementsSection, STATIC_MEMBERS.kFAQSection, STATIC_MEMBERS.kHowToSection] self.sectionArray = [STATIC_MEMBERS.kFAQSection, STATIC_MEMBERS.kHowToSection] } self.navigationController!.view.addGestureRecognizer(self.slidingViewController().panGesture) self.howToInstructionsTableView!.delegate = self self.howToInstructionsTableView!.dataSource = self self.howToInstructionsTableView!.tableFooterView = UIView(frame:CGRect.zero) } override func viewDidAppear(_ animated:Bool) -> () { NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_VIEW_HELP_SCREEN()), object:nil) } override func didReceiveMemoryWarning() -> () { super.didReceiveMemoryWarning() } override func prepare(for segue: UIStoryboardSegue, sender:Any!) -> () { if (segue.identifier == "SegueAchievements") { let vc = segue.destination vc.navigationItem.title = TLDisplayStrings.ACHIEVEMENTS_STRING() } else if (segue.identifier == "SegueText") { let vc = segue.destination as! TLTextViewViewController vc.navigationItem.title = TLDisplayStrings.EXPLANATION_STRING() vc.text = FAQText as? String } else if (segue.identifier == "SegueInstructions") { let vc = segue.destination as! TLInstructionsViewController vc.navigationItem.title = TLDisplayStrings.INSTRUCTIONS_STRING() vc.action = action as? String vc.actionInstructionsSteps = instructions } } @IBAction fileprivate func menuButtonClicked(_ sender:UIButton) -> () { self.slidingViewController().anchorTopViewToRight(animated: true) } func numberOfSections(in tableView:UITableView) -> Int { return self.sectionArray!.count } func tableView(_ tableView:UITableView, titleForHeaderInSection section:Int) -> String? { let sectionType = self.sectionArray![section] if(sectionType == STATIC_MEMBERS.kAchievementsSection) { return TLDisplayStrings.ACHIEVEMENTS_STRING() } else if(sectionType == STATIC_MEMBERS.kFAQSection) { return TLDisplayStrings.FAQ_STRING() } else if(sectionType == STATIC_MEMBERS.kHowToSection) { return TLDisplayStrings.HOW_TO_COLON_STRING() } else if(sectionType == STATIC_MEMBERS.kAdvancedFAQSection) { return TLDisplayStrings.ADVANCE_FAQ_STRING() } else if(sectionType == STATIC_MEMBERS.kAdvancedHowToFAQSection) { return TLDisplayStrings.ADVANCE_HOW_TO_COLON_STRING_STRING() } return nil } func tableView(_ tableView: UITableView, numberOfRowsInSection section:Int) -> Int { let sectionType = self.sectionArray![section] if(sectionType == STATIC_MEMBERS.kAchievementsSection) { return 1 } else if(sectionType == STATIC_MEMBERS.kFAQSection) { return FAQArray!.count } else if(sectionType == STATIC_MEMBERS.kHowToSection) { return eventActionArray!.count } else if(sectionType == STATIC_MEMBERS.kAdvancedFAQSection) { return advancedFAQArray!.count } else if(sectionType == STATIC_MEMBERS.kAdvancedHowToFAQSection) { return eventAdvanceActionArray!.count } return 0 } func tableView(_ tableView: UITableView, cellForRowAt indexPath:IndexPath) -> UITableViewCell{ let MyIdentifier = "HowToCellIdentifier" var cell = tableView.dequeueReusableCell(withIdentifier: MyIdentifier) if (cell == nil) { cell = UITableViewCell(style:UITableViewCellStyle.default, reuseIdentifier:MyIdentifier) } cell!.textLabel!.numberOfLines = 0 let sectionType = self.sectionArray![(indexPath as NSIndexPath).section] if(sectionType == STATIC_MEMBERS.kAchievementsSection) { cell!.textLabel!.text = TLDisplayStrings.VIEW_ACHIEVEMENTS_STRING() } else if(sectionType == STATIC_MEMBERS.kFAQSection) { cell!.textLabel!.text = FAQArray!.object(at: (indexPath as NSIndexPath).row) as? String } else if(sectionType == STATIC_MEMBERS.kHowToSection) { cell!.textLabel!.text = TLHelpDoc.getActionEventToHowToActionTitleDict().object(forKey: eventActionArray!.object(at: (indexPath as NSIndexPath).row) as! String) as? String } else if(sectionType == STATIC_MEMBERS.kAdvancedFAQSection) { cell!.textLabel!.text = advancedFAQArray!.object(at: (indexPath as NSIndexPath).row) as? String } else if(sectionType == STATIC_MEMBERS.kAdvancedHowToFAQSection) { cell!.textLabel!.text = TLHelpDoc.getActionEventToHowToActionTitleDict().object(forKey: eventAdvanceActionArray!.object(at: (indexPath as NSIndexPath).row) as! String) as? String } if ((indexPath as NSIndexPath).row % 2 == 0) { cell!.backgroundColor = TLColors.evenTableViewCellColor() } else { cell!.backgroundColor = TLColors.oddTableViewCellColor() } return cell! } func tableView(_ tableView:UITableView, willSelectRowAt indexPath:IndexPath) -> IndexPath? { let sectionType = self.sectionArray![(indexPath as NSIndexPath).section] if(sectionType == STATIC_MEMBERS.kAchievementsSection) { performSegue(withIdentifier: "SegueAchievements", sender:self) } else if(sectionType == STATIC_MEMBERS.kFAQSection) { FAQText = TLHelpDoc.getExplanation((indexPath as NSIndexPath).row) as NSString? performSegue(withIdentifier: "SegueText", sender:self) } else if(sectionType == STATIC_MEMBERS.kHowToSection) { action = TLHelpDoc.getActionEventToHowToActionTitleDict().object(forKey: eventActionArray!.object(at: (indexPath as NSIndexPath).row)) as! String as NSString? instructions = TLHelpDoc.getBasicActionInstructionStepsArray((indexPath as NSIndexPath).row) performSegue(withIdentifier: "SegueInstructions", sender:self) } else if(sectionType == STATIC_MEMBERS.kAdvancedFAQSection) { FAQText = TLHelpDoc.getAdvanceExplanation((indexPath as NSIndexPath).row) as NSString? performSegue(withIdentifier: "SegueText", sender:self) } else if(sectionType == STATIC_MEMBERS.kAdvancedHowToFAQSection) { action = TLHelpDoc.getActionEventToHowToActionTitleDict().object(forKey: eventAdvanceActionArray!.object(at: (indexPath as NSIndexPath).row)) as! String as NSString? instructions = TLHelpDoc.getAdvanceActionInstructionStepsArray((indexPath as NSIndexPath).row) performSegue(withIdentifier: "SegueInstructions", sender:self) } return nil } } ================================================ FILE: ArcBit/viewControllers/TLHistoryViewController.swift ================================================ // // TLHistoryViewController.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import UIKit import CoreData @objc(TLHistoryViewController) class TLHistoryViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UIAlertViewDelegate { required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } fileprivate let MAX_CONFIRMATIONS_TO_DISPLAY = 6 fileprivate var accountRefreshControl: UIRefreshControl? fileprivate var managedObjectContext: NSManagedObjectContext? fileprivate var paymentInfos: NSMutableArray? fileprivate var transactions: NSMutableArray? @IBOutlet fileprivate var transactionsTableView: UITableView? @IBOutlet fileprivate var accountNameLabel: UILabel? @IBOutlet fileprivate var accountBalanceLabel: UILabel? @IBOutlet fileprivate var balanceActivityIndicatorView: UIActivityIndicatorView? @IBOutlet fileprivate var selectAccountImageView: UIImageView? @IBOutlet fileprivate var fromViewContainer: UIButton? @IBOutlet fileprivate var tableviewBackgroundView: UIView? @IBOutlet fileprivate var fromBackgroundView: UIView? @IBOutlet fileprivate var revealButtonItem: UIBarButtonItem? @IBOutlet weak var fromLabel: UILabel! override func viewDidLoad() { super.viewDidLoad() setColors() setLogoImageView() self.fromLabel.text = TLDisplayStrings.FROM_COLON_STRING() self.fromViewContainer!.backgroundColor = TLColors.mainAppColor() self.accountNameLabel!.textColor = TLColors.mainAppOppositeColor() self.accountBalanceLabel!.textColor = TLColors.mainAppOppositeColor() self.balanceActivityIndicatorView!.color = TLColors.mainAppOppositeColor() self.navigationController!.view.addGestureRecognizer(self.slidingViewController().panGesture) NotificationCenter.default.addObserver(self , selector: #selector(TLHistoryViewController.updateViewToNewSelectedObject), name: NSNotification.Name(rawValue: TLNotificationEvents.EVENT_DISPLAY_LOCAL_CURRENCY_TOGGLED()), object: nil) NotificationCenter.default.addObserver(self , selector: #selector(TLHistoryViewController.updateViewToNewSelectedObject), name: NSNotification.Name(rawValue: TLNotificationEvents.EVENT_FETCHED_ADDRESSES_DATA()), object: nil) NotificationCenter.default.addObserver(self , selector: #selector(TLHistoryViewController.updateViewToNewSelectedObject), name: NSNotification.Name(rawValue: TLNotificationEvents.EVENT_MODEL_UPDATED_NEW_UNCONFIRMED_TRANSACTION()), object: nil) NotificationCenter.default.addObserver(self , selector: #selector(TLHistoryViewController.updateViewToNewSelectedObject), name: NSNotification.Name(rawValue: TLNotificationEvents.EVENT_DISPLAY_LOCAL_CURRENCY_TOGGLED()), object: nil) NotificationCenter.default.addObserver(self , selector: #selector(TLHistoryViewController.updateTransactionsTableView(_:)), name: NSNotification.Name(rawValue: TLNotificationEvents.EVENT_PREFERENCES_BITCOIN_DISPLAY_CHANGED()), object: nil) NotificationCenter.default.addObserver(self , selector: #selector(TLHistoryViewController.updateTransactionsTableView(_:)), name: NSNotification.Name(rawValue: TLNotificationEvents.EVENT_PREFERENCES_FIAT_DISPLAY_CHANGED()), object: nil) NotificationCenter.default.addObserver(self , selector: #selector(TLHistoryViewController.updateTransactionsTableView(_:)), name: NSNotification.Name(rawValue: TLNotificationEvents.EVENT_DISPLAY_LOCAL_CURRENCY_TOGGLED()), object: nil) NotificationCenter.default.addObserver(self , selector: #selector(TLHistoryViewController.updateTransactionsTableView(_:)), name: NSNotification.Name(rawValue: TLNotificationEvents.EVENT_MODEL_UPDATED_NEW_BLOCK()), object: nil) NotificationCenter.default.addObserver(self , selector: #selector(TLHistoryViewController.updateTransactionsTableView(_:)), name: NSNotification.Name(rawValue: TLNotificationEvents.EVENT_EXCHANGE_RATE_UPDATED()), object: nil) self.transactionsTableView!.delegate = self self.transactionsTableView!.dataSource = self self.transactionsTableView!.tableFooterView = UIView(frame: CGRect.zero) self.transactionsTableView!.backgroundColor = self.fromBackgroundView!.backgroundColor self.tableviewBackgroundView!.layer.masksToBounds = false self.tableviewBackgroundView!.layer.shadowOpacity = 0.75 self.tableviewBackgroundView!.layer.shadowRadius = 10.0 self.tableviewBackgroundView!.layer.shadowColor = UIColor.black.cgColor self.tableviewBackgroundView!.isHidden = true // If I want the shadow, comment out this line accountRefreshControl = UIRefreshControl() accountRefreshControl!.addTarget(self, action: #selector(TLHistoryViewController.refresh(_:)), for: .valueChanged) self.transactionsTableView!.addSubview(accountRefreshControl!) self.updateViewToNewSelectedObject() self.refreshSelectedAccount(false) } func refresh(_ refreshControl: UIRefreshControl) { self.refreshSelectedAccount(true) accountRefreshControl!.endRefreshing() } fileprivate func refreshSelectedAccount(_ fetchDataAgain: Bool) { if (!AppDelegate.instance().historySelectedObject!.hasFetchedCurrentFromData() || fetchDataAgain) { if (AppDelegate.instance().historySelectedObject!.getSelectedObjectType() == .account) { let accountObject = AppDelegate.instance().historySelectedObject!.getSelectedObject() as! TLAccountObject self.balanceActivityIndicatorView!.isHidden = false self.accountBalanceLabel!.isHidden = true self.balanceActivityIndicatorView!.startAnimating() AppDelegate.instance().pendingOperations.addSetUpAccountOperation(accountObject, fetchDataAgain: fetchDataAgain, success: { self.accountBalanceLabel!.isHidden = false self.balanceActivityIndicatorView!.stopAnimating() self.balanceActivityIndicatorView!.isHidden = true if accountObject.downloadState == .downloaded { self.updateAccountBalance() } }) } else if (AppDelegate.instance().historySelectedObject!.getSelectedObjectType() == .address) { let importedAddress = AppDelegate.instance().historySelectedObject!.getSelectedObject() as! TLImportedAddress self.balanceActivityIndicatorView!.isHidden = false self.accountBalanceLabel!.isHidden = true AppDelegate.instance().pendingOperations.addSetUpImportedAddressOperation(importedAddress, fetchDataAgain: fetchDataAgain, success: { self.accountBalanceLabel!.isHidden = false self.balanceActivityIndicatorView!.stopAnimating() self.balanceActivityIndicatorView!.isHidden = true if importedAddress.downloadState != .downloaded { self.updateAccountBalance() } }) } } else { let balance = TLCurrencyFormat.getProperAmount(AppDelegate.instance().historySelectedObject!.getBalanceForSelectedObject()!) accountBalanceLabel!.text = balance as String self.balanceActivityIndicatorView!.isHidden = true } } func updateViewToNewSelectedObject() { let label = AppDelegate.instance().historySelectedObject!.getLabelForSelectedObject() self.accountNameLabel!.text = label self.updateAccountBalance() self._updateTransactionsTableView() } func _updateTransactionsTableView() { self.transactionsTableView!.reloadData() } func updateTransactionsTableView(_ notification: Notification) { _updateTransactionsTableView() } override func viewWillAppear(_ animated: Bool) -> () { self.updateViewToNewSelectedObject() } override func viewDidAppear(_ animated: Bool) -> () { NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_VIEW_HISTORY()), object: nil, userInfo: nil) } fileprivate func updateAccountBalance() { let balance = AppDelegate.instance().historySelectedObject!.getBalanceForSelectedObject() let balanceString = TLCurrencyFormat.getProperAmount(balance!) self.balanceActivityIndicatorView!.stopAnimating() self.balanceActivityIndicatorView!.isHidden = true self.accountBalanceLabel!.text = balanceString as String self.accountBalanceLabel!.isHidden = false } func onAccountSelected(_ note: Notification) { NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: TLNotificationEvents.EVENT_ACCOUNT_SELECTED()), object: nil) let selectedDict = note.object as! NSDictionary let sendFromType = TLSendFromType(rawValue: selectedDict.object(forKey: "sendFromType") as! Int) let sendFromIndex = selectedDict.object(forKey: "sendFromIndex") as! Int AppDelegate.instance().updateHistorySelectedObject(sendFromType!, sendFromIndex: sendFromIndex) self.updateViewToNewSelectedObject() } override func prepare(for segue: UIStoryboardSegue, sender: Any!) -> () { if (segue.identifier == "selectAccount") { let vc = segue.destination vc.navigationItem.title = TLDisplayStrings.SELECT_ACCOUNT_STRING() NotificationCenter.default.addObserver(self , selector: #selector(TLHistoryViewController.onAccountSelected(_:)), name: NSNotification.Name(rawValue: TLNotificationEvents.EVENT_ACCOUNT_SELECTED()), object: nil) } } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return Int(AppDelegate.instance().historySelectedObject!.getTxObjectCount()) } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let MyIdentifier = "TransactionCellIdentifier" var cell = tableView.dequeueReusableCell(withIdentifier: MyIdentifier) as! TLTransactionTableViewCell? if (cell == nil) { cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: MyIdentifier) as? TLTransactionTableViewCell } cell!.amountButton!.titleEdgeInsets = UIEdgeInsetsMake(0.0, 5.0, 0.0, 5.0) let txObject = AppDelegate.instance().historySelectedObject!.getTxObject((indexPath as NSIndexPath).row) DLog("txObject hash: \(txObject!.getHash()!)") cell!.dateLabel!.text = txObject!.getTime() let amount = TLCurrencyFormat.getProperAmount(AppDelegate.instance().historySelectedObject!.getAccountAmountChangeForTx(txObject!.getHash()! as String)!) let amountType = AppDelegate.instance().historySelectedObject!.getAccountAmountChangeTypeForTx(txObject!.getHash()! as String) var amountTypeString = "" let txTag = AppDelegate.instance().appWallet.getTransactionTag(txObject!.getHash()! as String) cell!.descriptionLabel!.adjustsFontSizeToFitWidth = true if (amountType == .send) { amountTypeString = "-" cell!.amountButton!.backgroundColor = UIColor.red if txTag == nil || txTag == "" { let outputAddressToValueArray = txObject!.getOutputAddressToValueArray() for _dict in outputAddressToValueArray! { let dict = _dict as! NSDictionary if let address = dict.object(forKey: "addr") as? String { if AppDelegate.instance().historySelectedObject!.isAddressPartOfAccount(address) { cell!.descriptionLabel!.text = address } else { cell!.descriptionLabel!.text = address break } } else { cell!.descriptionLabel!.text = "" } } } else { cell!.descriptionLabel!.text = txTag } } else if (amountType == .receive) { amountTypeString = "+" cell!.amountButton!.backgroundColor = UIColor.green if txTag == nil || txTag == "" { cell!.descriptionLabel!.text = "" } else { cell!.descriptionLabel!.text = txTag } } else { cell!.amountButton!.backgroundColor = UIColor.gray if (txTag == nil) { cell!.descriptionLabel!.text = String(format: TLDisplayStrings.INTERNAL_ACCOUNT_TRANSFER_STRING()) } else { cell!.descriptionLabel!.text = txTag } } cell!.amountButton!.setTitle(String(format: "%@%@", amountTypeString, amount), for: UIControlState()) let confirmations = txObject!.getConfirmations() DLog("confirmations \(Int(confirmations))") if (Int(confirmations) > MAX_CONFIRMATIONS_TO_DISPLAY) { cell!.confirmationsLabel!.text = String(format: TLDisplayStrings.X_CONFIRMATIONS_STRING(), txObject!.getConfirmations()) // label is hidden cell!.confirmationsLabel!.backgroundColor = UIColor.green cell!.confirmationsLabel!.isHidden = true } else { if (confirmations == 0) { cell!.confirmationsLabel!.backgroundColor = UIColor.red } else if (confirmations == 1) { cell!.confirmationsLabel!.backgroundColor = UIColor.orange } else if (confirmations <= 2 && confirmations <= 5) { //cell!.confirmationsLabel.backgroundColor = UIColor.yellowColor) //yellow color too light cell!.confirmationsLabel!.backgroundColor = UIColor.green } else { cell!.confirmationsLabel!.backgroundColor = UIColor.green } if (confirmations == 0) { cell!.confirmationsLabel!.text = String(format: TLDisplayStrings.UNCONFIRMED_STRING()) } else if (confirmations == 1) { cell!.confirmationsLabel!.text = String(format: TLDisplayStrings.ONE_CONFIRMATION_STRING()) } else { cell!.confirmationsLabel!.text = String(format: TLDisplayStrings.X_CONFIRMATIONS_STRING(), txObject!.getConfirmations()) } cell!.confirmationsLabel!.isHidden = false } if ((indexPath as NSIndexPath).row % 2 == 0) { cell!.backgroundColor = TLColors.evenTableViewCellColor() } else { cell!.backgroundColor = TLColors.oddTableViewCellColor() } return cell! } func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? { let txObject = AppDelegate.instance().historySelectedObject!.getTxObject((indexPath as NSIndexPath).row) self.promptTransactionActionSheet(txObject!.getHash()!) return nil } fileprivate func promptTransactionActionSheet(_ txHash: NSString) { let otherButtonTitles = [TLDisplayStrings.VIEW_IN_WEB_STRING(), TLDisplayStrings.LABEL_TRANSACTION_STRING(), TLDisplayStrings.COPY_TRANSACTION_ID_TO_CLIPBOARD_STRING()] UIAlertController.showAlert(in: self, withTitle: String(format: TLDisplayStrings.TRANSACTION_ID_COLON_X_STRING(), txHash), message:"", preferredStyle: .actionSheet, cancelButtonTitle: TLDisplayStrings.CANCEL_STRING(), destructiveButtonTitle: nil, otherButtonTitles: otherButtonTitles as [AnyObject], tap: {(actionSheet, action, buttonIndex) in if (buttonIndex == actionSheet?.firstOtherButtonIndex) { TLBlockExplorerAPI.instance().openWebViewForTransaction(txHash as String) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_VIEW_TRANSACTION_IN_WEB()), object: nil, userInfo: nil) } else if (buttonIndex == (actionSheet?.firstOtherButtonIndex)! + 1) { TLPrompts.promtForInputText(self, title:TLDisplayStrings.EDIT_TRANSACTION_LABEL_STRING(), message: "", textFieldPlaceholder: TLDisplayStrings.LABEL_STRING(), success: { (inputText: String!) in if (inputText == "") { AppDelegate.instance().appWallet.deleteTransactionTag(txHash as String) } else { AppDelegate.instance().appWallet.setTransactionTag(txHash as String, tag: inputText) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_TAG_TRANSACTION()), object: nil, userInfo: nil) } self._updateTransactionsTableView() }, failure: { (isCancelled: Bool) in }) } else if (buttonIndex == (actionSheet?.firstOtherButtonIndex)! + 2) { let pasteboard = UIPasteboard.general pasteboard.string = txHash as String iToast.makeText(TLDisplayStrings.COPIED_TO_CLIPBOARD_STRING()).setGravity(iToastGravityCenter).setDuration(1000).show() } else if (buttonIndex == actionSheet?.cancelButtonIndex) { } }) } func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? { let moreAction = UITableViewRowAction(style: .default, title: TLDisplayStrings.MORE_STRING(), handler: { (action: UITableViewRowAction, indexPath: IndexPath) in tableView.isEditing = false let txObject = AppDelegate.instance().historySelectedObject!.getTxObject((indexPath as NSIndexPath).row) self.promptTransactionActionSheet(txObject!.getHash()!) }) moreAction.backgroundColor = UIColor.lightGray return [moreAction] } @IBAction fileprivate func menuButtonClicked(_ sender: AnyObject) { self.slidingViewController().anchorTopViewToRight(animated: true) } deinit { NotificationCenter.default.removeObserver(self) } } ================================================ FILE: ArcBit/viewControllers/TLInstructionsViewController.swift ================================================ // // TLInstructionsViewController.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit @objc(TLInstructionsViewController) class TLInstructionsViewController : UIViewController, UITableViewDataSource, UITableViewDelegate { required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } @IBOutlet fileprivate var instructionsTableView: UITableView? var action:String? var actionInstructionsSteps:NSArray? override func viewDidLoad() { super.viewDidLoad() setColors() self.instructionsTableView!.delegate = self self.instructionsTableView!.dataSource = self self.instructionsTableView!.tableFooterView = UIView(frame:CGRect.zero) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } func numberOfSections(in tableView:UITableView) -> Int { return 1 } func tableView(_ tableView:UITableView, titleForHeaderInSection section:Int) -> String? { return TLDisplayStrings.STEPS_STRING() } func tableView(_ tableView: UITableView, numberOfRowsInSection section:Int) -> Int { return actionInstructionsSteps!.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath:IndexPath) -> UITableViewCell{ let MyIdentifier = "InstructionStepCellIdentifier" var cell = tableView.dequeueReusableCell(withIdentifier: MyIdentifier) if (cell == nil) { cell = UITableViewCell(style:UITableViewCellStyle.default, reuseIdentifier:MyIdentifier) } cell!.textLabel!.numberOfLines = 0 let text = String(format:"%ld. %@", (indexPath as NSIndexPath).row+1, actionInstructionsSteps!.object(at: (indexPath as NSIndexPath).row) as! String) cell!.textLabel!.text = text if ((indexPath as NSIndexPath).row % 2 == 0) { cell!.backgroundColor = TLColors.evenTableViewCellColor() } else { cell!.backgroundColor = TLColors.oddTableViewCellColor() } return cell! } } ================================================ FILE: ArcBit/viewControllers/TLLinksViewController.swift ================================================ // // TLLinksViewController.m // ArcBit // // Created by Tim Lee on 3/18/15. // Copyright (c) 2015 ArcBit. All rights reserved. // // // TLLinksViewController.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit @objc(TLLinksViewController) class TLLinksViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, MFMailComposeViewControllerDelegate { required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } struct STATIC_MEMBERS { static let kWebWalletSection = "kColdWalletSection" static let kBrainSection = "kBrainSection" static let kOthersSection = "kOthersSection" static let kEmailSupportSection = "kEmailSupportSection" } @IBOutlet fileprivate var linksTableView:UITableView? fileprivate var action:NSString? fileprivate var clickRightBarButtonCount:Int = 0 fileprivate var selectedSection:String = "" fileprivate lazy var sectionArray = Array() override func viewDidLoad() { super.viewDidLoad() setColors() setLogoImageView() //self.sectionArray = [STATIC_MEMBERS.kWebWalletSection, STATIC_MEMBERS.kBrainSection, STATIC_MEMBERS.kOthersSection, STATIC_MEMBERS.kEmailSupportSection] self.sectionArray = [STATIC_MEMBERS.kWebWalletSection, STATIC_MEMBERS.kOthersSection, STATIC_MEMBERS.kEmailSupportSection] self.navigationController!.view.addGestureRecognizer(self.slidingViewController().panGesture) self.linksTableView!.delegate = self self.linksTableView!.dataSource = self self.linksTableView!.tableFooterView = UIView(frame:CGRect.zero) self.clickRightBarButtonCount = 0 let button = UIButton(frame: CGRect(x: 0, y: 0, width: 80, height: 30)) button.backgroundColor = TLColors.mainAppColor() button.setTitle("Status", for: UIControlState()) button.setTitleColor(TLColors.mainAppColor(), for: UIControlState()) button.addTarget(self, action: #selector(TLLinksViewController.rightBarButtonClicked), for: UIControlEvents.touchUpInside) let rightBarButtonItem = UIBarButtonItem(customView: button) navigationItem.rightBarButtonItem = rightBarButtonItem } func rightBarButtonClicked() { self.clickRightBarButtonCount += 1 if (self.clickRightBarButtonCount >= 10) { TLStealthWebSocket.instance().isWebSocketOpen() let av = UIAlertView(title: "Web Socket Server status", message: "Up: \(TLStealthWebSocket.instance().isWebSocketOpen())", delegate: nil, cancelButtonTitle: TLDisplayStrings.CANCEL_STRING(), otherButtonTitles: TLDisplayStrings.OK_STRING()) av.show() } } func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { self.dismiss(animated: true, completion: nil) } func showEmailSupportViewController() { let mc = MFMailComposeViewController() mc.mailComposeDelegate = self mc.setSubject(String(format: "%@ iOS Support", TLWalletUtils.APP_NAME())) let message = "Dear ArcBit Support,\n\n\n\n--\nApp Version: \(TLPreferences .getAppVersion())\nSystem: \(UIDevice.current.systemName) \(UIDevice.current.systemVersion)\n" DLog(message); mc.setMessageBody(message, isHTML: false) mc.setToRecipients(["support@arcbit.zendesk.com"]) self.present(mc, animated: true, completion: nil) } override func viewDidAppear(_ animated:Bool) -> () { NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_VIEW_HELP_SCREEN()), object:nil) } override func didReceiveMemoryWarning() -> () { super.didReceiveMemoryWarning() } override func prepare(for segue: UIStoryboardSegue, sender:Any!) -> () { if (segue.identifier == "SegueText2") { if (self.selectedSection == "webwallet") { let vc = segue.destination as! TLTextViewViewController vc.navigationItem.title = TLDisplayStrings.ARCBIT_WEB_WALLET_STRING() vc.text = TLDisplayStrings.ARCBIT_WEB_WALLET_DESC_STRING() } else if (self.selectedSection == "brainwallet") { let vc = segue.destination as! TLTextViewViewController vc.navigationItem.title = TLDisplayStrings.ARCBIT_BRAIN_WALLET_STRING() vc.text = TLDisplayStrings.ARCBIT_BRAIN_WALLET_STRING_DESC_STRING() } } } @IBAction fileprivate func menuButtonClicked(_ sender:UIButton) -> () { self.slidingViewController().anchorTopViewToRight(animated: true) } func numberOfSections(in tableView:UITableView) -> Int { return self.sectionArray.count } func tableView(_ tableView:UITableView, titleForHeaderInSection section:Int) -> String? { let sectionType = self.sectionArray[section] if sectionType == STATIC_MEMBERS.kWebWalletSection { return TLDisplayStrings.ARCBIT_WEB_WALLET_STRING() } else if sectionType == STATIC_MEMBERS.kBrainSection { return TLDisplayStrings.ARCBIT_BRAIN_WALLET_STRING() } else if sectionType == STATIC_MEMBERS.kOthersSection { return TLDisplayStrings.OTHER_LINKS_STRING() } else { return TLDisplayStrings.EMAIL_SUPPORT_STRING() } } func tableView(_ tableView: UITableView, numberOfRowsInSection section:Int) -> Int { let sectionType = self.sectionArray[section] if sectionType == STATIC_MEMBERS.kWebWalletSection || sectionType == STATIC_MEMBERS.kBrainSection || sectionType == STATIC_MEMBERS.kOthersSection { return 2 } else { return 1 } } func tableView(_ tableView: UITableView, cellForRowAt indexPath:IndexPath) -> UITableViewCell{ let MyIdentifier = "LinksCellIdentifier" var cell = tableView.dequeueReusableCell(withIdentifier: MyIdentifier) if (cell == nil) { cell = UITableViewCell(style:UITableViewCellStyle.default, reuseIdentifier:MyIdentifier) } cell!.textLabel!.numberOfLines = 0 let sectionType = self.sectionArray[indexPath.section] if sectionType == STATIC_MEMBERS.kWebWalletSection { if (indexPath as NSIndexPath).row == 0 { cell!.textLabel?.text = TLDisplayStrings.CHECK_OUT_THE_ARCBIT_WEB_WALLET_STRING() } else { cell!.textLabel?.text = TLDisplayStrings.VIEW_ARCBIT_WEB_WALLET_DETAILS_STRING() } } else if sectionType == STATIC_MEMBERS.kBrainSection { if (indexPath as NSIndexPath).row == 0 { cell!.textLabel?.text = TLDisplayStrings.CHECK_OUT_THE_ARCBIT_BRAIN_WALLET_STRING() } else { cell!.textLabel?.text = TLDisplayStrings.VIEW_ARCBIT_BRAIN_WALLET_DETAILS_STRING() } } else if sectionType == STATIC_MEMBERS.kOthersSection { if (indexPath as NSIndexPath).row == 0 { cell!.imageView?.image = UIImage(named: "home3") cell!.textLabel?.text = TLDisplayStrings.VISIT_OUR_HOME_PAGE_STRING() } else { cell!.imageView?.image = UIImage(named: "twitter") cell!.textLabel?.text = TLDisplayStrings.FOLLOW_US_ON_TWITTER_STRING() } } else { cell!.accessoryType = UITableViewCellAccessoryType.none cell!.imageView?.image = UIImage(named: "lifebuoy") cell!.textLabel?.text = TLDisplayStrings.EMAIL_SUPPORT_STRING() } return cell! } func tableView(_ tableView:UITableView, willSelectRowAt indexPath:IndexPath) -> IndexPath? { let sectionType = self.sectionArray[indexPath.section] if sectionType == STATIC_MEMBERS.kWebWalletSection { self.selectedSection = "webwallet" if (indexPath as NSIndexPath).row == 0 { let url = URL(string: "https://chrome.google.com/webstore/detail/arcbit-bitcoin-wallet/dkceiphcnbfahjbomhpdgjmphnpgogfk"); if (UIApplication.shared.canOpenURL(url!)) { UIApplication.shared.openURL(url!); } } else { performSegue(withIdentifier: "SegueText2", sender:self) } } else if sectionType == STATIC_MEMBERS.kBrainSection { self.selectedSection = "brainwallet" if (indexPath as NSIndexPath).row == 0 { let url = URL(string: "https://www.arcbitbrainwallet.com"); if (UIApplication.shared.canOpenURL(url!)) { UIApplication.shared.openURL(url!); } } else { performSegue(withIdentifier: "SegueText2", sender:self) } } else if sectionType == STATIC_MEMBERS.kOthersSection { if (indexPath as NSIndexPath).row == 0 { let url = URL(string: "http://arcbit.io/"); if (UIApplication.shared.canOpenURL(url!)) { UIApplication.shared.openURL(url!); } } else { let url = URL(string: "https://twitter.com/arc_bit"); if (UIApplication.shared.canOpenURL(url!)) { UIApplication.shared.openURL(url!); } } } else { self.showEmailSupportViewController() } return nil } } ================================================ FILE: ArcBit/viewControllers/TLManageAccountsViewController.swift ================================================ // // TLManageAccountsViewController.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit @objc(TLManageAccountsViewController) class TLManageAccountsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, CustomIOS7AlertViewDelegate { required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } let MAX_ACTIVE_CREATED_ACCOUNTS = 8 @IBOutlet fileprivate var accountsTableView: UITableView? fileprivate var QRImageModal: TLQRImageModal? fileprivate var accountActionsArray: NSArray? fileprivate var numberOfSections: Int = 0 fileprivate var accountListSection: Int = 0 fileprivate var coldWalletAccountSection: Int = 0 fileprivate var importedAccountSection: Int = 0 fileprivate var importedWatchAccountSection: Int = 0 fileprivate var importedAddressSection: Int = 0 fileprivate var importedWatchAddressSection: Int = 0 fileprivate var archivedAccountSection: Int = 0 fileprivate var archivedColdWalletAccountSection: Int = 0 fileprivate var archivedImportedAccountSection: Int = 0 fileprivate var archivedImportedWatchAccountSection: Int = 0 fileprivate var archivedImportedAddressSection: Int = 0 fileprivate var archivedImportedWatchAddressSection: Int = 0 fileprivate var accountActionSection: Int = 0 fileprivate var accountRefreshControl: UIRefreshControl? fileprivate var showAddressListAccountObject: TLAccountObject? fileprivate var showAddressListShowBalances: Bool = false override func viewDidLoad() { super.viewDidLoad() setColors() self.setLogoImageView() self.navigationController!.view.addGestureRecognizer(self.slidingViewController().panGesture) accountListSection = 0 self.accountsTableView!.delegate = self self.accountsTableView!.dataSource = self self.accountsTableView!.tableFooterView = UIView(frame: CGRect.zero) NotificationCenter.default.addObserver(self, selector: #selector(TLManageAccountsViewController.refreshWalletAccountsNotification(_:)), name: NSNotification.Name(rawValue: TLNotificationEvents.EVENT_DISPLAY_LOCAL_CURRENCY_TOGGLED()), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(TLManageAccountsViewController.refreshWalletAccountsNotification(_:)), name: NSNotification.Name(rawValue: TLNotificationEvents.EVENT_FETCHED_ADDRESSES_DATA()), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(TLManageAccountsViewController.accountsTableViewReloadDataWrapper(_:)), name: NSNotification.Name(rawValue: TLNotificationEvents.EVENT_ADVANCE_MODE_TOGGLED()), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(TLManageAccountsViewController.accountsTableViewReloadDataWrapper(_:)), name: NSNotification.Name(rawValue: TLNotificationEvents.EVENT_MODEL_UPDATED_NEW_UNCONFIRMED_TRANSACTION()), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(TLManageAccountsViewController.accountsTableViewReloadDataWrapper(_:)), name: NSNotification.Name(rawValue: TLNotificationEvents.EVENT_EXCHANGE_RATE_UPDATED()), object: nil) accountRefreshControl = UIRefreshControl() accountRefreshControl!.addTarget(self, action: #selector(TLManageAccountsViewController.refresh(_:)), for: .valueChanged) self.accountsTableView!.addSubview(accountRefreshControl!) checkToRecoverAccounts() refreshWalletAccounts(false) } func refresh(_ refresh:UIRefreshControl) -> () { self.refreshWalletAccounts(true) accountRefreshControl!.endRefreshing() } override func viewWillAppear(_ animated: Bool) -> () { // TODO: better way if AppDelegate.instance().scannedEncryptedPrivateKey != nil { TLPrompts.promptForEncryptedPrivKeyPassword(self, view:self.slidingViewController().topViewController.view, encryptedPrivKey:AppDelegate.instance().scannedEncryptedPrivateKey!, success:{(privKey: String!) in let privateKey = privKey let encryptedPrivateKey = AppDelegate.instance().scannedEncryptedPrivateKey self.checkAndImportAddress(privateKey!, encryptedPrivateKey: encryptedPrivateKey) AppDelegate.instance().scannedEncryptedPrivateKey = nil }, failure:{(isCanceled: Bool) in AppDelegate.instance().scannedEncryptedPrivateKey = nil }) } } override func viewDidAppear(_ animated: Bool) -> () { NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_VIEW_MANAGE_ACCOUNTS_SCREEN()), object: nil) } func checkToRecoverAccounts() { if (AppDelegate.instance().aAccountNeedsRecovering()) { TLHUDWrapper.showHUDAddedTo(self.slidingViewController().topViewController.view, labelText: TLDisplayStrings.RESTORING_WALLET_STRING(), animated: true) DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.high).async { AppDelegate.instance().checkToRecoverAccounts() DispatchQueue.main.async { self.refreshWalletAccounts(false) TLHUDWrapper.hideHUDForView(self.view, animated: true) } } } } fileprivate func refreshColdWalletAccounts(_ fetchDataAgain: Bool) -> () { for i in stride(from: 0, to: AppDelegate.instance().coldWalletAccounts!.getNumberOfAccounts(), by: 1) { let accountObject = AppDelegate.instance().coldWalletAccounts!.getAccountObjectForIdx(i) let indexPath = IndexPath(row: i, section: coldWalletAccountSection) if self.accountsTableView!.cellForRow(at: indexPath) == nil { return } if (!accountObject.hasFetchedAccountData() || fetchDataAgain) { let cell = self.accountsTableView!.cellForRow(at: indexPath) as? TLAccountTableViewCell if cell != nil { (cell!.accessoryView! as! UIActivityIndicatorView).isHidden = false (cell!.accessoryView! as! UIActivityIndicatorView).startAnimating() cell!.accountBalanceButton!.isHidden = true } AppDelegate.instance().pendingOperations.addSetUpAccountOperation(accountObject, fetchDataAgain: fetchDataAgain, success: { if cell != nil { (cell!.accessoryView as! UIActivityIndicatorView).stopAnimating() (cell!.accessoryView as! UIActivityIndicatorView).isHidden = true cell!.accountBalanceButton!.isHidden = false if accountObject.downloadState == .downloaded { let balance = TLCurrencyFormat.getProperAmount(accountObject.getBalance()) cell!.accountBalanceButton!.setTitle(balance as String, for: UIControlState()) } cell!.accountBalanceButton!.isHidden = false } }) } else { if let cell = self.accountsTableView!.cellForRow(at: indexPath) as? TLAccountTableViewCell { cell.accountNameLabel!.text = accountObject.getAccountName() let balance = TLCurrencyFormat.getProperAmount(accountObject.getBalance()) cell.accountBalanceButton!.setTitle(balance as String, for: UIControlState()) } } } } fileprivate func refreshImportedAccounts(_ fetchDataAgain: Bool) -> () { for i in stride(from: 0, to: AppDelegate.instance().importedAccounts!.getNumberOfAccounts(), by: 1) { let accountObject = AppDelegate.instance().importedAccounts!.getAccountObjectForIdx(i) let indexPath = IndexPath(row: i, section: importedAccountSection) if self.accountsTableView!.cellForRow(at: indexPath) == nil { return } if (!accountObject.hasFetchedAccountData() || fetchDataAgain) { let cell = self.accountsTableView!.cellForRow(at: indexPath) as? TLAccountTableViewCell if cell != nil { (cell!.accessoryView! as! UIActivityIndicatorView).isHidden = false cell!.accountBalanceButton!.isHidden = true (cell!.accessoryView! as! UIActivityIndicatorView).startAnimating() } AppDelegate.instance().pendingOperations.addSetUpAccountOperation(accountObject, fetchDataAgain: fetchDataAgain, success: { if cell != nil { (cell!.accessoryView as! UIActivityIndicatorView).stopAnimating() (cell!.accessoryView as! UIActivityIndicatorView).isHidden = true cell!.accountBalanceButton!.isHidden = false if accountObject.downloadState == .downloaded { let balance = TLCurrencyFormat.getProperAmount(accountObject.getBalance()) cell!.accountBalanceButton!.setTitle(balance as String, for: UIControlState()) } cell!.accountBalanceButton!.isHidden = false } }) } else { if let cell = self.accountsTableView!.cellForRow(at: indexPath) as? TLAccountTableViewCell { cell.accountNameLabel!.text = accountObject.getAccountName() let balance = TLCurrencyFormat.getProperAmount(accountObject.getBalance()) cell.accountBalanceButton!.setTitle(balance as String, for: UIControlState()) } } } } fileprivate func refreshImportedWatchAccounts(_ fetchDataAgain: Bool) -> () { for i in stride(from: 0, to: AppDelegate.instance().importedWatchAccounts!.getNumberOfAccounts(), by: 1) { let accountObject = AppDelegate.instance().importedWatchAccounts!.getAccountObjectForIdx(i) let indexPath = IndexPath(row: i, section: importedWatchAccountSection) if self.accountsTableView!.cellForRow(at: indexPath) == nil { return } if (!accountObject.hasFetchedAccountData() || fetchDataAgain) { let cell = self.accountsTableView!.cellForRow(at: indexPath) as? TLAccountTableViewCell if cell != nil { (cell!.accessoryView! as! UIActivityIndicatorView).isHidden = false (cell!.accessoryView! as! UIActivityIndicatorView).startAnimating() cell!.accountBalanceButton!.isHidden = true } AppDelegate.instance().pendingOperations.addSetUpAccountOperation(accountObject, fetchDataAgain: fetchDataAgain, success: { if cell != nil { (cell!.accessoryView as! UIActivityIndicatorView).stopAnimating() (cell!.accessoryView as! UIActivityIndicatorView).isHidden = true cell!.accountBalanceButton!.isHidden = false if accountObject.downloadState == .downloaded { let balance = TLCurrencyFormat.getProperAmount(accountObject.getBalance()) cell!.accountBalanceButton!.setTitle(balance as String, for: UIControlState()) } cell!.accountBalanceButton!.isHidden = false } }) } else { if let cell = self.accountsTableView!.cellForRow(at: indexPath) as? TLAccountTableViewCell { cell.accountNameLabel!.text = accountObject.getAccountName() let balance = TLCurrencyFormat.getProperAmount(accountObject.getBalance()) cell.accountBalanceButton!.setTitle(balance as String, for: UIControlState()) } } } } fileprivate func refreshImportedAddressBalances(_ fetchDataAgain: Bool) { if (AppDelegate.instance().importedAddresses!.getCount() > 0 && (!AppDelegate.instance().importedAddresses!.hasFetchedAddressesData() || fetchDataAgain)) { for i in stride(from: 0, to: AppDelegate.instance().importedAddresses!.getCount(), by: 1) { let indexPath = IndexPath(row: i, section: importedAddressSection) if let cell = self.accountsTableView!.cellForRow(at: indexPath) as? TLAccountTableViewCell { (cell.accessoryView as! UIActivityIndicatorView).isHidden = false cell.accountBalanceButton!.isHidden = true (cell.accessoryView as! UIActivityIndicatorView).startAnimating() } } AppDelegate.instance().pendingOperations.addSetUpImportedAddressesOperation(AppDelegate.instance().importedAddresses!, fetchDataAgain: fetchDataAgain, success: { for i in stride(from: 0, to: AppDelegate.instance().importedAddresses!.getCount(), by: 1) { let indexPath = IndexPath(row: i, section: self.importedAddressSection) if let cell = self.accountsTableView!.cellForRow(at: indexPath) as? TLAccountTableViewCell { (cell.accessoryView as! UIActivityIndicatorView).stopAnimating() (cell.accessoryView as! UIActivityIndicatorView).isHidden = true if AppDelegate.instance().importedAddresses!.downloadState == .downloaded { let importAddressObject = AppDelegate.instance().importedAddresses!.getAddressObjectAtIdx(i) let balance = TLCurrencyFormat.getProperAmount(importAddressObject.getBalance()!) cell.accountBalanceButton!.setTitle(balance as String, for: UIControlState()) } cell.accountBalanceButton!.isHidden = false } } }) } } fileprivate func refreshImportedWatchAddressBalances(_ fetchDataAgain: Bool) { if (AppDelegate.instance().importedWatchAddresses!.getCount() > 0 && (!AppDelegate.instance().importedWatchAddresses!.hasFetchedAddressesData() || fetchDataAgain)) { for i in stride(from: 0, to: AppDelegate.instance().importedWatchAddresses!.getCount(), by: 1) { let indexPath = IndexPath(row: i, section: importedWatchAddressSection) if let cell = self.accountsTableView!.cellForRow(at: indexPath) as? TLAccountTableViewCell { (cell.accessoryView as! UIActivityIndicatorView).isHidden = false cell.accountBalanceButton!.isHidden = true (cell.accessoryView as! UIActivityIndicatorView).startAnimating() } } AppDelegate.instance().pendingOperations.addSetUpImportedAddressesOperation(AppDelegate.instance().importedWatchAddresses!, fetchDataAgain: fetchDataAgain, success: { for i in stride(from: 0, to: AppDelegate.instance().importedWatchAddresses!.getCount(), by: 1) { let indexPath = IndexPath(row: i, section: self.importedWatchAddressSection) if let cell = self.accountsTableView!.cellForRow(at: indexPath) as? TLAccountTableViewCell { (cell.accessoryView as! UIActivityIndicatorView).stopAnimating() (cell.accessoryView as! UIActivityIndicatorView).isHidden = true if AppDelegate.instance().importedWatchAddresses!.downloadState == .downloaded { let importAddressObject = AppDelegate.instance().importedWatchAddresses!.getAddressObjectAtIdx(i) let balance = TLCurrencyFormat.getProperAmount(importAddressObject.getBalance()!) cell.accountBalanceButton!.setTitle(balance as String, for: UIControlState()) } cell.accountBalanceButton!.isHidden = false } } }) } } fileprivate func refreshAccountBalances(_ fetchDataAgain: Bool) -> () { for i in stride(from: 0, to: AppDelegate.instance().accounts!.getNumberOfAccounts(), by: 1) { let accountObject = AppDelegate.instance().accounts!.getAccountObjectForIdx(i) let indexPath = IndexPath(row: i, section: accountListSection) if self.accountsTableView?.cellForRow(at: indexPath) == nil { return } if (!accountObject.hasFetchedAccountData() || fetchDataAgain) { let cell = self.accountsTableView!.cellForRow(at: indexPath) as? TLAccountTableViewCell if cell != nil { (cell!.accessoryView! as! UIActivityIndicatorView).isHidden = false cell!.accountBalanceButton!.isHidden = true (cell!.accessoryView! as! UIActivityIndicatorView).startAnimating() } AppDelegate.instance().pendingOperations.addSetUpAccountOperation(accountObject, fetchDataAgain: fetchDataAgain, success: { if cell != nil { (cell!.accessoryView as! UIActivityIndicatorView).stopAnimating() (cell!.accessoryView as! UIActivityIndicatorView).isHidden = true cell!.accountBalanceButton!.isHidden = false if accountObject.downloadState != .failed { let balance = TLCurrencyFormat.getProperAmount(accountObject.getBalance()) cell!.accountBalanceButton!.setTitle(balance as String, for: UIControlState()) cell!.accountBalanceButton!.isHidden = false } } }) } else { if let cell = self.accountsTableView!.cellForRow(at: indexPath) as? TLAccountTableViewCell { cell.accountNameLabel!.text = (accountObject.getAccountName()) let balance = TLCurrencyFormat.getProperAmount(accountObject.getBalance()) cell.accountBalanceButton!.setTitle(balance as String, for: UIControlState()) } } } } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } override func prepare(for segue: UIStoryboardSegue, sender: Any!) -> () { if (segue.identifier == "SegueAddressList") { let vc = segue.destination as! TLAddressListViewController vc.navigationItem.title = TLDisplayStrings.ADDRESSES_STRING() vc.accountObject = showAddressListAccountObject vc.showBalances = showAddressListShowBalances } } func refreshWalletAccountsNotification(_ notification: Notification) -> () { self.refreshWalletAccounts(false) } fileprivate func refreshWalletAccounts(_ fetchDataAgain: Bool) -> () { self._accountsTableViewReloadDataWrapper() self.refreshAccountBalances(fetchDataAgain) if TLPreferences.enabledColdWallet() { self.refreshColdWalletAccounts(fetchDataAgain) } if (TLPreferences.enabledAdvancedMode()) { self.refreshImportedAccounts(fetchDataAgain) self.refreshImportedWatchAccounts(fetchDataAgain) self.refreshImportedAddressBalances(fetchDataAgain) self.refreshImportedWatchAddressBalances(fetchDataAgain) } } fileprivate func setUpCellAccounts(_ accountObject: TLAccountObject, cell: TLAccountTableViewCell, cellForRowAtIndexPath indexPath: IndexPath) -> () { cell.accountNameLabel!.isHidden = false cell.accountBalanceButton!.isHidden = false cell.textLabel!.isHidden = true cell.accountNameLabel!.text = accountObject.getAccountName() if (accountObject.hasFetchedAccountData()) { (cell.accessoryView! as! UIActivityIndicatorView).isHidden = true (cell.accessoryView! as! UIActivityIndicatorView).stopAnimating() let balance = TLCurrencyFormat.getProperAmount(accountObject.getBalance()) cell.accountBalanceButton!.setTitle(balance as String, for: UIControlState()) cell.accountBalanceButton!.isHidden = false } else { (cell.accessoryView! as! UIActivityIndicatorView).isHidden = false (cell.accessoryView! as! UIActivityIndicatorView).startAnimating() AppDelegate.instance().pendingOperations.addSetUpAccountOperation(accountObject, fetchDataAgain: false, success: { (cell.accessoryView as! UIActivityIndicatorView).stopAnimating() (cell.accessoryView as! UIActivityIndicatorView).isHidden = true if accountObject.downloadState == .downloaded { let balance = TLCurrencyFormat.getProperAmount(accountObject.getBalance()) cell.accountBalanceButton!.setTitle(balance as String, for: UIControlState()) cell.accountBalanceButton!.isHidden = false } }) } } fileprivate func setUpCellImportedAddresses(_ importedAddressObject: TLImportedAddress, cell: TLAccountTableViewCell, cellForRowAtIndexPath indexPath: IndexPath) -> () { cell.accountNameLabel!.isHidden = false cell.accountBalanceButton!.isHidden = false cell.textLabel!.isHidden = true let label = importedAddressObject.getLabel() cell.accountNameLabel!.text = label if (importedAddressObject.hasFetchedAccountData()) { (cell.accessoryView! as! UIActivityIndicatorView).isHidden = true (cell.accessoryView! as! UIActivityIndicatorView).stopAnimating() let balance = TLCurrencyFormat.getProperAmount(importedAddressObject.getBalance()!) cell.accountBalanceButton!.setTitle(balance as String, for: UIControlState()) } } fileprivate func setUpCellArchivedImportedAddresses(_ importedAddressObject: TLImportedAddress, cell: TLAccountTableViewCell, cellForRowAtIndexPath indexPath: IndexPath) -> () { cell.accountNameLabel!.isHidden = true cell.accountBalanceButton!.isHidden = true cell.textLabel!.isHidden = false let label = importedAddressObject.getLabel() cell.textLabel!.text = label } fileprivate func setUpCellArchivedAccounts(_ accountObject: TLAccountObject, cell: TLAccountTableViewCell, cellForRowAtIndexPath indexPath: IndexPath) -> () { cell.accountNameLabel!.isHidden = true cell.accountBalanceButton!.isHidden = true cell.textLabel!.isHidden = false cell.textLabel!.text = accountObject.getAccountName() (cell.accessoryView! as! UIActivityIndicatorView).isHidden = true } fileprivate func promtForLabel(_ success: @escaping TLPrompts.UserInputCallback, failure: @escaping TLPrompts.Failure) -> () { func addTextField(_ textField: UITextField!){ textField.placeholder = TLDisplayStrings.LABEL_STRING() } UIAlertController.showAlert(in: self, withTitle: TLDisplayStrings.ENTER_LABEL_STRING(), message: "", preferredStyle: .alert, cancelButtonTitle: TLDisplayStrings.SAVE_STRING(), destructiveButtonTitle: nil, otherButtonTitles: [TLDisplayStrings.SAVE_STRING()], preShow: {(controller) in controller!.addTextField(configurationHandler: addTextField) }, tap: {(alertView, action, buttonIndex) in if (buttonIndex == alertView!.firstOtherButtonIndex) { if(alertView!.textFields != nil) { let label = (alertView!.textFields![0] ).text success(label) } } else if (buttonIndex == alertView!.cancelButtonIndex) { failure(true) } }) } fileprivate func promtForNameAccount(_ success: @escaping TLPrompts.UserInputCallback, failure: @escaping TLPrompts.Failure) -> () { func addTextField(_ textField: UITextField!){ textField.placeholder = TLDisplayStrings.ACCOUNT_NAME_STRING() } UIAlertController.showAlert(in: self, withTitle: TLDisplayStrings.ENTER_LABEL_STRING(), message: "", preferredStyle: .alert, cancelButtonTitle: TLDisplayStrings.CANCEL_STRING(), destructiveButtonTitle: nil, otherButtonTitles: [TLDisplayStrings.SAVE_STRING()], preShow: {(controller) in controller!.addTextField(configurationHandler: addTextField) }, tap: {(alertView, action, buttonIndex) in if (buttonIndex == alertView!.firstOtherButtonIndex) { let accountName = (alertView!.textFields![0]).text success(accountName) } else if (buttonIndex == alertView!.cancelButtonIndex) { failure(true) } }) } func _accountsTableViewReloadDataWrapper() -> () { accountActionsArray = TLHelpDoc.getAccountActionsArray() numberOfSections = 2 var sectionCounter = 1 if TLPreferences.enabledColdWallet() { if (AppDelegate.instance().coldWalletAccounts!.getNumberOfAccounts() > 0) { coldWalletAccountSection = sectionCounter sectionCounter += 1 numberOfSections += 1 } else { coldWalletAccountSection = NSIntegerMax } } if (TLPreferences.enabledAdvancedMode()) { if (AppDelegate.instance().importedAccounts!.getNumberOfAccounts() > 0) { importedAccountSection = sectionCounter sectionCounter += 1 numberOfSections += 1 } else { importedAccountSection = NSIntegerMax } if (AppDelegate.instance().importedWatchAccounts!.getNumberOfAccounts() > 0) { importedWatchAccountSection = sectionCounter sectionCounter += 1 numberOfSections += 1 } else { importedWatchAccountSection = NSIntegerMax } if (AppDelegate.instance().importedAddresses!.getCount() > 0) { importedAddressSection = sectionCounter sectionCounter += 1 numberOfSections += 1 } else { importedAddressSection = NSIntegerMax } if (AppDelegate.instance().importedWatchAddresses!.getCount() > 0) { importedWatchAddressSection = sectionCounter sectionCounter += 1 numberOfSections += 1 } else { importedWatchAddressSection = NSIntegerMax } } else { importedAccountSection = NSIntegerMax importedWatchAccountSection = NSIntegerMax importedAddressSection = NSIntegerMax importedWatchAddressSection = NSIntegerMax } if (AppDelegate.instance().accounts!.getNumberOfArchivedAccounts() > 0) { archivedAccountSection = sectionCounter sectionCounter += 1 numberOfSections += 1 } else { archivedAccountSection = NSIntegerMax } if TLPreferences.enabledColdWallet() { if (AppDelegate.instance().coldWalletAccounts!.getNumberOfArchivedAccounts() > 0) { archivedColdWalletAccountSection = sectionCounter sectionCounter += 1 numberOfSections += 1 } else { archivedColdWalletAccountSection = NSIntegerMax } } if (TLPreferences.enabledAdvancedMode()) { if (AppDelegate.instance().importedAccounts!.getNumberOfArchivedAccounts() > 0) { archivedImportedAccountSection = sectionCounter sectionCounter += 1 numberOfSections += 1 } else { archivedImportedAccountSection = NSIntegerMax } if (AppDelegate.instance().importedWatchAccounts!.getNumberOfArchivedAccounts() > 0) { archivedImportedWatchAccountSection = sectionCounter sectionCounter += 1 numberOfSections += 1 } else { archivedImportedWatchAccountSection = NSIntegerMax } if (AppDelegate.instance().importedAddresses!.getArchivedCount() > 0) { archivedImportedAddressSection = sectionCounter sectionCounter += 1 numberOfSections += 1 } else { archivedImportedAddressSection = NSIntegerMax } if (AppDelegate.instance().importedWatchAddresses!.getArchivedCount() > 0) { archivedImportedWatchAddressSection = sectionCounter sectionCounter += 1 numberOfSections += 1 } else { archivedImportedWatchAddressSection = NSIntegerMax } } else { archivedImportedAccountSection = NSIntegerMax archivedImportedWatchAccountSection = NSIntegerMax } accountActionSection = sectionCounter self.accountsTableView!.reloadData() } func accountsTableViewReloadDataWrapper(_ notification: Notification) -> () { _accountsTableViewReloadDataWrapper() } fileprivate func promptAccountsActionSheet(_ idx: Int) -> () { let accountObject = AppDelegate.instance().accounts!.getAccountObjectForIdx(idx) let accountHDIndex = accountObject.getAccountHDIndex() let title = String(format: TLDisplayStrings.ACCOUNT_ID_COLON_X_STRING(), accountHDIndex) let otherButtonTitles:[String] if (TLPreferences.enabledAdvancedMode()) { if TLWalletUtils.ALLOW_MANUAL_SCAN_FOR_STEALTH_PAYMENT() { otherButtonTitles = [TLDisplayStrings.VIEW_ACCOUNT_PUBLIC_KEY_QR_CODE_STRING(), TLDisplayStrings.VIEW_ACCOUNT_PRIVATE_KEY_QR_CODE_STRING(), TLDisplayStrings.VIEW_ADDRESSES_STRING(), TLDisplayStrings.SCAN_FOR_REUSABLE_ADDRESS_PAYMENT_STRING(), TLDisplayStrings.EDIT_ACCOUNT_NAME_STRING(), TLDisplayStrings.ARCHIVE_ACCOUNT_STRING()] } else { otherButtonTitles = [TLDisplayStrings.VIEW_ACCOUNT_PUBLIC_KEY_QR_CODE_STRING(), TLDisplayStrings.VIEW_ACCOUNT_PRIVATE_KEY_QR_CODE_STRING(), TLDisplayStrings.VIEW_ADDRESSES_STRING(), TLDisplayStrings.EDIT_ACCOUNT_NAME_STRING(), TLDisplayStrings.ARCHIVE_ACCOUNT_STRING()] } } else { otherButtonTitles = [TLDisplayStrings.VIEW_ADDRESSES_STRING(), TLDisplayStrings.EDIT_ACCOUNT_NAME_STRING(), TLDisplayStrings.ARCHIVE_ACCOUNT_STRING()] } UIAlertController.showAlert(in: self, withTitle: title, message: "", preferredStyle: .actionSheet, cancelButtonTitle: TLDisplayStrings.CANCEL_STRING(), destructiveButtonTitle: nil, otherButtonTitles: otherButtonTitles as [AnyObject], tap: {(actionSheet, action, buttonIndex) in var VIEW_EXTENDED_PUBLIC_KEY_BUTTON_IDX = actionSheet!.firstOtherButtonIndex var VIEW_EXTENDED_PRIVATE_KEY_BUTTON_IDX = actionSheet!.firstOtherButtonIndex+1 var VIEW_ADDRESSES_BUTTON_IDX = actionSheet!.firstOtherButtonIndex+2 var MANUALLY_SCAN_TX_FOR_STEALTH_TRANSACTION_BUTTON_IDX:NSInteger var RENAME_ACCOUNT_BUTTON_IDX:NSInteger var ARCHIVE_ACCOUNT_BUTTON_IDX:NSInteger if TLWalletUtils.ALLOW_MANUAL_SCAN_FOR_STEALTH_PAYMENT() { MANUALLY_SCAN_TX_FOR_STEALTH_TRANSACTION_BUTTON_IDX = actionSheet!.firstOtherButtonIndex+3 RENAME_ACCOUNT_BUTTON_IDX = actionSheet!.firstOtherButtonIndex+4 ARCHIVE_ACCOUNT_BUTTON_IDX = actionSheet!.firstOtherButtonIndex+5 } else { MANUALLY_SCAN_TX_FOR_STEALTH_TRANSACTION_BUTTON_IDX = -1 RENAME_ACCOUNT_BUTTON_IDX = actionSheet!.firstOtherButtonIndex+3 ARCHIVE_ACCOUNT_BUTTON_IDX = actionSheet!.firstOtherButtonIndex+4 } if (!TLPreferences.enabledAdvancedMode()) { VIEW_EXTENDED_PUBLIC_KEY_BUTTON_IDX = -1 VIEW_EXTENDED_PRIVATE_KEY_BUTTON_IDX = -1 MANUALLY_SCAN_TX_FOR_STEALTH_TRANSACTION_BUTTON_IDX = -1 VIEW_ADDRESSES_BUTTON_IDX = actionSheet!.firstOtherButtonIndex RENAME_ACCOUNT_BUTTON_IDX = actionSheet!.firstOtherButtonIndex+1 ARCHIVE_ACCOUNT_BUTTON_IDX = actionSheet!.firstOtherButtonIndex+2 } if (buttonIndex == VIEW_EXTENDED_PUBLIC_KEY_BUTTON_IDX) { self.QRImageModal = TLQRImageModal(data: accountObject.getExtendedPubKey() as NSString, buttonCopyText: TLDisplayStrings.COPY_TO_CLIPBOARD_STRING(), vc: self) self.QRImageModal!.show() NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_VIEW_EXTENDED_PUBLIC_KEY()), object: accountObject, userInfo: nil) } else if (buttonIndex == VIEW_EXTENDED_PRIVATE_KEY_BUTTON_IDX) { self.QRImageModal = TLQRImageModal(data: accountObject.getExtendedPrivKey()! as NSString, buttonCopyText: TLDisplayStrings.COPY_TO_CLIPBOARD_STRING(), vc: self) self.QRImageModal!.show() NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_VIEW_EXTENDED_PRIVATE_KEY()), object: accountObject, userInfo: nil) } else if (buttonIndex == MANUALLY_SCAN_TX_FOR_STEALTH_TRANSACTION_BUTTON_IDX) { self.promptInfoAndToManuallyScanForStealthTransactionAccount(accountObject) } else if (buttonIndex == VIEW_ADDRESSES_BUTTON_IDX) { self.showAddressListAccountObject = accountObject self.showAddressListShowBalances = true self.performSegue(withIdentifier: "SegueAddressList", sender: self) } else if (buttonIndex == RENAME_ACCOUNT_BUTTON_IDX) { self.promtForNameAccount({ (accountName: String!) in AppDelegate.instance().accounts!.renameAccount(accountObject.getAccountIdxNumber(), accountName: accountName) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_EDIT_ACCOUNT_NAME()), object: accountObject, userInfo: nil) self._accountsTableViewReloadDataWrapper() }, failure: ({ (isCanceled: Bool) in })) } else if (buttonIndex == ARCHIVE_ACCOUNT_BUTTON_IDX) { self.promptToArchiveAccountHDWalletAccount(accountObject) } else if (buttonIndex == actionSheet!.cancelButtonIndex) { } }) } fileprivate func promptColdWalletAccountsActionSheet(_ indexPath: IndexPath) -> () { let accountObject = AppDelegate.instance().coldWalletAccounts!.getAccountObjectForIdx((indexPath as NSIndexPath).row) let accountHDIndex = accountObject.getAccountHDIndex() let title = String(format: TLDisplayStrings.ACCOUNT_ID_COLON_X_STRING(), accountHDIndex) let otherButtons:[String] if (TLPreferences.enabledAdvancedMode()) { otherButtons = [TLDisplayStrings.VIEW_ACCOUNT_PUBLIC_KEY_QR_CODE_STRING(), TLDisplayStrings.VIEW_ADDRESSES_STRING(), TLDisplayStrings.EDIT_ACCOUNT_NAME_STRING(), TLDisplayStrings.ARCHIVE_ACCOUNT_STRING()] } else { otherButtons = [TLDisplayStrings.VIEW_ADDRESSES_STRING(), TLDisplayStrings.EDIT_ACCOUNT_NAME_STRING(), TLDisplayStrings.ARCHIVE_ACCOUNT_STRING()] } UIAlertController.showAlert(in: self, withTitle: title, message:"", preferredStyle: .actionSheet, cancelButtonTitle: TLDisplayStrings.CANCEL_STRING(), destructiveButtonTitle: nil, otherButtonTitles: otherButtons as [AnyObject], tap: {(actionSheet, action, buttonIndex) in var VIEW_EXTENDED_PUBLIC_KEY_BUTTON_IDX = actionSheet!.firstOtherButtonIndex var VIEW_ADDRESSES_BUTTON_IDX = actionSheet!.firstOtherButtonIndex+1 var RENAME_ACCOUNT_BUTTON_IDX = actionSheet!.firstOtherButtonIndex+2 var ARCHIVE_ACCOUNT_BUTTON_IDX = actionSheet!.firstOtherButtonIndex+3 if (!TLPreferences.enabledAdvancedMode()) { VIEW_EXTENDED_PUBLIC_KEY_BUTTON_IDX = -1 VIEW_ADDRESSES_BUTTON_IDX = actionSheet!.firstOtherButtonIndex RENAME_ACCOUNT_BUTTON_IDX = actionSheet!.firstOtherButtonIndex+1 ARCHIVE_ACCOUNT_BUTTON_IDX = actionSheet!.firstOtherButtonIndex+2 } if (buttonIndex == VIEW_EXTENDED_PUBLIC_KEY_BUTTON_IDX) { self.QRImageModal = TLQRImageModal(data: accountObject.getExtendedPubKey() as NSString, buttonCopyText: TLDisplayStrings.COPY_TO_CLIPBOARD_STRING(), vc: self) self.QRImageModal!.show() NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_VIEW_EXTENDED_PUBLIC_KEY()), object: accountObject, userInfo: nil) } else if (buttonIndex == VIEW_ADDRESSES_BUTTON_IDX) { self.showAddressListAccountObject = accountObject self.showAddressListShowBalances = true self.performSegue(withIdentifier: "SegueAddressList", sender: self) } else if (buttonIndex == RENAME_ACCOUNT_BUTTON_IDX) { self.promtForNameAccount({ (accountName: String!) in AppDelegate.instance().coldWalletAccounts!.renameAccount(accountObject.getAccountIdxNumber(), accountName: accountName) self._accountsTableViewReloadDataWrapper() }, failure: { (isCancelled: Bool) in }) } else if (buttonIndex == ARCHIVE_ACCOUNT_BUTTON_IDX) { self.promptToArchiveAccount(accountObject) } else if (buttonIndex == actionSheet!.cancelButtonIndex) { } }) } fileprivate func promptImportedAccountsActionSheet(_ indexPath: IndexPath) -> () { let accountObject = AppDelegate.instance().importedAccounts!.getAccountObjectForIdx((indexPath as NSIndexPath).row) let accountHDIndex = accountObject.getAccountHDIndex() let title = String(format: TLDisplayStrings.ACCOUNT_ID_COLON_X_STRING(), accountHDIndex) let otherButtonTitles:[String] if TLWalletUtils.ALLOW_MANUAL_SCAN_FOR_STEALTH_PAYMENT() { otherButtonTitles = [TLDisplayStrings.VIEW_ACCOUNT_PUBLIC_KEY_QR_CODE_STRING(), TLDisplayStrings.VIEW_ACCOUNT_PRIVATE_KEY_QR_CODE_STRING(), TLDisplayStrings.VIEW_ADDRESSES_STRING(), TLDisplayStrings.SCAN_REUSABLE_ADDRESS_PAYMENT_STRING(), TLDisplayStrings.EDIT_ACCOUNT_NAME_STRING(), TLDisplayStrings.ARCHIVE_ACCOUNT_STRING()] } else { otherButtonTitles = [TLDisplayStrings.VIEW_ACCOUNT_PUBLIC_KEY_QR_CODE_STRING(), TLDisplayStrings.VIEW_ACCOUNT_PRIVATE_KEY_QR_CODE_STRING(), TLDisplayStrings.VIEW_ADDRESSES_STRING(), TLDisplayStrings.EDIT_ACCOUNT_NAME_STRING(), TLDisplayStrings.ARCHIVE_ACCOUNT_STRING()] } UIAlertController.showAlert(in: self, withTitle: title, message: "", preferredStyle: .actionSheet, cancelButtonTitle: TLDisplayStrings.CANCEL_STRING(), destructiveButtonTitle: nil, otherButtonTitles: otherButtonTitles, tap: {(actionSheet, action, buttonIndex) in let VIEW_EXTENDED_PUBLIC_KEY_BUTTON_IDX = actionSheet!.firstOtherButtonIndex let VIEW_EXTENDED_PRIVATE_KEY_BUTTON_IDX = actionSheet!.firstOtherButtonIndex+1 let VIEW_ADDRESSES_BUTTON_IDX = actionSheet!.firstOtherButtonIndex+2 let MANUALLY_SCAN_TX_FOR_STEALTH_TRANSACTION_BUTTON_IDX:NSInteger let RENAME_ACCOUNT_BUTTON_IDX:NSInteger let ARCHIVE_ACCOUNT_BUTTON_IDX:NSInteger if TLWalletUtils.ALLOW_MANUAL_SCAN_FOR_STEALTH_PAYMENT() { MANUALLY_SCAN_TX_FOR_STEALTH_TRANSACTION_BUTTON_IDX = actionSheet!.firstOtherButtonIndex+3 RENAME_ACCOUNT_BUTTON_IDX = actionSheet!.firstOtherButtonIndex+4 ARCHIVE_ACCOUNT_BUTTON_IDX = actionSheet!.firstOtherButtonIndex+5 } else { MANUALLY_SCAN_TX_FOR_STEALTH_TRANSACTION_BUTTON_IDX = -1 RENAME_ACCOUNT_BUTTON_IDX = actionSheet!.firstOtherButtonIndex+3 ARCHIVE_ACCOUNT_BUTTON_IDX = actionSheet!.firstOtherButtonIndex+4 } if (buttonIndex == VIEW_EXTENDED_PUBLIC_KEY_BUTTON_IDX) { self.QRImageModal = TLQRImageModal(data: accountObject.getExtendedPubKey() as NSString, buttonCopyText: TLDisplayStrings.COPY_TO_CLIPBOARD_STRING(), vc: self) self.QRImageModal!.show() NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_VIEW_EXTENDED_PUBLIC_KEY()), object: accountObject, userInfo: nil) } else if (buttonIndex == VIEW_EXTENDED_PRIVATE_KEY_BUTTON_IDX) { self.QRImageModal = TLQRImageModal(data: accountObject.getExtendedPrivKey()! as NSString, buttonCopyText: TLDisplayStrings.COPY_TO_CLIPBOARD_STRING(), vc: self) self.QRImageModal!.show() NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_VIEW_EXTENDED_PRIVATE_KEY()), object: accountObject, userInfo: nil) } else if (buttonIndex == VIEW_ADDRESSES_BUTTON_IDX) { self.showAddressListAccountObject = accountObject self.showAddressListShowBalances = true self.performSegue(withIdentifier: "SegueAddressList", sender: self) } else if (buttonIndex == MANUALLY_SCAN_TX_FOR_STEALTH_TRANSACTION_BUTTON_IDX) { self.promptInfoAndToManuallyScanForStealthTransactionAccount(accountObject) } else if (buttonIndex == RENAME_ACCOUNT_BUTTON_IDX) { self.promtForNameAccount({ (accountName: String!) in AppDelegate.instance().importedAccounts!.renameAccount(accountObject.getAccountIdxNumber(), accountName: accountName) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_EDIT_ACCOUNT_NAME()), object: nil, userInfo: nil) self._accountsTableViewReloadDataWrapper() } , failure: ({ (isCanceled: Bool) in }))} else if (buttonIndex == ARCHIVE_ACCOUNT_BUTTON_IDX) { self.promptToArchiveAccount(accountObject) } else if (buttonIndex == actionSheet!.cancelButtonIndex) { } }) } fileprivate func promptImportedWatchAccountsActionSheet(_ indexPath: IndexPath) -> () { let accountObject = AppDelegate.instance().importedWatchAccounts!.getAccountObjectForIdx((indexPath as NSIndexPath).row) let accountHDIndex = accountObject.getAccountHDIndex() let title = String(format: TLDisplayStrings.ACCOUNT_ID_COLON_X_STRING(), accountHDIndex) var addClearPrivateKeyButton = false let otherButtons:[String] if (accountObject.hasSetExtendedPrivateKeyInMemory()) { addClearPrivateKeyButton = true otherButtons = [TLDisplayStrings.CLEAR_ACCOUNT_PRIVATE_KEY_FROM_MEMORY_STRING(), TLDisplayStrings.VIEW_ACCOUNT_PUBLIC_KEY_QR_CODE_STRING(), TLDisplayStrings.VIEW_ADDRESSES_STRING(), TLDisplayStrings.EDIT_ACCOUNT_NAME_STRING(), TLDisplayStrings.ARCHIVE_ACCOUNT_STRING()] } else { otherButtons = [TLDisplayStrings.VIEW_ACCOUNT_PUBLIC_KEY_QR_CODE_STRING(), TLDisplayStrings.VIEW_ADDRESSES_STRING(), TLDisplayStrings.EDIT_ACCOUNT_NAME_STRING(), TLDisplayStrings.ARCHIVE_ACCOUNT_STRING()] } UIAlertController.showAlert(in: self, withTitle: title, message:"", preferredStyle: .actionSheet, cancelButtonTitle: TLDisplayStrings.CANCEL_STRING(), destructiveButtonTitle: nil, otherButtonTitles: otherButtons as [AnyObject], tap: {(actionSheet, action, buttonIndex) in var CLEAR_ACCOUNT_PRIVATE_KEY_BUTTON_IDX = -1 var VIEW_EXTENDED_PUBLIC_KEY_BUTTON_IDX = actionSheet!.firstOtherButtonIndex var VIEW_ADDRESSES_BUTTON_IDX = actionSheet!.firstOtherButtonIndex+1 var RENAME_ACCOUNT_BUTTON_IDX = actionSheet!.firstOtherButtonIndex+2 var ARCHIVE_ACCOUNT_BUTTON_IDX = actionSheet!.firstOtherButtonIndex+3 if (accountObject.hasSetExtendedPrivateKeyInMemory()) { CLEAR_ACCOUNT_PRIVATE_KEY_BUTTON_IDX = actionSheet!.firstOtherButtonIndex VIEW_EXTENDED_PUBLIC_KEY_BUTTON_IDX = actionSheet!.firstOtherButtonIndex+1 VIEW_ADDRESSES_BUTTON_IDX = actionSheet!.firstOtherButtonIndex+2 RENAME_ACCOUNT_BUTTON_IDX = actionSheet!.firstOtherButtonIndex+3 ARCHIVE_ACCOUNT_BUTTON_IDX = actionSheet!.firstOtherButtonIndex+4 } if (addClearPrivateKeyButton && buttonIndex == CLEAR_ACCOUNT_PRIVATE_KEY_BUTTON_IDX) { assert(accountObject.hasSetExtendedPrivateKeyInMemory(), "") accountObject.clearExtendedPrivateKeyFromMemory() TLPrompts.promptSuccessMessage(nil, message: TLDisplayStrings.CLEARED_FROM_MEMORY_STRING()) } else if (buttonIndex == VIEW_EXTENDED_PUBLIC_KEY_BUTTON_IDX) { self.QRImageModal = TLQRImageModal(data: accountObject.getExtendedPubKey() as NSString, buttonCopyText: TLDisplayStrings.COPY_TO_CLIPBOARD_STRING(), vc: self) self.QRImageModal!.show() NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_VIEW_EXTENDED_PUBLIC_KEY()), object: accountObject, userInfo: nil) } else if (buttonIndex == VIEW_ADDRESSES_BUTTON_IDX) { self.showAddressListAccountObject = accountObject self.showAddressListShowBalances = true self.performSegue(withIdentifier: "SegueAddressList", sender: self) } else if (buttonIndex == RENAME_ACCOUNT_BUTTON_IDX) { self.promtForNameAccount({ (accountName: String!) in AppDelegate.instance().importedWatchAccounts!.renameAccount(accountObject.getAccountIdxNumber(), accountName: accountName) self._accountsTableViewReloadDataWrapper() }, failure: { (isCancelled: Bool) in }) } else if (buttonIndex == ARCHIVE_ACCOUNT_BUTTON_IDX) { self.promptToArchiveAccount(accountObject) } else if (buttonIndex == actionSheet!.cancelButtonIndex) { } }) } fileprivate func promptImportedAddressActionSheet(_ importedAddressIdx: Int) -> () { let importAddressObject = AppDelegate.instance().importedAddresses!.getAddressObjectAtIdx(importedAddressIdx) UIAlertController.showAlert(in: self, withTitle: nil, message:"", preferredStyle: .actionSheet, cancelButtonTitle: TLDisplayStrings.CANCEL_STRING(), destructiveButtonTitle: nil, otherButtonTitles: [TLDisplayStrings.VIEW_ADDRESS_QR_CODE_STRING(), TLDisplayStrings.VIEW_PRIVATE_KEY_QR_CODE_STRING(), TLDisplayStrings.VIEW_ADDRESS_IN_WEB_STRING(), TLDisplayStrings.EDIT_LABEL_STRING(), TLDisplayStrings.ARCHIVE_ADDRESS_STRING()], tap: {(actionSheet, action, buttonIndex) in if (buttonIndex == actionSheet?.firstOtherButtonIndex) { self.QRImageModal = TLQRImageModal(data: importAddressObject.getAddress() as NSString, buttonCopyText: TLDisplayStrings.COPY_TO_CLIPBOARD_STRING(), vc: self) self.QRImageModal!.show() } else if (buttonIndex == (actionSheet?.firstOtherButtonIndex)!+1) { self.QRImageModal = TLQRImageModal(data: importAddressObject.getEitherPrivateKeyOrEncryptedPrivateKey()! as NSString, buttonCopyText: TLDisplayStrings.COPY_TO_CLIPBOARD_STRING(), vc: self) self.QRImageModal!.show() } else if (buttonIndex == (actionSheet?.firstOtherButtonIndex)!+2) { TLBlockExplorerAPI.instance().openWebViewForAddress(importAddressObject.getAddress()) } else if (buttonIndex == (actionSheet?.firstOtherButtonIndex)!+3) { self.promtForLabel({ (inputText: String!) in AppDelegate.instance().importedAddresses!.setLabel(inputText, positionInWalletArray: Int(importAddressObject.getPositionInWalletArrayNumber())) self._accountsTableViewReloadDataWrapper() }, failure: { (isCancelled: Bool) in }) } else if (buttonIndex == (actionSheet?.firstOtherButtonIndex)!+4) { self.promptToArchiveAddress(importAddressObject) } else if (buttonIndex == actionSheet?.cancelButtonIndex) { } }) } fileprivate func promptImportedWatchAddressActionSheet(_ importedAddressIdx: Int) -> () { let importAddressObject = AppDelegate.instance().importedWatchAddresses!.getAddressObjectAtIdx(importedAddressIdx) var addClearPrivateKeyButton = false let otherButtonTitles:[String] if (importAddressObject.hasSetPrivateKeyInMemory()) { addClearPrivateKeyButton = true otherButtonTitles = [TLDisplayStrings.CLEAR_PRIVATE_KEY_FROM_MEMORY_STRING(), TLDisplayStrings.VIEW_ADDRESS_QR_CODE_STRING(), TLDisplayStrings.VIEW_ADDRESS_IN_WEB_STRING(), TLDisplayStrings.EDIT_LABEL_STRING(), TLDisplayStrings.ARCHIVE_ADDRESS_STRING()] } else { otherButtonTitles = [TLDisplayStrings.VIEW_ADDRESS_QR_CODE_STRING(), TLDisplayStrings.VIEW_ADDRESS_IN_WEB_STRING(), TLDisplayStrings.EDIT_LABEL_STRING(), TLDisplayStrings.ARCHIVE_ADDRESS_STRING()] } UIAlertController.showAlert(in: self, withTitle: nil, message:"", preferredStyle: .actionSheet, cancelButtonTitle: TLDisplayStrings.CANCEL_STRING(), destructiveButtonTitle: nil, otherButtonTitles: otherButtonTitles as [AnyObject], tap: {(actionSheet, action, buttonIndex) in var CLEAR_PRIVATE_KEY_BUTTON_IDX = -1 var VIEW_ADDRESS_BUTTON_IDX = actionSheet?.firstOtherButtonIndex var VIEW_ADDRESS_IN_WEB_BUTTON_IDX = (actionSheet?.firstOtherButtonIndex)!+1 var RENAME_ADDRESS_BUTTON_IDX = (actionSheet?.firstOtherButtonIndex)!+2 var ARCHIVE_ADDRESS_BUTTON_IDX = (actionSheet?.firstOtherButtonIndex)!+3 if (importAddressObject.hasSetPrivateKeyInMemory()) { CLEAR_PRIVATE_KEY_BUTTON_IDX = (actionSheet?.firstOtherButtonIndex)! VIEW_ADDRESS_BUTTON_IDX = (actionSheet?.firstOtherButtonIndex)! + 1 VIEW_ADDRESS_IN_WEB_BUTTON_IDX = (actionSheet?.firstOtherButtonIndex)! + 2 RENAME_ADDRESS_BUTTON_IDX = (actionSheet?.firstOtherButtonIndex)! + 3 ARCHIVE_ADDRESS_BUTTON_IDX = (actionSheet?.firstOtherButtonIndex)! + 4 } if (addClearPrivateKeyButton && buttonIndex == CLEAR_PRIVATE_KEY_BUTTON_IDX) { assert(importAddressObject.hasSetPrivateKeyInMemory(), "") importAddressObject.clearPrivateKeyFromMemory() TLPrompts.promptSuccessMessage(nil, message: TLDisplayStrings.CLEARED_FROM_MEMORY_STRING()) } if (buttonIndex == VIEW_ADDRESS_BUTTON_IDX) { self.QRImageModal = TLQRImageModal(data: importAddressObject.getAddress() as NSString, buttonCopyText: TLDisplayStrings.COPY_TO_CLIPBOARD_STRING(), vc: self) self.QRImageModal!.show() } else if (buttonIndex == VIEW_ADDRESS_IN_WEB_BUTTON_IDX) { TLBlockExplorerAPI.instance().openWebViewForAddress(importAddressObject.getAddress()) } else if (buttonIndex == RENAME_ADDRESS_BUTTON_IDX) { self.promtForLabel({ (inputText: String!) in AppDelegate.instance().importedWatchAddresses!.setLabel(inputText, positionInWalletArray: Int(importAddressObject.getPositionInWalletArrayNumber())) self._accountsTableViewReloadDataWrapper() }, failure: ({ (isCanceled: Bool) in })) } else if (buttonIndex == ARCHIVE_ADDRESS_BUTTON_IDX) { self.promptToArchiveAddress(importAddressObject) } else if (buttonIndex == actionSheet?.cancelButtonIndex) { } }) } fileprivate func promptArchivedImportedAddressActionSheet(_ importedAddressIdx: Int) -> () { let importAddressObject = AppDelegate.instance().importedAddresses!.getArchivedAddressObjectAtIdx(importedAddressIdx) UIAlertController.showAlert(in: self, withTitle: nil, message:"", preferredStyle: .actionSheet, cancelButtonTitle: TLDisplayStrings.CANCEL_STRING(), destructiveButtonTitle: nil, otherButtonTitles: [TLDisplayStrings.VIEW_ADDRESS_QR_CODE_STRING(), TLDisplayStrings.VIEW_PRIVATE_KEY_QR_CODE_STRING(), TLDisplayStrings.VIEW_ADDRESS_IN_WEB_STRING(), TLDisplayStrings.EDIT_LABEL_STRING(), TLDisplayStrings.UNARCHIVED_ADDRESS_STRING(), TLDisplayStrings.DELETE_ADDRESS_STRING()], tap: {(actionSheet, action, buttonIndex) in if (buttonIndex == actionSheet?.firstOtherButtonIndex) { self.QRImageModal = TLQRImageModal(data: importAddressObject.getAddress() as NSString, buttonCopyText: TLDisplayStrings.COPY_TO_CLIPBOARD_STRING(), vc: self) self.QRImageModal!.show() } else if (buttonIndex == (actionSheet?.firstOtherButtonIndex)!+1) { self.QRImageModal = TLQRImageModal(data: importAddressObject.getEitherPrivateKeyOrEncryptedPrivateKey()! as NSString, buttonCopyText: TLDisplayStrings.COPY_TO_CLIPBOARD_STRING(), vc: self) self.QRImageModal!.show() } else if (buttonIndex == (actionSheet?.firstOtherButtonIndex)!+2) { TLBlockExplorerAPI.instance().openWebViewForAddress(importAddressObject.getAddress()) } else if (buttonIndex == (actionSheet?.firstOtherButtonIndex)!+3) { self.promtForLabel({ (inputText: String!) in AppDelegate.instance().importedAddresses!.setLabel(inputText, positionInWalletArray: Int(importAddressObject.getPositionInWalletArrayNumber())) self._accountsTableViewReloadDataWrapper() }, failure: ({ (isCanceled: Bool) in })) } else if (buttonIndex == (actionSheet?.firstOtherButtonIndex)!+4) { self.promptToUnarchiveAddress(importAddressObject) } else if (buttonIndex == (actionSheet?.firstOtherButtonIndex)!+5) { self.promptToDeleteImportedAddress(importedAddressIdx) } else if (buttonIndex == actionSheet?.cancelButtonIndex) { } }) } fileprivate func promptArchivedImportedWatchAddressActionSheet(_ importedAddressIdx: Int) -> () { let importAddressObject = AppDelegate.instance().importedWatchAddresses!.getArchivedAddressObjectAtIdx(importedAddressIdx) var addClearPrivateKeyButton = false let otherButtonTitles:[String] if (importAddressObject.hasSetPrivateKeyInMemory()) { addClearPrivateKeyButton = true otherButtonTitles = [TLDisplayStrings.CLEAR_PRIVATE_KEY_FROM_MEMORY_STRING(), TLDisplayStrings.VIEW_ADDRESS_QR_CODE_STRING(), TLDisplayStrings.VIEW_ADDRESS_IN_WEB_STRING(), TLDisplayStrings.EDIT_LABEL_STRING(), TLDisplayStrings.UNARCHIVED_ADDRESS_STRING(), TLDisplayStrings.DELETE_ADDRESS_STRING()] } else { otherButtonTitles = [TLDisplayStrings.VIEW_ADDRESS_QR_CODE_STRING(), TLDisplayStrings.VIEW_ADDRESS_IN_WEB_STRING(), TLDisplayStrings.EDIT_LABEL_STRING(), TLDisplayStrings.UNARCHIVED_ADDRESS_STRING(), TLDisplayStrings.DELETE_ADDRESS_STRING()] } UIAlertController.showAlert(in: self, withTitle: nil, message:"", preferredStyle: .actionSheet, cancelButtonTitle: TLDisplayStrings.CANCEL_STRING(), destructiveButtonTitle: nil, otherButtonTitles: otherButtonTitles as [AnyObject], tap: {(actionSheet, action, buttonIndex) in var CLEAR_PRIVATE_KEY_BUTTON_IDX = -1 var VIEW_ADDRESS_BUTTON_IDX = (actionSheet?.firstOtherButtonIndex)! + 0 var VIEW_ADDRESS_IN_WEB_BUTTON_IDX = (actionSheet?.firstOtherButtonIndex)! + 1 var RENAME_ADDRESS_BUTTON_IDX = (actionSheet?.firstOtherButtonIndex)! + 2 var UNARCHIVE_ADDRESS_BUTTON_IDX = (actionSheet?.firstOtherButtonIndex)! + 3 var DELETE_ADDRESS_BUTTON_IDX = (actionSheet?.firstOtherButtonIndex)! + 4 if (importAddressObject.hasSetPrivateKeyInMemory()) { CLEAR_PRIVATE_KEY_BUTTON_IDX = (actionSheet?.firstOtherButtonIndex)! VIEW_ADDRESS_BUTTON_IDX = (actionSheet?.firstOtherButtonIndex)! + 1 VIEW_ADDRESS_IN_WEB_BUTTON_IDX = (actionSheet?.firstOtherButtonIndex)! + 2 RENAME_ADDRESS_BUTTON_IDX = (actionSheet?.firstOtherButtonIndex)! + 3 UNARCHIVE_ADDRESS_BUTTON_IDX = (actionSheet?.firstOtherButtonIndex)! + 4 DELETE_ADDRESS_BUTTON_IDX = (actionSheet?.firstOtherButtonIndex)! + 5 } else { } if (addClearPrivateKeyButton && buttonIndex == CLEAR_PRIVATE_KEY_BUTTON_IDX) { assert(importAddressObject.hasSetPrivateKeyInMemory(), "") importAddressObject.clearPrivateKeyFromMemory() TLPrompts.promptSuccessMessage(nil, message: TLDisplayStrings.CLEARED_FROM_MEMORY_STRING()) } if (buttonIndex == VIEW_ADDRESS_BUTTON_IDX) { self.QRImageModal = TLQRImageModal(data: importAddressObject.getAddress() as NSString, buttonCopyText: TLDisplayStrings.COPY_TO_CLIPBOARD_STRING(), vc: self) self.QRImageModal!.show() } else if (buttonIndex == VIEW_ADDRESS_IN_WEB_BUTTON_IDX) { TLBlockExplorerAPI.instance().openWebViewForAddress(importAddressObject.getAddress()) } else if (buttonIndex == RENAME_ADDRESS_BUTTON_IDX) { self.promtForLabel({ (inputText: String!) in AppDelegate.instance().importedWatchAddresses!.setLabel(inputText, positionInWalletArray: Int(importAddressObject.getPositionInWalletArrayNumber())) self._accountsTableViewReloadDataWrapper() }, failure: ({ (isCanceled: Bool) in })) } else if (buttonIndex == UNARCHIVE_ADDRESS_BUTTON_IDX) { self.promptToUnarchiveAddress(importAddressObject) } else if (buttonIndex == DELETE_ADDRESS_BUTTON_IDX) { self.promptToDeleteImportedWatchAddress(importedAddressIdx) } else if (buttonIndex == actionSheet?.cancelButtonIndex) { } }) } fileprivate func promptArchivedImportedAccountsActionSheet(_ indexPath: IndexPath, accountType: TLAccountType) -> () { assert(accountType == .imported || accountType == .importedWatch || accountType == .coldWallet, "not TLAccountTypeImported or TLAccountTypeImportedWatch or not TLAccountTypeIColdWallet") var accountObject: TLAccountObject? if (accountType == .coldWallet) { accountObject = AppDelegate.instance().coldWalletAccounts!.getArchivedAccountObjectForIdx((indexPath as NSIndexPath).row) } else if (accountType == .imported) { accountObject = AppDelegate.instance().importedAccounts!.getArchivedAccountObjectForIdx((indexPath as NSIndexPath).row) } else if (accountType == .importedWatch) { accountObject = AppDelegate.instance().importedWatchAccounts!.getArchivedAccountObjectForIdx((indexPath as NSIndexPath).row) } let accountHDIndex = accountObject!.getAccountHDIndex() let title = String(format: TLDisplayStrings.ACCOUNT_ID_COLON_X_STRING(), accountHDIndex) let otherButtonTitles:[String] if (accountObject!.getAccountType() == .imported) { otherButtonTitles = [TLDisplayStrings.VIEW_ACCOUNT_PUBLIC_KEY_QR_CODE_STRING(), TLDisplayStrings.VIEW_ACCOUNT_PRIVATE_KEY_QR_CODE_STRING(), TLDisplayStrings.VIEW_ADDRESSES_STRING(), TLDisplayStrings.EDIT_ACCOUNT_NAME_STRING(), TLDisplayStrings.UNARCHIVE_ACCOUNT_STRING(), TLDisplayStrings.DELETE_ACCOUNT_STRING()] } else { otherButtonTitles = [TLDisplayStrings.VIEW_ACCOUNT_PUBLIC_KEY_QR_CODE_STRING(), TLDisplayStrings.VIEW_ADDRESSES_STRING(), TLDisplayStrings.EDIT_ACCOUNT_NAME_STRING(), TLDisplayStrings.UNARCHIVE_ACCOUNT_STRING(), TLDisplayStrings.DELETE_ACCOUNT_STRING()] } UIAlertController.showAlert(in: self, withTitle: title, message:"", preferredStyle: .actionSheet, cancelButtonTitle: TLDisplayStrings.CANCEL_STRING(), destructiveButtonTitle: nil, otherButtonTitles: otherButtonTitles as [AnyObject], tap: {(actionSheet, action, buttonIndex) in let VIEW_EXTENDED_PUBLIC_KEY_BUTTON_IDX = (actionSheet?.firstOtherButtonIndex)! + 0 var VIEW_EXTENDED_PRIVATE_KEY_BUTTON_IDX = (actionSheet?.firstOtherButtonIndex)! + 1 var VIEW_ADDRESSES_BUTTON_IDX = (actionSheet?.firstOtherButtonIndex)! + 2 var RENAME_ACCOUNT_BUTTON_IDX = (actionSheet?.firstOtherButtonIndex)! + 3 var UNARCHIVE_ACCOUNT_BUTTON_IDX = (actionSheet?.firstOtherButtonIndex)! + 4 var DELETE_ACCOUNT_BUTTON_IDX = (actionSheet?.firstOtherButtonIndex)! + 5 if (accountObject!.getAccountType() == .imported) { } else { VIEW_EXTENDED_PRIVATE_KEY_BUTTON_IDX = -1 VIEW_ADDRESSES_BUTTON_IDX = (actionSheet?.firstOtherButtonIndex)! + 1 RENAME_ACCOUNT_BUTTON_IDX = (actionSheet?.firstOtherButtonIndex)! + 2 UNARCHIVE_ACCOUNT_BUTTON_IDX = (actionSheet?.firstOtherButtonIndex)! + 3 DELETE_ACCOUNT_BUTTON_IDX = (actionSheet?.firstOtherButtonIndex)! + 4 } if (buttonIndex == VIEW_EXTENDED_PUBLIC_KEY_BUTTON_IDX) { self.QRImageModal = TLQRImageModal(data: accountObject!.getExtendedPubKey() as NSString, buttonCopyText: TLDisplayStrings.COPY_TO_CLIPBOARD_STRING(), vc: self) self.QRImageModal!.show() NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_VIEW_EXTENDED_PUBLIC_KEY()), object: accountObject, userInfo: nil) } else if (buttonIndex == VIEW_EXTENDED_PRIVATE_KEY_BUTTON_IDX) { self.QRImageModal = TLQRImageModal(data: accountObject!.getExtendedPrivKey()! as NSString, buttonCopyText: TLDisplayStrings.COPY_TO_CLIPBOARD_STRING(), vc: self) self.QRImageModal!.show() NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_VIEW_EXTENDED_PRIVATE_KEY()), object: accountObject, userInfo: nil) } else if (buttonIndex == VIEW_ADDRESSES_BUTTON_IDX) { self.showAddressListAccountObject = accountObject self.showAddressListShowBalances = false self.performSegue(withIdentifier: "SegueAddressList", sender: self) } else if (buttonIndex == RENAME_ACCOUNT_BUTTON_IDX) { self.promtForNameAccount({ (accountName: String!) in if (accountType == .coldWallet) { AppDelegate.instance().coldWalletAccounts!.renameAccount(accountObject!.getAccountIdxNumber(), accountName: accountName) } else if (accountType == .imported) { AppDelegate.instance().importedAccounts!.renameAccount(accountObject!.getAccountIdxNumber(), accountName: accountName) } else if (accountType == .importedWatch) { AppDelegate.instance().importedWatchAccounts!.renameAccount(accountObject!.getAccountIdxNumber(), accountName: accountName) } self._accountsTableViewReloadDataWrapper() }, failure: ({ (isCanceled: Bool) in })) } else if (buttonIndex == UNARCHIVE_ACCOUNT_BUTTON_IDX) { self.promptToUnarchiveAccount(accountObject!) } else if (buttonIndex == DELETE_ACCOUNT_BUTTON_IDX) { if (accountType == .coldWallet) { self.promptToDeleteColdWalletAccount(indexPath) } else if (accountType == .imported) { self.promptToDeleteImportedAccount(indexPath) } else if (accountType == .importedWatch) { self.promptToDeleteImportedWatchAccount(indexPath) } } else if (buttonIndex == actionSheet?.cancelButtonIndex) { } }) } fileprivate func promptArchivedAccountsActionSheet(_ idx: Int) -> () { let accountObject = AppDelegate.instance().accounts!.getArchivedAccountObjectForIdx(idx) let accountHDIndex = accountObject.getAccountHDIndex() let title = String(format: TLDisplayStrings.ACCOUNT_ID_COLON_X_STRING(), accountHDIndex) let otherButtonTitles:[String] if (TLPreferences.enabledAdvancedMode()) { otherButtonTitles = [TLDisplayStrings.VIEW_ACCOUNT_PUBLIC_KEY_QR_CODE_STRING(), TLDisplayStrings.VIEW_ACCOUNT_PRIVATE_KEY_QR_CODE_STRING(), TLDisplayStrings.VIEW_ADDRESSES_STRING(), TLDisplayStrings.EDIT_ACCOUNT_NAME_STRING(), TLDisplayStrings.UNARCHIVE_ACCOUNT_STRING()] } else { otherButtonTitles = [TLDisplayStrings.VIEW_ADDRESSES_STRING(), TLDisplayStrings.EDIT_ACCOUNT_NAME_STRING(), TLDisplayStrings.UNARCHIVE_ACCOUNT_STRING()] } UIAlertController.showAlert(in: self, withTitle: title, message:"", preferredStyle: .actionSheet, cancelButtonTitle: TLDisplayStrings.CANCEL_STRING(), destructiveButtonTitle: nil, otherButtonTitles: otherButtonTitles as [AnyObject], tap: {(actionSheet, action, buttonIndex) in var VIEW_EXTENDED_PUBLIC_KEY_BUTTON_IDX = actionSheet!.firstOtherButtonIndex + 0 var VIEW_EXTENDED_PRIVATE_KEY_BUTTON_IDX = actionSheet!.firstOtherButtonIndex + 1 var VIEW_ADDRESSES_BUTTON_IDX = actionSheet!.firstOtherButtonIndex + 2 var RENAME_ACCOUNT_BUTTON_IDX = actionSheet!.firstOtherButtonIndex + 3 var UNARCHIVE_ACCOUNT_BUTTON_IDX = actionSheet!.firstOtherButtonIndex + 4 if (!TLPreferences.enabledAdvancedMode()) { VIEW_EXTENDED_PUBLIC_KEY_BUTTON_IDX = -1 VIEW_EXTENDED_PRIVATE_KEY_BUTTON_IDX = -1 VIEW_ADDRESSES_BUTTON_IDX = actionSheet!.firstOtherButtonIndex + 0 RENAME_ACCOUNT_BUTTON_IDX = actionSheet!.firstOtherButtonIndex + 1 UNARCHIVE_ACCOUNT_BUTTON_IDX = actionSheet!.firstOtherButtonIndex + 2 } if (buttonIndex == VIEW_EXTENDED_PUBLIC_KEY_BUTTON_IDX) { self.QRImageModal = TLQRImageModal(data: accountObject.getExtendedPubKey() as NSString, buttonCopyText: TLDisplayStrings.COPY_TO_CLIPBOARD_STRING(), vc: self) self.QRImageModal!.show() NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_VIEW_EXTENDED_PUBLIC_KEY()), object: accountObject, userInfo: nil) } else if (buttonIndex == VIEW_EXTENDED_PRIVATE_KEY_BUTTON_IDX) { self.QRImageModal = TLQRImageModal(data: accountObject.getExtendedPrivKey()! as NSString, buttonCopyText: TLDisplayStrings.COPY_TO_CLIPBOARD_STRING(), vc: self) self.QRImageModal!.show() NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_VIEW_EXTENDED_PRIVATE_KEY()), object: accountObject, userInfo: nil) } else if (buttonIndex == VIEW_ADDRESSES_BUTTON_IDX) { self.showAddressListAccountObject = accountObject self.showAddressListShowBalances = false self.performSegue(withIdentifier: "SegueAddressList", sender: self) } else if (buttonIndex == RENAME_ACCOUNT_BUTTON_IDX) { self.promtForNameAccount({ (accountName: String!) in AppDelegate.instance().accounts!.renameAccount(accountObject.getAccountIdxNumber(), accountName: accountName) self._accountsTableViewReloadDataWrapper() }, failure: ({ (isCanceled: Bool) in })) } else if (buttonIndex == UNARCHIVE_ACCOUNT_BUTTON_IDX) { self.promptToUnarchiveAccount(accountObject) } else if (buttonIndex == actionSheet!.cancelButtonIndex) { } }) } fileprivate func promptToManuallyScanForStealthTransactionAccount(_ accountObject: TLAccountObject) -> () { func addTextField(_ textField: UITextField!){ textField.placeholder = TLDisplayStrings.TRANSACTION_ID_STRING() } UIAlertController.showAlert(in: self, withTitle: TLDisplayStrings.SCAN_FOR_REUSABLE_ADDRESS_TRANSACTION_STRING(), message: "", preferredStyle: .alert, cancelButtonTitle: TLDisplayStrings.CANCEL_STRING(), destructiveButtonTitle: nil, otherButtonTitles: [TLDisplayStrings.YES_STRING()], preShow: {(controller) in controller!.addTextField(configurationHandler: addTextField) } , tap: {(alertView, action, buttonIndex) in if (buttonIndex == alertView!.firstOtherButtonIndex) { let txid = (alertView!.textFields![0] ).text self.manuallyScanForStealthTransactionAccount(accountObject, txid: txid!) } else if (buttonIndex == alertView!.cancelButtonIndex) { } } ) } fileprivate func manuallyScanForStealthTransactionAccount(_ accountObject: TLAccountObject, txid: String) -> () { if accountObject.stealthWallet!.paymentTxidExist(txid) { TLPrompts.promptSuccessMessage("", message: String(format: TLDisplayStrings.TRANSACTION_X_ALREADY_ACCOUNTED_FOR_STRING(), txid)) return } if txid.characters.count != 64 || TLWalletUtils.hexStringToData(txid) == nil { TLPrompts.promptErrorMessage(TLDisplayStrings.INVALID_TRANSACTION_ID(), message: "") return } TLHUDWrapper.showHUDAddedTo(self.slidingViewController().topViewController.view, labelText: TLDisplayStrings.CHECKING_TRANSACTION_STRING(), animated: true) TLBlockExplorerAPI.instance().getTx(txid, success: { (jsonData) in let stealthDataScriptAndOutputAddresses = TLStealthWallet.getStealthDataScriptAndOutputAddresses(jsonData as! NSDictionary) if stealthDataScriptAndOutputAddresses == nil || stealthDataScriptAndOutputAddresses!.stealthDataScript == nil { TLHUDWrapper.hideHUDForView(self.view, animated: true) TLPrompts.promptSuccessMessage("", message: TLDisplayStrings.TRANSACTION_NOT_REUSABLE_ADDRESS_TRANSACTION_STRING()) return } let scanPriv = accountObject.stealthWallet!.getStealthAddressScanKey() let spendPriv = accountObject.stealthWallet!.getStealthAddressSpendKey() let stealthDataScript = stealthDataScriptAndOutputAddresses!.stealthDataScript! if let secret = TLStealthAddress.getPaymentAddressPrivateKeySecretFromScript(stealthDataScript, scanPrivateKey: scanPriv, spendPrivateKey: spendPriv) { let paymentAddress = TLCoreBitcoinWrapper.getAddressFromSecret(secret, isTestnet: AppDelegate.instance().appWallet.walletConfig.isTestnet) if (stealthDataScriptAndOutputAddresses!.outputAddresses).index(of: (paymentAddress!)) != nil { TLBlockExplorerAPI.instance().getUnspentOutputs([paymentAddress!], success: { (jsonData2: AnyObject!) in let unspentOutputs = (jsonData2 as! NSDictionary).object(forKey: "unspent_outputs") as! NSArray! if (unspentOutputs!.count > 0) { let privateKey = TLCoreBitcoinWrapper.privateKeyFromSecret(secret, isTestnet: AppDelegate.instance().appWallet.walletConfig.isTestnet) let txObject = TLTxObject(dict:jsonData as! NSDictionary) let txTime = txObject.getTxUnixTime() accountObject.stealthWallet!.addStealthAddressPaymentKey(privateKey, paymentAddress: paymentAddress!, txid: txid, txTime: txTime, stealthPaymentStatus: TLStealthPaymentStatus.unspent) TLHUDWrapper.hideHUDForView(self.view, animated: true) TLPrompts.promptSuccessMessage(TLDisplayStrings.SUCCESS_STRING(), message: String(format: TLDisplayStrings.FUNDS_IMPORTED(), txid)) AppDelegate.instance().pendingOperations.addSetUpAccountOperation(accountObject, fetchDataAgain: true, success: { self.refreshWalletAccounts(false) TLStealthExplorerAPI.instance().lookupTx(accountObject.stealthWallet!.getStealthAddress(), txid: txid, success: { (jsonData) -> () in DLog("lookupTx success \(jsonData.description)") }) { (code, status) -> () in DLog("lookupTx failure code: \(code) \(status)") } }) } else { TLHUDWrapper.hideHUDForView(self.view, animated: true) TLPrompts.promptSuccessMessage("", message: TLDisplayStrings.FUNDS_HAVE_BEEN_CLAIMED_ALREADY_STRING()) } }, failure: {(code, status) in TLHUDWrapper.hideHUDForView(self.view, animated: true) TLPrompts.promptSuccessMessage("", message: TLDisplayStrings.FUNDS_HAVE_BEEN_CLAIMED_ALREADY_STRING()) }) } else { TLHUDWrapper.hideHUDForView(self.view, animated: true) TLPrompts.promptSuccessMessage("", message: String(format: TLDisplayStrings.TRANSACTION_X_DOES_NOT_BELONG_TO_THIS_ACCOUNT_STRING(), txid)) } } else { TLHUDWrapper.hideHUDForView(self.view, animated: true) TLPrompts.promptSuccessMessage("", message: String(format: TLDisplayStrings.TRANSACTION_X_DOES_NOT_BELONG_TO_THIS_ACCOUNT_STRING(), txid)) } }, failure: { (code, status) in TLHUDWrapper.hideHUDForView(self.view, animated: true) TLPrompts.promptSuccessMessage(TLDisplayStrings.ERROR_STRING(), message: TLDisplayStrings.ERROR_FETCHING_TRANSACTION_STRING()) }) } fileprivate func promptInfoAndToManuallyScanForStealthTransactionAccount(_ accountObject: TLAccountObject) -> () { if (TLSuggestions.instance().enabledShowManuallyScanTransactionForStealthTxInfo()) { TLPrompts.promtForOK(self, title:"", message: TLDisplayStrings.MANUALLY_SCAN_TRANSACTION_FOR_STEALTH_TX_INFO_STRING(), success: { () in self.promptToManuallyScanForStealthTransactionAccount(accountObject) TLSuggestions.instance().setEnabledShowManuallyScanTransactionForStealthTxInfo(false) }) } else { self.promptToManuallyScanForStealthTransactionAccount(accountObject) } } fileprivate func promptToUnarchiveAccount(_ accountObject: TLAccountObject) -> () { UIAlertController.showAlert(in: self, withTitle: TLDisplayStrings.UNARCHIVE_ACCOUNT_STRING(), message: String(format: TLDisplayStrings.ARE_YOU_SURE_YOU_WANT_TO_UNARCHIVE_ACCOUNT_X_STRING(), accountObject.getAccountName()), cancelButtonTitle: TLDisplayStrings.CANCEL_STRING(), destructiveButtonTitle: nil, otherButtonTitles: [TLDisplayStrings.YES_STRING()], tap: {(alertView, action, buttonIndex) in if (buttonIndex == alertView?.firstOtherButtonIndex) { if (accountObject.getAccountType() == .hdWallet) { AppDelegate.instance().accounts!.unarchiveAccount(accountObject.getAccountIdxNumber()) } else if (accountObject.getAccountType() == .coldWallet) { AppDelegate.instance().coldWalletAccounts!.unarchiveAccount(accountObject.getPositionInWalletArray()) } else if (accountObject.getAccountType() == .imported) { AppDelegate.instance().importedAccounts!.unarchiveAccount(accountObject.getPositionInWalletArray()) } else if (accountObject.getAccountType() == .importedWatch) { AppDelegate.instance().importedWatchAccounts!.unarchiveAccount(accountObject.getPositionInWalletArray()) } if TLWalletUtils.ALLOW_MANUAL_SCAN_FOR_STEALTH_PAYMENT() && !accountObject.isWatchOnly() && !accountObject.isColdWalletAccount() && !accountObject.stealthWallet!.hasUpdateStealthPaymentStatuses { accountObject.stealthWallet!.updateStealthPaymentStatusesAsync() } self._accountsTableViewReloadDataWrapper() AppDelegate.instance().pendingOperations.addSetUpAccountOperation(accountObject, fetchDataAgain: true, success: { self._accountsTableViewReloadDataWrapper() }) } else if (buttonIndex == alertView?.cancelButtonIndex) { } } ) } fileprivate func promptToArchiveAccount(_ accountObject: TLAccountObject) -> () { UIAlertController.showAlert(in: self, withTitle: TLDisplayStrings.ARCHIVE_ACCOUNT_STRING(), message: String(format: TLDisplayStrings.ARE_YOU_SURE_YOU_WANT_TO_ARCHIVE_ACCOUNT_X_STRING(), accountObject.getAccountName()), cancelButtonTitle: TLDisplayStrings.CANCEL_STRING(), destructiveButtonTitle: nil, otherButtonTitles: [TLDisplayStrings.YES_STRING()], tap: {(alertView, action, buttonIndex) in if (buttonIndex == alertView?.firstOtherButtonIndex) { if (accountObject.getAccountType() == .hdWallet) { AppDelegate.instance().accounts!.archiveAccount(accountObject.getAccountIdxNumber()) } else if (accountObject.getAccountType() == .coldWallet) { AppDelegate.instance().coldWalletAccounts!.archiveAccount(accountObject.getPositionInWalletArray()) } else if (accountObject.getAccountType() == .imported) { AppDelegate.instance().importedAccounts!.archiveAccount(accountObject.getPositionInWalletArray()) } else if (accountObject.getAccountType() == .importedWatch) { AppDelegate.instance().importedWatchAccounts!.archiveAccount(accountObject.getPositionInWalletArray()) } self._accountsTableViewReloadDataWrapper() NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_ARCHIVE_ACCOUNT()), object: nil, userInfo: nil) } else if (buttonIndex == alertView?.cancelButtonIndex) { } } ) } fileprivate func promptToArchiveAccountHDWalletAccount(_ accountObject: TLAccountObject) -> () { if (accountObject.getAccountIdxNumber() == 0) { let av = UIAlertView(title: TLDisplayStrings.CANNOT_ARCHIVE_YOUR_DEFAULT_ACCOUNT_STRING(), message: "", delegate: nil, cancelButtonTitle: nil, otherButtonTitles: TLDisplayStrings.OK_STRING()) av.show() } else if (AppDelegate.instance().accounts!.getNumberOfAccounts() <= 1) { let av = UIAlertView(title: TLDisplayStrings.CANNOT_ARCHIVE_YOUR_ONE_AND_ONLY_ACCOUNT_STRING(), message: "", delegate: nil, cancelButtonTitle: nil, otherButtonTitles: TLDisplayStrings.OK_STRING()) av.show() } else { self.promptToArchiveAccount(accountObject) } } fileprivate func promptToArchiveAddress(_ importedAddressObject: TLImportedAddress) -> () { UIAlertController.showAlert(in: self, withTitle: TLDisplayStrings.ARCHIVE_ADDRESS_STRING(), message: String(format: TLDisplayStrings.ARE_YOU_SURE_YOU_WANT_TO_ARCHIVE_ADDRESS_X_STRING(), importedAddressObject.getLabel()), cancelButtonTitle: TLDisplayStrings.CANCEL_STRING(), destructiveButtonTitle: nil, otherButtonTitles: [TLDisplayStrings.YES_STRING()], tap: {(alertView, action, buttonIndex) in if (buttonIndex == alertView?.firstOtherButtonIndex) { if (importedAddressObject.isWatchOnly()) { AppDelegate.instance().importedWatchAddresses!.archiveAddress(Int(importedAddressObject.getPositionInWalletArrayNumber())) } else { AppDelegate.instance().importedAddresses!.archiveAddress(Int(importedAddressObject.getPositionInWalletArrayNumber())) } self._accountsTableViewReloadDataWrapper() NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_ARCHIVE_ACCOUNT()), object: nil, userInfo: nil) } else if (buttonIndex == alertView?.cancelButtonIndex) { } } ) } fileprivate func promptToUnarchiveAddress(_ importedAddressObject: TLImportedAddress) -> () { UIAlertController.showAlert(in: self, withTitle: TLDisplayStrings.UNARCHIVE_ADDRESS_STRING(), message: String(format: TLDisplayStrings.ARE_YOU_SURE_YOU_WANT_TO_UNARCHIVE_ADDRESS_X_STRING(), importedAddressObject.getLabel()), cancelButtonTitle: TLDisplayStrings.CANCEL_STRING(), destructiveButtonTitle: nil, otherButtonTitles: [TLDisplayStrings.YES_STRING()], tap: {(alertView, action, buttonIndex) in if (buttonIndex == alertView?.firstOtherButtonIndex) { if (importedAddressObject.isWatchOnly()) { AppDelegate.instance().importedWatchAddresses!.unarchiveAddress(Int(importedAddressObject.getPositionInWalletArrayNumber())) } else { AppDelegate.instance().importedAddresses!.unarchiveAddress(Int(importedAddressObject.getPositionInWalletArrayNumber())) } self._accountsTableViewReloadDataWrapper() importedAddressObject.getSingleAddressData({ () in self._accountsTableViewReloadDataWrapper() }, failure: { () in }) } else if (buttonIndex == alertView?.cancelButtonIndex) { } } ) } fileprivate func promptToDeleteColdWalletAccount(_ indexPath: IndexPath) -> () { let accountObject = AppDelegate.instance().coldWalletAccounts!.getArchivedAccountObjectForIdx((indexPath as NSIndexPath).row) UIAlertController.showAlert(in: self, withTitle: String(format: TLDisplayStrings.DELETE_X_STRING(), accountObject.getAccountName()), message: TLDisplayStrings.ARE_YOU_SURE_YOU_WANT_TO_DELETE_THIS_ACCOUNT_STRING(), cancelButtonTitle: TLDisplayStrings.NO_STRING(), destructiveButtonTitle: nil, otherButtonTitles: [TLDisplayStrings.YES_STRING()], tap: {(alertView, action, buttonIndex) in if (buttonIndex == alertView!.firstOtherButtonIndex) { AppDelegate.instance().coldWalletAccounts!.deleteAccount((indexPath as NSIndexPath).row) //* self.accountsTableView!.beginUpdates() let index = NSIndexPath(indexes:[self.archivedColdWalletAccountSection, (indexPath as NSIndexPath).row], length:2) as IndexPath let deleteIndexPaths = [index] self.accountsTableView!.deleteRows(at: deleteIndexPaths, with: .fade) self.accountsTableView!.endUpdates() //*/ self._accountsTableViewReloadDataWrapper() } else if (buttonIndex == alertView!.cancelButtonIndex) { self.accountsTableView!.isEditing = false } }) } fileprivate func promptToDeleteImportedAccount(_ indexPath: IndexPath) -> () { let accountObject = AppDelegate.instance().importedAccounts!.getArchivedAccountObjectForIdx((indexPath as NSIndexPath).row) UIAlertController.showAlert(in: self, withTitle: String(format: TLDisplayStrings.DELETE_X_STRING(), accountObject.getAccountName()), message: TLDisplayStrings.ARE_YOU_SURE_YOU_WANT_TO_DELETE_THIS_ACCOUNT_STRING(), cancelButtonTitle: TLDisplayStrings.NO_STRING(), destructiveButtonTitle: nil, otherButtonTitles: [TLDisplayStrings.YES_STRING()], tap: {(alertView, action, buttonIndex) in if (buttonIndex == alertView!.firstOtherButtonIndex) { AppDelegate.instance().importedAccounts!.deleteAccount((indexPath as NSIndexPath).row) self.accountsTableView!.beginUpdates() let index = NSIndexPath(indexes: [self.archivedImportedAccountSection, (indexPath as NSIndexPath).row], length:2) as IndexPath let deleteIndexPaths = [index] self.accountsTableView!.deleteRows(at: deleteIndexPaths, with: .fade) self.accountsTableView!.endUpdates() self._accountsTableViewReloadDataWrapper() } else if (buttonIndex == alertView!.cancelButtonIndex) { self.accountsTableView!.isEditing = false } } ) } fileprivate func promptToDeleteImportedWatchAccount(_ indexPath: IndexPath) -> () { let accountObject = AppDelegate.instance().importedWatchAccounts!.getArchivedAccountObjectForIdx((indexPath as NSIndexPath).row) UIAlertController.showAlert(in: self, withTitle: String(format: TLDisplayStrings.DELETE_X_STRING(), accountObject.getAccountName()), message: TLDisplayStrings.ARE_YOU_SURE_YOU_WANT_TO_DELETE_THIS_ACCOUNT_STRING(), cancelButtonTitle: TLDisplayStrings.NO_STRING(), destructiveButtonTitle: nil, otherButtonTitles: [TLDisplayStrings.YES_STRING()], tap: {(alertView, action, buttonIndex) in if (buttonIndex == alertView!.firstOtherButtonIndex) { AppDelegate.instance().importedWatchAccounts!.deleteAccount((indexPath as NSIndexPath).row) //* self.accountsTableView!.beginUpdates() let index = NSIndexPath(indexes:[self.archivedImportedWatchAccountSection, (indexPath as NSIndexPath).row], length:2) as IndexPath let deleteIndexPaths = [index] self.accountsTableView!.deleteRows(at: deleteIndexPaths, with: .fade) self.accountsTableView!.endUpdates() //*/ self._accountsTableViewReloadDataWrapper() } else if (buttonIndex == alertView!.cancelButtonIndex) { self.accountsTableView!.isEditing = false } }) } fileprivate func promptToDeleteImportedAddress(_ importedAddressIdx: Int) -> () { let importedAddressObject = AppDelegate.instance().importedAddresses!.getArchivedAddressObjectAtIdx(importedAddressIdx) UIAlertController.showAlert(in: self, withTitle: String(format: TLDisplayStrings.DELETE_X_STRING(), importedAddressObject.getLabel()), message: TLDisplayStrings.ARE_YOU_SURE_YOU_WANT_TO_DELETE_THIS_ACCOUNT_STRING(), cancelButtonTitle: TLDisplayStrings.NO_STRING(), destructiveButtonTitle: nil, otherButtonTitles: [TLDisplayStrings.YES_STRING()], tap: {(alertView, action, buttonIndex) in if (buttonIndex == alertView!.firstOtherButtonIndex) { self.accountsTableView!.setEditing(true, animated: true) AppDelegate.instance().importedAddresses!.deleteAddress(importedAddressIdx) self._accountsTableViewReloadDataWrapper() self.accountsTableView!.setEditing(false, animated: true) } else if (buttonIndex == alertView!.cancelButtonIndex) { self.accountsTableView!.isEditing = false } }) } fileprivate func promptToDeleteImportedWatchAddress(_ importedAddressIdx: Int) -> () { let importedAddressObject = AppDelegate.instance().importedWatchAddresses!.getArchivedAddressObjectAtIdx(importedAddressIdx) UIAlertController.showAlert(in: self, withTitle: String(format: TLDisplayStrings.DELETE_X_STRING(), importedAddressObject.getLabel()), message: TLDisplayStrings.ARE_YOU_SURE_YOU_WANT_TO_DELETE_THIS_ACCOUNT_STRING(), cancelButtonTitle: TLDisplayStrings.NO_STRING(), destructiveButtonTitle: nil, otherButtonTitles: [TLDisplayStrings.YES_STRING()], tap: {(alertView, action, buttonIndex) in if (buttonIndex == alertView!.firstOtherButtonIndex) { self.accountsTableView!.setEditing(true, animated: true) AppDelegate.instance().importedWatchAddresses!.deleteAddress(importedAddressIdx) self._accountsTableViewReloadDataWrapper() self.accountsTableView!.setEditing(false, animated: true) } else if (buttonIndex == alertView!.cancelButtonIndex) { self.accountsTableView!.isEditing = false } }) } fileprivate func setEditingAndRefreshAccounts() -> () { self.accountsTableView!.setEditing(true, animated: true) self.refreshWalletAccounts(false) self._accountsTableViewReloadDataWrapper() self.accountsTableView!.setEditing(false, animated: true) } fileprivate func importColdWalletAccount(_ extendedPublicKey: String) -> (Bool) { if (TLHDWalletWrapper.isValidExtendedPublicKey(extendedPublicKey)) { AppDelegate.instance().saveWalletJsonCloudBackground() AppDelegate.instance().saveWalletJSONEnabled = false let defaultAccountName = String(format: TLDisplayStrings.IMPORTED_COLD_WALLET_ACCOUNT_STRING(), String(AppDelegate.instance().coldWalletAccounts!.getNumberOfAccounts() + AppDelegate.instance().coldWalletAccounts!.getNumberOfArchivedAccounts() + 1)) let accountObject = AppDelegate.instance().coldWalletAccounts!.addAccountWithExtendedKey(extendedPublicKey, accountName: defaultAccountName) TLHUDWrapper.showHUDAddedTo(self.slidingViewController().topViewController.view, labelText: TLDisplayStrings.IMPORTING_COLD_WALLET_ACCOUNT_STRING(), animated: true) DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.high).async { SwiftTryCatch.`try`({ () -> () in accountObject.recoverAccount(false, recoverStealthPayments: true) AppDelegate.instance().saveWalletJSONEnabled = true AppDelegate.instance().saveWalletJsonCloudBackground() NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_IMPORT_COLD_WALLET_ACCOUNT()), object: nil) // don't need to call do accountObject.getAccountData like in importAccount() cause watch account does not see stealth payments. yet DispatchQueue.main.async { TLHUDWrapper.hideHUDForView(self.view, animated: true) self.promtForNameAccount({ (_accountName: String?) in var accountName = _accountName if (accountName == nil || accountName == "") { accountName = defaultAccountName } AppDelegate.instance().coldWalletAccounts!.renameAccount(accountObject.getAccountIdxNumber(), accountName: accountName!) let titleStr = String(format: TLDisplayStrings.ACCOUNT_X_IMPORTED_STRING(), accountName!) let av = UIAlertView(title: titleStr, message: "", delegate: nil, cancelButtonTitle: TLDisplayStrings.OK_STRING()) av.show() self.setEditingAndRefreshAccounts() }, failure: { (isCanceled: Bool) in self.setEditingAndRefreshAccounts() }) } }, catch: { (exception) -> Void in DispatchQueue.main.async { AppDelegate.instance().coldWalletAccounts!.deleteAccount(AppDelegate.instance().coldWalletAccounts!.getNumberOfAccounts() - 1) TLHUDWrapper.hideHUDForView(self.view, animated: true) TLPrompts.promptErrorMessage(TLDisplayStrings.ERROR_IMPORTING_ACCOUNT_STRING(), message: TLDisplayStrings.TRY_AGAIN_STRING()) self.setEditingAndRefreshAccounts() } }, finally: { () in }) } return true } else { let av = UIAlertView(title: TLDisplayStrings.INVALID_ACCOUNT_PUBLIC_KEY_STRING(), message: "", delegate: nil, cancelButtonTitle: TLDisplayStrings.CANCEL_STRING(), otherButtonTitles: TLDisplayStrings.OK_STRING()) av.show() return false } } fileprivate func importAccount(_ extendedPrivateKey: String) -> (Bool) { let handleImportAccountFail = { DispatchQueue.main.async { AppDelegate.instance().importedAccounts!.deleteAccount(AppDelegate.instance().importedAccounts!.getNumberOfAccounts() - 1) TLHUDWrapper.hideHUDForView(self.view, animated: true) TLPrompts.promptErrorMessage(TLDisplayStrings.ERROR_IMPORTING_ACCOUNT_STRING(), message: TLDisplayStrings.TRY_AGAIN_STRING()) self.setEditingAndRefreshAccounts() } } if (TLHDWalletWrapper.isValidExtendedPrivateKey(extendedPrivateKey)) { AppDelegate.instance().saveWalletJsonCloudBackground() AppDelegate.instance().saveWalletJSONEnabled = false let defaultAccountName = String(format: TLDisplayStrings.IMPORTED_ACCOUNT_STRING(), String(AppDelegate.instance().importedAccounts!.getNumberOfAccounts() + AppDelegate.instance().importedAccounts!.getNumberOfArchivedAccounts() + 1)) let accountObject = AppDelegate.instance().importedAccounts!.addAccountWithExtendedKey(extendedPrivateKey, accountName: defaultAccountName) TLHUDWrapper.showHUDAddedTo(self.slidingViewController().topViewController.view, labelText: TLDisplayStrings.IMPORTING_ACCOUNT_STRING(), animated: true) DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.high).async { SwiftTryCatch.`try`({ () -> () in accountObject.recoverAccount(false, recoverStealthPayments: true) AppDelegate.instance().saveWalletJSONEnabled = true AppDelegate.instance().saveWalletJsonCloudBackground() let handleImportAccountSuccess = { DispatchQueue.main.async { TLHUDWrapper.hideHUDForView(self.view, animated: true) self.promtForNameAccount({ (_accountName: String?) in var accountName = _accountName if (accountName == nil || accountName == "") { accountName = defaultAccountName } AppDelegate.instance().importedAccounts!.renameAccount(accountObject.getAccountIdxNumber(), accountName: accountName!) let av = UIAlertView(title: String(format: TLDisplayStrings.ACCOUNT_X_IMPORTED_STRING(), accountName!), message: nil, delegate: nil, cancelButtonTitle: TLDisplayStrings.OK_STRING()) av.show() self.setEditingAndRefreshAccounts() }, failure: ({ (isCanceled: Bool) in self.setEditingAndRefreshAccounts() })) } } NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_IMPORT_ACCOUNT()), object: nil, userInfo: nil) TLStealthWebSocket.instance().sendMessageGetChallenge() AppDelegate.instance().pendingOperations.addSetUpAccountOperation(accountObject, fetchDataAgain: true, success: { self.refreshWalletAccounts(false) handleImportAccountSuccess() }) }, catch: { (e) -> Void in handleImportAccountFail() }, finally: { () in }) } return true } else { let av = UIAlertView(title: TLDisplayStrings.INVALID_ACCOUNT_PRIVATE_KEY_STRING(), message: "", delegate: nil, cancelButtonTitle: TLDisplayStrings.CANCEL_STRING(), otherButtonTitles: TLDisplayStrings.OK_STRING()) av.show() return false } } fileprivate func importWatchOnlyAccount(_ extendedPublicKey: String) -> (Bool) { if (TLHDWalletWrapper.isValidExtendedPublicKey(extendedPublicKey)) { AppDelegate.instance().saveWalletJsonCloudBackground() AppDelegate.instance().saveWalletJSONEnabled = false let defaultAccountName = String(format: TLDisplayStrings.IMPORTED_WATCH_ACCOUNT_STRING(), String(AppDelegate.instance().importedWatchAccounts!.getNumberOfAccounts() + AppDelegate.instance().importedWatchAccounts!.getNumberOfArchivedAccounts() + 1)) let accountObject = AppDelegate.instance().importedWatchAccounts!.addAccountWithExtendedKey(extendedPublicKey, accountName: defaultAccountName) TLHUDWrapper.showHUDAddedTo(self.slidingViewController().topViewController.view, labelText: TLDisplayStrings.IMPORTING_ACCOUNT_STRING(), animated: true) DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.high).async { SwiftTryCatch.`try`({ () -> () in accountObject.recoverAccount(false, recoverStealthPayments: true) AppDelegate.instance().saveWalletJSONEnabled = true AppDelegate.instance().saveWalletJsonCloudBackground() NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_IMPORT_WATCH_ONLY_ACCOUNT()), object: nil) // don't need to call do accountObject.getAccountData like in importAccount() cause watch account does not see stealth payments. yet DispatchQueue.main.async { TLHUDWrapper.hideHUDForView(self.view, animated: true) self.promtForNameAccount({ (_accountName: String?) in var accountName = _accountName if (accountName == nil || accountName == "") { accountName = defaultAccountName } AppDelegate.instance().importedWatchAccounts!.renameAccount(accountObject.getAccountIdxNumber(), accountName: accountName!) let titleStr = String(format: TLDisplayStrings.ACCOUNT_X_IMPORTED_STRING(), accountName!) let av = UIAlertView(title: titleStr, message: "", delegate: nil, cancelButtonTitle: TLDisplayStrings.OK_STRING()) av.show() self.setEditingAndRefreshAccounts() }, failure: { (isCanceled: Bool) in self.setEditingAndRefreshAccounts() }) } }, catch: { (exception) -> Void in DispatchQueue.main.async { AppDelegate.instance().importedWatchAccounts!.deleteAccount(AppDelegate.instance().importedWatchAccounts!.getNumberOfAccounts() - 1) TLHUDWrapper.hideHUDForView(self.view, animated: true) TLPrompts.promptErrorMessage(TLDisplayStrings.ERROR_IMPORTING_ACCOUNT_STRING(), message: TLDisplayStrings.TRY_AGAIN_STRING()) self.setEditingAndRefreshAccounts() } }, finally: { () in }) } return true } else { let av = UIAlertView(title: TLDisplayStrings.INVALID_ACCOUNT_PUBLIC_KEY_STRING(), message: "", delegate: nil, cancelButtonTitle: TLDisplayStrings.CANCEL_STRING(), otherButtonTitles: TLDisplayStrings.OK_STRING()) av.show() return false } } fileprivate func checkAndImportAddress(_ privateKey: String, encryptedPrivateKey: String?) -> (Bool) { if (TLCoreBitcoinWrapper.isValidPrivateKey(privateKey, isTestnet: AppDelegate.instance().appWallet.walletConfig.isTestnet)) { if (encryptedPrivateKey != nil) { UIAlertController.showAlert(in: self, withTitle: TLDisplayStrings.IMPORT_PRIVATE_KEY_ENCRYPTED_OR_UNENCRYPTED_STRING(), message: TLDisplayStrings.IMPORT_PRIVATE_KEY_ENCRYPTED_OR_UNENCRYPTED_DESC_STRING(), cancelButtonTitle: TLDisplayStrings.ENCRYPTED_STRING(), destructiveButtonTitle: nil, otherButtonTitles: [TLDisplayStrings.UNENCRYPTED_STRING()], tap: {(alertView, action, buttonIndex) in if (buttonIndex == alertView?.firstOtherButtonIndex) { let importedAddressObject = AppDelegate.instance().importedAddresses!.addImportedPrivateKey(privateKey, encryptedPrivateKey: nil) self.refreshAfterImportAddress(importedAddressObject) } else if (buttonIndex == alertView?.cancelButtonIndex) { let importedAddressObject = AppDelegate.instance().importedAddresses!.addImportedPrivateKey(privateKey, encryptedPrivateKey: encryptedPrivateKey) self.refreshAfterImportAddress(importedAddressObject) } }) } else { let importedAddressObject = AppDelegate.instance().importedAddresses!.addImportedPrivateKey(privateKey, encryptedPrivateKey: nil) self.refreshAfterImportAddress(importedAddressObject) } return true } else { let av = UIAlertView(title: TLDisplayStrings.INVALID_PRIVATE_KEY_STRING(), message: "", delegate: nil, cancelButtonTitle: TLDisplayStrings.OK_STRING()) av.show() return false } } fileprivate func refreshAfterImportAddress(_ importedAddressObject: TLImportedAddress) -> () { let lastIdx = AppDelegate.instance().importedAddresses!.getCount() let indexPath = IndexPath(row: lastIdx, section: importedAddressSection) let cell = self.accountsTableView!.cellForRow(at: indexPath) as? TLAccountTableViewCell if cell != nil { (cell!.accessoryView! as! UIActivityIndicatorView).isHidden = false (cell!.accessoryView! as! UIActivityIndicatorView).startAnimating() } importedAddressObject.getSingleAddressData({ () in if cell != nil { (cell!.accessoryView! as! UIActivityIndicatorView).stopAnimating() (cell!.accessoryView! as! UIActivityIndicatorView).isHidden = true let balance = TLCurrencyFormat.getProperAmount(importedAddressObject.getBalance()!) cell!.accountBalanceButton!.setTitle(balance as String, for: UIControlState()) self.setEditingAndRefreshAccounts() } }, failure: { () in if cell != nil { (cell!.accessoryView! as! UIActivityIndicatorView).stopAnimating() (cell!.accessoryView! as! UIActivityIndicatorView).isHidden = true } }) let address = importedAddressObject.getAddress() let msg = String(format: TLDisplayStrings.IMPORTED_ADDRESS_STRING()) let av = UIAlertView(title: msg, message: "", delegate: nil, cancelButtonTitle: TLDisplayStrings.OK_STRING()) av.show() NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_IMPORT_PRIVATE_KEY()), object: nil, userInfo: nil) } fileprivate func checkAndImportWatchAddress(_ address: String) -> (Bool) { if (TLCoreBitcoinWrapper.isValidAddress(address, isTestnet: AppDelegate.instance().appWallet.walletConfig.isTestnet)) { if (TLStealthAddress.isStealthAddress(address, isTestnet: AppDelegate.instance().appWallet.walletConfig.isTestnet)) { TLPrompts.promptErrorMessage(TLDisplayStrings.ERROR_STRING(), message: TLDisplayStrings.CANNOT_IMPORT_REUSABLE_ADDRESS_STRING()) return false } let importedAddressObject = AppDelegate.instance().importedWatchAddresses!.addImportedWatchAddress(address) let lastIdx = AppDelegate.instance().importedWatchAddresses!.getCount() let indexPath = IndexPath(row: lastIdx, section: importedWatchAddressSection) let cell = self.accountsTableView!.cellForRow(at: indexPath) as? TLAccountTableViewCell if cell != nil { (cell!.accessoryView! as! UIActivityIndicatorView).isHidden = false (cell!.accessoryView! as! UIActivityIndicatorView).startAnimating() } importedAddressObject.getSingleAddressData( { () in if cell != nil { (cell!.accessoryView! as! UIActivityIndicatorView).stopAnimating() (cell!.accessoryView! as! UIActivityIndicatorView).isHidden = true let balance = TLCurrencyFormat.getProperAmount(importedAddressObject.getBalance()!) cell!.accountBalanceButton!.setTitle(balance as String, for: UIControlState()) self.setEditingAndRefreshAccounts() } }, failure: { () in if cell != nil { (cell!.accessoryView! as! UIActivityIndicatorView).stopAnimating() (cell!.accessoryView! as! UIActivityIndicatorView).isHidden = true } }) let av = UIAlertView(title: String(format: TLDisplayStrings.ACCOUNT_X_IMPORTED_STRING()), message: "", delegate: nil, cancelButtonTitle: TLDisplayStrings.OK_STRING()) av.show() NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_IMPORT_WATCH_ONLY_ADDRESS()), object: nil, userInfo: nil) return true } else { TLPrompts.promptErrorMessage(TLDisplayStrings.INVALID_ADDRESS_STRING(), message: "") return false } } fileprivate func promptColdWalletAccountActionSheet() -> () { UIAlertController.showAlert(in: self, withTitle: TLDisplayStrings.IMPORT_COLD_WALLET_ACCOUNT_STRING(), message:"", preferredStyle: .actionSheet, cancelButtonTitle: TLDisplayStrings.CANCEL_STRING(), destructiveButtonTitle: nil, otherButtonTitles: [TLDisplayStrings.IMPORT_WITH_QR_CODE_STRING(), TLDisplayStrings.IMPORT_WITH_TEXT_INPUT_STRING()], tap: {(actionSheet, action, buttonIndex) in if (buttonIndex == (actionSheet?.firstOtherButtonIndex)! + 0) { AppDelegate.instance().showExtendedPublicKeyReaderController(self, success: { (data: String!) in self.importColdWalletAccount(data) }, error: { (data: String?) in }) } else if (buttonIndex == (actionSheet?.firstOtherButtonIndex)! + 1) { TLPrompts.promtForInputText(self, title: TLDisplayStrings.IMPORT_COLD_WALLET_ACCOUNT_STRING(), message: TLDisplayStrings.INPUT_ACCOUNT_PUBLIC_KEY_STRING(), textFieldPlaceholder: nil, success: { (inputText: String!) in self.importColdWalletAccount(inputText) }, failure: { (isCanceled: Bool) in }) } else if (buttonIndex == actionSheet?.cancelButtonIndex) { } }) } fileprivate func promptImportAccountActionSheet() -> () { UIAlertController.showAlert(in: self, withTitle: TLDisplayStrings.IMPORT_ACCOUNT_STRING(), message:"", preferredStyle: .actionSheet, cancelButtonTitle: TLDisplayStrings.CANCEL_STRING(), destructiveButtonTitle: nil, otherButtonTitles: [TLDisplayStrings.IMPORT_WITH_QR_CODE_STRING(), TLDisplayStrings.IMPORT_WITH_TEXT_INPUT_STRING()], tap: {(actionSheet, action, buttonIndex) in if (buttonIndex == (actionSheet?.firstOtherButtonIndex)! + 0) { AppDelegate.instance().showExtendedPrivateKeyReaderController(self, success: { (data: String!) in self.importAccount(data) }, error: { (data: String?) in }) } else if (buttonIndex == (actionSheet?.firstOtherButtonIndex)! + 1) { TLPrompts.promtForInputText(self, title: TLDisplayStrings.IMPORT_ACCOUNT_STRING(), message: TLDisplayStrings.INPUT_ACCOUNT_PRIVATE_KEY_STRING(), textFieldPlaceholder: nil, success: { (inputText: String!) in self.importAccount(inputText) }, failure: { (isCanceled: Bool) in }) } else if (buttonIndex == actionSheet?.cancelButtonIndex) { } }) } fileprivate func promptImportWatchAccountActionSheet() -> () { UIAlertController.showAlert(in: self, withTitle: TLDisplayStrings.IMPORT_WATCH_ACCOUNT_STRING(), message:"", preferredStyle: .actionSheet, cancelButtonTitle: TLDisplayStrings.CANCEL_STRING(), destructiveButtonTitle: nil, otherButtonTitles: [TLDisplayStrings.IMPORT_WITH_QR_CODE_STRING(), TLDisplayStrings.IMPORT_WITH_TEXT_INPUT_STRING()], tap: {(actionSheet, action, buttonIndex) in if (buttonIndex == (actionSheet?.firstOtherButtonIndex)! + 0) { AppDelegate.instance().showExtendedPublicKeyReaderController(self, success: { (data: String!) in self.importWatchOnlyAccount(data) }, error: { (data: String?) in }) } else if (buttonIndex == (actionSheet?.firstOtherButtonIndex)! + 1) { TLPrompts.promtForInputText(self, title: TLDisplayStrings.IMPORT_WATCH_ACCOUNT_STRING(), message: TLDisplayStrings.INPUT_ACCOUNT_PUBLIC_KEY_STRING(), textFieldPlaceholder: nil, success: { (inputText: String!) in self.importWatchOnlyAccount(inputText) }, failure: { (isCanceled: Bool) in }) } else if (buttonIndex == actionSheet?.cancelButtonIndex) { } }) } fileprivate func promptImportPrivateKeyActionSheet() -> () { UIAlertController.showAlert(in: self, withTitle: TLDisplayStrings.IMPORT_PRIVATE_KEY_STRING(), message:"", preferredStyle: .actionSheet, cancelButtonTitle: TLDisplayStrings.CANCEL_STRING(), destructiveButtonTitle: nil, otherButtonTitles: [TLDisplayStrings.IMPORT_WITH_QR_CODE_STRING(), TLDisplayStrings.IMPORT_WITH_TEXT_INPUT_STRING()], tap: {(actionSheet, action, buttonIndex) in if (buttonIndex == (actionSheet?.firstOtherButtonIndex)! + 0) { AppDelegate.instance().showPrivateKeyReaderController(self, success: { (data: NSDictionary) in let privateKey = data.object(forKey: "privateKey") as? String let encryptedPrivateKey = data.object(forKey: "encryptedPrivateKey") as? String if encryptedPrivateKey == nil { self.checkAndImportAddress(privateKey!, encryptedPrivateKey: encryptedPrivateKey) } }, error: { (data: String?) in }) } else if (buttonIndex == (actionSheet?.firstOtherButtonIndex)! + 1) { TLPrompts.promtForInputText(self, title: TLDisplayStrings.IMPORT_PRIVATE_KEY_STRING(), message: "", textFieldPlaceholder: nil, success: { (inputText: String!) in if (TLCoreBitcoinWrapper.isBIP38EncryptedKey(inputText, isTestnet: AppDelegate.instance().appWallet.walletConfig.isTestnet)) { TLPrompts.promptForEncryptedPrivKeyPassword(self, view:self.slidingViewController().topViewController.view, encryptedPrivKey: inputText, success: { (privKey: String!) in self.checkAndImportAddress(privKey, encryptedPrivateKey: inputText) }, failure: { (isCanceled: Bool) in }) } else { self.checkAndImportAddress(inputText, encryptedPrivateKey: nil) } }, failure: { (isCanceled: Bool) in }) } else if (buttonIndex == actionSheet?.cancelButtonIndex) { } }) } fileprivate func promptImportWatchAddressActionSheet() -> () { UIAlertController.showAlert(in: self, withTitle: TLDisplayStrings.IMPORT_WATCH_ADDRESS_STRING(), message:"", preferredStyle: .actionSheet, cancelButtonTitle: TLDisplayStrings.CANCEL_STRING(), destructiveButtonTitle: nil, otherButtonTitles: [TLDisplayStrings.IMPORT_WITH_QR_CODE_STRING(), TLDisplayStrings.IMPORT_WITH_TEXT_INPUT_STRING()], tap: {(actionSheet, action, buttonIndex) in if (buttonIndex == (actionSheet?.firstOtherButtonIndex)! + 0) { AppDelegate.instance().showAddressReaderControllerFromViewController(self, success: { (data: String!) in self.checkAndImportWatchAddress(data) }, error: { (data: String?) in }) } else if (buttonIndex == (actionSheet?.firstOtherButtonIndex)! + 1) { TLPrompts.promtForInputText(self, title: TLDisplayStrings.IMPORT_WATCH_ADDRESS_STRING(), message: "", textFieldPlaceholder: nil, success: { (inputText: String!) in self.checkAndImportWatchAddress(inputText) }, failure: { (isCanceled: Bool) in }) } else if (buttonIndex == actionSheet?.cancelButtonIndex) { } }) } fileprivate func doAccountAction(_ accountSelectIdx: Int) -> () { var count = 0 let CREATE_NEW_ACCOUNT_BUTTON_IDX = count count += 1 var IMPORT_COLD_WALLET_ACCOUNT_BUTTON_IDX = -1 if TLPreferences.enabledColdWallet() { IMPORT_COLD_WALLET_ACCOUNT_BUTTON_IDX = count count += 1 } let IMPORT_ACCOUNT_BUTTON_IDX = count count += 1 let IMPORT_WATCH_ACCOUNT_BUTTON_IDX = count count += 1 let IMPORT_PRIVATE_KEY_BUTTON_IDX = count count += 1 let IMPORT_WATCH_ADDRESS_BUTTON_IDX = count if (accountSelectIdx == CREATE_NEW_ACCOUNT_BUTTON_IDX) { self.promtForNameAccount({ (accountName: String!) in AppDelegate.instance().accounts!.createNewAccount(accountName, accountType: .normal) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_CREATE_NEW_ACCOUNT()), object: nil, userInfo: nil) self.refreshWalletAccounts(false) TLStealthWebSocket.instance().sendMessageGetChallenge() }, failure: { (isCanceled: Bool) in }) } else if (accountSelectIdx == IMPORT_COLD_WALLET_ACCOUNT_BUTTON_IDX) { self.promptColdWalletAccountActionSheet() } else if (accountSelectIdx == IMPORT_ACCOUNT_BUTTON_IDX) { self.promptImportAccountActionSheet() } else if (accountSelectIdx == IMPORT_WATCH_ACCOUNT_BUTTON_IDX) { self.promptImportWatchAccountActionSheet() } else if (accountSelectIdx == IMPORT_PRIVATE_KEY_BUTTON_IDX) { self.promptImportPrivateKeyActionSheet() } else if (accountSelectIdx == IMPORT_WATCH_ADDRESS_BUTTON_IDX) { self.promptImportWatchAddressActionSheet() } } @IBAction fileprivate func menuButtonClicked(_ sender: UIButton) { self.slidingViewController().anchorTopViewToRight(animated: true) } func tableView(_ tableView: UITableView, heightForRowAt heightForRowAtIndexPath: IndexPath) -> CGFloat { // hard code height here to prevent cell auto-resizing return 74 } func numberOfSections(in tableView: UITableView) -> Int { return numberOfSections } func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { if (TLPreferences.enabledAdvancedMode()) { if (section == accountListSection) { return TLDisplayStrings.ACCOUNTS_STRING() } else if (section == coldWalletAccountSection) { return TLDisplayStrings.COLD_WALLET_ACCOUNTS_STRING() } else if (section == importedAccountSection) { return TLDisplayStrings.IMPORTED_ACCOUNTS_STRING() } else if (section == importedWatchAccountSection) { return TLDisplayStrings.IMPORTED_WATCH_ACCOUNTS_STRING() } else if (section == importedAddressSection) { return TLDisplayStrings.IMPORTED_ADDRESSES_STRING() } else if (section == importedWatchAddressSection) { return TLDisplayStrings.IMPORTED_WATCH_ADDRESSES_STRING() } else if (section == archivedAccountSection) { return TLDisplayStrings.ARCHIVED_ACCOUNTS_STRING() } else if (section == archivedColdWalletAccountSection) { return TLDisplayStrings.ARCHIVED_COLD_WALLET_ACCOUNTS_STRING() } else if (section == archivedImportedAccountSection) { return TLDisplayStrings.ARCHIVED_IMPORTED_ACCOUNTS_STRING() } else if (section == archivedImportedWatchAccountSection) { return TLDisplayStrings.ARCHIVED_IMPORTED_WATCH_ACCOUNTS_STRING() } else if (section == archivedImportedAddressSection) { return TLDisplayStrings.ARCHIVED_IMPORTED_ADDRESSES_STRING() } else if (section == archivedImportedWatchAddressSection) { return TLDisplayStrings.ARCHIVED_IMPORTED_WATCH_ADDRESSES_STRING() } else { return TLDisplayStrings.ACCOUNT_ACTIONS_STRING() } } else { if (section == accountListSection) { return TLDisplayStrings.ACCOUNTS_STRING() } else if (section == coldWalletAccountSection) { return TLDisplayStrings.COLD_WALLET_ACCOUNTS_STRING() } else if (section == archivedAccountSection) { return TLDisplayStrings.ARCHIVED_ACCOUNTS_STRING() } else if (section == archivedColdWalletAccountSection) { return TLDisplayStrings.ARCHIVED_COLD_WALLET_ACCOUNTS_STRING() } else { return TLDisplayStrings.ACCOUNT_ACTIONS_STRING() } } } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if (TLPreferences.enabledAdvancedMode()) { if (section == accountListSection) { return AppDelegate.instance().accounts!.getNumberOfAccounts() } else if (section == coldWalletAccountSection) { return AppDelegate.instance().coldWalletAccounts!.getNumberOfAccounts() } else if (section == importedAccountSection) { return AppDelegate.instance().importedAccounts!.getNumberOfAccounts() } else if (section == importedWatchAccountSection) { return AppDelegate.instance().importedWatchAccounts!.getNumberOfAccounts() } else if (section == importedAddressSection) { return AppDelegate.instance().importedAddresses!.getCount() } else if (section == importedWatchAddressSection) { return AppDelegate.instance().importedWatchAddresses!.getCount() } else if (section == archivedAccountSection) { return AppDelegate.instance().accounts!.getNumberOfArchivedAccounts() } else if (section == archivedColdWalletAccountSection) { return AppDelegate.instance().coldWalletAccounts!.getNumberOfArchivedAccounts() } else if (section == archivedImportedAccountSection) { return AppDelegate.instance().importedAccounts!.getNumberOfArchivedAccounts() } else if (section == archivedImportedWatchAccountSection) { return AppDelegate.instance().importedWatchAccounts!.getNumberOfArchivedAccounts() } else if (section == archivedImportedAddressSection) { return AppDelegate.instance().importedAddresses!.getArchivedCount() } else if (section == archivedImportedWatchAddressSection) { return AppDelegate.instance().importedWatchAddresses!.getArchivedCount() } else { return accountActionsArray!.count } } else if (section == accountListSection) { return AppDelegate.instance().accounts!.getNumberOfAccounts() } else if (section == coldWalletAccountSection) { return AppDelegate.instance().coldWalletAccounts!.getNumberOfAccounts() } else if (section == archivedAccountSection) { return AppDelegate.instance().accounts!.getNumberOfArchivedAccounts() } else if (section == archivedColdWalletAccountSection) { return AppDelegate.instance().coldWalletAccounts!.getNumberOfArchivedAccounts() } else { return accountActionsArray!.count } } fileprivate func setUpCellAccountActions(_ cell: UITableViewCell, cellForRowAtIndexPath indexPath: IndexPath) -> () { cell.accessoryType = UITableViewCellAccessoryType.none cell.textLabel!.text = accountActionsArray!.object(at: (indexPath as NSIndexPath).row) as? String if(cell.accessoryView != nil) { (cell.accessoryView as! UIActivityIndicatorView).isHidden = true } } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { if ((indexPath as NSIndexPath).section == accountActionSection) { let MyIdentifier = "AccountActionCellIdentifier" var cell = tableView.dequeueReusableCell(withIdentifier: MyIdentifier) if (cell == nil) { cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: MyIdentifier) } cell!.textLabel!.textAlignment = .center cell!.textLabel!.font = UIFont.boldSystemFont(ofSize: cell!.textLabel!.font.pointSize) self.setUpCellAccountActions(cell!, cellForRowAtIndexPath: indexPath) if ((indexPath as NSIndexPath).row % 2 == 0) { cell!.backgroundColor = TLColors.evenTableViewCellColor() } else { cell!.backgroundColor = TLColors.oddTableViewCellColor() } return cell! } else { let MyIdentifier = "AccountCellIdentifier" var cell = tableView.dequeueReusableCell(withIdentifier: MyIdentifier) as? TLAccountTableViewCell if (cell == nil) { cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: MyIdentifier) as? TLAccountTableViewCell } cell!.accountNameLabel!.textAlignment = .natural let activityView = UIActivityIndicatorView(activityIndicatorStyle: .gray) cell!.accessoryView = activityView if (TLPreferences.enabledAdvancedMode()) { if ((indexPath as NSIndexPath).section == accountListSection) { let accountObject = AppDelegate.instance().accounts!.getAccountObjectForIdx((indexPath as NSIndexPath).row) self.setUpCellAccounts(accountObject, cell: cell!, cellForRowAtIndexPath: indexPath) } else if ((indexPath as NSIndexPath).section == coldWalletAccountSection) { let accountObject = AppDelegate.instance().coldWalletAccounts!.getAccountObjectForIdx((indexPath as NSIndexPath).row) self.setUpCellAccounts(accountObject, cell: cell!, cellForRowAtIndexPath: indexPath) } else if ((indexPath as NSIndexPath).section == importedAccountSection) { let accountObject = AppDelegate.instance().importedAccounts!.getAccountObjectForIdx((indexPath as NSIndexPath).row) self.setUpCellAccounts(accountObject, cell: cell!, cellForRowAtIndexPath: indexPath) } else if ((indexPath as NSIndexPath).section == importedWatchAccountSection) { let accountObject = AppDelegate.instance().importedWatchAccounts!.getAccountObjectForIdx((indexPath as NSIndexPath).row) self.setUpCellAccounts(accountObject, cell: cell!, cellForRowAtIndexPath: indexPath) } else if ((indexPath as NSIndexPath).section == importedAddressSection) { let importedAddressObject = AppDelegate.instance().importedAddresses!.getAddressObjectAtIdx((indexPath as NSIndexPath).row) self.setUpCellImportedAddresses(importedAddressObject, cell: cell!, cellForRowAtIndexPath: indexPath) } else if ((indexPath as NSIndexPath).section == importedWatchAddressSection) { let importedAddressObject = AppDelegate.instance().importedWatchAddresses!.getAddressObjectAtIdx((indexPath as NSIndexPath).row) self.setUpCellImportedAddresses(importedAddressObject, cell: cell!, cellForRowAtIndexPath: indexPath) } else if ((indexPath as NSIndexPath).section == archivedAccountSection) { let accountObject = AppDelegate.instance().accounts!.getArchivedAccountObjectForIdx((indexPath as NSIndexPath).row) self.setUpCellArchivedAccounts(accountObject, cell: cell!, cellForRowAtIndexPath: indexPath) } else if ((indexPath as NSIndexPath).section == archivedColdWalletAccountSection) { let accountObject = AppDelegate.instance().coldWalletAccounts!.getArchivedAccountObjectForIdx((indexPath as NSIndexPath).row) self.setUpCellArchivedAccounts(accountObject, cell: cell!, cellForRowAtIndexPath: indexPath) } else if ((indexPath as NSIndexPath).section == archivedImportedAccountSection) { let accountObject = AppDelegate.instance().importedAccounts!.getArchivedAccountObjectForIdx((indexPath as NSIndexPath).row) self.setUpCellArchivedAccounts(accountObject, cell: cell!, cellForRowAtIndexPath: indexPath) } else if ((indexPath as NSIndexPath).section == archivedImportedWatchAccountSection) { let accountObject = AppDelegate.instance().importedWatchAccounts!.getArchivedAccountObjectForIdx((indexPath as NSIndexPath).row) self.setUpCellArchivedAccounts(accountObject, cell: cell!, cellForRowAtIndexPath: indexPath) } else if ((indexPath as NSIndexPath).section == archivedImportedAddressSection) { let importedAddressObject = AppDelegate.instance().importedAddresses!.getArchivedAddressObjectAtIdx((indexPath as NSIndexPath).row) self.setUpCellArchivedImportedAddresses(importedAddressObject, cell: cell!, cellForRowAtIndexPath: indexPath) } else if ((indexPath as NSIndexPath).section == archivedImportedWatchAddressSection) { let importedAddressObject = AppDelegate.instance().importedWatchAddresses!.getArchivedAddressObjectAtIdx((indexPath as NSIndexPath).row) self.setUpCellArchivedImportedAddresses(importedAddressObject, cell: cell!, cellForRowAtIndexPath: indexPath) } else { } } else { if ((indexPath as NSIndexPath).section == accountListSection) { let accountObject = AppDelegate.instance().accounts!.getAccountObjectForIdx((indexPath as NSIndexPath).row) self.setUpCellAccounts(accountObject, cell: cell!, cellForRowAtIndexPath: indexPath) } else if ((indexPath as NSIndexPath).section == coldWalletAccountSection) { let accountObject = AppDelegate.instance().coldWalletAccounts!.getAccountObjectForIdx((indexPath as NSIndexPath).row) self.setUpCellAccounts(accountObject, cell: cell!, cellForRowAtIndexPath: indexPath) } else if ((indexPath as NSIndexPath).section == archivedAccountSection) { let accountObject = AppDelegate.instance().accounts!.getArchivedAccountObjectForIdx((indexPath as NSIndexPath).row) self.setUpCellArchivedAccounts(accountObject, cell: cell!, cellForRowAtIndexPath: indexPath) } else if ((indexPath as NSIndexPath).section == archivedColdWalletAccountSection) { let accountObject = AppDelegate.instance().coldWalletAccounts!.getArchivedAccountObjectForIdx((indexPath as NSIndexPath).row) self.setUpCellArchivedAccounts(accountObject, cell: cell!, cellForRowAtIndexPath: indexPath) } else { } } if ((indexPath as NSIndexPath).row % 2 == 0) { cell!.backgroundColor = TLColors.evenTableViewCellColor() } else { cell!.backgroundColor = TLColors.oddTableViewCellColor() } return cell! } } func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? { if (TLPreferences.enabledAdvancedMode()) { if ((indexPath as NSIndexPath).section == accountListSection) { self.promptAccountsActionSheet((indexPath as NSIndexPath).row) return nil } else if ((indexPath as NSIndexPath).section == coldWalletAccountSection) { self.promptColdWalletAccountsActionSheet(indexPath) return nil } else if ((indexPath as NSIndexPath).section == importedAccountSection) { self.promptImportedAccountsActionSheet(indexPath) return nil } else if ((indexPath as NSIndexPath).section == importedWatchAccountSection) { self.promptImportedWatchAccountsActionSheet(indexPath) return nil } else if ((indexPath as NSIndexPath).section == importedAddressSection) { self.promptImportedAddressActionSheet((indexPath as NSIndexPath).row) return nil } else if ((indexPath as NSIndexPath).section == importedWatchAddressSection) { self.promptImportedWatchAddressActionSheet((indexPath as NSIndexPath).row) return nil } else if ((indexPath as NSIndexPath).section == archivedAccountSection) { self.promptArchivedAccountsActionSheet((indexPath as NSIndexPath).row) return nil } else if ((indexPath as NSIndexPath).section == archivedColdWalletAccountSection) { self.promptArchivedImportedAccountsActionSheet(indexPath, accountType: .coldWallet) return nil } else if ((indexPath as NSIndexPath).section == archivedImportedAccountSection) { self.promptArchivedImportedAccountsActionSheet(indexPath, accountType: .imported) return nil } else if ((indexPath as NSIndexPath).section == archivedImportedWatchAccountSection) { self.promptArchivedImportedAccountsActionSheet(indexPath, accountType: .importedWatch) return nil } else if ((indexPath as NSIndexPath).section == archivedImportedAddressSection) { self.promptArchivedImportedAddressActionSheet((indexPath as NSIndexPath).row) return nil } else if ((indexPath as NSIndexPath).section == archivedImportedWatchAddressSection) { self.promptArchivedImportedWatchAddressActionSheet((indexPath as NSIndexPath).row) return nil } else { self.doAccountAction((indexPath as NSIndexPath).row) return nil } } else { if ((indexPath as NSIndexPath).section == accountListSection) { self.promptAccountsActionSheet((indexPath as NSIndexPath).row) return nil } else if ((indexPath as NSIndexPath).section == coldWalletAccountSection) { self.promptColdWalletAccountsActionSheet(indexPath) return nil } else if ((indexPath as NSIndexPath).section == archivedAccountSection) { self.promptArchivedAccountsActionSheet((indexPath as NSIndexPath).row) return nil } else if ((indexPath as NSIndexPath).section == archivedColdWalletAccountSection) { self.promptArchivedImportedAccountsActionSheet(indexPath, accountType: .coldWallet) return nil } else { self.doAccountAction((indexPath as NSIndexPath).row) return nil } } } func customIOS7dialogButtonTouchUp(inside alertView: CustomIOS7AlertView, clickedButtonAt buttonIndex: Int) -> () { if (buttonIndex == 0) { iToast.makeText(TLDisplayStrings.COPIED_TO_CLIPBOARD_STRING()).setGravity(iToastGravityCenter).setDuration(1000).show() let pasteboard = UIPasteboard.general pasteboard.string = self.QRImageModal!.QRcodeDisplayData } else { } alertView.close() } deinit { NotificationCenter.default.removeObserver(self) } } ================================================ FILE: ArcBit/viewControllers/TLMenuViewController.swift ================================================ // // TLMenuViewController.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit @objc(TLMenuViewController) class TLMenuViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } fileprivate var menuItems:NSArray? @IBOutlet fileprivate var menuTopView:UIView? @IBOutlet fileprivate var tableView:UITableView? // This is used so that status bar text color is set to white override var preferredStatusBarStyle : (UIStatusBarStyle) { return UIStatusBarStyle.lightContent } override func viewDidLoad() { super.viewDidLoad() setColors() self.menuTopView!.backgroundColor = TLColors.mainAppColor() self.tableView!.backgroundColor = TLColors.mainAppColor() self.tableView!.separatorInset = UIEdgeInsets.zero } override func viewDidAppear(_ animated:Bool) -> () { super.viewDidAppear(animated) UIApplication.shared.setStatusBarHidden(true, with:UIStatusBarAnimation.none) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_HAMBURGER_MENU_OPENED()), object:nil) } override func viewWillAppear(_ animated:Bool) { super.viewWillAppear(animated) if TLPreferences.enabledColdWallet() { menuItems = [TLDisplayStrings.SEND_STRING(), TLDisplayStrings.RECEIVE_STRING(), TLDisplayStrings.HISTORY_STRING(), TLDisplayStrings.ACCOUNTS_STRING(), TLDisplayStrings.COLD_WALLET_STRING(), TLDisplayStrings.HELP_STRING(), TLDisplayStrings.MORE_STRING(), TLDisplayStrings.SETTINGS_STRING()] } else { menuItems = [TLDisplayStrings.SEND_STRING(), TLDisplayStrings.RECEIVE_STRING(), TLDisplayStrings.HISTORY_STRING(), TLDisplayStrings.ACCOUNTS_STRING(), TLDisplayStrings.HELP_STRING(), TLDisplayStrings.MORE_STRING(), TLDisplayStrings.SETTINGS_STRING()] } self.tableView!.reloadData() } override func viewWillDisappear(_ animated:Bool) { super.viewWillDisappear(animated) UIApplication.shared.setStatusBarHidden(false, with:UIStatusBarAnimation.none) self.view.endEditing(true) } func tableView(_ tableView: UITableView, numberOfRowsInSection section:Int) -> Int { return menuItems!.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath:IndexPath) -> UITableViewCell{ let MyIdentifier = "MenuCell" var cell = tableView.dequeueReusableCell(withIdentifier: MyIdentifier) if (cell == nil) { cell = UITableViewCell(style:UITableViewCellStyle.default, reuseIdentifier:MyIdentifier) } cell!.selectionStyle = UITableViewCellSelectionStyle.none let menuItem = menuItems![(indexPath as NSIndexPath).row] as! String cell!.textLabel!.text = menuItem cell!.backgroundColor = UIColor.clear var imageName = "" var name = "" if TLPreferences.enabledColdWallet() { switch ((indexPath as NSIndexPath).row) { case 0: imageName = TLWalletUtils.SEND_ICON_IMAGE_NAME() name = TLDisplayStrings.SEND_STRING() break case 1: imageName = TLWalletUtils.RECEIVE_ICON_IMAGE_NAME() name = TLDisplayStrings.RECEIVE_STRING() break case 2: imageName = TLWalletUtils.HISTORY_ICON_IMAGE_NAME() name = TLDisplayStrings.HISTORY_STRING() break case 3: imageName = TLWalletUtils.ACCOUNT_ICON_IMAGE_NAME() name = TLDisplayStrings.ACCOUNTS_STRING() break case 4: imageName = TLWalletUtils.VAULT_ICON_IMAGE_NAME() name = TLDisplayStrings.COLD_WALLET_STRING() break case 5: imageName = TLWalletUtils.HELP_ICON_IMAGE_NAME() name = TLDisplayStrings.HELP_STRING() break case 6: imageName = TLWalletUtils.LINK_ICON_IMAGE_NAME() name = TLDisplayStrings.MORE_STRING() break default: imageName = TLWalletUtils.SETTINGS_ICON_IMAGE_NAME() name = TLDisplayStrings.SETTINGS_STRING() break } } else { switch ((indexPath as NSIndexPath).row) { case 0: imageName = TLWalletUtils.SEND_ICON_IMAGE_NAME() name = TLDisplayStrings.SEND_STRING() break case 1: imageName = TLWalletUtils.RECEIVE_ICON_IMAGE_NAME() name = TLDisplayStrings.RECEIVE_STRING() break case 2: imageName = TLWalletUtils.HISTORY_ICON_IMAGE_NAME() name = TLDisplayStrings.HISTORY_STRING() break case 3: imageName = TLWalletUtils.ACCOUNT_ICON_IMAGE_NAME() name = TLDisplayStrings.ACCOUNTS_STRING() break case 4: imageName = TLWalletUtils.HELP_ICON_IMAGE_NAME() name = TLDisplayStrings.HELP_STRING() break case 5: imageName = TLWalletUtils.LINK_ICON_IMAGE_NAME() name = TLDisplayStrings.MORE_STRING() break default: imageName = TLWalletUtils.SETTINGS_ICON_IMAGE_NAME() name = TLDisplayStrings.SETTINGS_STRING() break } } cell!.textLabel!.text = name cell!.textLabel!.textColor = TLColors.mainAppOppositeColor() cell!.imageView!.image = UIImage(named: imageName)!.withRenderingMode(UIImageRenderingMode.alwaysTemplate) cell!.imageView!.tintColor = TLColors.mainAppOppositeColor() return cell! } func tableView(_ tableView:UITableView, heightForHeaderInSection section:Int) -> CGFloat { return 30.0 } func tableView(_ tableView:UITableView, didSelectRowAt indexPath:IndexPath) -> () { if TLPreferences.enabledColdWallet() { if ((indexPath as NSIndexPath).row == 0) { self.slidingViewController().topViewController = self.storyboard!.instantiateViewController(withIdentifier: "SendNav") } else if ((indexPath as NSIndexPath).row == 1) { self.slidingViewController().topViewController = self.storyboard!.instantiateViewController(withIdentifier: "ReceiveNav") } else if ((indexPath as NSIndexPath).row == 2) { self.slidingViewController().topViewController = self.storyboard!.instantiateViewController(withIdentifier: "HistoryNav") } else if ((indexPath as NSIndexPath).row == 3) { self.slidingViewController().topViewController = self.storyboard!.instantiateViewController(withIdentifier: "ManageAccountNav") } else if ((indexPath as NSIndexPath).row == 4) { self.slidingViewController().topViewController = self.storyboard!.instantiateViewController(withIdentifier: "ColdWalletNav") } else if ((indexPath as NSIndexPath).row == 5) { self.slidingViewController().topViewController = self.storyboard!.instantiateViewController(withIdentifier: "HelpNav") } else if ((indexPath as NSIndexPath).row == 6) { self.slidingViewController().topViewController = self.storyboard!.instantiateViewController(withIdentifier: "LinksNav") } else { self.slidingViewController().topViewController = self.storyboard!.instantiateViewController(withIdentifier: "SettingsNav") } } else { if ((indexPath as NSIndexPath).row == 0) { self.slidingViewController().topViewController = self.storyboard!.instantiateViewController(withIdentifier: "SendNav") } else if ((indexPath as NSIndexPath).row == 1) { self.slidingViewController().topViewController = self.storyboard!.instantiateViewController(withIdentifier: "ReceiveNav") } else if ((indexPath as NSIndexPath).row == 2) { self.slidingViewController().topViewController = self.storyboard!.instantiateViewController(withIdentifier: "HistoryNav") } else if ((indexPath as NSIndexPath).row == 3) { self.slidingViewController().topViewController = self.storyboard!.instantiateViewController(withIdentifier: "ManageAccountNav") } else if ((indexPath as NSIndexPath).row == 4) { self.slidingViewController().topViewController = self.storyboard!.instantiateViewController(withIdentifier: "HelpNav") } else if ((indexPath as NSIndexPath).row == 5) { self.slidingViewController().topViewController = self.storyboard!.instantiateViewController(withIdentifier: "LinksNav") } else { self.slidingViewController().topViewController = self.storyboard!.instantiateViewController(withIdentifier: "SettingsNav") } } self.slidingViewController().resetTopView(animated: true) } } ================================================ FILE: ArcBit/viewControllers/TLPassPhraseViewController.swift ================================================ // // TLPassPhraseViewController.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit @objc(TLPassPhraseViewController) class TLPassPhraseViewController: UIViewController { required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } @IBOutlet fileprivate var navigationBar:UINavigationBar? @IBOutlet weak var walletBackupPassphraseLabel: UILabel! @IBOutlet fileprivate var backupPassphraseExplanation:UILabel? @IBOutlet fileprivate var passPhraseTextView:UITextView? @IBOutlet fileprivate var masterSeedHexTitleLabel:UILabel? @IBOutlet fileprivate var masterSeedHexTitleExplanation:UILabel? @IBOutlet fileprivate var masterSeedHexTextView:UITextView? override var preferredStatusBarStyle : (UIStatusBarStyle) { return UIStatusBarStyle.lightContent } override func viewDidLoad() { super.viewDidLoad() setNavigationBarColors(self.navigationBar!) self.navigationBar?.topItem?.title = TLDisplayStrings.PASSPHRASE_STRING() self.walletBackupPassphraseLabel?.text = TLDisplayStrings.WALLET_BACKUP_PASSPHRASE_STRING() self.passPhraseTextView!.isSelectable = false self.masterSeedHexTextView!.isSelectable = false let passPhraseTextViewGestureRecognizer = UITapGestureRecognizer(target: self, action:#selector(TLPassPhraseViewController.passPhraseTextViewTapped(_:))) self.passPhraseTextView!.addGestureRecognizer(passPhraseTextViewGestureRecognizer) let masterSeedHexGestureRecognizer = UITapGestureRecognizer(target:self, action:#selector(TLPassPhraseViewController.masterSeedHexTextViewTapped(_:))) self.masterSeedHexTextView!.addGestureRecognizer(masterSeedHexGestureRecognizer) self.passPhraseTextView!.backgroundColor = TLColors.mainAppColor() self.passPhraseTextView!.textColor = (TLColors.mainAppOppositeColor()) self.masterSeedHexTextView!.backgroundColor = TLColors.mainAppColor() self.masterSeedHexTextView!.textColor = TLColors.mainAppOppositeColor() let passphrase = TLWalletPassphrase.getDecryptedWalletPassphrase() self.passPhraseTextView!.text = (passphrase) // if (!TLPreferences.enabledAdvancedMode()) { self.backupPassphraseExplanation!.text = TLDisplayStrings.BACKUP_PASSPHRASE_EXPLANATION_STRING() self.masterSeedHexTitleLabel!.isHidden = true self.masterSeedHexTitleExplanation!.isHidden = true self.masterSeedHexTextView!.isHidden = true self.masterSeedHexTextView!.text = ("") // } else { // self.backupPassphraseExplanation!.text = TLDisplayStrings.BACKUP_PASSPHRASE_ADVANCED_EXPLANATION_STRING() // self.masterSeedHexTitleLabel!.isHidden = false // self.masterSeedHexTitleExplanation!.isHidden = false // self.masterSeedHexTextView!.isHidden = false // let masterHex = TLHDWalletWrapper.getMasterHex(passphrase ?? "") // self.masterSeedHexTextView!.text = (masterHex) // } if (!TLPreferences.hasShownBackupPassphrase()) { TLPreferences.setHasShownBackupPassphrase(true) } } func passPhraseTextViewTapped(_ sender:AnyObject) { } func masterSeedHexTextViewTapped(_ sender:AnyObject) { } override func viewDidAppear(_ animated:Bool) -> () { NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_BACKUP_PASSPHRASE()), object:nil, userInfo:nil) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } @IBAction fileprivate func cancel(_ sender:AnyObject) { self.passPhraseTextView!.resignFirstResponder() self.dismiss(animated: true, completion:nil) } } ================================================ FILE: ArcBit/viewControllers/TLPreloadViewController.swift ================================================ // // TLPreloadViewController.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit @objc(TLPreloadViewController) class TLPreloadViewController: UIViewController, UIAlertViewDelegate { required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } @IBOutlet fileprivate var walletLoadingActivityIndicatorView: UIActivityIndicatorView? @IBOutlet fileprivate var backgroundView: UIView? @IBOutlet weak var backgroundImageView: UIImageView! override func viewDidLoad() { super.viewDidLoad() self.walletLoadingActivityIndicatorView!.isHidden = true self.walletLoadingActivityIndicatorView!.color = UIColor.gray self.navigationController!.navigationBar.isHidden = true UIApplication.shared.setStatusBarHidden(true, with: UIStatusBarAnimation.none) let passphrase = TLWalletPassphrase.getDecryptedWalletPassphrase() if (TLPreferences.canRestoreDeletedApp() && !TLPreferences.hasSetupHDWallet() && passphrase != nil) { // is fresh app but not first time installing UIAlertController.showAlert(in: self, withTitle: TLDisplayStrings.BACKUP_PASSPHRASE_FOUND_IN_KEYCHAIN_STRING(), message: TLDisplayStrings.BACKUP_PASSPHRASE_FOUND_IN_KEYCHAIN_DESC_STRING(), cancelButtonTitle: TLDisplayStrings.RESTORE_STRING(), destructiveButtonTitle: nil, otherButtonTitles: [TLDisplayStrings.START_FRESH_STRING()], tap: {(alertView, action, buttonIndex) in if (buttonIndex == alertView?.firstOtherButtonIndex) { self.initializeWalletAppAndShowInitialScreenAndGoToMainScreen(nil) } else if (buttonIndex == alertView?.cancelButtonIndex) { TLHUDWrapper.showHUDAddedTo(self.view, labelText: TLDisplayStrings.RESTORING_WALLET_STRING(), animated: true) AppDelegate.instance().saveWalletJSONEnabled = false DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.high).async { AppDelegate.instance().initializeWalletAppAndShowInitialScreen(true, walletPayload: nil) AppDelegate.instance().refreshHDWalletAccounts(true) DispatchQueue.main.async { AppDelegate.instance().saveWalletJSONEnabled = true AppDelegate.instance().saveWalletJsonCloud() TLTransactionListener.instance().reconnect() TLStealthWebSocket.instance().reconnect() TLHUDWrapper.hideHUDForView(self.view, animated: true) self.slidingViewController()!.topViewController = self.storyboard!.instantiateViewController(withIdentifier: "SendNav") } } } }) } else { self.checkToLoadFromLocal() } } fileprivate func checkToLoadFromLocal() -> () { if (TLWalletJson.getDecryptedEncryptedWalletJSONPassphrase() != nil) { let localWalletPayload = AppDelegate.instance().getLocalWalletJsonDict() self.initializeWalletAppAndShowInitialScreenAndGoToMainScreen(localWalletPayload) } else { self.initializeWalletAppAndShowInitialScreenAndGoToMainScreen(nil) } } fileprivate func initializeWalletAppAndShowInitialScreenAndGoToMainScreen(_ walletPayload: NSDictionary?) -> () { AppDelegate.instance().initializeWalletAppAndShowInitialScreen(false, walletPayload: walletPayload) TLTransactionListener.instance().reconnect() TLStealthWebSocket.instance().reconnect() if self.slidingViewController() != nil { self.slidingViewController().topViewController = self.storyboard!.instantiateViewController(withIdentifier: "SendNav") } else { //is running unit test } } override func viewWillDisappear(_ animated: Bool) -> () { UIApplication.shared.setStatusBarHidden(false, with: UIStatusBarAnimation.none) } } ================================================ FILE: ArcBit/viewControllers/TLQRCodeScannerViewController.swift ================================================ // // TLQRCodeScannerViewController.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit import AVFoundation @objc(TLQRCodeScannerViewController) class TLQRCodeScannerViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate { fileprivate let n = 66 fileprivate let DEFAULT_HEADER_HEIGHT = 66 required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } fileprivate var success: ((String?) -> ())? fileprivate var error: ((String?) -> ())? fileprivate var captureSession: AVCaptureSession? fileprivate var videoPreviewLayer: AVCaptureVideoPreviewLayer? fileprivate var isReadingQRCode: Bool = false override var preferredStatusBarStyle : (UIStatusBarStyle) { return UIStatusBarStyle.lightContent } init(success __success: ((String?) -> ())?, error __error: ((String?) -> ())?) { super.init(nibName: nil, bundle: nil) self.modalTransitionStyle = UIModalTransitionStyle.crossDissolve self.success = __success self.error = __error } override func viewDidLoad() { super.viewDidLoad() let app = AppDelegate.instance() self.view.frame = CGRect(x: 0, y: 0, width: app.window!.frame.size.width, height: app.window!.frame.size.height - CGFloat(DEFAULT_HEADER_HEIGHT)) let topBarView = UIView(frame: CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: CGFloat(DEFAULT_HEADER_HEIGHT))) topBarView.backgroundColor = TLColors.mainAppColor() self.view.addSubview(topBarView) let logo = UIImageView(image: UIImage(named: "top_menu_logo.png")) logo.frame = CGRect(x: 88, y: 22, width: 143, height: 40) topBarView.addSubview(logo) let closeButton = UIButton(frame: CGRect(x: self.view.frame.size.width - 70, y: 15, width: 80, height: 51)) closeButton.setTitle(TLDisplayStrings.CLOSE_STRING(), for: UIControlState()) closeButton.setTitleColor(UIColor(white:0.56, alpha: 1.0), for: UIControlState.highlighted) closeButton.titleLabel!.font = UIFont.systemFont(ofSize: 15) closeButton.addTarget(self, action: #selector(TLQRCodeScannerViewController.closeButtonClicked(_:)), for: .touchUpInside) topBarView.addSubview(closeButton) self.startReadingQRCode() } func closeButtonClicked(_ sender: UIButton) -> () { self.stopReadingQRCode() } func startReadingQRCode() -> () { var error: NSError? let captureDevice = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo) var input: AnyObject! do { input = try AVCaptureDeviceInput(device: captureDevice) as AVCaptureDeviceInput } catch let error1 as NSError { error = error1 input = nil } if (input == nil) { // This should not happen - all devices we support have cameras DLog("QR code scanner problem: \(error!.localizedDescription)") return } captureSession = AVCaptureSession() captureSession!.addInput(input as! AVCaptureInput!) let captureMetadataOutput = AVCaptureMetadataOutput() captureSession!.addOutput(captureMetadataOutput) let dispatchQueue = DispatchQueue(label: "myQueue", attributes: []) captureMetadataOutput.setMetadataObjectsDelegate(self, queue: dispatchQueue) captureMetadataOutput.metadataObjectTypes = [AVMetadataObjectTypeQRCode] videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession) videoPreviewLayer?.videoGravity = (AVLayerVideoGravityResizeAspectFill) let app = AppDelegate.instance() let frame = CGRect(x: 0, y: CGFloat(DEFAULT_HEADER_HEIGHT), width: app.window!.frame.size.width, height: app.window!.frame.size.height - CGFloat(DEFAULT_HEADER_HEIGHT)) videoPreviewLayer!.frame = frame self.view.layer.addSublayer(videoPreviewLayer!) captureSession!.startRunning() } func stopReadingQRCode() -> () { if(captureSession != nil) { captureSession!.stopRunning() captureSession = nil } if(videoPreviewLayer != nil) { videoPreviewLayer!.removeFromSuperlayer() } self.dismiss(animated: true, completion: nil) if (self.error != nil) { self.error!(nil) } } func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!) -> () { if (metadataObjects != nil && metadataObjects!.count > 0) { let metadataObj: AnyObject = metadataObjects![0] as AnyObject if (metadataObj.type == AVMetadataObjectTypeQRCode) { // do something useful with results DispatchQueue.main.sync { let data:String = metadataObj.stringValue if (self.success != nil) { self.success!(data) self.stopReadingQRCode() } } } } } } ================================================ FILE: ArcBit/viewControllers/TLReceiveViewController.swift ================================================ // // TLReceiveViewController.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import UIKit @objc(TLReceiveViewController) class TLReceiveViewController: UIViewController, UIScrollViewDelegate, UITabBarDelegate { @IBOutlet fileprivate var addressQRCodeImageView: UIImageView? @IBOutlet fileprivate var addressLabel: UILabel? @IBOutlet fileprivate var accountNameLabel: UILabel? @IBOutlet fileprivate var receiveAddressesScrollView: UIScrollView? @IBOutlet fileprivate var receiveAddressesPageControl: UIPageControl? @IBOutlet fileprivate var fromViewContainerButton: UIButton? @IBOutlet fileprivate var scrollContentView: UIView? @IBOutlet fileprivate var selectAccountImageView: UIImageView? @IBOutlet fileprivate var balanceActivityIndicatorView: UIActivityIndicatorView? @IBOutlet fileprivate var accountBalanceLabel: UILabel? @IBOutlet fileprivate var fromViewContainer: UIButton? @IBOutlet fileprivate var pageControlViewContainer: UIView? @IBOutlet fileprivate var receivingAddressPageControl: UIPageControl? @IBOutlet fileprivate var tabBar: UITabBar? @IBOutlet weak var receiveLabel: UILabel! fileprivate var pageControlBeingUsed = false fileprivate var receiveAddresses: NSMutableArray? let newAddressInfoText = TLDisplayStrings.NEW_ADDRESSES_WILL_BE_AUTOMATICALLY_GENERATED_DESC_STRING() let importedWatchAccountStealthAddressInfoText = TLDisplayStrings.IMPORTED_WATCH_ONLY_ACCOUNTS_REUSABLE_ADDRESS_INFO_DESC_STRING() let coldWalletAccountStealthAddressInfoText = TLDisplayStrings.IMPORTED_COLD_WALLET_ACCOUNTS_REUSABLE_ADDRESS_INFO_DESC_STRING() func setupLabels() { let sendTabBarItem = self.tabBar?.items?[0] sendTabBarItem?.title = TLDisplayStrings.SEND_STRING() let receiveTabBarItem = self.tabBar?.items?[1] receiveTabBarItem?.title = TLDisplayStrings.RECEIVE_STRING() } override func viewDidLoad() { super.viewDidLoad() setColors() self.setLogoImageView() self.setupLabels() self.receiveLabel.text = TLDisplayStrings.FROM_COLON_STRING() self.fromViewContainer!.backgroundColor = TLColors.mainAppColor() self.accountNameLabel!.textColor = TLColors.mainAppOppositeColor() self.accountBalanceLabel!.textColor = TLColors.mainAppOppositeColor() self.pageControlViewContainer!.backgroundColor = TLColors.mainAppColor() self.receivingAddressPageControl!.backgroundColor = TLColors.mainAppColor() self.receiveAddressesScrollView!.backgroundColor = TLColors.mainAppColor() self.balanceActivityIndicatorView!.color = TLColors.mainAppOppositeColor() self.navigationController!.setToolbarHidden(false, animated: false) self.navigationController!.isToolbarHidden = true self.tabBar!.selectedItem = ((self.tabBar!.items as NSArray!).object(at: 1)) as? UITabBarItem if UIScreen.main.bounds.size.height <= 480.0 { // is 3.5 inch screen self.tabBar!.isHidden = true } self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.refresh, target: self, action: #selector(TLReceiveViewController.refreshSelectedAccountAgain)) self.navigationController!.view.addGestureRecognizer(self.slidingViewController().panGesture) NotificationCenter.default.addObserver(self, selector: #selector(TLReceiveViewController.updateReceiveViewController(_:)), name: NSNotification.Name(rawValue: TLNotificationEvents.EVENT_ADVANCE_MODE_TOGGLED()), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(TLReceiveViewController.updateViewToNewSelectedObject), name: NSNotification.Name(rawValue: TLNotificationEvents.EVENT_UPDATED_RECEIVING_ADDRESSES()), object: nil) NotificationCenter.default.addObserver(self , selector: #selector(TLReceiveViewController.updateViewToNewSelectedObjectAndAlertNewText), name: NSNotification.Name(rawValue: TLNotificationEvents.EVENT_MODEL_UPDATED_NEW_UNCONFIRMED_TRANSACTION()), object: nil) NotificationCenter.default.addObserver(self , selector: #selector(TLReceiveViewController.updateViewToNewSelectedObject), name: NSNotification.Name(rawValue: TLNotificationEvents.EVENT_DISPLAY_LOCAL_CURRENCY_TOGGLED()), object: nil) NotificationCenter.default.addObserver(self , selector: #selector(TLReceiveViewController.updateViewToNewSelectedObject), name: NSNotification.Name(rawValue: TLNotificationEvents.EVENT_FETCHED_ADDRESSES_DATA()), object: nil) NotificationCenter.default.addObserver(self , selector: #selector(TLReceiveViewController.updateViewToNewSelectedObject), name: NSNotification.Name(rawValue: TLNotificationEvents.EVENT_DISPLAY_LOCAL_CURRENCY_TOGGLED()), object: nil) NotificationCenter.default.addObserver(self , selector: #selector(TLReceiveViewController.updateViewToNewSelectedObject), name: NSNotification.Name(rawValue: TLNotificationEvents.EVENT_EXCHANGE_RATE_UPDATED()), object: nil) NotificationCenter.default.addObserver(self , selector: #selector(TLReceiveViewController.checkToShowPassphraseViewController), name: NSNotification.Name(rawValue: TLNotificationEvents.EVENT_MODEL_UPDATED_NEW_UNCONFIRMED_TRANSACTION()), object: nil) self.receiveAddressesScrollView!.delegate = self let singleFingerTap = UITapGestureRecognizer(target: self, action: #selector(TLReceiveViewController.singleFingerTap)) self.receiveAddressesScrollView!.addGestureRecognizer(singleFingerTap) if (AppDelegate.instance().justSetupHDWallet) { AppDelegate.instance().justSetupHDWallet = false TLPrompts.promptSuccessMessage(TLDisplayStrings.WELCOME_EXCLAMATION_STRING(), message: TLDisplayStrings.WELCOME_DESC_STRING()) } self.refreshSelectedAccount(false) } override func viewWillAppear(_ animated: Bool) -> () { self.updateViewToNewSelectedObject() } override func viewDidAppear(_ animated: Bool) -> () { NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_VIEW_RECEIVE_SCREEN()), object: nil, userInfo: nil) if (TLSuggestions.instance().conditionToPromptToSuggestedBackUpWalletPassphraseSatisfied()) { TLSuggestions.instance().promptToSuggestBackUpWalletPassphrase(self) } else if let balance = AppDelegate.instance().receiveSelectedObject!.getBalanceForSelectedObject(), balance.greater(TLCoin.zero()) && !TLPreferences.hasShownBackupPassphrase() { self.showPromptThenPassphraseViewController() } } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } func refreshSelectedAccountAgain() { self.refreshSelectedAccount(true) } func checkToShowPassphraseViewController() { if !TLPreferences.hasReceivePaymentForFirstTime() && !TLPreferences.hasShownBackupPassphrase() { self.showPromptThenPassphraseViewController() } TLPreferences.setHasReceivePaymentForFirstTime(true) } func showPromptThenPassphraseViewController() { TLPrompts.promptWithOneButton(self, title: TLDisplayStrings.WALLET_BACKUP_PASSPHRASE_WILL_BE_SHOWN(), message: TLDisplayStrings.PLEASE_WRITE_DOWN_OR_MEMORIZE_YOUR_WALLET_BACKUP_PASSPHRASE(), buttonText: TLDisplayStrings.I_UNDERSTAND(), success: { let vc = self.storyboard!.instantiateViewController(withIdentifier: "Passphrase") self.slidingViewController().present(vc, animated: true, completion: nil) }) } fileprivate func refreshSelectedAccount(_ fetchDataAgain: Bool) { if (!AppDelegate.instance().receiveSelectedObject!.hasFetchedCurrentFromData() || fetchDataAgain) { if (AppDelegate.instance().receiveSelectedObject!.getSelectedObjectType() == .account) { let accountObject = AppDelegate.instance().receiveSelectedObject!.getSelectedObject() as! TLAccountObject self.balanceActivityIndicatorView!.isHidden = false self.accountBalanceLabel!.isHidden = true self.balanceActivityIndicatorView!.startAnimating() AppDelegate.instance().pendingOperations.addSetUpAccountOperation(accountObject, fetchDataAgain: fetchDataAgain, success: { self.accountBalanceLabel!.isHidden = false self.balanceActivityIndicatorView!.stopAnimating() self.balanceActivityIndicatorView!.isHidden = true if accountObject.downloadState != .downloaded { self.updateAccountBalance() } }) } else if (AppDelegate.instance().receiveSelectedObject!.getSelectedObjectType() == .address) { let importedAddress = AppDelegate.instance().receiveSelectedObject!.getSelectedObject() as! TLImportedAddress self.balanceActivityIndicatorView!.isHidden = false self.accountBalanceLabel!.isHidden = true self.balanceActivityIndicatorView!.startAnimating() AppDelegate.instance().pendingOperations.addSetUpImportedAddressOperation(importedAddress, fetchDataAgain: fetchDataAgain, success: { self.accountBalanceLabel!.isHidden = false self.balanceActivityIndicatorView!.stopAnimating() self.balanceActivityIndicatorView!.isHidden = true if importedAddress.downloadState != .downloaded { self.updateAccountBalance() } }) } } else { let balance = TLCurrencyFormat.getProperAmount(AppDelegate.instance().historySelectedObject!.getBalanceForSelectedObject()!) accountBalanceLabel!.text = balance as String self.balanceActivityIndicatorView!.isHidden = true } } override func showSendView() { self.slidingViewController().topViewController = self.storyboard!.instantiateViewController(withIdentifier: "SendNav") } func singleFingerTap() { if self.receiveAddresses == nil { // receiveAddresses not loaded yet return } let address = self.receiveAddresses!.object(at: self.receiveAddressesPageControl!.currentPage) as! String let pasteboard = UIPasteboard.general pasteboard.string = address iToast.makeText(TLDisplayStrings.COPIED_TO_CLIPBOARD_STRING()).setGravity(iToastGravityCenter).setDuration(1000).show() } @IBAction fileprivate func scrollViewClicked(_ sender: AnyObject) { if self.receiveAddresses == nil { // receiveAddresses not loaded yet return } let address = self.receiveAddresses!.object(at: self.receiveAddressesPageControl!.currentPage) as! String let pasteboard = UIPasteboard.general pasteboard.string = address iToast.makeText(TLDisplayStrings.COPIED_TO_CLIPBOARD_STRING()).setGravity(iToastGravityCenter).setDuration(1000).show() } fileprivate func getAddressInfoLabel(_ frame: CGRect, text: String) -> UILabel { let addressInfoLabel = UILabel(frame: frame) addressInfoLabel.textAlignment = .center addressInfoLabel.text = text addressInfoLabel.textColor = TLColors.mainAppOppositeColor() addressInfoLabel.numberOfLines = 0 return addressInfoLabel } fileprivate func getPageWidth() -> CGFloat { if (UIScreen.main.bounds.size.width > 414) { //is iPad return UIScreen.main.bounds.size.width - 16 } if (UIScreen.main.bounds.size.width == 414) { //is iPhone6+ return UIScreen.main.bounds.size.width - 16 - 8 } return UIScreen.main.bounds.size.width - 16 } fileprivate func getLastPageView(_ lastPageCount: Int, text: String) -> UIView { let pageWidth = self.getPageWidth() var frame = CGRect() frame.origin.x = CGFloat(pageWidth * CGFloat(lastPageCount)) frame.origin.y = 0 frame.size = self.receiveAddressesScrollView!.frame.size let pageView = UIView(frame: frame) let QRCodeImageWidth = pageWidth - 40 let xToBeInCenter = (pageWidth - QRCodeImageWidth) / 2 let imageViewFrame = CGRect(x: xToBeInCenter, y: 0, width: QRCodeImageWidth, height: QRCodeImageWidth) pageView.addSubview(self.getAddressInfoLabel(imageViewFrame, text: text)) pageView.backgroundColor = TLColors.mainAppColor() return pageView } fileprivate func updateReceiveAddressesView() { self.scrollContentView!.autoresizesSubviews = false var numPages = 0 if (self.receiveAddresses!.count != 1) { if (TLWalletUtils.ENABLE_STEALTH_ADDRESS()) { numPages = TLAccountObject.MAX_ACCOUNT_WAIT_TO_RECEIVE_ADDRESS() + TLAccountObject.NUM_ACCOUNT_STEALTH_ADDRESSES() + 1 } else { numPages = TLAccountObject.MAX_ACCOUNT_WAIT_TO_RECEIVE_ADDRESS() + 1 } } else { numPages = 1 } let pageWidth = self.getPageWidth() let pageHeight:CGFloat if UIScreen.main.bounds.size.height > 480.0 { pageHeight = pageWidth } else { // is 3.5 inch screen pageHeight = pageWidth - 100 } var pageCount = 0 for i in stride(from: 0, to: self.receiveAddresses!.count, by: 1) { pageCount += 1 var frame = CGRect() frame.origin.x = pageWidth * CGFloat(i) frame.origin.y = 0 frame.size = self.receiveAddressesScrollView!.frame.size let pageView = UIView(frame: frame) let QRCodeImageWidth:CGFloat if UIScreen.main.bounds.size.height > 480.0 { QRCodeImageWidth = pageWidth - 30 } else { // is 3.5 inch screen QRCodeImageWidth = pageWidth - 110 } let xToBeInCenter = (pageWidth - QRCodeImageWidth) / 2.0 let imageViewFrame = CGRect(x: xToBeInCenter, y: 0, width: QRCodeImageWidth, height: QRCodeImageWidth) if (i < self.receiveAddresses!.count - 1 || AppDelegate.instance().receiveSelectedObject!.getSelectedObjectType() == .address) { let address = self.receiveAddresses!.object(at: i) as! String let QRCodeImage = getQRCodeImage(address, size: QRCodeImageWidth - 5) let QRCodeImageView = UIImageView(frame: imageViewFrame) QRCodeImageView.image = QRCodeImage pageView.addSubview(QRCodeImageView) let addressLabelY: CGFloat let infoLabelY: CGFloat if (UIScreen.main.bounds.size.width <= 320) { //is <= iPhone5s addressLabelY = QRCodeImageWidth + 5 infoLabelY = QRCodeImageWidth - 15 } else { addressLabelY = QRCodeImageWidth + 21 infoLabelY = QRCodeImageWidth } let addressLabelFrame = CGRect(x: xToBeInCenter, y: addressLabelY, width: QRCodeImageWidth, height: 21) let labelEdgeInsets = UIEdgeInsetsMake(0, 5, 0, 5) let addressLabel = UILabel(frame: UIEdgeInsetsInsetRect(addressLabelFrame, labelEdgeInsets)) addressLabel.textColor = TLColors.mainAppOppositeColor() addressLabel.adjustsFontSizeToFitWidth = true addressLabel.textAlignment = .center addressLabel.font = UIFont.boldSystemFont(ofSize: addressLabel.font.pointSize) addressLabel.text = address if address.characters.count > 35 { // is stealth address addressLabel.numberOfLines = 2 } else { addressLabel.numberOfLines = 1 } pageView.addSubview(addressLabel) if (TLStealthAddress.isStealthAddress(address, isTestnet: AppDelegate.instance().appWallet.walletConfig.isTestnet)) { let infoLabelFrame = CGRect(x: xToBeInCenter, y: infoLabelY, width: QRCodeImageWidth, height: 21) let infoLabel = UILabel(frame: UIEdgeInsetsInsetRect(infoLabelFrame, labelEdgeInsets)) infoLabel.textColor = TLColors.mainAppOppositeColor() infoLabel.font = UIFont.boldSystemFont(ofSize: addressLabel.font.pointSize - 5) infoLabel.text = TLDisplayStrings.REUSABLE_ADDRESS_COLON_STRING() pageView.addSubview(infoLabel) //QRCodeImageView.backgroundColor = UIColor.orangeColor() } } else { if AppDelegate.instance().receiveSelectedObject!.getAccountType() == .coldWallet { pageView.addSubview(self.getAddressInfoLabel(imageViewFrame, text: coldWalletAccountStealthAddressInfoText)) } else if AppDelegate.instance().receiveSelectedObject!.getAccountType() == .importedWatch { pageView.addSubview(self.getAddressInfoLabel(imageViewFrame, text: importedWatchAccountStealthAddressInfoText)) } else { pageView.addSubview(self.getAddressInfoLabel(imageViewFrame, text: newAddressInfoText)) } } pageView.backgroundColor = TLColors.mainAppColor() //if (i % 2 == 0) {pageView.backgroundColor = UIColor.yellowColor()} self.scrollContentView!.addSubview(pageView) } while (pageCount < numPages) { self.scrollContentView!.addSubview(self.getLastPageView(pageCount, text: newAddressInfoText)) pageCount += 1 } self.receiveAddressesScrollView!.contentSize = CGSize(width: pageWidth * CGFloat(numPages), height: CGFloat(pageHeight)) self.receiveAddressesPageControl!.currentPage = 0 self.receiveAddressesPageControl!.numberOfPages = numPages if (self.receiveAddressesPageControl!.numberOfPages > 1) { self.receiveAddressesPageControl!.isHidden = false self.pageControlViewContainer!.isHidden = false } else { self.receiveAddressesPageControl!.isHidden = true self.pageControlViewContainer!.isHidden = true } } fileprivate func updateReceiveAddressArray() { let receivingAddressesCount = AppDelegate.instance().receiveSelectedObject!.getReceivingAddressesCount() self.receiveAddresses = NSMutableArray(capacity: Int(receivingAddressesCount)) for i in stride(from: 0, to: Int(receivingAddressesCount), by: 1) { let address = AppDelegate.instance().receiveSelectedObject!.getReceivingAddressForSelectedObject(i) self.receiveAddresses!.add(address!) } if (TLWalletUtils.ENABLE_STEALTH_ADDRESS()) { if (AppDelegate.instance().receiveSelectedObject!.getSelectedObjectType() == .account) { if let stealthAddress = AppDelegate.instance().receiveSelectedObject!.getStealthAddress() { if (TLPreferences.enabledStealthAddressDefault()) { self.receiveAddresses!.insert(stealthAddress, at: 0) } else { self.receiveAddresses!.add(stealthAddress) } } } } if (AppDelegate.instance().receiveSelectedObject!.getSelectedObjectType() == .account) { self.receiveAddresses!.add("End") } } func updateViewToNewSelectedObjectAndAlertNewText() { updateViewToNewSelectedObject() } func updateViewToNewSelectedObject() { DispatchQueue.main.async { self.updateAccountBalance() let receivingAddressesCount = AppDelegate.instance().receiveSelectedObject!.getReceivingAddressesCount() if (receivingAddressesCount == 0) { // this happens if receiving addresses have not been computed yet (cuz it requires look ups), thus don't update UI yet // EVENT_UPDATED_RECEIVING_ADDRESSES will fire and this method be called return } let label = AppDelegate.instance().receiveSelectedObject!.getLabelForSelectedObject() self.accountNameLabel!.text = label self.updateReceiveAddressArray() self.updateReceiveAddressesView() self.scrollToPage(0) } } fileprivate func updateAccountBalance() { let balance = AppDelegate.instance().receiveSelectedObject!.getBalanceForSelectedObject() let balanceString = TLCurrencyFormat.getProperAmount(balance!) if AppDelegate.instance().receiveSelectedObject!.getDownloadState() == .downloaded { self.balanceActivityIndicatorView!.stopAnimating() self.balanceActivityIndicatorView!.isHidden = true self.accountBalanceLabel!.text = balanceString as String self.accountBalanceLabel!.isHidden = false } } fileprivate func getQRCodeImage(_ address: String, size: CGFloat) -> UIImage { //let QRCodeData = TLWalletUtils.getBitcoinURI(address, amount: TLCoin.zero(), label: nil, message: nil) let QRCodeData = address let QRCodeImage = TLWalletUtils.getQRCodeImage(QRCodeData, imageDimension: Int(size)) return QRCodeImage } func updateReceiveViewController(_ notification: Notification) { if (TLPreferences.enabledAdvancedMode()) { //self.addressLabel!.hidden = false } else { //self.addressLabel!.hidden = true } } override func prepare(for segue: UIStoryboardSegue, sender: Any!) -> () { if(segue.identifier == "selectAccount") { let vc = segue.destination vc.navigationItem.title = TLDisplayStrings.SELECT_ACCOUNT_STRING() NotificationCenter.default.addObserver(self, selector: #selector(TLReceiveViewController.onAccountSelected(_:)), name: NSNotification.Name(rawValue: TLNotificationEvents.EVENT_ACCOUNT_SELECTED()), object: nil) } } func onAccountSelected(_ note: Notification) { NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: TLNotificationEvents.EVENT_ACCOUNT_SELECTED()), object: nil) let selectedDict = note.object as! NSDictionary let sendFromType = TLSendFromType(rawValue: selectedDict.object(forKey: "sendFromType") as! Int) let sendFromIndex = selectedDict.object(forKey: "sendFromIndex") as! Int AppDelegate.instance().updateReceiveSelectedObject(sendFromType!, sendFromIndex: sendFromIndex) self.updateViewToNewSelectedObject() } fileprivate func scrollToPage(_ page: NSInteger) { // Update the scroll view to the appropriate page var frame = CGRect() frame.origin.x = self.receiveAddressesScrollView!.frame.size.width * CGFloat(page) frame.origin.y = 0 frame.size = self.receiveAddressesScrollView!.frame.size self.receiveAddressesScrollView!.scrollRectToVisible(frame, animated: true) // Keep track of when scrolls happen in response to the page control // value changing. If we don't do this, a noticeable "flashing" occurs // as the the scroll delegate will temporarily switch back the page // number. pageControlBeingUsed = true } @IBAction fileprivate func changePage(_ sender: AnyObject) { self.scrollToPage(self.receiveAddressesPageControl!.currentPage) } @IBAction fileprivate func menuButtonClicked(_ sender: AnyObject) { self.slidingViewController().anchorTopViewToRight(animated: true) } func scrollViewDidScroll(_ sender: UIScrollView) { if (!pageControlBeingUsed) { // change page when more than 50% of the previous/next page is visible let pageWidth = self.receiveAddressesScrollView!.frame.size.width let page = floor((self.receiveAddressesScrollView!.contentOffset.x - pageWidth / 2) / pageWidth) + 1 self.receiveAddressesPageControl!.currentPage = Int(page) } } func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { pageControlBeingUsed = false } func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { pageControlBeingUsed = false } func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) { if (item.tag == 0) { self.showSendView() } } deinit { NotificationCenter.default.removeObserver(self) } } ================================================ FILE: ArcBit/viewControllers/TLRestoreWalletViewController.swift ================================================ // // TLRestoreWalletViewController.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit @objc(TLRestoreWalletViewController) class TLRestoreWalletViewController: UIViewController, UITextViewDelegate { required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } @IBOutlet fileprivate var navigationBar:UINavigationBar? @IBOutlet fileprivate var inputMnemonicTextView:UITextView? @IBOutlet fileprivate var restoreWalletDescriptionLabel:UILabel? var encryptedWalletJSON:String? override var preferredStatusBarStyle : (UIStatusBarStyle) { return UIStatusBarStyle.lightContent } override func viewDidLoad() { super.viewDidLoad() setNavigationBarColors(self.navigationBar!) self.navigationBar?.topItem?.title = TLDisplayStrings.RESTORE_WALLET_STRING() self.restoreWalletDescriptionLabel!.text = TLDisplayStrings.ENTER_A_WALLET_BACKUP_PASSPHRASE_STRING() self.inputMnemonicTextView!.returnKeyType = .done self.inputMnemonicTextView!.delegate = self self.inputMnemonicTextView!.backgroundColor = TLColors.mainAppColor() self.inputMnemonicTextView!.textColor = TLColors.mainAppOppositeColor() self.inputMnemonicTextView!.isSecureTextEntry = true self.inputMnemonicTextView!.becomeFirstResponder() } fileprivate func showPromptToRestoreWallet(_ mnemonicPassphrase:String, walletPayload:NSDictionary?) -> () { let msg = TLDisplayStrings.RESTORING_WALLET_DESC_STRING() UIAlertController.showAlert(in: self, withTitle:TLDisplayStrings.RESTORING_WALLET_STRING(), message:msg, cancelButtonTitle:TLDisplayStrings.CANCEL_STRING(), destructiveButtonTitle: nil, otherButtonTitles:[TLDisplayStrings.CONTINUE_STRING()], tap: {(alertView, action, buttonIndex) in if (buttonIndex == alertView?.firstOtherButtonIndex) { self.inputMnemonicTextView!.resignFirstResponder() TLHUDWrapper.showHUDAddedTo(self.view, labelText:TLDisplayStrings.RESTORING_WALLET_STRING(), animated:true) DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.high).async { AppDelegate.instance().saveWalletJSONEnabled = false AppDelegate.instance().recoverHDWallet(mnemonicPassphrase, shouldRefreshApp:false) AppDelegate.instance().refreshHDWalletAccounts(true) AppDelegate.instance().refreshApp(mnemonicPassphrase, clearWalletInMemory:false) AppDelegate.instance().saveWalletJSONEnabled = true self.handleAfterRecoverWallet(mnemonicPassphrase) } } else if (buttonIndex == alertView?.cancelButtonIndex) { } }) } fileprivate func handleAfterRecoverWallet(_ mnemonicPassphrase:String) -> () { AppDelegate.instance().updateGodSend(TLSendFromType.hdWallet, sendFromIndex:0) AppDelegate.instance().updateReceiveSelectedObject(TLSendFromType.hdWallet, sendFromIndex:0) AppDelegate.instance().updateHistorySelectedObject(TLSendFromType.hdWallet, sendFromIndex:0) AppDelegate.instance().saveWalletJsonCloud() NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_RESTORE_WALLET()), object:nil, userInfo:nil) DispatchQueue.main.async { TLTransactionListener.instance().reconnect() TLStealthWebSocket.instance().reconnect() TLHUDWrapper.hideHUDForView(self.view, animated:true) self.dismiss(animated: true, completion:nil) TLPrompts.promptSuccessMessage(TLDisplayStrings.SUCCESS_STRING(), message:TLDisplayStrings.YOUR_WALLET_IS_NOW_RESTORED_STRING()) } } @IBAction fileprivate func cancel(_ sender:AnyObject!) { self.inputMnemonicTextView!.resignFirstResponder() NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_ENTER_MNEMONIC_VIEWCONTROLLER_DISMISSED()), object:nil, userInfo:nil) self.dismiss(animated: true, completion:nil) } func textView(_ textView:UITextView, shouldChangeTextIn range:NSRange, replacementText text:String) -> Bool { if(text == "\n") { var passphrase = textView.text passphrase = passphrase?.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) if (TLHDWalletWrapper.phraseIsValid(passphrase!)) { showPromptToRestoreWallet(passphrase!, walletPayload:nil) } else { TLPrompts.promptErrorMessage(TLDisplayStrings.ERROR_STRING(), message:TLDisplayStrings.INVALID_BACKUP_PASSPHRASE_STRING()) } return false } return true } fileprivate func textViewShouldReturn(_ textView:UITextView) -> (Bool){ textView.resignFirstResponder() return true } func textViewShouldBeginEditing(_ textView:UITextView) -> (Bool) { return true } func textViewShouldEndEditing(_ textView:UITextView) -> (Bool) { textView.resignFirstResponder() return true } } ================================================ FILE: ArcBit/viewControllers/TLReviewPaymentViewController.swift ================================================ // // TLReviewPaymentViewController.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit import AVFoundation fileprivate func < (lhs: T?, rhs: T?) -> Bool { switch (lhs, rhs) { case let (l?, r?): return l < r case (nil, _?): return true default: return false } } fileprivate func > (lhs: T?, rhs: T?) -> Bool { switch (lhs, rhs) { case let (l?, r?): return l > r default: return rhs < lhs } } @objc(TLReviewPaymentViewController) class TLReviewPaymentViewController: UIViewController, CustomIOS7AlertViewDelegate { required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } @IBOutlet weak var navigationBar:UINavigationBar? @IBOutlet weak var fromTitleLabel: UILabel! @IBOutlet weak var toTitleLabel: UILabel! @IBOutlet weak var toAmountTitleLabel: UILabel! @IBOutlet weak var feeAmountTitleLabel: UILabel! @IBOutlet weak var totalAmountTitleLabel: UILabel! @IBOutlet weak var fromLabel: UILabel! @IBOutlet weak var toLabel: UILabel! @IBOutlet weak var unitLabel: UILabel! @IBOutlet weak var fiatUnitLabel: UILabel! @IBOutlet weak var toAmountLabel: UILabel! @IBOutlet weak var feeAmountLabel: UILabel! @IBOutlet weak var toAmountFiatLabel: UILabel! @IBOutlet weak var feeAmountFiatLabel: UILabel! @IBOutlet weak var totalAmountLabel: UILabel! @IBOutlet weak var totalFiatAmountLabel: UILabel! @IBOutlet weak var customizeFeeButton: UIButton! @IBOutlet weak var sendButton: UIButton! weak var reviewPaymentViewController: TLReviewPaymentViewController? fileprivate lazy var showedPromptedForSentPaymentTxHashSet:NSMutableSet = NSMutableSet() fileprivate var QRImageModal: TLQRImageModal? fileprivate var airGapDataBase64PartsArray: Array? var sendTimer:Timer? var sendTxHash:String? var toAddress:String? var toAmount:TLCoin? var amountMovedFromAccount: TLCoin? = nil var realToAddresses: Array? = nil private var scannedSignedTxAirGapDataPartsDict = [Int:String]() private var totalExpectedParts:Int = 0 private var scannedSignedTxAirGapData:String? = nil private var shouldPromptToScanSignedTxAirGapData = false private var shouldPromptToBroadcastSignedTx = false private var signedAirGapTxHex:String? = nil private var signedAirGapTxHash:String? = nil override var preferredStatusBarStyle : (UIStatusBarStyle) { return UIStatusBarStyle.lightContent } override func viewDidLoad() { super.viewDidLoad() setNavigationBarColors(self.navigationBar!) self.navigationBar?.topItem?.title = TLDisplayStrings.CONFIRM_PAYMENT_STRING() self.fromTitleLabel.text = TLDisplayStrings.FROM_COLON_STRING() self.toTitleLabel.text = TLDisplayStrings.TO_COLON_STRING() self.toAmountTitleLabel.text = TLDisplayStrings.AMOUNT_COLON_STRING() self.feeAmountTitleLabel.text = TLDisplayStrings.FEE_COLON_STRING() self.totalAmountTitleLabel.text = TLDisplayStrings.TOTAL_COLON_STRING() self.customizeFeeButton.setTitle(TLDisplayStrings.CUSTOMIZE_FEE_STRING(), for: .normal) self.sendButton.setTitle(TLDisplayStrings.SEND_STRING(), for: .normal) self.sendButton.backgroundColor = TLColors.mainAppColor() self.sendButton.setTitleColor(TLColors.mainAppOppositeColor(), for: UIControlState()) NotificationCenter.default.addObserver(self ,selector:#selector(TLReviewPaymentViewController.finishSend(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_MODEL_UPDATED_NEW_UNCONFIRMED_TRANSACTION()), object:nil) updateView() self.reviewPaymentViewController = self if !TLPreferences.enabledAdvancedMode() { self.customizeFeeButton.isHidden = true } } override func viewWillAppear(_ animated: Bool) { if self.shouldPromptToScanSignedTxAirGapData { self.promptToScanSignedTxAirGapData() } else if self.shouldPromptToBroadcastSignedTx { self.shouldPromptToBroadcastSignedTx = false self.promptToBroadcastColdWalletAccountSignedTx(self.signedAirGapTxHex!, txHash: self.signedAirGapTxHash!) } } override func viewDidAppear(_ animated: Bool) { if (!TLPreferences.disabledShowFeeExplanationInfo()) { TLPrompts.promptSuccessMessage(TLDisplayStrings.TRANSACTION_FEE_STRING(), message: TLDisplayStrings.FEE_INFO_DESC_STRING()) TLPreferences.setDisableShowFeeExplanationInfo(true); } } func updateView() { self.fromLabel.text = TLSendFormData.instance().fromLabel self.toLabel.text = TLSendFormData.instance().getAddress() self.unitLabel.text = TLCurrencyFormat.getBitcoinDisplay() self.fiatUnitLabel.text = TLCurrencyFormat.getFiatCurrency() self.toAmountLabel.text = TLCurrencyFormat.coinToProperBitcoinAmountString(TLSendFormData.instance().toAmount!, withCode: false) self.toAmountFiatLabel.text = TLCurrencyFormat.coinToProperFiatAmountString(TLSendFormData.instance().toAmount!, withCode: false) self.feeAmountLabel.text = TLCurrencyFormat.coinToProperBitcoinAmountString(TLSendFormData.instance().feeAmount!, withCode: false) self.feeAmountFiatLabel.text = TLCurrencyFormat.coinToProperFiatAmountString(TLSendFormData.instance().feeAmount!, withCode: false) let total = TLSendFormData.instance().toAmount!.add(TLSendFormData.instance().feeAmount!) self.totalAmountLabel.text = TLCurrencyFormat.coinToProperBitcoinAmountString(total, withCode: false) self.totalFiatAmountLabel.text = TLCurrencyFormat.coinToProperFiatAmountString(total, withCode: false) } fileprivate func showPromptForSetTransactionFee() { let msg = String(format: TLDisplayStrings.SET_TRANSACTION_FEE_IN_X_STRING(), TLCurrencyFormat.getBitcoinDisplay()) func addTextField(_ textField: UITextField!){ textField.placeholder = "" textField.keyboardType = .decimalPad } UIAlertController.showAlert(in: self, withTitle: TLDisplayStrings.TRANSACTION_FEE_STRING(), message: msg, preferredStyle: .alert, cancelButtonTitle: TLDisplayStrings.CANCEL_STRING(), destructiveButtonTitle: nil, otherButtonTitles: [TLDisplayStrings.OK_STRING()], preShow: {(controller) in controller!.addTextField(configurationHandler: addTextField) } , tap: {(alertView, action, buttonIndex) in if (buttonIndex == alertView!.firstOtherButtonIndex) { let feeAmountString = (alertView!.textFields![0]).text let feeAmount = TLCurrencyFormat.properBitcoinAmountStringToCoin(feeAmountString!) let amountNeeded = TLSendFormData.instance().toAmount!.add(feeAmount) let sendFromBalance = AppDelegate.instance().godSend!.getCurrentFromBalance() if (amountNeeded.greater(sendFromBalance)) { TLPrompts.promptErrorMessage(TLDisplayStrings.INSUFFICIENT_FUNDS_STRING(), message: TLDisplayStrings.YOUR_NEW_TRANSACTION_FEE_IS_TOO_HIGH_STRING()) return } TLSendFormData.instance().feeAmount = feeAmount self.updateView() } else if (buttonIndex == alertView!.cancelButtonIndex) { } }) } func showPromptPaymentSent(_ txHash: String, address: String, amount: TLCoin) { DLog("showPromptPaymentSent \(txHash)") let msg = String(format:TLDisplayStrings.SENT_X_TO_Y_STRING(), TLCurrencyFormat.getProperAmount(amount), address) TLHUDWrapper.hideHUDForView(self.view, animated: true) TLPrompts.promtForOK(self, title: "", message: msg, success: { self.dismiss(animated: true, completion: nil) }) } func cancelSend() { sendTimer?.invalidate() TLHUDWrapper.hideHUDForView(self.view, animated: true) } func retryFinishSend() { DLog("retryFinishSend \(self.sendTxHash)") if !AppDelegate.instance().webSocketNotifiedTxHashSet.contains(self.sendTxHash!) { let nonUpdatedBalance = AppDelegate.instance().godSend!.getCurrentFromBalance() let accountNewBalance = nonUpdatedBalance.subtract(self.amountMovedFromAccount!) DLog("retryFinishSend 2 \(self.sendTxHash)") AppDelegate.instance().godSend!.setCurrentFromBalance(accountNewBalance) } if !self.showedPromptedForSentPaymentTxHashSet.contains(self.sendTxHash!) { self.showedPromptedForSentPaymentTxHashSet.add(self.sendTxHash!) self.showPromptPaymentSent(self.sendTxHash!, address: self.toAddress!, amount: self.toAmount!) } } func finishSend(_ note: Notification) { let webSocketNotifiedTxHash = note.object as? String DLog("finishSend \(webSocketNotifiedTxHash)") if webSocketNotifiedTxHash! == self.sendTxHash! && !self.showedPromptedForSentPaymentTxHashSet.contains(webSocketNotifiedTxHash!) { DLog("finishSend 2 \(webSocketNotifiedTxHash)") self.showedPromptedForSentPaymentTxHashSet.add(webSocketNotifiedTxHash!) sendTimer?.invalidate() self.showPromptPaymentSent(webSocketNotifiedTxHash!, address: self.toAddress!, amount: self.toAmount!) } } func initiateSend() { let unspentOutputsSum = AppDelegate.instance().godSend!.getCurrentFromUnspentOutputsSum() if (unspentOutputsSum.less(TLSendFormData.instance().toAmount!)) { // can only happen if unspentOutputsSum is for some reason less then the balance computed from the transactions, which it shouldn't cancelSend() let unspentOutputsSumString = TLCurrencyFormat.coinToProperBitcoinAmountString(unspentOutputsSum) TLPrompts.promptErrorMessage(TLDisplayStrings.INSUFFICIENT_FUNDS_STRING(), message: String(format: TLDisplayStrings.SOME_FUNDS_MAY_BE_PENDING_CONFIRMATION_DESC_STRING(), "\(unspentOutputsSumString) \(TLCurrencyFormat.getBitcoinDisplay())")) return } let toAddressesAndAmount = NSMutableDictionary() toAddressesAndAmount.setObject(TLSendFormData.instance().getAddress()!, forKey: "address" as NSCopying) toAddressesAndAmount.setObject(TLSendFormData.instance().toAmount!, forKey: "amount" as NSCopying) let toAddressesAndAmounts = NSArray(objects: toAddressesAndAmount) let signTx = !AppDelegate.instance().godSend!.isColdWalletAccount() let ret = AppDelegate.instance().godSend!.createSignedSerializedTransactionHex(toAddressesAndAmounts, feeAmount: TLSendFormData.instance().feeAmount!, signTx: signTx, error: { (data: String?) in self.cancelSend() TLPrompts.promptErrorMessage(TLDisplayStrings.ERROR_STRING(), message: data ?? "") }) let txHexAndTxHash = ret.0 self.realToAddresses = ret.1 if txHexAndTxHash == nil { cancelSend() return } let txHex = txHexAndTxHash!.object(forKey: "txHex") as? String if (txHex == nil) { //should not reach here, because I check sum of unspent outputs already, // unless unspent outputs contains dust and are require to filled the amount I want to send cancelSend() return } if AppDelegate.instance().godSend!.isColdWalletAccount() { cancelSend() let txInputsAccountHDIdxes = ret.2 let inputScripts = txHexAndTxHash!.object(forKey: "inputScripts") as! NSArray self.promptToSignTransaction(txHex!, inputScripts:inputScripts, txInputsAccountHDIdxes:txInputsAccountHDIdxes!) return; } let txHash = txHexAndTxHash!.object(forKey: "txHash") as? String prepAndBroadcastTx(txHex!, txHash: txHash!) } func prepAndBroadcastTx(_ txHex: String, txHash: String) { if (TLSendFormData.instance().getAddress() == AppDelegate.instance().godSend!.getStealthAddress()) { AppDelegate.instance().pendingSelfStealthPaymentTxid = txHash } if AppDelegate.instance().godSend!.isPaymentToOwnAccount(TLSendFormData.instance().getAddress()!) { self.amountMovedFromAccount = TLSendFormData.instance().feeAmount! } else { self.amountMovedFromAccount = TLSendFormData.instance().toAmount!.add(TLSendFormData.instance().feeAmount!) } for address in self.realToAddresses! { TLTransactionListener.instance().listenToIncomingTransactionForAddress(address) } TLSendFormData.instance().beforeSendBalance = AppDelegate.instance().godSend!.getCurrentFromBalance() self.sendTxHash = txHash self.toAddress = TLSendFormData.instance().getAddress() self.toAmount = TLSendFormData.instance().toAmount DLog("showPromptReviewTx txHex: \(txHex)") DLog("showPromptReviewTx txHash: \(txHash)") broadcastTx(txHex, txHash: txHash, toAddress: TLSendFormData.instance().getAddress()!) } func broadcastTx(_ txHex: String, txHash: String, toAddress: String) { let handlePushTxSuccess = { () -> () in NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_SEND_PAYMENT()), object: nil, userInfo: nil) } TLPushTxAPI.instance().sendTx(txHex, txHash: txHash, toAddress: toAddress, success: { (jsonData) in DLog("showPromptReviewTx pushTx: success \(jsonData)") if TLStealthAddress.isStealthAddress(toAddress, isTestnet:false) == true { // doing stealth payment with push tx insight get wrong hash back?? let txid = (jsonData as! NSDictionary).object(forKey: "txid") as! String DLog("showPromptReviewTx pushTx: success txid \(txid)") DLog("showPromptReviewTx pushTx: success txHash \(txHash)") if txid != txHash { NSException(name: NSExceptionName(rawValue: "API Error"), reason:"txid return does not match txid in app", userInfo:nil).raise() } } if let label = AppDelegate.instance().appWallet.getLabelForAddress(toAddress) { AppDelegate.instance().appWallet.setTransactionTag(txHash, tag: label) } handlePushTxSuccess() }, failure: { (code, status) in DLog("showPromptReviewTx pushTx: failure \(code) \(status)") if (code == 200) { handlePushTxSuccess() } else { TLPrompts.promptErrorMessage(TLDisplayStrings.ERROR_STRING(), message: status!) self.cancelSend() } }) } func showNextUnsignedTxPartQRCode() { if self.airGapDataBase64PartsArray == nil { return } let nextAipGapDataPart = self.airGapDataBase64PartsArray![0] self.airGapDataBase64PartsArray!.remove(at: 0) self.QRImageModal = TLQRImageModal(data: nextAipGapDataPart as NSString, buttonCopyText: TLDisplayStrings.NEXT_STRING(), vc: self) self.QRImageModal!.show() } func promptToSignTransaction(_ unSignedTx: String, inputScripts:NSArray, txInputsAccountHDIdxes:NSArray) { let extendedPublicKey = AppDelegate.instance().godSend!.getExtendedPubKey() if let airGapDataBase64 = TLColdWallet.createSerializedUnsignedTxAipGapData(unSignedTx, extendedPublicKey: extendedPublicKey!, inputScripts: inputScripts, txInputsAccountHDIdxes: txInputsAccountHDIdxes) { self.airGapDataBase64PartsArray = TLColdWallet.splitStringToArray(airGapDataBase64) DLog("airGapDataBase64PartsArray \(airGapDataBase64PartsArray)") TLPrompts.promtForOKCancel(self, title: TLDisplayStrings.SPENDING_FROM_A_COLD_WALLET_ACCOUNT_STRING(), message: TLDisplayStrings.SPENDING_FROM_A_COLD_WALLET_ACCOUNT_DESC_STRING(), success: { () in self.showNextUnsignedTxPartQRCode() }, failure: { (isCancelled: Bool) in }) } } @IBAction func customizeFeeButtonClicked(_ sender: AnyObject) { showPromptForSetTransactionFee() } @IBAction func feeInfoButtonClicked(_ sender: AnyObject) { TLPrompts.promptSuccessMessage(TLDisplayStrings.TRANSACTION_FEE_STRING(), message: TLDisplayStrings.FEE_INFO_DESC_STRING()) } @IBAction func sendButtonClicked(_ sender: AnyObject) { self.startSendTimer() if !AppDelegate.instance().godSend!.haveUpDatedUTXOs() { AppDelegate.instance().godSend!.getAndSetUnspentOutputs({ self.initiateSend() }, failure: { self.cancelSend() TLPrompts.promptErrorMessage(TLDisplayStrings.ERROR_STRING(), message: TLDisplayStrings.ERROR_FETCHING_UNSPENT_OUTPUTS_TRY_AGAIN_STRING()) }) } else { self.initiateSend() } } @IBAction fileprivate func cancel(_ sender:AnyObject) { self.dismiss(animated: true, completion:nil) } func startSendTimer() { TLHUDWrapper.showHUDAddedTo(self.view, labelText: TLDisplayStrings.SENDING_STRING(), animated: true) // relying on websocket to know when a payment has been sent can be unreliable, so cancel after a certain time let TIME_TO_WAIT_TO_HIDE_HUD_AND_REFRESH_ACCOUNT = 13.0 sendTimer = Timer.scheduledTimer(timeInterval: TIME_TO_WAIT_TO_HIDE_HUD_AND_REFRESH_ACCOUNT, target: self, selector: #selector(retryFinishSend), userInfo: nil, repeats: false) } func promptToBroadcastColdWalletAccountSignedTx(_ txHex: String, txHash: String) { TLPrompts.promptAlertController(self, title: TLDisplayStrings.SEND_AUTHORIZED_PAYMENT_STRING(), message: "", okText: TLDisplayStrings.SEND_STRING(), cancelTx: TLDisplayStrings.CANCEL_STRING(), success: { () in self.startSendTimer() self.prepAndBroadcastTx(txHex, txHash: txHash) }, failure: { (isCancelled: Bool) in }) } func didClickScanSignedTxButton() { scanSignedTx(success: { () in DLog("didClickScanSignedTxButton success"); if self.totalExpectedParts != 0 && self.scannedSignedTxAirGapDataPartsDict.count == self.totalExpectedParts { self.shouldPromptToScanSignedTxAirGapData = false self.scannedSignedTxAirGapData = "" for i in stride(from: 1, through: self.totalExpectedParts, by: 1) { let dataPart = self.scannedSignedTxAirGapDataPartsDict[i] self.scannedSignedTxAirGapData = self.scannedSignedTxAirGapData! + dataPart! } self.scannedSignedTxAirGapDataPartsDict = [Int:String]() DLog("didClickScanSignedTxButton self.scannedSignedTxAirGapData \(self.scannedSignedTxAirGapData)"); if let signedTxData = TLColdWallet.getSignedTxData(self.scannedSignedTxAirGapData!) { DLog("didClickScanSignedTxButton signedTxData \(signedTxData)"); let txHex = signedTxData["txHex"] as! String let txHash = signedTxData["txHash"] as! String let txSize = signedTxData["txSize"] as! NSNumber DLog("didClickScanSignedTxButton txHex \(txHex)"); DLog("didClickScanSignedTxButton txHash \(txHash)"); DLog("didClickScanSignedTxButton txSize \(txSize)"); self.signedAirGapTxHex = txHex self.signedAirGapTxHash = txHash self.shouldPromptToBroadcastSignedTx = true } } else { self.shouldPromptToScanSignedTxAirGapData = true } }, error: { () in DLog("didClickScanSignedTxButton error"); }) } func scanSignedTx(success: @escaping (TLWalletUtils.Success), error: @escaping (TLWalletUtils.Error)) { AppDelegate.instance().showColdWalletSpendReaderControllerFromViewController(self, success: { (data: String!) in let ret = TLColdWallet.parseScannedPart(data) let dataPart = ret.0 let partNumber = ret.1 let totalParts = ret.2 self.totalExpectedParts = totalParts self.scannedSignedTxAirGapDataPartsDict[partNumber] = dataPart DLog("scanSignedTx \(dataPart) \(partNumber) \(totalParts)"); success() }, error: { (data: String?) in error() }) } func promptToScanSignedTxAirGapData() { let msg = String(format: TLDisplayStrings.X_SLASH_Y_PARTS_SCANNED_STRING(), self.scannedSignedTxAirGapDataPartsDict.count, self.totalExpectedParts) TLPrompts.promptAlertController(self, title: TLDisplayStrings.SCAN_NEXT_PART_STRING(), message: msg, okText: TLDisplayStrings.SCAN_STRING(), cancelTx: TLDisplayStrings.CANCEL_STRING(), success: { () in self.didClickScanSignedTxButton() }, failure: { (isCancelled: Bool) in }) } func customIOS7dialogButtonTouchUp(inside alertView: CustomIOS7AlertView, clickedButtonAt buttonIndex: Int) { if (buttonIndex == 0) { if self.airGapDataBase64PartsArray?.count > 0 { self.showNextUnsignedTxPartQRCode() } else { TLPrompts.promptAlertController(self, title: TLDisplayStrings.FINISHED_PASSING_TRANSACTION_DATA_STRING(), message: TLDisplayStrings.FINISHED_PASSING_TRANSACTION_DATA_DESC_STRING(), okText: TLDisplayStrings.CONTINUE_STRING(), cancelTx: TLDisplayStrings.CANCEL_STRING(), success: { () in self.didClickScanSignedTxButton() }, failure: { (isCancelled: Bool) in }) } } alertView.close() } } ================================================ FILE: ArcBit/viewControllers/TLSendViewController.swift ================================================ // // TLSendViewController.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import UIKit import StoreKit @objc(TLSendViewController) class TLSendViewController: UIViewController, UITextFieldDelegate, UITabBarDelegate { required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } @IBOutlet fileprivate var currencySymbolButton: UIButton? @IBOutlet fileprivate var toAddressTextField: UITextField? @IBOutlet fileprivate var amountTextField: UITextField? @IBOutlet fileprivate var qrCodeImageView: UIImageView? @IBOutlet fileprivate var selectAccountImageView: UIImageView? @IBOutlet fileprivate var bitcoinDisplayLabel: UILabel? @IBOutlet fileprivate var fiatCurrencyDisplayLabel: UILabel? @IBOutlet fileprivate var scanQRButton: UIButton? @IBOutlet fileprivate var reviewPaymentButton: UIButton? @IBOutlet fileprivate var addressBookButton: UIButton? @IBOutlet fileprivate var fiatAmountTextField: UITextField? @IBOutlet fileprivate var topView: UIView? @IBOutlet fileprivate var bottomView: UIView? @IBOutlet fileprivate var accountNameLabel: UILabel? @IBOutlet fileprivate var accountBalanceLabel: UILabel? @IBOutlet fileprivate var balanceActivityIndicatorView: UIActivityIndicatorView? @IBOutlet fileprivate var fromViewContainer: UIView? @IBOutlet fileprivate var fromLabel: UILabel! @IBOutlet fileprivate var toLabel: UILabel! @IBOutlet fileprivate var amountLabel: UILabel! @IBOutlet fileprivate var tabBar: UITabBar? fileprivate var tapGesture: UITapGestureRecognizer? fileprivate func setAmountFromUrlHandler() -> () { let dict = AppDelegate.instance().bitcoinURIOptionsDict if (dict != nil) { let addr = dict!.object(forKey: "address") as! String let amount = dict!.object(forKey: "amount") as! String self.setAmountFromUrlHandler(TLCurrencyFormat.bitcoinAmountStringToCoin(amount), address: addr) AppDelegate.instance().bitcoinURIOptionsDict = nil } } fileprivate func setAmountFromUrlHandler(_ amount: TLCoin, address: String) { self.toAddressTextField!.text = address let amountString = TLCurrencyFormat.coinToProperBitcoinAmountString(amount) self.amountTextField!.text = amountString TLSendFormData.instance().setAddress(address) TLSendFormData.instance().setAmount(amountString) self.updateFiatAmountTextFieldExchangeRate(nil) } fileprivate func setAllCoinsBarButton() { let allBarButtonItem = UIBarButtonItem(title: TLDisplayStrings.USE_ALL_FUNDS_STRING(), style: UIBarButtonItemStyle.plain, target: self, action: #selector(TLSendViewController.checkToFetchUTXOsAndDynamicFeesAndFillAmountFieldWithWholeBalance)) navigationItem.rightBarButtonItem = allBarButtonItem } fileprivate func clearRightBarButton() { let allBarButtonItem = UIBarButtonItem(title: "", style: UIBarButtonItemStyle.plain, target: self, action: nil) navigationItem.rightBarButtonItem = allBarButtonItem } func checkToFetchUTXOsAndDynamicFeesAndFillAmountFieldWithWholeBalance() { if TLPreferences.enabledInAppSettingsKitDynamicFee() { if !AppDelegate.instance().godSend!.haveUpDatedUTXOs() { AppDelegate.instance().godSend!.getAndSetUnspentOutputs({ self.checkToFetchDynamicFeesAndFillAmountFieldWithWholeBalance() }, failure: { TLPrompts.promptErrorMessage(TLDisplayStrings.ERROR_STRING(), message: TLDisplayStrings.ERROR_FETCHING_UNSPENT_OUTPUTS_TRY_AGAIN_LATER_STRING()) }) } else { self.checkToFetchDynamicFeesAndFillAmountFieldWithWholeBalance() } } else { self.fillAmountFieldWithWholeBalance(false) } } func checkToFetchDynamicFeesAndFillAmountFieldWithWholeBalance() { if !AppDelegate.instance().txFeeAPI.haveUpdatedCachedDynamicFees() { AppDelegate.instance().txFeeAPI.getDynamicTxFee({ (_jsonData) in self.fillAmountFieldWithWholeBalance(true) }, failure: { (code, status) in TLPrompts.promptErrorMessage(TLDisplayStrings.ERROR_STRING(), message: TLDisplayStrings.UNABLE_TO_GET_DYNAMIC_FEES_STRING()) self.fillAmountFieldWithWholeBalance(false) }) } else { self.fillAmountFieldWithWholeBalance(true) } } func fillAmountFieldWithWholeBalance(_ useDynamicFees: Bool) { let fee:TLCoin let txSizeBytes:UInt64 if useDynamicFees { if (AppDelegate.instance().godSend!.getSelectedObjectType() == .account) { let accountObject = AppDelegate.instance().godSend!.getSelectedSendObject() as! TLAccountObject let inputCount = accountObject.stealthPaymentUnspentOutputsCount + accountObject.unspentOutputsCount txSizeBytes = TLSpaghettiGodSend.getEstimatedTxSize(inputCount, outputCount: 1) DLog("fillAmountFieldWithWholeBalance TLAccountObject useDynamicFees inputCount txSizeBytes: \(inputCount) \(txSizeBytes)") } else { let importedAddress = AppDelegate.instance().godSend!.getSelectedSendObject() as! TLImportedAddress txSizeBytes = TLSpaghettiGodSend.getEstimatedTxSize(importedAddress.unspentOutputsCount, outputCount: 1) DLog("fillAmountFieldWithWholeBalance importedAddress useDynamicFees inputCount txSizeBytes: \(importedAddress.unspentOutputsCount) \(txSizeBytes)") } if let dynamicFeeSatoshis:NSNumber? = AppDelegate.instance().txFeeAPI.getCachedDynamicFee() { fee = TLCoin(uint64: txSizeBytes*dynamicFeeSatoshis!.uint64Value) DLog("fillAmountFieldWithWholeBalance coinFeeAmount dynamicFeeSatoshis: \(txSizeBytes*dynamicFeeSatoshis!.uint64Value)") } else { fee = TLCurrencyFormat.bitcoinAmountStringToCoin(TLPreferences.getInAppSettingsKitTransactionFee()!) } } else { let feeAmount = TLPreferences.getInAppSettingsKitTransactionFee() fee = TLCurrencyFormat.bitcoinAmountStringToCoin(feeAmount!) } let accountBalance = AppDelegate.instance().godSend!.getCurrentFromBalance() let sendAmount = accountBalance.subtract(fee) DLog("fillAmountFieldWithWholeBalance accountBalance: \(accountBalance.toUInt64())") DLog("fillAmountFieldWithWholeBalance sendAmount: \(sendAmount.toUInt64())") DLog("fillAmountFieldWithWholeBalance fee: \(fee.toUInt64())") TLSendFormData.instance().feeAmount = fee TLSendFormData.instance().useAllFunds = true if accountBalance.greater(fee) && sendAmount.greater(TLCoin.zero()) { TLSendFormData.instance().setAmount(TLCurrencyFormat.coinToProperBitcoinAmountString(sendAmount)) } else { TLSendFormData.instance().setAmount(nil) } TLSendFormData.instance().setFiatAmount(nil) self.updateSendForm() } func setupLabels() { let sendTabBarItem = self.tabBar?.items?[0] sendTabBarItem?.title = TLDisplayStrings.SEND_STRING() let receiveTabBarItem = self.tabBar?.items?[1] receiveTabBarItem?.title = TLDisplayStrings.RECEIVE_STRING() self.toAddressTextField?.placeholder = TLDisplayStrings.ADDRESS_STRING() self.toLabel.text = TLDisplayStrings.TO_COLON_STRING() self.fromLabel.text = TLDisplayStrings.FROM_COLON_STRING() self.amountLabel.text = TLDisplayStrings.AMOUNT_COLON_STRING() self.scanQRButton?.setTitle(TLDisplayStrings.SCAN_QR_STRING(), for: .normal) self.addressBookButton?.setTitle(TLDisplayStrings.CONTACTS_STRING(), for: .normal) self.reviewPaymentButton?.setTitle(TLDisplayStrings.REVIEW_PAYMENT_STRING(), for: .normal) } override func viewDidLoad() { super.viewDidLoad() setColors() self.setLogoImageView() self.setupLabels() self.bottomView!.backgroundColor = TLColors.mainAppColor() self.fromViewContainer!.backgroundColor = TLColors.mainAppColor() self.accountNameLabel!.textColor = TLColors.mainAppOppositeColor() self.accountBalanceLabel!.textColor = TLColors.mainAppOppositeColor() self.balanceActivityIndicatorView!.color = TLColors.mainAppOppositeColor() self.scanQRButton!.backgroundColor = TLColors.mainAppColor() self.reviewPaymentButton!.backgroundColor = TLColors.mainAppColor() self.addressBookButton!.backgroundColor = TLColors.mainAppColor() self.scanQRButton!.setTitleColor(TLColors.mainAppOppositeColor(), for: UIControlState()) self.reviewPaymentButton!.setTitleColor(TLColors.mainAppOppositeColor(), for: UIControlState()) self.addressBookButton!.setTitleColor(TLColors.mainAppOppositeColor(), for: UIControlState()) if TLUtils.isIPhone5() || TLUtils.isIPhone4() { let keyboardDoneButtonView = UIToolbar() keyboardDoneButtonView.sizeToFit() let item = UIBarButtonItem(title: TLDisplayStrings.DONE_STRING(), style: UIBarButtonItemStyle.plain, target: self, action: #selector(TLSendViewController.dismissKeyboard) ) let toolbarButtons = [item] keyboardDoneButtonView.setItems(toolbarButtons, animated: false) self.amountTextField!.inputAccessoryView = keyboardDoneButtonView self.fiatAmountTextField!.inputAccessoryView = keyboardDoneButtonView } NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_SEND_SCREEN_LOADING()), object: nil) self.tabBar!.selectedItem = ((self.tabBar!.items as NSArray!).object(at: 0)) as? UITabBarItem if UIScreen.main.bounds.size.height <= 480.0 { // is 3.5 inch screen self.tabBar!.isHidden = true } NotificationCenter.default.addObserver(self ,selector:#selector(TLSendViewController.dismissTextFieldsAndScrollDown(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_HAMBURGER_MENU_OPENED()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLSendViewController.clearSendForm(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_PREFERENCES_FIAT_DISPLAY_CHANGED()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLSendViewController.clearSendForm(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_PREFERENCES_BITCOIN_DISPLAY_CHANGED()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLSendViewController.updateCurrencyView(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_PREFERENCES_FIAT_DISPLAY_CHANGED()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLSendViewController.updateBitcoinDisplayView(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_PREFERENCES_BITCOIN_DISPLAY_CHANGED()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLSendViewController.updateAccountBalanceView(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_PREFERENCES_FIAT_DISPLAY_CHANGED()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLSendViewController.updateAccountBalanceView(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_PREFERENCES_BITCOIN_DISPLAY_CHANGED()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLSendViewController.updateAccountBalanceView(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_DISPLAY_LOCAL_CURRENCY_TOGGLED()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLSendViewController.updateAccountBalanceView(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_FETCHED_ADDRESSES_DATA()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLSendViewController.hideHUDAndUpdateBalanceView), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_MODEL_UPDATED_NEW_UNCONFIRMED_TRANSACTION()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLSendViewController.clearSendForm(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_SEND_PAYMENT()), object:nil) NotificationCenter.default.addObserver(self ,selector:#selector(TLSendViewController.updateAccountBalanceView(_:)), name:NSNotification.Name(rawValue: TLNotificationEvents.EVENT_EXCHANGE_RATE_UPDATED()), object:nil) self.updateSendForm() self.amountTextField!.keyboardType = .decimalPad self.amountTextField!.delegate = self self.fiatAmountTextField!.keyboardType = .decimalPad self.fiatAmountTextField!.delegate = self self.toAddressTextField!.delegate = self self.toAddressTextField!.clearButtonMode = UITextFieldViewMode.whileEditing self.currencySymbolButton?.setBackgroundImage(UIImage(named: "balance_bg_pressed.9.png"), for: .highlighted) self.currencySymbolButton?.setBackgroundImage(UIImage(named: "balance_bg_normal.9.png"), for: UIControlState()) self.sendViewSetup() if self.slidingViewController() != nil { self.slidingViewController().topViewAnchoredGesture = [.tapping, .panning] } } override func viewWillDisappear(_ animated: Bool) { self.topView!.scrollToY(0) } fileprivate func sendViewSetup() -> () { self._updateCurrencyView() self._updateBitcoinDisplayView() self.updateViewToNewSelectedObject() if (AppDelegate.instance().godSend!.hasFetchedCurrentFromData()) { let balance = AppDelegate.instance().godSend!.getCurrentFromBalance() let balanceString = TLCurrencyFormat.getProperAmount(balance) self.accountBalanceLabel!.text = balanceString as String self.accountBalanceLabel!.isHidden = false self.balanceActivityIndicatorView!.stopAnimating() self.balanceActivityIndicatorView!.isHidden = true } else { self.refreshAccountDataAndSetBalanceView() } if (AppDelegate.instance().justSetupHDWallet) { self.showReceiveView() } } func refreshAccountDataAndSetBalanceView(_ fetchDataAgain: Bool = false) -> () { if (AppDelegate.instance().godSend!.getSelectedObjectType() == .account) { let accountObject = AppDelegate.instance().godSend!.getSelectedSendObject() as! TLAccountObject self.balanceActivityIndicatorView!.isHidden = false self.accountBalanceLabel!.isHidden = true self.balanceActivityIndicatorView!.startAnimating() AppDelegate.instance().pendingOperations.addSetUpAccountOperation(accountObject, fetchDataAgain: fetchDataAgain, success: { if accountObject.downloadState == .downloaded { self.accountBalanceLabel!.isHidden = false self.balanceActivityIndicatorView!.stopAnimating() self.balanceActivityIndicatorView!.isHidden = true self._updateAccountBalanceView() } }) } else if (AppDelegate.instance().godSend!.getSelectedObjectType() == .address) { let importedAddress = AppDelegate.instance().godSend!.getSelectedSendObject() as! TLImportedAddress self.balanceActivityIndicatorView!.isHidden = false self.accountBalanceLabel!.isHidden = true self.balanceActivityIndicatorView!.startAnimating() AppDelegate.instance().pendingOperations.addSetUpImportedAddressOperation(importedAddress, fetchDataAgain: fetchDataAgain, success: { if importedAddress.downloadState == .downloaded { self.accountBalanceLabel!.isHidden = false self.balanceActivityIndicatorView!.stopAnimating() self.balanceActivityIndicatorView!.isHidden = true self._updateAccountBalanceView() } }) } } fileprivate func showReceiveView() -> () { self.slidingViewController().topViewController = self.storyboard!.instantiateViewController(withIdentifier: "ReceiveNav") } fileprivate func updateViewToNewSelectedObject() -> () { let label = AppDelegate.instance().godSend!.getCurrentFromLabel() self.accountNameLabel!.text = label self._updateAccountBalanceView() } override func viewWillAppear(_ animated: Bool) { self.updateViewToNewSelectedObject() // TODO: better way if AppDelegate.instance().scannedEncryptedPrivateKey != nil { TLPrompts.promptForEncryptedPrivKeyPassword(self, view:self.slidingViewController().topViewController.view, encryptedPrivKey:AppDelegate.instance().scannedEncryptedPrivateKey!, success:{(privKey: String!) in if AppDelegate.instance().scannedEncryptedPrivateKey == nil { return } if (!TLCoreBitcoinWrapper.isValidPrivateKey(privKey, isTestnet: AppDelegate.instance().appWallet.walletConfig.isTestnet)) { TLPrompts.promptErrorMessage(TLDisplayStrings.ERROR_STRING(), message: TLDisplayStrings.INVALID_PRIVATE_KEY_STRING()) } else { let importedAddress = AppDelegate.instance().godSend!.getSelectedSendObject() as! TLImportedAddress? let success = importedAddress!.setPrivateKeyInMemory(privKey) if (!success) { TLPrompts.promptSuccessMessage(TLDisplayStrings.ERROR_STRING(), message: TLDisplayStrings.PRIVATE_KEY_DOES_NOT_MATCH_ADDRESS_STRING()) } else { self._reviewPaymentClicked() } AppDelegate.instance().scannedEncryptedPrivateKey = nil } }, failure:{(isCanceled: Bool) in AppDelegate.instance().scannedEncryptedPrivateKey = nil }) } } override func viewDidAppear(_ animated: Bool) { NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_VIEW_SEND_SCREEN()), object: nil, userInfo: nil) self.setAmountFromUrlHandler() if (!TLPreferences.getInAppSettingsKitEnablePinCode() && TLSuggestions.instance().conditionToPromptToSuggestEnablePinSatisfied()) { TLSuggestions.instance().promptToSuggestEnablePin(self) } else if TLSuggestions.instance().conditionToPromptRateAppSatisfied() { if #available(iOS 10.3, *) { SKStoreReviewController.requestReview() } else { TLPrompts.promptAlertController(self, title: TLDisplayStrings.LIKE_USING_ARCBIT_STRING(), message: TLDisplayStrings.RATE_US_IN_THE_APP_STORE_STRING(), okText: TLDisplayStrings.RATE_STRING(), cancelTx: TLDisplayStrings.NOT_NOW_STRING(), success: { () -> () in let url = URL(string: "https://itunes.apple.com/app/id999487888"); if (UIApplication.shared.canOpenURL(url!)) { UIApplication.shared.openURL(url!); } TLPreferences.setDisabledPromptRateApp(true) if !TLPreferences.hasRatedOnce() { TLPreferences.setHasRatedOnce() } }, failure: { (Bool) -> () in }) } } else if TLSuggestions.instance().conditionToPromptShowWebWallet() { TLPrompts.promptAlertController(self, title: TLDisplayStrings.CHECK_OUT_THE_ARCBIT_WEB_WALLET_EXCLAMATION_STRING(), message: TLDisplayStrings.CHECK_OUT_THE_ARCBIT_WEB_WALLET_DESC_STRING(), okText: TLDisplayStrings.GO_STRING(), cancelTx: TLDisplayStrings.NOT_NOW_STRING(), success: { () -> () in let url = URL(string: "https://chrome.google.com/webstore/detail/arcbit-bitcoin-wallet/dkceiphcnbfahjbomhpdgjmphnpgogfk"); if (UIApplication.shared.canOpenURL(url!)) { UIApplication.shared.openURL(url!); } TLPreferences.setDisabledPromptShowWebWallet(true) }, failure: { (Bool) -> () in }) } else if TLSuggestions.instance().conditionToPromptTryColdWallet() { TLPreferences.setEnableColdWallet(true) TLPreferences.setEnableInAppSettingsKitColdWallet(true) let msg = TLDisplayStrings.TRY_OUR_NEW_COLD_WALLET_FEATURE_DESC_STRING() TLPrompts.promtForOK(self, title:TLDisplayStrings.TRY_OUR_NEW_COLD_WALLET_FEATURE_STRING(), message:msg, success: { () in TLPreferences.setDisabledPromptShowTryColdWallet(true) }) } else if let balance = AppDelegate.instance().receiveSelectedObject!.getBalanceForSelectedObject(), balance.greater(TLCoin.zero()) && !TLPreferences.hasShownBackupPassphrase() { self.showPromptThenPassphraseViewController() } if TLPreferences.getEnableBackupWithiCloud() { TLPreferences.setEnableBackupWithiCloud(false) TLPreferences.setInAppSettingsKitEnableBackupWithiCloud(false) TLPrompts.promptWithOneButton(self, title: TLDisplayStrings.ICLOUD_SUPPORT_DISCONTINUED(), message: TLDisplayStrings.ICLOUD_SUPPORT_DISCONTINUED_DESCRIPTION(), buttonText: TLDisplayStrings.I_UNDERSTAND(), success: { }) } self.navigationController!.view.addGestureRecognizer(self.slidingViewController().panGesture) } func showPromptThenPassphraseViewController() { TLPrompts.promptWithOneButton(self, title: TLDisplayStrings.WALLET_BACKUP_PASSPHRASE_WILL_BE_SHOWN(), message: TLDisplayStrings.PLEASE_WRITE_DOWN_OR_MEMORIZE_YOUR_WALLET_BACKUP_PASSPHRASE(), buttonText: TLDisplayStrings.I_UNDERSTAND(), success: { let vc = self.storyboard!.instantiateViewController(withIdentifier: "Passphrase") self.slidingViewController().present(vc, animated: true, completion: nil) }) } func _clearSendForm() { TLSendFormData.instance().setAddress(nil) TLSendFormData.instance().setAmount(nil) self.updateSendForm() } func clearSendForm(_ notification: Notification) { _clearSendForm() } fileprivate func updateSendForm() { self.toAddressTextField!.text = TLSendFormData.instance().getAddress() if (TLSendFormData.instance().getAmount() != nil) { self.amountTextField!.text = TLSendFormData.instance().getAmount()! self.updateFiatAmountTextFieldExchangeRate(nil) } else if (TLSendFormData.instance().getFiatAmount() != nil) { self.fiatAmountTextField!.text = TLSendFormData.instance().getFiatAmount()! self.updateAmountTextFieldExchangeRate(nil) } else { self.amountTextField!.text = nil self.fiatAmountTextField!.text = nil } } func _updateCurrencyView() { let currency = TLCurrencyFormat.getFiatCurrency() self.fiatCurrencyDisplayLabel!.text = currency self.updateSendForm() self.updateAmountTextFieldExchangeRate(nil) } func updateCurrencyView(_ notification: Notification) { _updateCurrencyView() } func _updateBitcoinDisplayView() { let bitcoinDisplay = TLCurrencyFormat.getBitcoinDisplay() self.bitcoinDisplayLabel!.text = bitcoinDisplay self.updateSendForm() self.updateFiatAmountTextFieldExchangeRate(nil) } func updateBitcoinDisplayView(_ notification: Notification) { _updateBitcoinDisplayView() } func dismissKeyboard() { self.amountTextField!.resignFirstResponder() self.fiatAmountTextField!.resignFirstResponder() self.toAddressTextField!.resignFirstResponder() if self.tapGesture != nil { self.view.removeGestureRecognizer(self.tapGesture!) self.tapGesture = nil } self.topView!.scrollToY(0) } func hideHUDAndUpdateBalanceView() { self.accountBalanceLabel!.isHidden = false self.balanceActivityIndicatorView!.stopAnimating() self.balanceActivityIndicatorView!.isHidden = true self._updateAccountBalanceView() } func _updateAccountBalanceView() { let balance = AppDelegate.instance().godSend!.getCurrentFromBalance() let balanceString = TLCurrencyFormat.getProperAmount(balance) self.accountBalanceLabel!.text = balanceString as String } func updateAccountBalanceView(_ notification: Notification) { self._updateAccountBalanceView() } func onAccountSelected(_ note: Notification) { NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: TLNotificationEvents.EVENT_ACCOUNT_SELECTED()), object: nil) let selectedDict = note.object as! NSDictionary let sendFromType = TLSendFromType(rawValue: selectedDict.object(forKey: "sendFromType") as! Int) let sendFromIndex = selectedDict.object(forKey: "sendFromIndex") as! Int AppDelegate.instance().updateGodSend(sendFromType!, sendFromIndex: sendFromIndex) self.updateViewToNewSelectedObject() } fileprivate func fillToAddressTextField(_ address: String) -> Bool { if (TLCoreBitcoinWrapper.isValidAddress(address, isTestnet: AppDelegate.instance().appWallet.walletConfig.isTestnet)) { self.toAddressTextField!.text = address TLSendFormData.instance().setAddress(address) return true } else { let av = UIAlertView(title: TLDisplayStrings.INVALID_ADDRESS_STRING(), message: "", delegate: nil, cancelButtonTitle: TLDisplayStrings.OK_STRING() ) av.show() return false } } fileprivate func checkTofetchFeeThenFinalPromptReviewTx() { if TLPreferences.enabledInAppSettingsKitDynamicFee() && !AppDelegate.instance().txFeeAPI.haveUpdatedCachedDynamicFees() { AppDelegate.instance().txFeeAPI.getDynamicTxFee({ (_jsonData) in self.showFinalPromptReviewTx() }, failure: { (code, status) in self.showFinalPromptReviewTx() }) } else { self.showFinalPromptReviewTx() } } fileprivate func showFinalPromptReviewTx() { let bitcoinAmount = self.amountTextField!.text let toAddress = self.toAddressTextField!.text if (!TLCoreBitcoinWrapper.isValidAddress(toAddress!, isTestnet: AppDelegate.instance().appWallet.walletConfig.isTestnet)) { TLPrompts.promptErrorMessage(TLDisplayStrings.ERROR_STRING(), message: TLDisplayStrings.INVALID_ADDRESS_STRING()) return } DLog("showFinalPromptReviewTx bitcoinAmount \(bitcoinAmount!)") let inputtedAmount = TLCurrencyFormat.properBitcoinAmountStringToCoin(bitcoinAmount!) if (inputtedAmount.equalTo(TLCoin.zero())) { TLPrompts.promptErrorMessage(TLDisplayStrings.ERROR_STRING(), message: TLDisplayStrings.INVALID_AMOUNT_STRING()) return } func showReviewPaymentViewController(_ useDynamicFees: Bool) { let fee:TLCoin let txSizeBytes:UInt64 if useDynamicFees { if TLSendFormData.instance().useAllFunds { fee = TLSendFormData.instance().feeAmount! } else { if (AppDelegate.instance().godSend!.getSelectedObjectType() == .account) { let accountObject = AppDelegate.instance().godSend!.getSelectedSendObject() as! TLAccountObject let inputCount = accountObject.getInputsNeededToConsume(inputtedAmount) //TODO account for change output, output count likely 2 (3 if have stealth payment) cause if user dont do click use all funds because will likely have change // but for now dont need to be fully accurate with tx fee, for now we will underestimate tx fee, wont underestimate much because outputs contributes little to tx size txSizeBytes = TLSpaghettiGodSend.getEstimatedTxSize(inputCount, outputCount: 1) DLog("showPromptReviewTx TLAccountObject useDynamicFees inputCount txSizeBytes: \(inputCount) \(txSizeBytes)") } else { let importedAddress = AppDelegate.instance().godSend!.getSelectedSendObject() as! TLImportedAddress // TODO same as above let inputCount = importedAddress.getInputsNeededToConsume(inputtedAmount) txSizeBytes = TLSpaghettiGodSend.getEstimatedTxSize(inputCount, outputCount: 1) DLog("showPromptReviewTx importedAddress useDynamicFees inputCount txSizeBytes: \(importedAddress.unspentOutputsCount) \(txSizeBytes)") } if let dynamicFeeSatoshis:NSNumber? = AppDelegate.instance().txFeeAPI.getCachedDynamicFee() { fee = TLCoin(uint64: txSizeBytes*dynamicFeeSatoshis!.uint64Value) DLog("showPromptReviewTx coinFeeAmount dynamicFeeSatoshis: \(txSizeBytes*dynamicFeeSatoshis!.uint64Value)") } else { fee = TLCurrencyFormat.bitcoinAmountStringToCoin(TLPreferences.getInAppSettingsKitTransactionFee()!) } TLSendFormData.instance().feeAmount = fee } } else { let feeAmount = TLPreferences.getInAppSettingsKitTransactionFee() fee = TLCurrencyFormat.bitcoinAmountStringToCoin(feeAmount!) TLSendFormData.instance().feeAmount = fee } let amountNeeded = inputtedAmount.add(fee) let accountBalance = AppDelegate.instance().godSend!.getCurrentFromBalance() if (amountNeeded.greater(accountBalance)) { let msg = String(format: TLDisplayStrings.YOU_HAVE_X_Y_BUT_Z_IS_NEEDED_STRING(), "\(TLCurrencyFormat.coinToProperBitcoinAmountString(accountBalance)) \(TLCurrencyFormat.getBitcoinDisplay())", TLCurrencyFormat.coinToProperBitcoinAmountString(amountNeeded)) TLPrompts.promptErrorMessage(TLDisplayStrings.INSUFFICIENT_FUNDS_STRING(), message: msg) return } DLog("showPromptReviewTx accountBalance: \(accountBalance.toUInt64())") DLog("showPromptReviewTx inputtedAmount: \(inputtedAmount.toUInt64())") DLog("showPromptReviewTx fee: \(fee.toUInt64())") TLSendFormData.instance().fromLabel = AppDelegate.instance().godSend!.getCurrentFromLabel()! let vc = self.storyboard!.instantiateViewController(withIdentifier: "ReviewPayment") as! TLReviewPaymentViewController self.slidingViewController().present(vc, animated: true, completion: nil) } func checkToFetchDynamicFees() { if !AppDelegate.instance().txFeeAPI.haveUpdatedCachedDynamicFees() { AppDelegate.instance().txFeeAPI.getDynamicTxFee({ (_jsonData) in showReviewPaymentViewController(true) }, failure: { (code, status) in TLPrompts.promptErrorMessage(TLDisplayStrings.ERROR_STRING(), message: TLDisplayStrings.UNABLE_TO_GET_DYNAMIC_FEES_STRING()) showReviewPaymentViewController(false) }) } else { showReviewPaymentViewController(true) } } if TLPreferences.enabledInAppSettingsKitDynamicFee() { if !AppDelegate.instance().godSend!.haveUpDatedUTXOs() { AppDelegate.instance().godSend!.getAndSetUnspentOutputs({ checkToFetchDynamicFees() }, failure: { TLPrompts.promptErrorMessage(TLDisplayStrings.ERROR_STRING(), message: TLDisplayStrings.ERROR_FETCHING_UNSPENT_OUTPUTS_TRY_AGAIN_LATER_STRING()) }) } else { checkToFetchDynamicFees() } } else { showReviewPaymentViewController(false) } } fileprivate func handleTempararyImportPrivateKey(_ privateKey: String) { if (!TLCoreBitcoinWrapper.isValidPrivateKey(privateKey, isTestnet: AppDelegate.instance().appWallet.walletConfig.isTestnet)) { TLPrompts.promptErrorMessage(TLDisplayStrings.ERROR_STRING(), message: TLDisplayStrings.INVALID_PRIVATE_KEY_STRING()) } else { let importedAddress = AppDelegate.instance().godSend!.getSelectedSendObject() as! TLImportedAddress? let success = importedAddress!.setPrivateKeyInMemory(privateKey) if (!success) { TLPrompts.promptSuccessMessage(TLDisplayStrings.ERROR_STRING(), message: TLDisplayStrings.PRIVATE_KEY_DOES_NOT_MATCH_ADDRESS_STRING()) } else { self.checkTofetchFeeThenFinalPromptReviewTx() } } } fileprivate func showPromptReviewTx() { if (AppDelegate.instance().godSend!.needWatchOnlyAccountPrivateKey()) { let accountObject = AppDelegate.instance().godSend!.getSelectedSendObject() as! TLAccountObject TLPrompts.promptForTempararyImportExtendedPrivateKey(self, success: { (data: String!) in if (!TLHDWalletWrapper.isValidExtendedPrivateKey(data)) { TLPrompts.promptErrorMessage(TLDisplayStrings.ERROR_STRING(), message: TLDisplayStrings.INVALID_ACCOUNT_PRIVATE_KEY_STRING()) } else { let success = accountObject.setExtendedPrivateKeyInMemory(data) if (!success) { TLPrompts.promptErrorMessage(TLDisplayStrings.ERROR_STRING(), message: TLDisplayStrings.ACCOUNT_PRIVATE_KEY_DOES_NOT_MATCH_STRING()) } else { self.checkTofetchFeeThenFinalPromptReviewTx() } } }, error: { (data: String?) in }) } else if (AppDelegate.instance().godSend!.needWatchOnlyAddressPrivateKey()) { TLPrompts.promptForTempararyImportPrivateKey(self, success: { (data: String!) in if (TLCoreBitcoinWrapper.isBIP38EncryptedKey(data, isTestnet: AppDelegate.instance().appWallet.walletConfig.isTestnet)) { TLPrompts.promptForEncryptedPrivKeyPassword(self, view:self.slidingViewController().topViewController.view, encryptedPrivKey: data, success: { (privKey: String!) in self.handleTempararyImportPrivateKey(privKey) }, failure: { (isCanceled: Bool) in }) } else { if AppDelegate.instance().scannedEncryptedPrivateKey == nil { self.handleTempararyImportPrivateKey(data) } } }, error: { (data: String?) in }) } else if (AppDelegate.instance().godSend!.needEncryptedPrivateKeyPassword()) { let encryptedPrivateKey = AppDelegate.instance().godSend!.getEncryptedPrivateKey() TLPrompts.promptForEncryptedPrivKeyPassword(self, view:self.slidingViewController().topViewController.view, encryptedPrivKey: encryptedPrivateKey, success: { (privKey: String!) in let importedAddress = AppDelegate.instance().godSend!.getSelectedSendObject() as! TLImportedAddress? let success = importedAddress!.setPrivateKeyInMemory(privKey) if (!success) { TLPrompts.promptSuccessMessage(TLDisplayStrings.ERROR_STRING(), message: TLDisplayStrings.PRIVATE_KEY_DOES_NOT_MATCH_ADDRESS_STRING()) } else { self.checkTofetchFeeThenFinalPromptReviewTx() } }, failure: { (isCanceled: Bool) in }) } else { self.checkTofetchFeeThenFinalPromptReviewTx() } } func dismissTextFieldsAndScrollDown(_ notification: Notification) { self.fiatAmountTextField!.resignFirstResponder() self.amountTextField!.resignFirstResponder() self.toAddressTextField!.resignFirstResponder() self.topView!.scrollToY(0) NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIApplicationDidEnterBackground, object: nil) } func onAddressSelected(_ note: Notification) { NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: TLNotificationEvents.EVENT_ADDRESS_SELECTED()), object: nil) let address = note.object as! String self.toAddressTextField!.text = address TLSendFormData.instance().setAddress(address) } func _reviewPaymentClicked() { self.showPromptReviewTx() } fileprivate func handleScannedAddress(_ data: String) { if (data.hasPrefix("bitcoin:")) { let parsedBitcoinURI = TLWalletUtils.parseBitcoinURI(data) if parsedBitcoinURI == nil { TLPrompts.promptErrorMessage(TLDisplayStrings.ERROR_STRING(), message: TLDisplayStrings.URL_DOES_NOT_CONTAIN_AN_ADDRESS_STRING()) return } let address = parsedBitcoinURI!.object(forKey: "address") as! String? if (address == nil) { TLPrompts.promptErrorMessage(TLDisplayStrings.ERROR_STRING(), message: TLDisplayStrings.URL_DOES_NOT_CONTAIN_AN_ADDRESS_STRING()) return } let success = self.fillToAddressTextField(address!) if (success) { let parsedBitcoinURIAmount = parsedBitcoinURI!.object(forKey: "amount") as! String? if (parsedBitcoinURIAmount != nil) { let coinAmount = TLCoin(bitcoinAmount: parsedBitcoinURIAmount!, bitcoinDenomination: TLBitcoinDenomination.bitcoin) let amountString = TLCurrencyFormat.coinToProperBitcoinAmountString(coinAmount) self.amountTextField!.text = amountString self.updateFiatAmountTextFieldExchangeRate(nil) TLSendFormData.instance().setAmount(amountString) TLSendFormData.instance().setFiatAmount(nil) } } } else { self.fillToAddressTextField(data) } } override func prepare(for segue: UIStoryboardSegue, sender: Any!) -> () { if (segue.identifier == "selectAccount") { let vc = segue.destination vc.navigationItem.title = TLDisplayStrings.SELECT_ACCOUNT_STRING() NotificationCenter.default.addObserver(self, selector: #selector(TLSendViewController.onAccountSelected(_:)), name: NSNotification.Name(rawValue: TLNotificationEvents.EVENT_ACCOUNT_SELECTED()), object: nil) } } func preFetchUTXOsAndDynamicFees() { DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.background).async { DLog("preFetchUTXOsAndDynamicFees") if TLPreferences.enabledInAppSettingsKitDynamicFee() { DLog("preFetchUTXOsAndDynamicFees enabledInAppSettingsKitDynamicFee") if !AppDelegate.instance().txFeeAPI.haveUpdatedCachedDynamicFees() { AppDelegate.instance().txFeeAPI.getDynamicTxFee({ (_jsonData) in DLog("preFetchUTXOsAndDynamicFees getDynamicTxFee success") }, failure: { (code, status) in DLog("preFetchUTXOsAndDynamicFees getDynamicTxFee failure") }) } if !AppDelegate.instance().godSend!.haveUpDatedUTXOs() { AppDelegate.instance().godSend!.getAndSetUnspentOutputs({ DLog("preFetchUTXOsAndDynamicFees getAndSetUnspentOutputs success") }, failure: { DLog("preFetchUTXOsAndDynamicFees getAndSetUnspentOutputs failure") }) } } } } @IBAction fileprivate func updateFiatAmountTextFieldExchangeRate(_ sender: AnyObject?) { let currency = TLCurrencyFormat.getFiatCurrency() let amount = TLCurrencyFormat.properBitcoinAmountStringToCoin(self.amountTextField!.text!) if (amount.greater(TLCoin.zero())) { self.fiatAmountTextField!.text = TLExchangeRate.instance().fiatAmountStringFromBitcoin(currency, bitcoinAmount: amount) TLSendFormData.instance().toAmount = amount } else { self.fiatAmountTextField!.text = nil TLSendFormData.instance().toAmount = nil } } @IBAction fileprivate func updateAmountTextFieldExchangeRate(_ sender: AnyObject?) { let currency = TLCurrencyFormat.getFiatCurrency() let fiatFormatter = NumberFormatter() fiatFormatter.numberStyle = .decimal fiatFormatter.maximumFractionDigits = 2 let fiatAmount = fiatFormatter.number(from: self.fiatAmountTextField!.text!) if fiatAmount != nil && fiatAmount! != 0 { let bitcoinAmount = TLExchangeRate.instance().bitcoinAmountFromFiat(currency, fiatAmount: fiatAmount!.doubleValue) self.amountTextField!.text = TLCurrencyFormat.coinToProperBitcoinAmountString(bitcoinAmount) TLSendFormData.instance().toAmount = bitcoinAmount } else { self.amountTextField!.text = nil TLSendFormData.instance().toAmount = nil } } @IBAction fileprivate func reviewPaymentClicked(_ sender: AnyObject) { self.dismissKeyboard() let toAddress = self.toAddressTextField!.text if toAddress != nil && TLStealthAddress.isStealthAddress(toAddress!, isTestnet: false) { if !TLWalletUtils.ENABLE_STEALTH_ADDRESS() { TLPrompts.promptWithOneButton(self, title: "", message: TLDisplayStrings.REUSABLE_ADDRESS_DISABLED(), buttonText: TLDisplayStrings.OK_STRING(), success: { }) return } func checkToShowStealthPaymentDelayInfo() { if TLSuggestions.instance().enabledShowStealthPaymentDelayInfo() && TLBlockExplorerAPI.STATIC_MEMBERS.blockExplorerAPI == .blockchain { let msg = TLDisplayStrings.REUSABLE_ADDRESS_BLOCKCHAIN_API_WARNING_STRING() TLPrompts.promtForOK(self, title:TLDisplayStrings.WARNING_STRING(), message:msg, success: { () in TLSuggestions.instance().setEnableShowStealthPaymentDelayInfo(false) }) } else { self._reviewPaymentClicked() } } if TLSuggestions.instance().enabledShowStealthPaymentNote() { let msg = TLDisplayStrings.STEALTH_PAYMENT_NOTE_STRING() TLPrompts.promtForOK(self, title:TLDisplayStrings.WARNING_STRING(), message:msg, success: { () in self._reviewPaymentClicked() TLSuggestions.instance().setEnableShowStealthPaymentNote(false) checkToShowStealthPaymentDelayInfo() }) } else { checkToShowStealthPaymentDelayInfo(); } } else { self._reviewPaymentClicked() } } @IBAction fileprivate func scanQRCodeClicked(_ sender: AnyObject) { AppDelegate.instance().showAddressReaderControllerFromViewController(self, success: { (data: String!) in self.handleScannedAddress(data) }, error: { (data: String?) in }) } @IBAction fileprivate func addressBookClicked(_ sender: AnyObject) { NotificationCenter.default.addObserver(self, selector: #selector(TLSendViewController.onAddressSelected(_:)), name: NSNotification.Name(rawValue: TLNotificationEvents.EVENT_ADDRESS_SELECTED()), object: nil) let vc = self.storyboard!.instantiateViewController(withIdentifier: "AddressBook") self.slidingViewController().present(vc, animated: true, completion: nil) } @IBAction fileprivate func menuButtonClicked(_ sender: AnyObject) { self.slidingViewController().anchorTopViewToRight(animated: true) } func textField(_ textField: UITextField, shouldChangeCharactersIn range: (NSRange), replacementString string: String) -> Bool { let newString = (textField.text as! NSString).replacingCharacters(in: range, with: string) if (textField == self.toAddressTextField) { TLSendFormData.instance().setAddress(newString) } else if (textField == self.amountTextField) { TLSendFormData.instance().setAmount(newString) TLSendFormData.instance().setFiatAmount(nil) TLSendFormData.instance().useAllFunds = false } else if (textField == self.fiatAmountTextField) { TLSendFormData.instance().setFiatAmount(newString) TLSendFormData.instance().setAmount(nil) TLSendFormData.instance().useAllFunds = false } return true } func textFieldDidBeginEditing(_ textField: UITextField) { if (self.tapGesture == nil) { self.tapGesture = UITapGestureRecognizer(target: self, action: #selector(TLSendViewController.dismissKeyboard)) self.view.addGestureRecognizer(self.tapGesture!) } self.setAllCoinsBarButton() } func textFieldShouldReturn(_ textField: UITextField) -> Bool { textField.resignFirstResponder() return true } func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { NotificationCenter.default.addObserver(self, selector: #selector(TLSendViewController.dismissTextFieldsAndScrollDown(_:)), name: NSNotification.Name.UIApplicationDidEnterBackground, object: nil) if TLUtils.isIPhone5() { if textField == self.amountTextField || textField == self.fiatAmountTextField { self.topView!.scrollToY(-140) } else { self.topView!.scrollToView(self.fiatAmountTextField!) } } else if TLUtils.isIPhone4() { if textField == self.amountTextField || textField == self.fiatAmountTextField { self.topView!.scrollToY(-230) } else { self.topView!.scrollToView(self.fiatAmountTextField!) } } if textField == self.amountTextField || textField == self.fiatAmountTextField { self.preFetchUTXOsAndDynamicFees() } return true } func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { if textField != self.toAddressTextField { self.clearRightBarButton() } textField.resignFirstResponder() return true } func textFieldShouldClear(_ textField: UITextField) -> Bool { if (textField == self.toAddressTextField) { TLSendFormData.instance().setAddress(nil) } return true } func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) { if (item.tag == 1) { self.showReceiveView() } } deinit { NotificationCenter.default.removeObserver(self) } } ================================================ FILE: ArcBit/viewControllers/TLSettingsViewController.swift ================================================ // // TLSettingsViewController.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import UIKit import AVFoundation @objc(TLSettingsViewController) class TLSettingsViewController:IASKAppSettingsViewController, IASKSettingsDelegate,LTHPasscodeViewControllerDelegate { override var preferredStatusBarStyle : (UIStatusBarStyle) { return .lightContent } override func viewDidLoad() { super.viewDidLoad() setColors() AppDelegate.instance().setSettingsPasscodeViewColors() AppDelegate.instance().giveExitAppNoticeForBlockExplorerAPIToTakeEffect = false LTHPasscodeViewController.sharedUser().delegate = self self.neverShowPrivacySettings = true self.delegate = self NotificationCenter.default.addObserver(self, selector: #selector(TLSettingsViewController.settingDidChange(_:)), name: NSNotification.Name(rawValue: kIASKAppSettingChanged), object: nil) self.updateHiddenKeys() } override func viewDidAppear(_ animated: Bool) -> () { NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_VIEW_SETTINGS_SCREEN()), object: nil) if (AppDelegate.instance().giveExitAppNoticeForBlockExplorerAPIToTakeEffect) { TLPrompts.promptSuccessMessage("", message: TLDisplayStrings.KILL_THIS_APP_DESC_STRING()) AppDelegate.instance().giveExitAppNoticeForBlockExplorerAPIToTakeEffect = false } } @IBAction fileprivate func menuButtonClicked(_ sender: UIButton) { self.slidingViewController().anchorTopViewToRight(animated: true) } fileprivate func updateHiddenKeys() { let hiddenKeys = NSMutableSet() if (TLPreferences.enabledInAppSettingsKitDynamicFee()) { hiddenKeys.add("transactionfee") hiddenKeys.add("settransactionfee") } else { hiddenKeys.add("dynamicfeeoption") } if (!LTHPasscodeViewController.doesPasscodeExist()) { hiddenKeys.add("changepincode") } let blockExplorerAPI = TLPreferences.getBlockExplorerAPI() if (blockExplorerAPI == .blockchain) { hiddenKeys.add("blockexplorerurl") hiddenKeys.add("setblockexplorerurl") } else { if (blockExplorerAPI == .insight) { let blockExplorerURL = TLPreferences.getBlockExplorerURL(blockExplorerAPI) TLPreferences.setInAppSettingsKitBlockExplorerURL(blockExplorerURL!) } } if (!TLWalletUtils.ENABLE_STEALTH_ADDRESS()) { hiddenKeys.add("stealthaddressdefault") hiddenKeys.add("stealthaddressfooter") } self.setHiddenKeys(hiddenKeys as Set, animated: true) } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) } fileprivate func showPromptForSetBlockExplorerURL() { UIAlertController.showAlert(in: self, withTitle: TLDisplayStrings.CHANGE_BLOCK_EXPLORER_URL_STRING(), message: "", preferredStyle: .alert, cancelButtonTitle: TLDisplayStrings.CANCEL_STRING(), destructiveButtonTitle: nil, otherButtonTitles: [TLDisplayStrings.OK_STRING()], preShow: {(controller) in func addPromptTextField(_ textField: UITextField!){ textField.placeholder = "" textField.text = "http://" } controller!.addTextField(configurationHandler: addPromptTextField) } , tap: {(alertView, action, buttonIndex) in if (buttonIndex == alertView!.firstOtherButtonIndex) { let candidate = (alertView!.textFields![0] ).text let candidateURL = URL(string: candidate!) if (candidateURL != nil && candidateURL!.host != nil) { TLPreferences.setInAppSettingsKitBlockExplorerURL(candidateURL!.absoluteString) TLPreferences.setBlockExplorerURL(TLPreferences.getBlockExplorerAPI(), value: candidateURL!.absoluteString) TLPrompts.promptSuccessMessage("", message: TLDisplayStrings.KILL_THIS_APP_DESC_STRING()) } else { UIAlertController.showAlert(in: self, withTitle: TLDisplayStrings.INVALID_URL_STRING(), message: "", cancelButtonTitle: TLDisplayStrings.OK_STRING(), destructiveButtonTitle: nil, otherButtonTitles: nil, tap: {(alertView, action, buttonIndex) in self.showPromptForSetBlockExplorerURL() }) } } else if (buttonIndex == alertView!.cancelButtonIndex) { } }) } fileprivate func showPromptForSetTransactionFee() { let msg = String(format: TLDisplayStrings.SET_TRANSACTION_FEE_IN_X_STRING(), TLCurrencyFormat.getBitcoinDisplay()) func addTextField(_ textField: UITextField!){ textField.placeholder = "" textField.keyboardType = .decimalPad } UIAlertController.showAlert(in: self, withTitle: TLDisplayStrings.TRANSACTION_FEE_STRING(), message: msg, preferredStyle: .alert, cancelButtonTitle: TLDisplayStrings.CANCEL_STRING(), destructiveButtonTitle: nil, otherButtonTitles: [TLDisplayStrings.OK_STRING()], preShow: {(controller) in controller!.addTextField(configurationHandler: addTextField) } , tap: {(alertView, action, buttonIndex) in if (buttonIndex == alertView!.firstOtherButtonIndex) { let feeAmount = (alertView!.textFields![0] ).text let feeAmountCoin = TLCurrencyFormat.bitcoinAmountStringToCoin(feeAmount!) TLPreferences.setInAppSettingsKitTransactionFee(feeAmountCoin.bigIntegerToBitcoinAmountString(.bitcoin)) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_CHANGE_AUTOMATIC_TX_FEE()), object: nil) } else if (buttonIndex == alertView!.cancelButtonIndex) { } }) } override func tableView(_ tableView: UITableView, heightForHeaderInSection: Int) -> CGFloat { return 30.0 } func settingsViewControllerDidEnd(_ sender: IASKAppSettingsViewController) { self.dismiss(animated: true, completion: nil) } func settingsViewController(_ settingsViewController: IASKViewController?, tableView: UITableView, heightForHeaderForSection section: Int) -> CGFloat { let key = settingsViewController!.settingsReader.key(forSection: section) if (key == "IASKLogo") { return UIImage(named: "Icon.png")!.size.height + 25 } else if (key == "IASKCustomHeaderStyle") { return 55 } return 0 } fileprivate func switchPasscodeType(_ sender: UISwitch) { LTHPasscodeViewController.sharedUser().setIsSimple(sender.isOn, in: self, asModal: true) } fileprivate func showLockViewForEnablingPasscode() { LTHPasscodeViewController.sharedUser().showForEnablingPasscode(in: self, asModal: true) } fileprivate func showLockViewForChangingPasscode() { LTHPasscodeViewController.sharedUser().showForChangingPasscode(in: self, asModal: true) } fileprivate func showLockViewForTurningPasscodeOff() { LTHPasscodeViewController.sharedUser().showForDisablingPasscode(in: self, asModal: true) } func settingDidChange(_ info: Notification) { let userInfo = (info as NSNotification).userInfo! as NSDictionary for key1 in userInfo.allKeys { guard let didChangeKey = key1 as? String else { return } if (didChangeKey == "enablecoldwallet") { let enabled = (userInfo.object(forKey: "enablecoldwallet")) as! Bool TLPreferences.setEnableColdWallet(enabled) } else if (didChangeKey == "enableadvancemode") { let enabled = (userInfo.object(forKey: "enableadvancemode")) as! Bool TLPreferences.setAdvancedMode(enabled) } else if (didChangeKey == "canrestoredeletedapp") { let enabled = (userInfo.object(forKey: "canrestoredeletedapp")) as! Bool TLPreferences.setInAppSettingsCanRestoreDeletedApp(!enabled) // make sure below code completes firstbefore enabling if enabled { // settingDidChange gets called twice on one change, so need to do this if (TLPreferences.getEncryptedWalletPassphraseKey() == nil) { TLPreferences.setInAppSettingsCanRestoreDeletedApp(enabled) return } TLWalletPassphrase.enableRecoverableFeature(TLPreferences.canRestoreDeletedApp()) } else { // settingDidChange gets called twice on one change, so need to do this if (TLPreferences.getEncryptedWalletPassphraseKey() != nil) { TLPreferences.setInAppSettingsCanRestoreDeletedApp(enabled) return } TLWalletPassphrase.disableRecoverableFeature(TLPreferences.canRestoreDeletedApp()) } TLPreferences.setInAppSettingsCanRestoreDeletedApp(enabled) TLPreferences.setCanRestoreDeletedApp(enabled) } else if (didChangeKey == "enablepincode") { let enabled = userInfo.object(forKey: "enablepincode") as! Bool TLPreferences.setEnablePINCode(enabled) if (!LTHPasscodeViewController.doesPasscodeExist()) { self.showLockViewForEnablingPasscode() } else { self.showLockViewForTurningPasscodeOff() } } else if (didChangeKey == "displaylocalcurrency") { let enabled = userInfo.object(forKey: "displaylocalcurrency") as! Bool TLPreferences.setDisplayLocalCurrency(enabled) } else if (didChangeKey == "dynamicfeeoption") { } else if (didChangeKey == "enabledynamicfee") { self.updateHiddenKeys() } else if (didChangeKey == "currency") { let currencyIdx = userInfo.object(forKey: "currency") as! String TLPreferences.setCurrency(currencyIdx) } else if (didChangeKey == "bitcoindisplay") { let bitcoindisplayIdx = userInfo.object(forKey: "bitcoindisplay") as! String TLPreferences.setBitcoinDisplay(bitcoindisplayIdx) } else if (didChangeKey == "stealthaddressdefault") { let enabled = userInfo.object(forKey: "stealthaddressdefault") as! Bool TLPreferences.setEnabledStealthAddressDefault(enabled) } else if (didChangeKey == "blockexplorerapi") { let blockexplorerAPIIdx = userInfo.object(forKey: "blockexplorerapi") as! String TLPreferences.setBlockExplorerAPI(blockexplorerAPIIdx) TLPreferences.resetBlockExplorerAPIURL() self.updateHiddenKeys() AppDelegate.instance().giveExitAppNoticeForBlockExplorerAPIToTakeEffect = true NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_CHANGE_BLOCKEXPLORER_TYPE()), object: nil) } } } func settingsViewController(_ sender: IASKAppSettingsViewController, buttonTappedFor specifier: IASKSpecifier) { if (specifier.key() == "changepincode") { self.showLockViewForChangingPasscode() } else if (specifier.key() == "showpassphrase") { let vc = self.storyboard!.instantiateViewController(withIdentifier: "Passphrase") self.slidingViewController().present(vc, animated: true, completion: nil) } else if (specifier.key() == "restorewallet") { let vc = self.storyboard!.instantiateViewController(withIdentifier: "EnterMnemonic") self.slidingViewController().present(vc, animated: true, completion: nil) } else if (specifier.key() == "settransactionfee") { self.showPromptForSetTransactionFee() } else if (specifier.key() == "setblockexplorerurl") { self.showPromptForSetBlockExplorerURL() } } func passcodeViewControllerWillClose() { if (LTHPasscodeViewController.doesPasscodeExist()) { TLPreferences.setInAppSettingsKitEnablePinCode(true) TLPreferences.setEnablePINCode(true) NotificationCenter.default.post(name: Notification.Name(rawValue: TLNotificationEvents.EVENT_ENABLE_PIN_CODE()), object: nil) } else { TLPreferences.setInAppSettingsKitEnablePinCode(false) TLPreferences.setEnablePINCode(false) } self.updateHiddenKeys() } func maxNumberOfFailedAttemptsReached() { } func passcodeWasEnteredSuccessfully() { } func logoutButtonWasPressed() { } deinit { NotificationCenter.default.removeObserver(self) } } ================================================ FILE: ArcBit/viewControllers/TLTextViewViewController.swift ================================================ // // TLTextViewViewController.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit @objc(TLTextViewViewController) class TLTextViewViewController:UIViewController { var text:String? @IBOutlet fileprivate var textView:UITextView? required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } override func viewDidLoad() { super.viewDidLoad() setColors() self.textView!.backgroundColor = TLColors.mainAppColor() self.textView!.text = self.text self.textView!.textColor = (TLColors.mainAppOppositeColor()) if(self.textView!.font != nil) { self.textView!.font = (UIFont(name:self.textView!.font!.fontName, size:26.0)) } } } ================================================ FILE: ArcBit/viewControllers/TLTransactionTableViewCell.swift ================================================ // // TLTransactionTableViewCell.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit @objc(TLTransactionTableViewCell) class TLTransactionTableViewCell:UITableViewCell { required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } @IBOutlet var dateLabel:UILabel? @IBOutlet var currencyLabel:UILabel? @IBOutlet var amountLabel:UILabel? @IBOutlet var descriptionLabel:UILabel? @IBOutlet var confirmationsLabel:UILabel? @IBOutlet var amountButton:UIButton? @IBOutlet var confirmedStatusImageView:UIImageView? @IBAction fileprivate func amountButtonClicked(_ sender:UIButton) { TLPreferences.setDisplayLocalCurrency(!TLPreferences.isDisplayLocalCurrency()) TLPreferences.setInAppSettingsKitDisplayLocalCurrency(TLPreferences.isDisplayLocalCurrency()) } override init(style:UITableViewCellStyle, reuseIdentifier:String?) { super.init(style:style, reuseIdentifier:reuseIdentifier) } override func awakeFromNib() { super.awakeFromNib() self.amountButton!.backgroundColor = TLColors.mainAppColor() self.amountButton!.setTitleColor(TLColors.mainAppOppositeColor(), for:UIControlState()) self.amountButton!.titleLabel!.adjustsFontSizeToFitWidth = true } override func setSelected(_ selected:Bool, animated:Bool) -> () { super.setSelected(selected, animated:animated) } } ================================================ FILE: ArcBit/viewControllers/TransparentViewController.swift ================================================ // // TransparentViewController.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit @objc(TransparentViewController) class TransparentViewController:UIViewController { required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } override var preferredStatusBarStyle : (UIStatusBarStyle) { return UIStatusBarStyle.lightContent } override func viewWillAppear(_ animated: Bool) { self.slidingViewController()!.topViewController = self.storyboard!.instantiateViewController(withIdentifier: "SendNav") } } ================================================ FILE: ArcBit/viewControllers/tableViewCells/createColdWalletTableViewCells/TLAdvancedNewWalletTableViewCell.swift ================================================ // // TLAdvancedNewWalletTableViewCell.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit protocol TLAdvancedNewWalletTableViewCellDelegate { func didAdvancedNewWalletClickShowQRCodeButton(_ cell: TLAdvancedNewWalletTableViewCell, data: String) } @objc(TLAdvancedNewWalletTableViewCell) class TLAdvancedNewWalletTableViewCell:UITableViewCell { required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } @IBOutlet weak var backupPassphraseLabel: UILabel! @IBOutlet weak var accountIDLabel: UILabel! @IBOutlet weak var accountPublicKeyLabel: UILabel! @IBOutlet weak var accountPrivateKeyLabel: UILabel! @IBOutlet weak var startingReceivingAddressID: UILabel! @IBOutlet weak var startingChangeAddressID: UILabel! @IBOutlet var mnemonicTextView:UITextView! @IBOutlet var newWalletButton:UIButton! @IBOutlet var accountIDTextField: UITextField! @IBOutlet var accountPublicKeyTextView:UITextView! @IBOutlet var showAccountPublicKeyQRButton:UIButton! @IBOutlet var accountPrivateKeyTextView:UITextView! @IBOutlet var showAccountPrivateKeyQRButton:UIButton! @IBOutlet weak var startingAddressIDTextField: UITextField! @IBOutlet weak var addressLabel1: UILabel! @IBOutlet weak var addressTextField1: UITextField! @IBOutlet weak var privateKeyTextField1: UITextField! @IBOutlet weak var showAddressQRCodeButton1: UIButton! @IBOutlet weak var showPrivateKeyQRCodeButton1: UIButton! @IBOutlet weak var addressLabel2: UILabel! @IBOutlet weak var addressTextField2: UITextField! @IBOutlet weak var privateKeyTextField2: UITextField! @IBOutlet weak var showAddressQRCodeButton2: UIButton! @IBOutlet weak var showPrivateKeyQRCodeButton2: UIButton! @IBOutlet weak var addressLabel3: UILabel! @IBOutlet weak var addressTextField3: UITextField! @IBOutlet weak var privateKeyTextField3: UITextField! @IBOutlet weak var showAddressQRCodeButton3: UIButton! @IBOutlet weak var showPrivateKeyQRCodeButton3: UIButton! @IBOutlet weak var addressLabel4: UILabel! @IBOutlet weak var addressTextField4: UITextField! @IBOutlet weak var privateKeyTextField4: UITextField! @IBOutlet weak var showAddressQRCodeButton4: UIButton! @IBOutlet weak var showPrivateKeyQRCodeButton4: UIButton! @IBOutlet weak var addressLabel5: UILabel! @IBOutlet weak var addressTextField5: UITextField! @IBOutlet weak var privateKeyTextField5: UITextField! @IBOutlet weak var showAddressQRCodeButton5: UIButton! @IBOutlet weak var showPrivateKeyQRCodeButton5: UIButton! @IBOutlet weak var startingChangeAddressIDTextField: UITextField! @IBOutlet weak var changeAddressLabel1: UILabel! @IBOutlet weak var changeAddressTextField1: UITextField! @IBOutlet weak var changePrivateKeyTextField1: UITextField! @IBOutlet weak var showChangeAddressQRCodeButton1: UIButton! @IBOutlet weak var showChangePrivateKeyQRCodeButton1: UIButton! @IBOutlet weak var changeAddressLabel2: UILabel! @IBOutlet weak var changeAddressTextField2: UITextField! @IBOutlet weak var changePrivateKeyTextField2: UITextField! @IBOutlet weak var showChangeAddressQRCodeButton2: UIButton! @IBOutlet weak var showChangePrivateKeyQRCodeButton2: UIButton! @IBOutlet weak var changeAddressLabel3: UILabel! @IBOutlet weak var changeAddressTextField3: UITextField! @IBOutlet weak var changePrivateKeyTextField3: UITextField! @IBOutlet weak var showChangeAddressQRCodeButton3: UIButton! @IBOutlet weak var showChangePrivateKeyQRCodeButton3: UIButton! @IBOutlet weak var changeAddressLabel4: UILabel! @IBOutlet weak var changeAddressTextField4: UITextField! @IBOutlet weak var changePrivateKeyTextField4: UITextField! @IBOutlet weak var showChangeAddressQRCodeButton4: UIButton! @IBOutlet weak var showChangePrivateKeyQRCodeButton4: UIButton! @IBOutlet weak var changeAddressLabel5: UILabel! @IBOutlet weak var changeAddressTextField5: UITextField! @IBOutlet weak var changePrivateKeyTextField5: UITextField! @IBOutlet weak var showChangeAddressQRCodeButton5: UIButton! @IBOutlet weak var showChangePrivateKeyQRCodeButton5: UIButton! var delegate: TLAdvancedNewWalletTableViewCellDelegate? fileprivate lazy var coldWalletKeyType: TLColdWalletKeyType = .mnemonic fileprivate var isTestnet = AppDelegate.instance().appWallet.walletConfig.isTestnet fileprivate var extendedPrivateKey: String? fileprivate var extendedPublicKey: String? override init(style:UITableViewCellStyle, reuseIdentifier:String?) { super.init(style:style, reuseIdentifier:reuseIdentifier) } override func awakeFromNib() { super.awakeFromNib() self.backupPassphraseLabel.text = TLDisplayStrings.BACK_UP_PASSPHRASE_STRING()+":" self.accountIDLabel.text = TLDisplayStrings.ACCOUNT_ID_STRING()+":" self.accountPublicKeyLabel.text = TLDisplayStrings.ACCOUNT_PUBLIC_KEY_STRING()+":" self.accountPrivateKeyLabel.text = TLDisplayStrings.ACCOUNT_PRIVATE_KEY_STRING()+":" self.startingReceivingAddressID.text = TLDisplayStrings.STARTING_RECEIVING_ADDRESS_ID() self.startingChangeAddressID.text = TLDisplayStrings.STARTING_CHANGE_ADDRESS_ID() self.newWalletButton.setTitle(TLDisplayStrings.NEW_WALLET(), for: .normal) self.showAccountPublicKeyQRButton.setTitle(TLDisplayStrings.QR_CODE_STRING(), for: .normal) self.showAccountPrivateKeyQRButton.setTitle(TLDisplayStrings.QR_CODE_STRING(), for: .normal) self.showAddressQRCodeButton1.setTitle(TLDisplayStrings.QR_CODE_STRING(), for: .normal) self.showPrivateKeyQRCodeButton1.setTitle(TLDisplayStrings.QR_CODE_STRING(), for: .normal) self.showAddressQRCodeButton2.setTitle(TLDisplayStrings.QR_CODE_STRING(), for: .normal) self.showPrivateKeyQRCodeButton2.setTitle(TLDisplayStrings.QR_CODE_STRING(), for: .normal) self.showAddressQRCodeButton3.setTitle(TLDisplayStrings.QR_CODE_STRING(), for: .normal) self.showPrivateKeyQRCodeButton3.setTitle(TLDisplayStrings.QR_CODE_STRING(), for: .normal) self.showAddressQRCodeButton4.setTitle(TLDisplayStrings.QR_CODE_STRING(), for: .normal) self.showPrivateKeyQRCodeButton4.setTitle(TLDisplayStrings.QR_CODE_STRING(), for: .normal) self.showAddressQRCodeButton5.setTitle(TLDisplayStrings.QR_CODE_STRING(), for: .normal) self.showPrivateKeyQRCodeButton5.setTitle(TLDisplayStrings.QR_CODE_STRING(), for: .normal) self.showChangeAddressQRCodeButton1.setTitle(TLDisplayStrings.QR_CODE_STRING(), for: .normal) self.showChangePrivateKeyQRCodeButton1.setTitle(TLDisplayStrings.QR_CODE_STRING(), for: .normal) self.showChangeAddressQRCodeButton2.setTitle(TLDisplayStrings.QR_CODE_STRING(), for: .normal) self.showChangePrivateKeyQRCodeButton2.setTitle(TLDisplayStrings.QR_CODE_STRING(), for: .normal) self.showChangeAddressQRCodeButton3.setTitle(TLDisplayStrings.QR_CODE_STRING(), for: .normal) self.showChangePrivateKeyQRCodeButton3.setTitle(TLDisplayStrings.QR_CODE_STRING(), for: .normal) self.showChangeAddressQRCodeButton4.setTitle(TLDisplayStrings.QR_CODE_STRING(), for: .normal) self.showChangePrivateKeyQRCodeButton4.setTitle(TLDisplayStrings.QR_CODE_STRING(), for: .normal) self.showChangeAddressQRCodeButton5.setTitle(TLDisplayStrings.QR_CODE_STRING(), for: .normal) self.showChangePrivateKeyQRCodeButton5.setTitle(TLDisplayStrings.QR_CODE_STRING(), for: .normal) self.mnemonicTextView.layer.borderWidth = 1.0 self.mnemonicTextView.layer.borderColor = UIColor.black.cgColor self.mnemonicTextView.autocorrectionType = UITextAutocorrectionType.no self.mnemonicTextView.autocapitalizationType = .none self.newWalletButton.backgroundColor = TLColors.mainAppColor() self.newWalletButton.setTitleColor(TLColors.mainAppOppositeColor(), for:UIControlState()) self.newWalletButton.titleLabel!.adjustsFontSizeToFitWidth = true self.accountIDTextField.keyboardType = UIKeyboardType.numberPad self.accountPublicKeyTextView.layer.borderWidth = 1.0 self.showAccountPublicKeyQRButton.backgroundColor = TLColors.mainAppColor() self.showAccountPublicKeyQRButton.setTitleColor(TLColors.mainAppOppositeColor(), for:UIControlState()) self.accountPrivateKeyTextView.layer.borderWidth = 1.0 self.showAccountPrivateKeyQRButton.backgroundColor = TLColors.mainAppColor() self.showAccountPrivateKeyQRButton.setTitleColor(TLColors.mainAppOppositeColor(), for:UIControlState()) self.updateWalletKeys() self.startingAddressIDTextField.keyboardType = UIKeyboardType.numberPad self.showAddressQRCodeButton1.setTitleColor(TLColors.mainAppOppositeColor(), for:UIControlState()) self.showAddressQRCodeButton1.backgroundColor = TLColors.mainAppColor() self.showPrivateKeyQRCodeButton1.setTitleColor(TLColors.mainAppOppositeColor(), for:UIControlState()) self.showPrivateKeyQRCodeButton1.backgroundColor = TLColors.mainAppColor() self.showAddressQRCodeButton2.setTitleColor(TLColors.mainAppOppositeColor(), for:UIControlState()) self.showAddressQRCodeButton2.backgroundColor = TLColors.mainAppColor() self.showPrivateKeyQRCodeButton2.setTitleColor(TLColors.mainAppOppositeColor(), for:UIControlState()) self.showPrivateKeyQRCodeButton2.backgroundColor = TLColors.mainAppColor() self.showAddressQRCodeButton3.setTitleColor(TLColors.mainAppOppositeColor(), for:UIControlState()) self.showAddressQRCodeButton3.backgroundColor = TLColors.mainAppColor() self.showPrivateKeyQRCodeButton3.setTitleColor(TLColors.mainAppOppositeColor(), for:UIControlState()) self.showPrivateKeyQRCodeButton3.backgroundColor = TLColors.mainAppColor() self.showAddressQRCodeButton4.setTitleColor(TLColors.mainAppOppositeColor(), for:UIControlState()) self.showAddressQRCodeButton4.backgroundColor = TLColors.mainAppColor() self.showPrivateKeyQRCodeButton4.setTitleColor(TLColors.mainAppOppositeColor(), for:UIControlState()) self.showPrivateKeyQRCodeButton4.backgroundColor = TLColors.mainAppColor() self.showAddressQRCodeButton5.setTitleColor(TLColors.mainAppOppositeColor(), for:UIControlState()) self.showAddressQRCodeButton5.backgroundColor = TLColors.mainAppColor() self.showPrivateKeyQRCodeButton5.setTitleColor(TLColors.mainAppOppositeColor(), for:UIControlState()) self.showPrivateKeyQRCodeButton5.backgroundColor = TLColors.mainAppColor() self.startingChangeAddressIDTextField.keyboardType = UIKeyboardType.numberPad self.showChangeAddressQRCodeButton1.setTitleColor(TLColors.mainAppOppositeColor(), for:UIControlState()) self.showChangeAddressQRCodeButton1.backgroundColor = TLColors.mainAppColor() self.showChangePrivateKeyQRCodeButton1.setTitleColor(TLColors.mainAppOppositeColor(), for:UIControlState()) self.showChangePrivateKeyQRCodeButton1.backgroundColor = TLColors.mainAppColor() self.showChangeAddressQRCodeButton2.setTitleColor(TLColors.mainAppOppositeColor(), for:UIControlState()) self.showChangeAddressQRCodeButton2.backgroundColor = TLColors.mainAppColor() self.showChangePrivateKeyQRCodeButton2.setTitleColor(TLColors.mainAppOppositeColor(), for:UIControlState()) self.showChangePrivateKeyQRCodeButton2.backgroundColor = TLColors.mainAppColor() self.showChangeAddressQRCodeButton3.setTitleColor(TLColors.mainAppOppositeColor(), for:UIControlState()) self.showChangeAddressQRCodeButton3.backgroundColor = TLColors.mainAppColor() self.showChangePrivateKeyQRCodeButton3.setTitleColor(TLColors.mainAppOppositeColor(), for:UIControlState()) self.showChangePrivateKeyQRCodeButton3.backgroundColor = TLColors.mainAppColor() self.showChangeAddressQRCodeButton4.setTitleColor(TLColors.mainAppOppositeColor(), for:UIControlState()) self.showChangeAddressQRCodeButton4.backgroundColor = TLColors.mainAppColor() self.showChangePrivateKeyQRCodeButton4.setTitleColor(TLColors.mainAppOppositeColor(), for:UIControlState()) self.showChangePrivateKeyQRCodeButton4.backgroundColor = TLColors.mainAppColor() self.showChangeAddressQRCodeButton5.setTitleColor(TLColors.mainAppOppositeColor(), for:UIControlState()) self.showChangeAddressQRCodeButton5.backgroundColor = TLColors.mainAppColor() self.showChangePrivateKeyQRCodeButton5.setTitleColor(TLColors.mainAppOppositeColor(), for:UIControlState()) self.showChangePrivateKeyQRCodeButton5.backgroundColor = TLColors.mainAppColor() self.mnemonicTextView.text = nil self.accountPublicKeyTextView.text = nil self.accountPrivateKeyTextView.text = nil self.updateAccountIDTextField(false) self.updateAccountPublicKeyTextView(nil) self.updateAccountPrivateKeyTextView(nil) self.clearAddressFields() } class func cellHeight() -> CGFloat { return 1359 } fileprivate func enableTextView(_ textView:UITextView, enable:Bool) { if enable { textView.isUserInteractionEnabled = true textView.alpha = 1 } else { textView.isUserInteractionEnabled = false textView.alpha = 0.5 } } fileprivate func enableTextField(_ textField:UITextField, enable:Bool) { if enable { textField.isEnabled = true textField.alpha = 1.0 } else { textField.isEnabled = false textField.alpha = 0.5 } } fileprivate func enableButton(_ button:UIButton, enable:Bool) { if enable { button.isEnabled = true button.alpha = 1.0 } else { button.isEnabled = false button.alpha = 0.5 } } func updateWalletKeys() { if self.coldWalletKeyType == .mnemonic { self.enableTextView(self.mnemonicTextView, enable: true) self.enableButton(self.newWalletButton, enable: true) self.updateAccountIDTextField(false) self.enableTextView(self.accountPrivateKeyTextView, enable: false) self.enableTextView(self.accountPublicKeyTextView, enable: false) self.didUpdateMnemonic(self.mnemonicTextView.text!) } else if self.coldWalletKeyType == .accountPublicKey { self.mnemonicTextView.text = nil self.enableTextView(self.mnemonicTextView, enable: false) self.enableButton(self.newWalletButton, enable: false) self.updateAccountIDTextField(false) self.accountPrivateKeyTextView.text = nil self.enableTextView(self.accountPrivateKeyTextView, enable: false) self.enableTextView(self.accountPublicKeyTextView, enable: true) self.enableButton(self.showAccountPrivateKeyQRButton, enable: false) self.didUpdateAccountPublicKey(self.accountPublicKeyTextView.text!) } else if self.coldWalletKeyType == .accountPrivateKey { self.mnemonicTextView.text = nil self.enableTextView(self.mnemonicTextView, enable: false) self.enableButton(self.newWalletButton, enable: false) self.updateAccountIDTextField(false) self.enableTextView(self.accountPrivateKeyTextView, enable: true) self.enableTextView(self.accountPublicKeyTextView, enable: false) self.didUpdateAccountPrivateKey(self.accountPrivateKeyTextView.text!) } } func updateCellWithColdWalletKeyType(_ coldWalletKeyType: TLColdWalletKeyType) { self.coldWalletKeyType = coldWalletKeyType self.updateWalletKeys() } func didUpdateMnemonic(_ mnemonicPassphrase: String, accountID: Int? = nil) { if TLHDWalletWrapper.phraseIsValid(mnemonicPassphrase) { let masterHex = TLHDWalletWrapper.getMasterHex(mnemonicPassphrase) var HDAccountID:Int? = 0 if accountID == nil { HDAccountID = Int(self.accountIDTextField.text!) if HDAccountID == nil { HDAccountID = 0 } } else { HDAccountID = accountID } self.updateAccountIDTextField(true) let extendedPublicKey = TLHDWalletWrapper.getExtendPubKeyFromMasterHex(masterHex, accountIdx: UInt(HDAccountID!)) self.accountPublicKeyTextView.text = extendedPublicKey self.updateAccountPublicKeyTextView(extendedPublicKey) let extendedPrivateKey = TLHDWalletWrapper.getExtendPrivKey(masterHex, accountIdx: UInt(HDAccountID!)) self.accountPrivateKeyTextView.text = extendedPrivateKey self.updateAccountPrivateKeyTextView(extendedPrivateKey) self.updateAddressFieldsWithStartingAddressID() self.updateChangeAddressFieldsWithStartingAddressID() } else { self.updateAccountIDTextField(false) self.accountPublicKeyTextView.text = nil self.accountPrivateKeyTextView.text = nil self.updateAccountPublicKeyTextView(nil) self.updateAccountPrivateKeyTextView(nil) self.clearAddressFields() } } func didUpdateAccountPublicKey(_ accountPublicKey: String?) { if !accountPublicKey!.isEmpty && TLHDWalletWrapper.isValidExtendedPublicKey(accountPublicKey!) { let accoundIdx = TLHDWalletWrapper.getAccountIdxForExtendedKey(accountPublicKey!) self.accountIDTextField.text = String(accoundIdx) self.enableButton(self.showAccountPublicKeyQRButton, enable: true) self.updateAddressFieldsWithStartingAddressID() self.updateChangeAddressFieldsWithStartingAddressID() } else { self.accountIDTextField.text = nil self.enableButton(self.showAccountPublicKeyQRButton, enable: false) self.clearAddressFields() } } func didUpdateAccountPrivateKey(_ accountPrivateKey: String?) { if !accountPrivateKey!.isEmpty && TLHDWalletWrapper.isValidExtendedPrivateKey(accountPrivateKey!) { let accoundIdx = TLHDWalletWrapper.getAccountIdxForExtendedKey(accountPrivateKey!) self.accountIDTextField.text = String(accoundIdx) let accountPublicKey = TLHDWalletWrapper.getExtendPubKey(accountPrivateKey!) self.accountPublicKeyTextView.text = accountPublicKey self.enableButton(self.showAccountPrivateKeyQRButton, enable: true) self.enableButton(self.showAccountPublicKeyQRButton, enable: true) self.updateAccountPrivateKeyTextView(accountPrivateKey) self.updateAddressFieldsWithStartingAddressID() } else { self.accountIDTextField.text = nil self.accountPublicKeyTextView.text = nil self.updateAccountPublicKeyTextView(nil) self.enableButton(self.showAccountPrivateKeyQRButton, enable: false) self.enableButton(self.showAccountPublicKeyQRButton, enable: false) self.clearAddressFields() } } func updateAccountIDTextField(_ enable: Bool) { if enable { self.enableTextField(self.accountIDTextField, enable: true) } else { self.enableTextField(self.accountIDTextField, enable: false) self.accountIDTextField.text = nil } } func updateAccountPublicKeyTextView(_ extendedPublicKey: String?) { if extendedPublicKey == nil { self.enableButton(self.showAccountPublicKeyQRButton, enable: false) return } self.enableButton(self.showAccountPublicKeyQRButton, enable: true) // shrink text to fit textview var fontSize:CGFloat = (self.accountPublicKeyTextView.font?.pointSize)! self.accountPublicKeyTextView.font = self.accountPublicKeyTextView.font?.withSize(fontSize) while (self.accountPublicKeyTextView.contentSize.height > self.accountPublicKeyTextView.frame.size.height && fontSize > 8.0) { fontSize -= 1.0 self.accountPublicKeyTextView.font = self.accountPublicKeyTextView.font?.withSize(fontSize) } } func updateAccountPrivateKeyTextView(_ extendedPrivateKey: String?) { if extendedPrivateKey == nil { self.enableButton(self.showAccountPrivateKeyQRButton, enable: false) return } self.enableButton(self.showAccountPrivateKeyQRButton, enable: true) // shrink text to fit textview var fontSize:CGFloat = (self.accountPrivateKeyTextView.font?.pointSize)! self.accountPrivateKeyTextView.font = self.accountPrivateKeyTextView.font?.withSize(fontSize) while (self.accountPrivateKeyTextView.contentSize.height > self.accountPrivateKeyTextView.frame.size.height && fontSize > 8.0) { fontSize -= 1.0 self.accountPrivateKeyTextView.font = self.accountPrivateKeyTextView.font?.withSize(fontSize) } } func clearAddressFields() { self.clearReceivingAddressFields() self.clearChangeAddressFields() } func clearReceivingAddressFields() { self.enableTextField(self.startingAddressIDTextField, enable: false) self.enableButton(self.showAddressQRCodeButton1, enable: false) self.enableButton(self.showAddressQRCodeButton2, enable: false) self.enableButton(self.showAddressQRCodeButton3, enable: false) self.enableButton(self.showAddressQRCodeButton4, enable: false) self.enableButton(self.showAddressQRCodeButton5, enable: false) self.enableButton(self.showPrivateKeyQRCodeButton1, enable: false) self.enableButton(self.showPrivateKeyQRCodeButton2, enable: false) self.enableButton(self.showPrivateKeyQRCodeButton3, enable: false) self.enableButton(self.showPrivateKeyQRCodeButton4, enable: false) self.enableButton(self.showPrivateKeyQRCodeButton5, enable: false) self.enableTextField(self.addressTextField1, enable: false) self.enableTextField(self.addressTextField2, enable: false) self.enableTextField(self.addressTextField3, enable: false) self.enableTextField(self.addressTextField4, enable: false) self.enableTextField(self.addressTextField5, enable: false) self.enableTextField(self.privateKeyTextField1, enable: false) self.enableTextField(self.privateKeyTextField2, enable: false) self.enableTextField(self.privateKeyTextField3, enable: false) self.enableTextField(self.privateKeyTextField4, enable: false) self.enableTextField(self.privateKeyTextField5, enable: false) self.startingAddressIDTextField.text = nil self.privateKeyTextField1.text = nil self.addressTextField1.text = nil self.privateKeyTextField2.text = nil self.addressTextField2.text = nil self.privateKeyTextField3.text = nil self.addressTextField3.text = nil self.privateKeyTextField4.text = nil self.addressTextField4.text = nil self.privateKeyTextField5.text = nil self.addressTextField5.text = nil } func clearChangeAddressFields() { self.enableTextField(self.startingChangeAddressIDTextField, enable: false) self.enableButton(self.showChangeAddressQRCodeButton1, enable: false) self.enableButton(self.showChangeAddressQRCodeButton2, enable: false) self.enableButton(self.showChangeAddressQRCodeButton3, enable: false) self.enableButton(self.showChangeAddressQRCodeButton4, enable: false) self.enableButton(self.showChangeAddressQRCodeButton5, enable: false) self.enableButton(self.showChangePrivateKeyQRCodeButton1, enable: false) self.enableButton(self.showChangePrivateKeyQRCodeButton2, enable: false) self.enableButton(self.showChangePrivateKeyQRCodeButton3, enable: false) self.enableButton(self.showChangePrivateKeyQRCodeButton4, enable: false) self.enableButton(self.showChangePrivateKeyQRCodeButton5, enable: false) self.enableTextField(self.changeAddressTextField1, enable: false) self.enableTextField(self.changeAddressTextField2, enable: false) self.enableTextField(self.changeAddressTextField3, enable: false) self.enableTextField(self.changeAddressTextField4, enable: false) self.enableTextField(self.changeAddressTextField5, enable: false) self.enableTextField(self.changePrivateKeyTextField1, enable: false) self.enableTextField(self.changePrivateKeyTextField2, enable: false) self.enableTextField(self.changePrivateKeyTextField3, enable: false) self.enableTextField(self.changePrivateKeyTextField4, enable: false) self.enableTextField(self.changePrivateKeyTextField5, enable: false) self.startingChangeAddressIDTextField.text = nil self.changePrivateKeyTextField1.text = nil self.changeAddressTextField1.text = nil self.changePrivateKeyTextField2.text = nil self.changeAddressTextField2.text = nil self.changePrivateKeyTextField3.text = nil self.changeAddressTextField3.text = nil self.changePrivateKeyTextField4.text = nil self.changeAddressTextField4.text = nil self.changePrivateKeyTextField5.text = nil self.changeAddressTextField5.text = nil } func updateAddressFieldsWithStartingAddressID(_ startingAddressID: Int? = nil) { var addressID:Int? = 0 if startingAddressID == nil { addressID = Int(self.startingAddressIDTextField.text!) if addressID == nil { addressID = 0 } } else { addressID = startingAddressID } updateAddressFields(addressID!) } func updateChangeAddressFieldsWithStartingAddressID(_ startingAddressID: Int? = nil) { var addressID:Int? = 0 if startingAddressID == nil { addressID = Int(self.startingChangeAddressIDTextField.text!) if addressID == nil { addressID = 0 } } else { addressID = startingAddressID } updateChangeAddressFields(addressID!) } func updateAddressFields(_ startingAddressID: Int) { var extendedPrivateKey:String? = nil var extendedPublicKey:String? = nil if self.coldWalletKeyType == .mnemonic { extendedPublicKey = self.accountPublicKeyTextView.text extendedPrivateKey = self.accountPrivateKeyTextView.text } else if self.coldWalletKeyType == .accountPublicKey { extendedPublicKey = self.accountPublicKeyTextView.text } else if self.coldWalletKeyType == .accountPrivateKey { extendedPublicKey = self.accountPublicKeyTextView.text extendedPrivateKey = self.accountPrivateKeyTextView.text } if extendedPublicKey != nil && TLHDWalletWrapper.isValidExtendedPublicKey(extendedPublicKey!) && (extendedPrivateKey == nil || extendedPrivateKey != nil && TLHDWalletWrapper.isValidExtendedPrivateKey(extendedPrivateKey!)) { self.enableTextField(self.startingAddressIDTextField, enable: true) var HDAddressIdx = startingAddressID let addressSequence1 = [Int(TLAddressType.main.rawValue), HDAddressIdx] as [Any] self.addressLabel1.text = TLDisplayStrings.ADDRESS_ID_STRING() + String(HDAddressIdx) + ":" self.addressTextField1.text = TLHDWalletWrapper.getAddress(extendedPublicKey!, sequence: addressSequence1 as NSArray, isTestnet: isTestnet) HDAddressIdx += 1 let addressSequence2 = [Int(TLAddressType.main.rawValue), HDAddressIdx] self.addressLabel2.text = TLDisplayStrings.ADDRESS_ID_STRING() + String(HDAddressIdx) + ":" self.addressTextField2.text = TLHDWalletWrapper.getAddress(extendedPublicKey!, sequence: addressSequence2 as NSArray, isTestnet: isTestnet) HDAddressIdx += 1 let addressSequence3 = [Int(TLAddressType.main.rawValue), HDAddressIdx] self.addressLabel3.text = TLDisplayStrings.ADDRESS_ID_STRING() + String(HDAddressIdx) + ":" self.addressTextField3.text = TLHDWalletWrapper.getAddress(extendedPublicKey!, sequence: addressSequence3 as NSArray, isTestnet: isTestnet) HDAddressIdx += 1 let addressSequence4 = [Int(TLAddressType.main.rawValue), HDAddressIdx] self.addressLabel4.text = TLDisplayStrings.ADDRESS_ID_STRING() + String(HDAddressIdx) + ":" self.addressTextField4.text = TLHDWalletWrapper.getAddress(extendedPublicKey!, sequence: addressSequence4 as NSArray, isTestnet: isTestnet) HDAddressIdx += 1 let addressSequence5 = [Int(TLAddressType.main.rawValue), HDAddressIdx] self.addressLabel5.text = TLDisplayStrings.ADDRESS_ID_STRING() + String(HDAddressIdx) + ":" self.addressTextField5.text = TLHDWalletWrapper.getAddress(extendedPublicKey!, sequence: addressSequence5 as NSArray, isTestnet: isTestnet) self.enableButton(self.showAddressQRCodeButton1, enable: true) self.enableButton(self.showAddressQRCodeButton2, enable: true) self.enableButton(self.showAddressQRCodeButton3, enable: true) self.enableButton(self.showAddressQRCodeButton4, enable: true) self.enableButton(self.showAddressQRCodeButton5, enable: true) if extendedPrivateKey != nil { self.privateKeyTextField1.text = TLHDWalletWrapper.getPrivateKey(extendedPrivateKey! as NSString, sequence: addressSequence1 as NSArray, isTestnet: isTestnet) self.privateKeyTextField2.text = TLHDWalletWrapper.getPrivateKey(extendedPrivateKey! as NSString, sequence: addressSequence2 as NSArray, isTestnet: isTestnet) self.privateKeyTextField3.text = TLHDWalletWrapper.getPrivateKey(extendedPrivateKey! as NSString, sequence: addressSequence3 as NSArray, isTestnet: isTestnet) self.privateKeyTextField4.text = TLHDWalletWrapper.getPrivateKey(extendedPrivateKey! as NSString, sequence: addressSequence4 as NSArray, isTestnet: isTestnet) self.privateKeyTextField5.text = TLHDWalletWrapper.getPrivateKey(extendedPrivateKey! as NSString, sequence: addressSequence5 as NSArray, isTestnet: isTestnet) self.enableButton(self.showPrivateKeyQRCodeButton1, enable: true) self.enableButton(self.showPrivateKeyQRCodeButton2, enable: true) self.enableButton(self.showPrivateKeyQRCodeButton3, enable: true) self.enableButton(self.showPrivateKeyQRCodeButton4, enable: true) self.enableButton(self.showPrivateKeyQRCodeButton5, enable: true) } else { self.privateKeyTextField1.text = nil self.privateKeyTextField2.text = nil self.privateKeyTextField3.text = nil self.privateKeyTextField4.text = nil self.privateKeyTextField5.text = nil self.enableButton(self.showPrivateKeyQRCodeButton1, enable: false) self.enableButton(self.showPrivateKeyQRCodeButton2, enable: false) self.enableButton(self.showPrivateKeyQRCodeButton3, enable: false) self.enableButton(self.showPrivateKeyQRCodeButton4, enable: false) self.enableButton(self.showPrivateKeyQRCodeButton5, enable: false) } } } func updateChangeAddressFields(_ startingAddressID: Int) { var extendedPrivateKey:String? = nil var extendedPublicKey:String? = nil if self.coldWalletKeyType == .mnemonic { extendedPublicKey = self.accountPublicKeyTextView.text extendedPrivateKey = self.accountPrivateKeyTextView.text } else if self.coldWalletKeyType == .accountPublicKey { extendedPublicKey = self.accountPublicKeyTextView.text } else if self.coldWalletKeyType == .accountPrivateKey { extendedPublicKey = self.accountPublicKeyTextView.text extendedPrivateKey = self.accountPrivateKeyTextView.text } if extendedPublicKey != nil && TLHDWalletWrapper.isValidExtendedPublicKey(extendedPublicKey!) && (extendedPrivateKey == nil || extendedPrivateKey != nil && TLHDWalletWrapper.isValidExtendedPrivateKey(extendedPrivateKey!)) { self.enableTextField(self.startingChangeAddressIDTextField, enable: true) var HDAddressIdx = startingAddressID let addressSequence1 = [Int(TLAddressType.change.rawValue), HDAddressIdx] as [Any] self.changeAddressLabel1.text = TLDisplayStrings.CHANGE_ADDRESS_ID_STRING() + String(HDAddressIdx) + ":" self.changeAddressTextField1.text = TLHDWalletWrapper.getAddress(extendedPublicKey!, sequence: addressSequence1 as NSArray, isTestnet: isTestnet) HDAddressIdx += 1 let addressSequence2 = [Int(TLAddressType.change.rawValue), HDAddressIdx] self.changeAddressLabel2.text = TLDisplayStrings.CHANGE_ADDRESS_ID_STRING() + String(HDAddressIdx) + ":" self.changeAddressTextField2.text = TLHDWalletWrapper.getAddress(extendedPublicKey!, sequence: addressSequence2 as NSArray, isTestnet: isTestnet) HDAddressIdx += 1 let addressSequence3 = [Int(TLAddressType.change.rawValue), HDAddressIdx] self.changeAddressLabel3.text = TLDisplayStrings.CHANGE_ADDRESS_ID_STRING() + String(HDAddressIdx) + ":" self.changeAddressTextField3.text = TLHDWalletWrapper.getAddress(extendedPublicKey!, sequence: addressSequence3 as NSArray, isTestnet: isTestnet) HDAddressIdx += 1 let addressSequence4 = [Int(TLAddressType.change.rawValue), HDAddressIdx] self.changeAddressLabel4.text = TLDisplayStrings.CHANGE_ADDRESS_ID_STRING() + String(HDAddressIdx) + ":" self.changeAddressTextField4.text = TLHDWalletWrapper.getAddress(extendedPublicKey!, sequence: addressSequence4 as NSArray, isTestnet: isTestnet) HDAddressIdx += 1 let addressSequence5 = [Int(TLAddressType.change.rawValue), HDAddressIdx] self.changeAddressLabel5.text = TLDisplayStrings.CHANGE_ADDRESS_ID_STRING() + String(HDAddressIdx) + ":" self.changeAddressTextField5.text = TLHDWalletWrapper.getAddress(extendedPublicKey!, sequence: addressSequence5 as NSArray, isTestnet: isTestnet) self.enableButton(self.showChangeAddressQRCodeButton1, enable: true) self.enableButton(self.showChangeAddressQRCodeButton2, enable: true) self.enableButton(self.showChangeAddressQRCodeButton3, enable: true) self.enableButton(self.showChangeAddressQRCodeButton4, enable: true) self.enableButton(self.showChangeAddressQRCodeButton5, enable: true) if extendedPrivateKey != nil { self.changePrivateKeyTextField1.text = TLHDWalletWrapper.getPrivateKey(extendedPrivateKey! as NSString, sequence: addressSequence1 as NSArray, isTestnet: isTestnet) self.changePrivateKeyTextField2.text = TLHDWalletWrapper.getPrivateKey(extendedPrivateKey! as NSString, sequence: addressSequence2 as NSArray, isTestnet: isTestnet) self.changePrivateKeyTextField3.text = TLHDWalletWrapper.getPrivateKey(extendedPrivateKey! as NSString, sequence: addressSequence3 as NSArray, isTestnet: isTestnet) self.changePrivateKeyTextField4.text = TLHDWalletWrapper.getPrivateKey(extendedPrivateKey! as NSString, sequence: addressSequence4 as NSArray, isTestnet: isTestnet) self.changePrivateKeyTextField5.text = TLHDWalletWrapper.getPrivateKey(extendedPrivateKey! as NSString, sequence: addressSequence5 as NSArray, isTestnet: isTestnet) self.enableButton(self.showChangePrivateKeyQRCodeButton1, enable: true) self.enableButton(self.showChangePrivateKeyQRCodeButton2, enable: true) self.enableButton(self.showChangePrivateKeyQRCodeButton3, enable: true) self.enableButton(self.showChangePrivateKeyQRCodeButton4, enable: true) self.enableButton(self.showChangePrivateKeyQRCodeButton5, enable: true) } else { self.changePrivateKeyTextField1.text = nil self.changePrivateKeyTextField2.text = nil self.changePrivateKeyTextField3.text = nil self.changePrivateKeyTextField4.text = nil self.changePrivateKeyTextField5.text = nil self.enableButton(self.showChangePrivateKeyQRCodeButton1, enable: false) self.enableButton(self.showChangePrivateKeyQRCodeButton2, enable: false) self.enableButton(self.showChangePrivateKeyQRCodeButton3, enable: false) self.enableButton(self.showChangePrivateKeyQRCodeButton4, enable: false) self.enableButton(self.showChangePrivateKeyQRCodeButton5, enable: false) } } } @IBAction fileprivate func newWalletButtonClicked(_ sender:UIButton) { if let mnemonicPassphrase = TLHDWalletWrapper.generateMnemonicPassphrase() { self.mnemonicTextView.text = mnemonicPassphrase self.didUpdateMnemonic(mnemonicPassphrase) } } @IBAction fileprivate func showAccountPublicKeyQRButtonClicked(_ sender:UIButton) { let accountPublicKey = self.accountPublicKeyTextView.text if accountPublicKey != nil && !accountPublicKey!.isEmpty && TLHDWalletWrapper.isValidExtendedPublicKey(accountPublicKey!) { delegate?.didAdvancedNewWalletClickShowQRCodeButton(self, data: accountPublicKey!) } } @IBAction fileprivate func showAccountPrivateKeyQRButtonClicked(_ sender:UIButton) { let accountPrivateKey = self.accountPrivateKeyTextView.text if accountPrivateKey != nil && !accountPrivateKey!.isEmpty && TLHDWalletWrapper.isValidExtendedPrivateKey(accountPrivateKey!) { delegate?.didAdvancedNewWalletClickShowQRCodeButton(self, data: accountPrivateKey!) } } @IBAction fileprivate func showAddressQRButtonClicked1(_ sender:UIButton) { let address = self.addressTextField1.text if address != nil && !address!.isEmpty && TLCoreBitcoinWrapper.isValidAddress(address!, isTestnet: isTestnet) { delegate?.didAdvancedNewWalletClickShowQRCodeButton(self, data: address!) } } @IBAction fileprivate func showPrivateKeyQRButtonClicked1(_ sender:UIButton) { let privateKey = self.privateKeyTextField1.text if privateKey != nil && !privateKey!.isEmpty && TLCoreBitcoinWrapper.isValidPrivateKey(privateKey!, isTestnet: isTestnet) { delegate?.didAdvancedNewWalletClickShowQRCodeButton(self, data: privateKey!) } } @IBAction fileprivate func showAddressQRButtonClicked2(_ sender:UIButton) { let address = self.addressTextField2.text if address != nil && !address!.isEmpty && TLCoreBitcoinWrapper.isValidAddress(address!, isTestnet: isTestnet) { delegate?.didAdvancedNewWalletClickShowQRCodeButton(self, data: address!) } } @IBAction fileprivate func showPrivateKeyQRButtonClicked2(_ sender:UIButton) { let privateKey = self.privateKeyTextField2.text if privateKey != nil && !privateKey!.isEmpty && TLCoreBitcoinWrapper.isValidPrivateKey(privateKey!, isTestnet: isTestnet) { delegate?.didAdvancedNewWalletClickShowQRCodeButton(self, data: privateKey!) } } @IBAction fileprivate func showAddressQRButtonClicked3(_ sender:UIButton) { let address = self.addressTextField3.text if address != nil && !address!.isEmpty && TLCoreBitcoinWrapper.isValidAddress(address!, isTestnet: isTestnet) { delegate?.didAdvancedNewWalletClickShowQRCodeButton(self, data: address!) } } @IBAction fileprivate func showPrivateKeyQRButtonClicked3(_ sender:UIButton) { let privateKey = self.privateKeyTextField3.text if privateKey != nil && !privateKey!.isEmpty && TLCoreBitcoinWrapper.isValidPrivateKey(privateKey!, isTestnet: isTestnet) { delegate?.didAdvancedNewWalletClickShowQRCodeButton(self, data: privateKey!) } } @IBAction fileprivate func showAddressQRButtonClicked4(_ sender:UIButton) { let address = self.addressTextField4.text if address != nil && !address!.isEmpty && TLCoreBitcoinWrapper.isValidAddress(address!, isTestnet: isTestnet) { delegate?.didAdvancedNewWalletClickShowQRCodeButton(self, data: address!) } } @IBAction fileprivate func showPrivateKeyQRButtonClicked4(_ sender:UIButton) { let privateKey = self.privateKeyTextField4.text if privateKey != nil && !privateKey!.isEmpty && TLCoreBitcoinWrapper.isValidPrivateKey(privateKey!, isTestnet: isTestnet) { delegate?.didAdvancedNewWalletClickShowQRCodeButton(self, data: privateKey!) } } @IBAction fileprivate func showAddressQRButtonClicked5(_ sender:UIButton) { let address = self.addressTextField5.text if address != nil && !address!.isEmpty && TLCoreBitcoinWrapper.isValidAddress(address!, isTestnet: isTestnet) { delegate?.didAdvancedNewWalletClickShowQRCodeButton(self, data: address!) } } @IBAction fileprivate func showPrivateKeyQRButtonClicked5(_ sender:UIButton) { let privateKey = self.privateKeyTextField5.text if privateKey != nil && !privateKey!.isEmpty && TLCoreBitcoinWrapper.isValidPrivateKey(privateKey!, isTestnet: isTestnet) { delegate?.didAdvancedNewWalletClickShowQRCodeButton(self, data: privateKey!) } } @IBAction fileprivate func showChangeAddressQRButtonClicked1(_ sender:UIButton) { let address = self.changeAddressTextField1.text if address != nil && !address!.isEmpty && TLCoreBitcoinWrapper.isValidAddress(address!, isTestnet: isTestnet) { delegate?.didAdvancedNewWalletClickShowQRCodeButton(self, data: address!) } } @IBAction fileprivate func showChangePrivateKeyQRButtonClicked1(_ sender:UIButton) { let privateKey = self.changePrivateKeyTextField1.text if privateKey != nil && !privateKey!.isEmpty && TLCoreBitcoinWrapper.isValidPrivateKey(privateKey!, isTestnet: isTestnet) { delegate?.didAdvancedNewWalletClickShowQRCodeButton(self, data: privateKey!) } } @IBAction fileprivate func showChangeAddressQRButtonClicked2(_ sender:UIButton) { let address = self.changeAddressTextField2.text if address != nil && !address!.isEmpty && TLCoreBitcoinWrapper.isValidAddress(address!, isTestnet: isTestnet) { delegate?.didAdvancedNewWalletClickShowQRCodeButton(self, data: address!) } } @IBAction fileprivate func showChangePrivateKeyQRButtonClicked2(_ sender:UIButton) { let privateKey = self.changePrivateKeyTextField2.text if privateKey != nil && !privateKey!.isEmpty && TLCoreBitcoinWrapper.isValidPrivateKey(privateKey!, isTestnet: isTestnet) { delegate?.didAdvancedNewWalletClickShowQRCodeButton(self, data: privateKey!) } } @IBAction fileprivate func showChangeAddressQRButtonClicked3(_ sender:UIButton) { let address = self.changeAddressTextField3.text if address != nil && !address!.isEmpty && TLCoreBitcoinWrapper.isValidAddress(address!, isTestnet: isTestnet) { delegate?.didAdvancedNewWalletClickShowQRCodeButton(self, data: address!) } } @IBAction fileprivate func showChangePrivateKeyQRButtonClicked3(_ sender:UIButton) { let privateKey = self.changePrivateKeyTextField3.text if privateKey != nil && !privateKey!.isEmpty && TLCoreBitcoinWrapper.isValidPrivateKey(privateKey!, isTestnet: isTestnet) { delegate?.didAdvancedNewWalletClickShowQRCodeButton(self, data: privateKey!) } } @IBAction fileprivate func showChangeAddressQRButtonClicked4(_ sender:UIButton) { let address = self.changeAddressTextField4.text if address != nil && !address!.isEmpty && TLCoreBitcoinWrapper.isValidAddress(address!, isTestnet: isTestnet) { delegate?.didAdvancedNewWalletClickShowQRCodeButton(self, data: address!) } } @IBAction fileprivate func showChangePrivateKeyQRButtonClicked4(_ sender:UIButton) { let privateKey = self.changePrivateKeyTextField4.text if privateKey != nil && !privateKey!.isEmpty && TLCoreBitcoinWrapper.isValidPrivateKey(privateKey!, isTestnet: isTestnet) { delegate?.didAdvancedNewWalletClickShowQRCodeButton(self, data: privateKey!) } } @IBAction fileprivate func showChangeAddressQRButtonClicked5(_ sender:UIButton) { let address = self.changeAddressTextField5.text if address != nil && !address!.isEmpty && TLCoreBitcoinWrapper.isValidAddress(address!, isTestnet: isTestnet) { delegate?.didAdvancedNewWalletClickShowQRCodeButton(self, data: address!) } } @IBAction fileprivate func showChangePrivateKeyQRButtonClicked5(_ sender:UIButton) { let privateKey = self.changePrivateKeyTextField5.text if privateKey != nil && !privateKey!.isEmpty && TLCoreBitcoinWrapper.isValidPrivateKey(privateKey!, isTestnet: isTestnet) { delegate?.didAdvancedNewWalletClickShowQRCodeButton(self, data: privateKey!) } } } ================================================ FILE: ArcBit/viewControllers/tableViewCells/createColdWalletTableViewCells/TLColdWalletSelectWayTableViewCell.swift ================================================ // // TLColdWalletSelectKeyTypeTableViewCell.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit enum TLColdWalletKeyType:Int { case mnemonic = 0 case accountPrivateKey = 1 case accountPublicKey = 2 } protocol TLColdWalletSelectKeyTypeTableViewCellDelegate { func didSelectColdWalletKeyType(_ cell: TLColdWalletSelectKeyTypeTableViewCell, keyType: TLColdWalletKeyType) } @objc(TLColdWalletSelectKeyTypeTableViewCell) class TLColdWalletSelectKeyTypeTableViewCell:UITableViewCell { required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } @IBOutlet var coldWalletSelectSegmentedControl:UISegmentedControl! var delegate: TLColdWalletSelectKeyTypeTableViewCellDelegate? override init(style:UITableViewCellStyle, reuseIdentifier:String?) { super.init(style:style, reuseIdentifier:reuseIdentifier) } override func awakeFromNib() { super.awakeFromNib() let attr:NSDictionary if UIScreen.main.bounds.size.width <= 320 { attr = NSDictionary(object: UIFont(name: "HelveticaNeue", size: 10.0)!, forKey: NSFontAttributeName as NSCopying) } else { attr = NSDictionary(object: UIFont(name: "HelveticaNeue", size: 12.0)!, forKey: NSFontAttributeName as NSCopying) } self.coldWalletSelectSegmentedControl.setTitleTextAttributes(attr as! [AnyHashable: Any] , for: UIControlState()) self.coldWalletSelectSegmentedControl.setTitle(TLDisplayStrings.BACK_UP_PASSPHRASE_STRING(), forSegmentAt: 0) self.coldWalletSelectSegmentedControl.setTitle(TLDisplayStrings.ACCOUNT_PRIVATE_KEY_STRING(), forSegmentAt: 1) self.coldWalletSelectSegmentedControl.setTitle(TLDisplayStrings.ACCOUNT_PUBLIC_KEY_STRING(), forSegmentAt: 2) } class func cellHeight() -> CGFloat { return 61 } @IBAction func coldWalletKeyTypeValueChanged(_ sender: UISegmentedControl) { let selectedIndex = sender.selectedSegmentIndex delegate?.didSelectColdWalletKeyType(self, keyType: TLColdWalletKeyType(rawValue: selectedIndex)!) } } ================================================ FILE: ArcBit/viewControllers/tableViewCells/createColdWalletTableViewCells/TLNewWalletTableViewCell.swift ================================================ // // TLNewWalletTableViewCell.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit protocol TLNewWalletTableViewCellDelegate { func didClickShowQRCodeButton(_ cell: TLNewWalletTableViewCell, data: String) func didClickMnemonicInfoButton(_ cell: TLNewWalletTableViewCell) func didClickAccountInfoButton(_ cell: TLNewWalletTableViewCell) } @objc(TLNewWalletTableViewCell) class TLNewWalletTableViewCell:UITableViewCell { required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } @IBOutlet var mnemonicLabel:UILabel! @IBOutlet var mnemonicTextView:UITextView! @IBOutlet var newWalletButton:UIButton! @IBOutlet weak var accountIDLabel: UILabel! @IBOutlet var accountIDTextField: UITextField! @IBOutlet weak var accountPublicKeyLabel: UILabel! @IBOutlet var accountPublicKeyTextView:UITextView! @IBOutlet var showAccountPublicKeyQRButton:UIButton! var delegate: TLNewWalletTableViewCellDelegate? override init(style:UITableViewCellStyle, reuseIdentifier:String?) { super.init(style:style, reuseIdentifier:reuseIdentifier) } override func awakeFromNib() { super.awakeFromNib() self.mnemonicLabel.text = TLDisplayStrings.BACK_UP_PASSPHRASE_STRING()+":" self.accountIDLabel.text = TLDisplayStrings.ACCOUNT_ID_STRING()+":" self.accountPublicKeyLabel.text = TLDisplayStrings.ACCOUNT_PUBLIC_KEY_STRING()+":" self.newWalletButton.setTitle(TLDisplayStrings.NEW_WALLET(), for: .normal) self.showAccountPublicKeyQRButton.setTitle(TLDisplayStrings.QR_CODE_STRING(), for: .normal) self.mnemonicTextView.layer.borderWidth = 1.0 self.mnemonicTextView.layer.borderColor = UIColor.black.cgColor self.mnemonicTextView.text = nil self.mnemonicTextView.autocorrectionType = UITextAutocorrectionType.no self.mnemonicTextView.autocapitalizationType = .none self.newWalletButton.backgroundColor = TLColors.mainAppColor() self.newWalletButton.setTitleColor(TLColors.mainAppOppositeColor(), for:UIControlState()) self.newWalletButton.titleLabel!.adjustsFontSizeToFitWidth = true self.accountIDTextField.keyboardType = UIKeyboardType.numberPad self.accountIDTextField.autocorrectionType = UITextAutocorrectionType.no self.accountPublicKeyTextView.layer.borderWidth = 1.0 self.accountPublicKeyTextView.alpha = 0.5 self.accountPublicKeyTextView.text = nil self.accountPublicKeyTextView.isUserInteractionEnabled = false self.showAccountPublicKeyQRButton.backgroundColor = TLColors.mainAppColor() self.showAccountPublicKeyQRButton.setTitleColor(TLColors.mainAppOppositeColor(), for:UIControlState()) self.showAccountPublicKeyQRButton.alpha = 0.5 self.showAccountPublicKeyQRButton.isEnabled = false } class func cellHeight() -> CGFloat { return 303 } @IBAction func clickedMnemonicInfoButton(_ sender: AnyObject) { delegate?.didClickMnemonicInfoButton(self) } @IBAction func clickedAccountInfoButton(_ sender: AnyObject) { delegate?.didClickAccountInfoButton(self) } @IBAction fileprivate func newWalletButtonClicked(_ sender:UIButton) { if let mnemonicPassphrase = TLHDWalletWrapper.generateMnemonicPassphrase() { self.mnemonicTextView.text = mnemonicPassphrase self.didUpdateMnemonic(mnemonicPassphrase) } } @IBAction fileprivate func showAccountPublicKeyQRButtonClicked(_ sender:UIButton) { let accountPublicKey = self.accountPublicKeyTextView.text if accountPublicKey != nil && !accountPublicKey!.isEmpty && TLHDWalletWrapper.isValidExtendedPublicKey(accountPublicKey!) { delegate?.didClickShowQRCodeButton(self, data: accountPublicKey!) } } func didUpdateMnemonic(_ mnemonicPassphrase: String) { let masterHex = TLHDWalletWrapper.getMasterHex(mnemonicPassphrase) if let accountID = UInt(self.accountIDTextField.text!) { let extendedPublicKey = TLHDWalletWrapper.getExtendPubKeyFromMasterHex(masterHex, accountIdx: accountID) self.updateAccountPublicKeyTextView(extendedPublicKey) } else { self.accountIDTextField.text = "0" let extendedPublicKey = TLHDWalletWrapper.getExtendPubKeyFromMasterHex(masterHex, accountIdx: 0) self.updateAccountPublicKeyTextView(extendedPublicKey) } } func updateAccountPublicKeyTextView(_ extendedPublicKey: String?) { if extendedPublicKey == nil { self.showAccountPublicKeyQRButton.isEnabled = false self.showAccountPublicKeyQRButton.alpha = 0.5 self.accountPublicKeyTextView.text = nil return } self.showAccountPublicKeyQRButton.isEnabled = true self.showAccountPublicKeyQRButton.alpha = 1 self.accountPublicKeyTextView.text = extendedPublicKey! // shrink text to fit textview var fontSize:CGFloat = (self.accountPublicKeyTextView.font?.pointSize)! self.accountPublicKeyTextView.font = self.accountPublicKeyTextView.font?.withSize(fontSize) while (self.accountPublicKeyTextView.contentSize.height > self.accountPublicKeyTextView.frame.size.height && fontSize > 8.0) { fontSize -= 1.0 self.accountPublicKeyTextView.font = self.accountPublicKeyTextView.font?.withSize(fontSize) } } } ================================================ FILE: ArcBit/viewControllers/tableViewCells/spendColdWalletTableViewCells/TLInputColdWalletKeyTableViewCell.swift ================================================ // // TLInputColdWalletKeyTableViewCell.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit protocol TLInputColdWalletKeyTableViewCellDelegate { func didClickInputColdWalletKeyInfoButton(_ cell: TLInputColdWalletKeyTableViewCell) } @objc(TLInputColdWalletKeyTableViewCell) class TLInputColdWalletKeyTableViewCell:UITableViewCell { required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } @IBOutlet var keyInputTextView:UITextView! @IBOutlet var statusLabel: UILabel! @IBOutlet weak var step2Label: UILabel! var delegate: TLInputColdWalletKeyTableViewCellDelegate? override init(style:UITableViewCellStyle, reuseIdentifier:String?) { super.init(style:style, reuseIdentifier:reuseIdentifier) } override func awakeFromNib() { super.awakeFromNib() self.step2Label.text = TLDisplayStrings.AUTHORIZE_PAYMENT_STEP_2() self.statusLabel.text = TLDisplayStrings.INCOMPLETE_STRING() self.keyInputTextView.layer.borderWidth = 1.0 self.keyInputTextView.layer.borderColor = UIColor.black.cgColor self.keyInputTextView.autocorrectionType = UITextAutocorrectionType.no self.keyInputTextView.autocapitalizationType = .none self.keyInputTextView.text = nil self.setstatusLabel(false) } class func cellHeight() -> CGFloat { return 151 } @IBAction fileprivate func infoButtonClicked(_ sender:UIButton) { delegate?.didClickInputColdWalletKeyInfoButton(self) } func setstatusLabel(_ complete:Bool) { if complete { statusLabel.textColor = UIColor.green } else { statusLabel.textColor = UIColor.red } } } ================================================ FILE: ArcBit/viewControllers/tableViewCells/spendColdWalletTableViewCells/TLPassSignedTxTableViewCell.swift ================================================ // // TLPassSignedTxTableViewCell.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit protocol TLPassSignedTxTableViewCellDelegate { func didClickPassButton(_ cell: TLPassSignedTxTableViewCell) func didClickPassSignedTxInfoButton(_ cell: TLPassSignedTxTableViewCell) } @objc(TLPassSignedTxTableViewCell) class TLPassSignedTxTableViewCell:UITableViewCell { required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } @IBOutlet var passButton:UIButton! @IBOutlet weak var step3Label: UILabel! var delegate: TLPassSignedTxTableViewCellDelegate? override init(style:UITableViewCellStyle, reuseIdentifier:String?) { super.init(style:style, reuseIdentifier:reuseIdentifier) } override func awakeFromNib() { super.awakeFromNib() self.step3Label.text = TLDisplayStrings.AUTHORIZE_PAYMENT_STEP_3() self.passButton.setTitle(TLDisplayStrings.PASS(), for: .normal) self.passButton.backgroundColor = TLColors.mainAppColor() self.passButton.setTitleColor(TLColors.mainAppOppositeColor(), for:UIControlState()) self.passButton.isEnabled = false self.passButton.alpha = 0.5 } class func cellHeight() -> CGFloat { return 88 } @IBAction fileprivate func infoButtonClicked(_ sender:UIButton) { delegate?.didClickPassSignedTxInfoButton(self) } @IBAction fileprivate func passButtonClicked(_ sender:UIButton) { delegate?.didClickPassButton(self) } } ================================================ FILE: ArcBit/viewControllers/tableViewCells/spendColdWalletTableViewCells/TLScanUnsignedTxTableViewCell.swift ================================================ // // TLScanUnsignedTxTableViewCell.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit protocol TLScanUnsignedTxTableViewCellDelegate { func didClickScanButton(_ cell: TLScanUnsignedTxTableViewCell) func didClickScanUnsignedTxInfoButton(_ cell: TLScanUnsignedTxTableViewCell) } @objc(TLScanUnsignedTxTableViewCell) class TLScanUnsignedTxTableViewCell:UITableViewCell { required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } @IBOutlet var scanButton:UIButton! @IBOutlet var statusLabel: UILabel! @IBOutlet weak var step1Label: UILabel! var delegate: TLScanUnsignedTxTableViewCellDelegate? override init(style:UITableViewCellStyle, reuseIdentifier:String?) { super.init(style:style, reuseIdentifier:reuseIdentifier) } override func awakeFromNib() { super.awakeFromNib() self.step1Label.text = TLDisplayStrings.AUTHORIZE_PAYMENT_STEP_1() self.scanButton.setTitle(TLDisplayStrings.SCAN(), for: .normal) self.scanButton.backgroundColor = TLColors.mainAppColor() self.scanButton.setTitleColor(TLColors.mainAppOppositeColor(), for:UIControlState()) self.setstatusLabel(0, totalParts: 0) } class func cellHeight() -> CGFloat { return 88 } @IBAction fileprivate func infoButtonClicked(_ sender:UIButton) { delegate?.didClickScanUnsignedTxInfoButton(self) } @IBAction fileprivate func scanButtonClicked(_ sender:UIButton) { delegate?.didClickScanButton(self) } func setInvalidScannedData() { statusLabel.textColor = UIColor.red statusLabel.text = TLDisplayStrings.INVALID_SCANNED_DATA_STRING() } func setstatusLabel(_ partsScanned: Int, totalParts: Int) { if partsScanned == 0 && totalParts == 0 { statusLabel.textColor = UIColor.red statusLabel.text = TLDisplayStrings.INCOMPLETE_STRING() } else if partsScanned < totalParts { statusLabel.textColor = UIColor.red statusLabel.text = "\(partsScanned)/\(totalParts) " + TLDisplayStrings.COMPLETE_STRING() } else { statusLabel.textColor = UIColor.green statusLabel.text = "\(partsScanned)/\(totalParts) " + TLDisplayStrings.COMPLETE_STRING() } } } ================================================ FILE: ArcBit/viewControllers/tableViewCells/walletTableViewCells/TLAccountTableViewCell.swift ================================================ // // TLAccountTableViewCell.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit @objc(TLAccountTableViewCell) class TLAccountTableViewCell:UITableViewCell { required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } @IBOutlet var accountNameLabel : UILabel? @IBOutlet var accountBalanceButton : UIButton? override func awakeFromNib() { super.awakeFromNib() accountBalanceButton!.backgroundColor = TLColors.mainAppColor() self.accountBalanceButton!.setTitleColor(TLColors.mainAppOppositeColor(), for:UIControlState()) self.accountBalanceButton!.titleLabel!.adjustsFontSizeToFitWidth = true } override init(style: UITableViewCellStyle, reuseIdentifier: String?) { super.init(style: UITableViewCellStyle.subtitle, reuseIdentifier: reuseIdentifier) } override func setSelected(_ selected:Bool, animated:Bool) -> () { super.setSelected(selected, animated:animated) } @IBAction fileprivate func accountBalanceButtonClicked(_ sender:UIButton) { TLPreferences.setDisplayLocalCurrency(!TLPreferences.isDisplayLocalCurrency()) TLPreferences.setInAppSettingsKitDisplayLocalCurrency(TLPreferences.isDisplayLocalCurrency()) } } ================================================ FILE: ArcBit/viewControllers/tableViewCells/walletTableViewCells/TLAddressTableViewCell.swift ================================================ // // TLAddressTableViewCell.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit @objc(TLAddressTableViewCell) class TLAddressTableViewCell:UITableViewCell { required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } @IBOutlet var addressLabel : UILabel? @IBOutlet var numberOfTransactionsCountLabel : UILabel? @IBOutlet var amountButton : UIButton? override func awakeFromNib() { super.awakeFromNib() self.amountButton!.backgroundColor = TLColors.mainAppColor() self.amountButton!.setTitleColor(TLColors.mainAppOppositeColor(), for:UIControlState()) self.amountButton!.titleLabel!.adjustsFontSizeToFitWidth = true } override func setSelected(_ selected:Bool, animated:Bool) -> () { super.setSelected(selected, animated:animated) } @IBAction fileprivate func amountButtonClicked(_ sender:UIButton) { TLPreferences.setDisplayLocalCurrency(!TLPreferences.isDisplayLocalCurrency()) TLPreferences.setInAppSettingsKitDisplayLocalCurrency(TLPreferences.isDisplayLocalCurrency()) } } ================================================ FILE: ArcBit/viewControllers/tableViewCells/walletTableViewCells/TLTransactionTableViewCell.swift ================================================ // // TLTransactionTableViewCell.swift // ArcBit // // Created by Timothy Lee on 3/14/15. // Copyright (c) 2015 Timothy Lee // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, // MA 02110-1301 USA import Foundation import UIKit @objc(TLTransactionTableViewCell) class TLTransactionTableViewCell:UITableViewCell { required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } @IBOutlet var dateLabel:UILabel? @IBOutlet var currencyLabel:UILabel? @IBOutlet var amountLabel:UILabel? @IBOutlet var descriptionLabel:UILabel? @IBOutlet var confirmationsLabel:UILabel? @IBOutlet var amountButton:UIButton? @IBOutlet var confirmedStatusImageView:UIImageView? @IBAction fileprivate func amountButtonClicked(_ sender:UIButton) { TLPreferences.setDisplayLocalCurrency(!TLPreferences.isDisplayLocalCurrency()) TLPreferences.setInAppSettingsKitDisplayLocalCurrency(TLPreferences.isDisplayLocalCurrency()) } override init(style:UITableViewCellStyle, reuseIdentifier:String?) { super.init(style:style, reuseIdentifier:reuseIdentifier) } override func awakeFromNib() { super.awakeFromNib() self.amountButton!.backgroundColor = TLColors.mainAppColor() self.amountButton!.setTitleColor(TLColors.mainAppOppositeColor(), for:UIControlState()) self.amountButton!.titleLabel!.adjustsFontSizeToFitWidth = true } override func setSelected(_ selected:Bool, animated:Bool) -> () { super.setSelected(selected, animated:animated) } } ================================================ FILE: ArcBit/zh-Hans-CN.lproj/Localizable.strings ================================================ "" = ""; "%@ is not allowed to access the camera" = "%@不允许访问相机"; "%@ servers not reachable." = "%@服务器无法访问。"; "%d/%d parts scanned." = "%d/%d个部分进行扫描。"; "%llu confirmations" = "%llu个确认深度"; "1 Confirmation" = "1个确认深度"; "A bitcoin address typically begins with a '1' or '3'. You can see the transactions and track the balance of an address, but you cannot spend from an imported address.\nYou can temporarily import this watch addresses private key to spend its bitcoins. Simply go the Send screen and select a watch address to spend from and you will be prompted to temporarily import your addresses' private key when you click 'Review Payment' in the Send screen. The private key will stay in memory until the app exits or until you remove it manually in the Accounts screen." = "比特币地址通常以“1”或“3”开头。 您可以查看交易并跟踪地址的余额,但无法从导入的地址消费。\n您可以暂时导入此监控地址私钥以使用比特币。 只需进入发送屏幕并选择一个监控地址,然后在发送屏幕中点击“审查付款”时,系统会提示您临时导入地址的私钥。 私钥将保留在内存中,直到应用程序退出或直到您在“账户”屏幕中手动删除它。"; "A bitcoin wallet is a software application that allows people to send, receive and manage their bitcoins.\nBe aware of how other bitcoin applications store your bitcoins' private keys, which are needed to spend your bitcoins.\nThere are generally three different ways applications can your store your bitcoins.\n1.\nThe banking model, where your bitcoin private keys are held for you by someone else.\n2.\nThe security box model, where your bitcoin private keys are stored encrypted on someone else’s servers.\n3.\nThe wallet model, where your bitcoin private keys are stored only on your device." = "比特币钱包是一种允许人们发送、接收和管理比特币的软件应用程序。\n请注意其他比特币应用程序如何存储您的比特币私钥(这是比特币支付所必需的)。\n对APP而言通常可以有三种不同的方式存储您的比特币私钥。\n1.\n银行模式,您的比特币私钥由其他人持有。\n2.\n安全箱模式,您的比特币私钥在其他人的服务器上加密存储。\n3.\n钱包模式,您的比特币私钥只存储在您的设备上。"; "A private key begins with an 'L', 'K', or '5'.\nBIP 38 encrypted private keys can also be imported. They can either be imported encrypted or unencrypted. If you choose to import it encrypted, you will need to input the password each time you spend from your encrypted private key." = "私钥以字符“L”,“K”或“5”打头。\nBIP38加密的私钥也可以导入。您可以将它们以加密或不加密方式导入。如果您选择加密导入,则每次使用加密私钥时都需要输入密码。"; "Account %@ imported" = "账户%@已导入"; "Account %lu" = "账户 %lu"; "Account 1" = "账户 1"; "Account ID" = "账户ID"; "Account ID: %u" = "账户ID: %u"; "Account Private Key" = "账户私钥"; "Account Public Key" = "账户公钥"; "Account private key does not match imported account public key" = "账户私钥不匹配导入的账户公钥"; "Account private key missing" = "账户私钥缺失"; "Accounts" = "账户列表"; "Achievement List" = "成就列表"; "Achievements" = "成就"; "Actions" = "操作"; "Active Change Addresses" = "活跃的找零地址"; "Active Main Addresses" = "活跃的主地址"; "Add Contacts Entry" = "添加联系人条目"; "Address" = "地址"; "Address ID " = "地址ID "; "Address ID: %lu" = "地址ID: %lu"; "Addresses" = "地址列表"; "Advanced Achievement List" = "高级成就列表"; "Advanced FAQ" = "高级常见问题"; "Advanced how To:" = "高级操作说明:"; "After a transaction is broadcast to the Bitcoin network, it may be included in a block that is published to the network. When that happens, it is said that the transaction has been mined at a depth of 1 block. With each subsequent block that is found, the number of blocks deep is increased by one. To be secure against double spending, a transaction should not be considered as confirmed until it is a certain number of blocks deep.\nA good rule of thumb is that 1 confirmation is good for small value amounts of bitcoins and a user should wait for more confirmations for larger value amounts.\nArcBit will display the confirmation number up until the 6th confirmation." = "在交易被广播到比特币网络之后,它可能被包含在发布到网络的块中。当这种情况发生时,意味着这笔交易已经被开采了1个区块的深度。随着后续区块的陆续开采,此块的深度逐步增加。为了防止双重支出,交易在达到一定的区块深度之前不应该被视为得到确认。\n一个较好的经验法则是,对于小额支付,1个确认深度是合适的;对于较大的金额,用户应该等待更多的确认深度。\nArcBit将显示确认深度直到达到6个。"; "Amount:" = "数量:"; "An account is a collection of bitcoin addresses. With accounts, you will no longer have to manage bitcoin addresses directly anymore. Since address reuse results in a loss of privacy for people using Bitcoin, ArcBit’s HD wallet account system will automatically handle the cycling of bitcoin addresses for you. This ensures you don’t use the same bitcoin address more then once.\nEach account also has a reusable address. You can find it in your Receive screen. Swipe all the way to right on the QRCode in your Receive screen and you will find a reusable address.\nYou can create an unlimited amount of accounts with ArcBit. See the help section on how to create a new account in ArcBit." = "一个账户是若干比特币地址的集合。有了账户,您将不再需要直接管理比特币地址了。由于地址重用会导致比特币用户损失隐私,ArcBit的HD钱包账户系统将自动为您处理比特币地址的循环。这可以确保您不会重复使用同一个比特币地址。\n每个账户也有一个可重用地址。您可以在接收屏幕中找到它。在接收屏幕上一直滑动到最右侧的二维码,您将找到可重用地址。\n您可以使用ArcBit创建任意数量的账户。请参阅帮助部分了解如何在ArcBit中创建新账户。"; "An account private key begins with the letters 'xprv'. You can see, spend and recover the transactions and bitcoins of an entire account from an account private key." = "一个账号私钥以字母“xprv”开头。您可以通过账户私钥查看、支付和恢复整个账户的交易和比特币。"; "An account public key begins with the letters 'xpub'. You can see the transactions and bitcoins of an entire account from an account private key, with the exception of reusable address payments. Future releases will address this issue.\nYou can temporarily import the corresponding account private key for this accounts' account public key to spend your watch accounts' bitcoins. Simply go to the Send screen and select a watch account to spend from and you will be prompted to temporarily import your account's private key when you click 'Review Payment' on the Send screen. The private key will stay in memory until the app exits or until you remove it manually on the Accounts screen." = "账号公钥以字母“xpub”开头。您可以通过账户私钥查看整个账户的交易和比特币,但可重用地址付款除外。未来的版本将解决这个问题。\n您可以临时导入账户相对应的私钥以支付您的监视账户中的比特币。只需进入发送屏幕,选择一个监视账户,然后在发送屏幕上单击“审查付款”,此时系统会提示您临时导入账户的私钥。私钥将保留在内存中,直到应用程序退出,或您在“账户”屏幕上手动删除它。"; "ArcBit Brain Wallet" = "ArcBit脑钱包"; "ArcBit Web Wallet" = "ArcBit网页钱包"; "ArcBit uses the the bitcoin wallet model (See the section ’What is a bitcoin wallet?’ to understand the 3 different security models of bitcoin software). However, if you use iCloud to backup your wallet, you will be using the security box model. It is recommended that you do not use iCloud and be responsible for your bitcoins yourself. For those who don’t want to remember a simple backup passphrase, iCloud backup is a good alternative." = "ArcBit使用比特币钱包模型(请参阅“什么是比特币钱包”以了解比特币软件的三种不同安全模型)。但是,如果您使用iCloud备份您的钱包,则将使用安全箱模型。建议您不要使用iCloud并自行负责您的比特币。对于那些不想记住简单的备份短语的人来说,iCloud备份是一个不错的选择。"; "Archive Account" = "封存账户"; "Archive address" = "封存地址"; "Archived Accounts" = "封存账户列表"; "Archived Change Addresses" = "封存活跃的找零地址"; "Archived Cold Wallet Accounts" = "封存的冷钱包账户列表"; "Archived Imported Accounts" = "已封存的导入账户列表"; "Archived Imported Addresses" = "已封存的导入地址列表"; "Archived Imported Watch Accounts" = "已封存的导入的监视账户列表"; "Archived Imported Watch Addresses" = "已封存的导入的监视地址列表"; "Archived Main Addresses" = "已封存的主地址"; "Are you sure you want to archive account %@?" = "您确定要封存账户%@吗?"; "Are you sure you want to archive address %@?" = "您确定要封存地址%@吗?"; "Are you sure you want to delete this account?" = "您确定要删除这个账户吗?"; "Are you sure you want to unarchive account %@" = "您确定您想解除封存账户%@吗?"; "Are you sure you want to unarchive address %@?" = "您确定您想解除封存地址%@吗?"; "Authorize Cold Wallet Account Payment" = "授权冷钱包账户付款"; "Authorize Payment" = "授权付款"; "Backup Passphrase" = "备份短语"; "Backup wallet" = "备份钱包"; "Backup local wallet" = "备份本地钱包"; "Backup passphrase found in keychain" = "在钥匙扣中找到备份短语"; "Bitcoin cuts out the middleman and allows you to send money anywhere in the world with an internet connection with minimum to zero fees." = "比特币免去了中间商,并允许您通过互联网向世界任何地方汇款,收费最低为零。"; "Bitcoin, uppercase 'B', is an online payment system invented in 2008 and released as open-source software in 2009 by a programmer named Satoshi Nakamoto. The system is decentralized and peer-to-peer allowing users to transact directly without needing an intermediary.\nBitcoin is also a platform of which other decentralized applications can be built upon. Bitcoin, lowercase 'b' is the currency unit that Bitcoin uses." = "比特币(大写字母'B')是一个在线支付系统。中本聪于2008年发明了该系统,并于2009年作为开源软件发布。 该系统是去中心化的、对等的,允许用户直接进行交易,而不需要中间人。\n比特币也是一个平台,允许其他去中心化的应用程序建立在这个平台上。 比特币(小写字母'b')是比特币使用的货币单位。"; "Bitcoins can be purchased from various bitcoin exchanges. ArcBit is not a bitcoin exchange. ArcBit is a bitcoin wallet. After you purchase some bitcoins from an exchange, you can move it to a bitcoin wallet." = "您可以从很多比特币交易所买到比特币。 ArcBit不是比特币交易所,而是比特币钱包。您从交易所购买比特币后,可以将其转移到比特币钱包。"; "Cancel" = "取消"; "Cannot archive your default account" = "不能封存默认账户"; "Cannot archive your one and only account" = "不能封存唯一的账户"; "Cannot create transactions with outputs less then %@" = "无法创建输出小于%@的交易"; "Cannot decrypt iCloud backup wallet." = "无法解密iCloud备份钱包。"; "Cannot import reusable address" = "无法导入可重用地址"; "Change Address ID " = "找零地址ID "; "Change Automatic Transaction Fee" = "更改自动交易费"; "Change Block Explorer URL" = "更改区块浏览器网址"; "Change Blockexplorer Type" = "更改区块浏览器类型"; "Check out the ArcBit Brain Wallet" = "检查ArcBit脑钱包"; "Check out the ArcBit Web Wallet" = "检查ArcBit网页钱包"; "Check out the ArcBit Web Wallet!" = "检查ArcBit网页钱包!"; "Checking Transaction" = "检查交易"; "Clear account private key from memory" = "从内存中清除账户私钥"; "Clear private key from memory" = "从内存中清除私钥"; "Cleared from memory" = "已从内存中清除"; "Click an address" = "点击一个地址"; "Click the button with the arrow" = "点击带有箭头的按键"; "Click the plus button at the top right" = "点击右上角的加号键"; "Click the ‘Contacts’ button" = "点击“联系人”按键"; "Click ‘Accounts’" = "点击“账户”"; "Click ‘Advanced settings’" = "点击“高级设置”"; "Click ‘Archive Account’" = "点击“封存账户”"; "Click ‘Create New Account’" = "点击“创建新账户”"; "Click ‘Delete’" = "点击“删除”"; "Click ‘Done’" = "点击“完成”"; "Click ‘Edit Account Name’" = "点击“编辑账户名称”"; "Click ‘Edit’" = "点击“编辑”"; "Click ‘Enable PIN Code’" = "点击“启用PIN代码”"; "Click ‘History’" = "点击“历史”"; "Click ‘Import Account’" = "点击“导入账户”"; "Click ‘Import Private Key’" = "点击“导入私钥”"; "Click ‘Import Watch Only Account’" = "点击“导入监视账户”"; "Click ‘Import Watch Only Address’" = "点击“导入监视地址”"; "Click ‘Label transaction’" = "点击“给交易贴标签”"; "Click ‘Restore Wallet’" = "点击“恢复钱包”"; "Click ‘Restore’" = "点击“恢复”"; "Click ‘Review Payment’" = "点击“审查付款”"; "Click ‘Send’" = "点击“发送”"; "Click ‘Set Transaction Fee’" = "点击“设置交易费”"; "Click ‘Settings’" = "点击“设置”"; "Click ‘Show Backup Passphrase’" = "点击“显示备份短语”"; "Click ‘View Addresses’" = "点击“查看地址列表”"; "Click ‘View account private key QR code’" = "点击“查看账户私钥二维码”"; "Click ‘View account public key QR code’" = "点击“查看账户公钥二维码”"; "Click ‘View address QR code’" = "点击“查看地址二维码”"; "Click ‘View in web’" = "点击“在网页中查看”"; "Click ‘View private key QR code’" = "点击“查看私钥二维码”"; "Click ‘blockexplorer API type’" = "点击“区块浏览器API类型”"; "Click ’Receive’" = "点击“接收”"; "Close" = "关闭"; "Cold Wallet" = "冷钱包"; "Cold Wallet Accounts" = "冷钱包账户列表"; "Cold Wallet Accounts can't see reusable address payments, thus this accounts' reusable address is not available." = "冷钱包账户无法查看可重用地址付款,因而该账户的可重用地址不可用。"; "Cold Wallet Overview" = "冷钱包概述"; "Cold wallet private keys are not stored here and cannot be viewed" = "冷钱包私钥不存储在这里,无法查看。"; "Complete" = "完成"; "Complete step 1" = "完成第1步"; "Confirm Payment" = "确认付款"; "Confirm Pin Code" = "确认PIN码"; "Contacts" = "联系人"; "Continue" = "继续"; "Copied To clipboard" = "已复制到剪贴板"; "Copy" = "复制"; "Copy Transaction ID to Clipboard" = "复制交易ID到剪贴板"; "Create Cold Wallet" = "新建冷钱包"; "Create New Account" = "新建新账户"; "Create new contact" = "新建联系人"; "Customize Fee" = "定制交易费"; "Decrypting" = "解密"; "Delete" = "删除"; "Delete %@" = "删除 %@"; "Delete Account" = "删除账户"; "Delete Contact" = "删除联系人"; "Delete address" = "删除地址"; "Do not use the QR code from here to receive bitcoins. Go to the Receive screen to get a QR code to receive bitcoins." = "不要使用这里的二维码接受比特币。进入接收屏幕得到一个QR二维码以接收比特币。"; "Do you like using ArcBit?" = "您喜欢用ArcBit吗?"; "Do you want to load and backup your current local wallet file?" = "您要加载和备份您当前的本地钱包文件吗?"; "Do you want to load local wallet file?" = "要加载本地钱包文件吗?"; "Do you want to restore from your backup passphrase or start a new wallet?" = "您想从您的备份短语恢复或启动一个新的钱包?"; "Do you want to temporary import your account private key?" = "您想临时导入账户私钥?"; "Do you want to temporary import your private key?" = "您想临时导入私钥?"; "Don't remind me" = "不要提醒我"; "Done" = "完成"; "Each account has a public and private account key. Account keys should be kept secret as they are used to view the account's transactions and spend the account's bitcoins." = "每个账户都有一个公钥和一个私钥。账户公钥和私钥都应当妥善保管,因为它们可以被用来查看账户的交易,以及支付账户里的比特币。"; "Edit" = "编辑"; "Edit Account Name" = "编辑账户名称"; "Edit Contact Name" = "编辑联系人姓名"; "Edit Label" = "编辑标签"; "Edit Transaction label" = "编辑交易标签"; "Email Support" = "电子邮件支持"; "Enable PIN code in settings to better secure your wallet." = "在设置中启用PIN码以更好地保护您的钱包"; "Enable Pin Code" = "启用PIN码"; "Enable Transaction Fee" = "启用交易费"; "Enable advanced mode" = "启用高级模式"; "Encountered error creating transaction. Please try again." = "创建交易时遇到错误。请再试一次。"; "Encrypted" = "加密"; "Enter Label" = "输入标签"; "Enter PIN" = "输入PIN码"; "Enter a wallet backup passphrase to wipe the current wallet and start/restore another." = "输入一个钱包备份短语以擦除当前的钱包,并启用/还原另一个。"; "Enter account private key" = "输入账户私钥"; "Enter account public key" = "输入账户公钥"; "Enter address" = "输入网址"; "Enter an account ID and click 'QR Code'. Then on your primary online device, enable Cold Wallet in settings. Then go to the Accounts screen and click 'Import Cold Wallet Account' and scan the Account Public Key QR Code. Afterwards use this cold wallet account as you would a normal account and deposit bitcoins into it. When you want to make a payment from a cold wallet account, go to the next section on the previous screen and follow the step by step instructions there." = "首先输入一个账户ID并点击“二维码”。然后在您的在线主设备上,在设置中启用冷钱包。然后进入账户屏幕,点击“导入冷钱包账户”并扫描账户公钥二维码。之后像使用正常账户一样使用这个冷钱包账户,并将比特币存入其中。如果您想从冷钱包账户中支付,请转至上一屏幕的下一部分,然后按照说明进行操作。"; "Enter backup passphrase" = "输入备份短语"; "Enter passphrase for your iCloud backup wallet." = "为您的iCloud备份钱包输入短语。"; "Enter password for encrypted private key" = "输入加密私钥的密码"; "Enter the 12 word passphrase that belongs to the cold wallet account that you want to make a payment from. This is the passphrase that was used to generate your account public key that was generated on the 'Create Cold Wallet' section found on the previous screen." = "输入您要从中付款的冷钱包账户所对应的12个单词的短语。这是前面的屏幕中的“创建冷钱包”部分生成的12单词短语,该短语被用来生成账户公钥。"; "Error" = "错误"; "Error fetching Transaction." = "获取交易时遇到错误"; "Error fetching unspent outputs. Try again later." = "获取未使用的输出时遇到错误。请稍后再试。"; "Error fetching unspent outputs. Try again." = "获取未使用的输出时遇到错误。请再试一次。"; "Error getting block height." = "获取块高度时遇到错误。"; "Error importing account" = "导入账户时出错"; "Error loading wallet JSON file" = "加载钱包JSON文件时出错"; "Explanation" = "说明"; "FAQ" = "常见问题"; "Fee:" = "费用:"; "Fill address field" = "填充地址域"; "Finished Passing Transaction Data" = "交易数据传送已完成"; "First make sure you are using your secondary offline device for this screen (as mentioned in the overview on the previous screen). Click 'New Wallet' and write down or memorize the generated 12 word passphrase. This passphrase can recover and generate all your accounts and the bitcoins associated with it, so keep it safe and to yourself. Also instead of creating a new wallet, you can also input an existing 12 word passphrase that was generated here to create additional accounts." = "首先请确保您正在使用您的离线辅设备打开此屏幕(如前一屏幕中的概述所述)。点击“新建钱包”,记下或记住生成的12个单词的短语。这个短语可以恢复并生成所有的账户、以及与之相关的比特币,所以请务必注意保密。或者,有别于创建一个新的钱包,您也可以输入这里生成的12个单词短语来创建额外的账户。"; "Follow us on Twitter" = "在推特上关注我们"; "From:" = "从:"; "Funds have been claimed already." = "资金已经被申报。"; "Funds imported" = "资金导入完毕"; "Go" = "出发"; "Go to the side menu" = "转到侧边菜单"; "Have sender scan QR code" = "让付款人扫描二维码"; "Have sender send you payment" = "让付款人向您发送付款"; "Help" = "帮助"; "Here are some features that no other mobile bitcoin wallet supports.\n- Reusable address support\n- Ability to import individual account (extended) keys\n- iCloud backup support\n- Over 150 local currencies supported" = "这里有一些ArcBit独有的功能。\n-可重用地址支持\n-能够导入个人账户(扩展)密钥\n- iCloud备份支持\n-支持150多种本地货币"; "Hierarchical Deterministic Wallet" = "分层确定性钱包"; "History" = "历史"; "How To:" = "如何:"; "How do I get bitcoins?" = "如何获得比特币?"; "How does ArcBit Wallet work?" = "ArcBit钱包是如何工作的?"; "Import Account" = "导入账户"; "Import Cold Wallet Account" = "导入冷钱包账户"; "Import Feature" = "导入特性"; "Import Private Key" = "导入私钥"; "Import Private/Encrypted Key" = "导入私有/加密密钥"; "Import Watch Account" = "导入监视账户"; "Import Watch Address" = "导入监视地址"; "Import private key encrypted or unencrypted?" = "将私钥导入为加密形式还是不加密形式?"; "Import with QR code" = "以二维码导入"; "Import with text input" = "以文本输入导入"; "Imported Account %@" = "导入的账户%@"; "Imported Accounts" = "导入的账户列表"; "Imported Address" = "导入的地址"; "Imported Addresses" = "导入的地址列表"; "Imported Cold Wallet Account %@" = "导入冷钱包账户%@"; "Imported Watch Account %@" = "导入监视账户%@"; "Imported Watch Accounts" = "导入的监视账户列表"; "Imported Watch Addresses" = "导入的监视地址列表"; "Imported Watch Only Accounts can't see reusable address payments, thus this accounts' reusable address is not available. If you want see the reusable address for this account, import the account private key that corresponds to this accounts public key." = "导入的监视账户列表无法看到可重用地址上的付款,因此此账户的可重用地址不可用。如果您想查看此账户的可重用地址,请导入与此账户对应的私钥。"; "Importing Account" = "正在导入账户"; "Importing Cold Wallet Account" = "正在导入冷钱包账户"; "Importing a Private Key" = "正在导入私钥"; "Importing a Watch Only Account" = "正在导入监视账户"; "Importing a Watch Only Address" = "正在导入监视地址"; "Importing an Account" = "正在导入账户"; "Importing an encrypted key will require you to input the password every time you want to send bitcoins from it." = "将密钥导入为加密形式将会在您每次从中付款时要求输入密码。"; "In advanced mode, you can import bitcoin keys and addresses from other sources. You can import account private keys, account public keys, private keys, and addresses.\nPlease note that your 12 word passphrase cannot recover your bitcoins, so it is recommended that you backup imported keys and addresses separately." = "在高级模式下,您可以从其他来源导入比特币密钥和地址。您可以导入账户私钥、账户公钥、私钥和地址。\n请注意,您的12个单词短语无法恢复您以此种方式导入的比特币,因此建议您单独备份这些导入的密钥和地址。"; "Incomplete" = "不完整"; "Incorrect passphrase, could not decrypt iCloud wallet backup." = "短语错误,无法解密iCloud钱包备份。"; "Input a bitcoin address" = "输入一个比特币地址"; "Input a label" = "输入一个标签"; "Input a new label" = "输入一个新标签"; "Input a recommended amount. Somewhere between %@ and %@ BTC" = "输入一个推荐值(介于 %@ 和 %@ BTC之间)"; "Input amount" = "输入数量"; "Input label" = "输入标签"; "Input new account name" = "输入新账户名称"; "Input transaction fee in bitcoins" = "输入交易费(以比特币为单位)"; "Instructions" = "指令"; "Insufficient Funds" = "资金不足"; "Insufficient Funds. Account balance is %@ when %@ is required." = "资金不足。账户余额为%@,但需要%@。"; "Insufficient Funds. Account contains bitcoin dust. You can only send up to %@ for now." = "资金不足。账户内有比特币灰尘。当前您最多可以支出%@。"; "Internal Wallet Data" = "内部钱包数据"; "Internal account transfer" = "内部转账"; "Invalid Address" = "无效的地址"; "Invalid Passphrase" = "无效短语"; "Invalid URL" = "无效的网址"; "Invalid account private key" = "无效账户私钥"; "Invalid account public Key" = "无效账户公钥"; "Invalid amount" = "无效数量"; "Invalid backup passphrase" = "无效的备份短语"; "Invalid private key" = "无效私钥"; "Invalid scanned data" = "无效的扫描数据"; "Invalid transaction ID" = "无效的交易ID"; "It is not recommended that you manually manage private keys yourself. A leak of a private key can lead to the compromise of your accounts." = "我们不建议您自己手工管理私钥。私钥的泄漏可能会导致您的账户受到损害。"; "It is not recommended that you use a regular bitcoin address for multiple payments, but instead you should import a reusable address. Add address anyways?" = "我们不建议您使用常规的地址多次付款,建议您导入一个可重用地址。仍然坚持添加地址吗?"; "Label" = "标签"; "Label Transaction" = "给交易加标签"; "Local backup to wallet failed!" = "本地备份到钱包失败了!"; "Local wallet will be lost. Are you sure you want to restore wallet from iCloud?" = "本地钱包将会丢失。您确定要从iCloud还原钱包吗?"; "Maximum accounts reached" = "达到最大的账户数量"; "More" = "更多"; "Name" = "名称"; "Network Error" = "网络错误"; "New Wallet" = "新建錢包"; "New addresses will be automatically generated and cycled for you as you use your current available addresses." = "您使用当前可用地址的时候将为您自动生成新地址并循环。"; "Next" = "下一个"; "No" = "没有"; "None currently" = "目前没有"; "Not now" = "不是现在"; "Now authorize the transaction on your air gap device. When you have done so, click continue on this device to scan the authorized transaction data and make your payment." = "现在,请在您的离线辅设备上授权此交易。然后,在此设备上单击“继续”以扫描授权的交易数据并进行付款。"; "OK" = "OK"; "On your primary online device, when you want to make a payment from a cold wallet account, simply do it as you normally would on a normal account. When you click 'Send' on the Review Payment screen, instead of the payment going out immediately, you will be prompted to pass the unauthorized transaction data. Then on your secondary offline device, within this screen click 'Scan' to import the transaction so it can be authorized." = "在您的在线主设备上,当您想通过冷钱包账户付款时,只需按照通常在正常账户中的方式进行付款。当您在“审核付款”屏幕上点击“发送”时,系统会提示您传递未经授权的交易数据,而不是立即付款。然后在您的离线辅设备上,在此屏幕中单击“扫描”以导入交易,以便完成对交易的授权。"; "Once the transaction has been authorized by completing the above two steps, pass the authorized transaction back to your primary online device to finalize your payment." = "一旦该交易已通过以上两个步骤完成授权,将已授权交易送回在线主设备以最终完成支付。"; "Other Links" = "其它链接"; "Pass" = "通过"; "Passphrase" = "短语"; "Passphrase does not match the transaction" = "短语不匹配交易"; "Password" = "密码"; "Payment Index: %lu" = "付款索引: %lu"; "Private key does not match address" = "私钥不匹配地址"; "Private key missing" = "私钥缺失"; "QR code" = "二维码"; "Quit and re-enter app" = "退出并重新进入APP"; "Rate" = "比率"; "Rate us in the App Store!" = "在App商店给我们评分!"; "Receive" = "接收"; "Receive Payment" = "接收付款"; "Receive Payment From Reusable Address" = "从可重用地址接收付款"; "Remind me Later" = "稍后提醒我"; "Restore" = "恢复"; "Restore Wallet" = "恢复钱包"; "Restore from iCloud" = "从iCloud还原"; "Restoring Wallet" = "恢复钱包"; "Retry" = "重试"; "Reusable Address Payment Addresses" = "可重用地址付款地址"; "Reusable Address:" = "可重用地址:"; "Reusable Addresses" = "可重用地址列表"; "Review Payment" = "审查付款"; "Save" = "保存"; "Scan" = "扫描"; "Scan For Reusable Address Payment" = "扫描支付到可重用地址的付款"; "Scan QR Code" = "扫描二维码"; "Scan for reusable address transaction" = "扫描可重用地址交易"; "Scan next part" = "扫描下一部分"; "Scroll down to the section ‘Account Actions’" = "向下滚动到“账户操作”部分"; "Select Account" = "选择账户"; "Select and click a blockexplorer API" = "选择并点击一个区块浏览API"; "Select and click a transaction" = "选择并点击一个交易"; "Select and click an account" = "选择并点击一个账户"; "Select and click an account to receive from" = "选择并点击一个账户以从中收款"; "Select and click an account to view it’s transaction history" = "选择并点击一个账户以查看其交易历史记录"; "Select and click an address" = "选择并点击一个地址"; "Send" = "发送"; "Send Payment" = "发送付款"; "Send To Address In Contacts" = "发送到联系人的地址"; "Send authorized payment?" = "发送已授权的付款?"; "Sending" = "发送中"; "Sending payment to a reusable address might take longer to show up then a normal transaction with the blockchain.info API. You might have to wait until at least 1 confirmation for the transaction to show up. This is due to the limitations of the blockchain.info API. For reusable address payments to show up faster, configure your app to use the Insight API in advance settings." = "在使用blockchain.info API时,比起正常交易,发送款项到可重用地址可能需要较长时间才能显示出来。您可能不得不至少等到该交易得到1次确认。这是由blockchain.info API的限制导致的。如果希望发送到可重用地址的款项能够尽快地显示,请在高级设置中配置您的应用程序使用Insight API。"; "Sent %@ to %@" = "已发送%@到%@"; "Set Transaction Fee in %@" = "在%@中设置交易费"; "Settings" = "设置"; "Some funds may be pending confirmation and cannot be spent yet. (Check your account history) Account only has a spendable balance of %@" = "一些资金尚处于待确认状态,暂时不能使用。(请检查您的账户历史记录)账户仅有%@的可支付余额"; "Some people have compared bitcoin addresses to a bank routing number. It is a good analogy, however bitcoin addresses are public. So if you reuse the same bitcoin address for multiple payments like you would a routing number, people will be able to figure out how much bitcoin you have. Thus it is recommended that you only use one address per payment.\nThis causes usability issues making the user use a new address whenever receiving a payment is cumbersome.\nStealth/reusable addresses provides a better solution. When you give a sender a reusable address, the sender will derive a one time regular bitcoin address from the reusable address. Then the sender will send a payment to that regular bitcoin address. Now you can give many people just one reusable address and have them all send you payments without letting other people know how much bitcoin you have.\nA reusable address looks like this vJmxthatTBXibYe9aZavx18iAT9gyiJETGkhwPX2WbHQGuzX83YvQXynD2t8yHU4Xjfonu5x9m6B4yxquytFP1c2CRbVR9mecxesvE. A reusable address is a lot longer then a regular bitcoin address, it is 102 characters in length.\nReusable addresses are great, however there are no other mobile bitcoin wallets but ArcBit that supports reusable addresses for now. Which is why ArcBit supports receiving payments from both regular bitcoin addresses and reusable addresses.\nFor each account, you have one reusable address. You can find it on your Receive screen. Swipe all the way to right on the QRCode on your Receive screen and you will find a reusable address." = "有些人将比特币地址与银行路由号码进行了比较。这是一个很好的比喻。不过,比特币地址是公开的。因此,如果您重复使用相同的比特币地址进行多次付款(就像您的路由号码),则人们将能够计算出您拥有多少比特币。因此,建议您每个地址只收一次款。\n但是要求用户在每次收款时换用新地址是很麻烦的,这会导致可用性问题。\n隐形地址/可重用地址提供了更好的解决方案。当您给付款人一个可重用地址时,付款人将从可重用地址得到一个一次性的比特币地址。然后付款人会发送一个支付到该地址。现在您可以给许多人同一个可重用地址,让他们都给您付款,而不会让别人知道您有多少比特币。\n一个可重用地址看起来像这样:vJmxthatTBXibYe9aZavx18iAT9gyiJETGkhwPX2WbHQGuzX83YvQXynD2t8yHU4Xjfonu5x9m6B4yxquytFP1c2CRbVR9mecxesvE。可重用地址的长度为102个字符,比普通的比特币地址长得多。\n可重用地址非常棒,但是到目前为止,除了ArcBit,还没有其他比特币手机钱包支持。这就是为什么ArcBit支持从常规比特币地址和可重用地址接收付款的原因。\n对于每个账户,您都有一个可重用地址。在接收屏幕上一直向右滑动二维码,您就可以找到它。"; "Spending from a cold wallet account" = "从冷钱包账户支出"; "Start fresh" = "全新开始"; "Start/Restore Another Wallet" = "开始/恢复另一个钱包"; "Starting Change address ID:" = "找零地址ID:"; "Starting Receiving Address ID:" = "起始接收地址ID:"; "Step 1: Scan transaction to authorize" = "第1步:扫描交易授权"; "Step 2: Input 12 word backup passphrase" = "第2步:输入12字备份短语"; "Step 3: Pass authorized transaction data" = "第3步:传递经过授权的交易数据"; "Steps" = "步骤"; "Success" = "成功"; "Swipe right on an address" = "在地址上向右滑动"; "Swipe to the right on the QR Code Image until you see the reusable address" = "在二维码图像上向右滑动,直到看到可重用地址"; "Temporarily import account private key" = "临时导入账户私钥"; "Temporarily import private key" = "临时导入私钥"; "The cold wallet feature will allow you to create accounts which offer better security compared to normal online wallets. You will need 2 devices to use this feature. Your normal day to day device that is connected to the internet and a secondary device that is not connected to the internet (Your secondary device would need to be online once to download the ArcBit app. Afterwards keep the secondary device offline for maximum security). This feature allows you to authorize bitcoin payments from an offline device so that the keys to your bitcoins will never need to be stored on your online device. Follow the step by step instructions by clicking the info buttons within the below sections." = "冷钱包功能将允许您创建比普通在线钱包更安全的账户。您需要2个设备才能使用此功能。您连接到互联网的日常使用设备和不连接到互联网的辅助设备(您的辅助设备需要在线一次以下载ArcBit应用程序,然后请保持辅助设备永久性离线以保证最大的安全性)。 此功能允许您从离线设备授权比特币付款,这样比特币的密钥永远不需要存储在您的在线设备上。点击下面的部分中的信息按钮,按照分步指令的指引进行操作。"; "The cold wallet feature will allow you to create accounts which offer better security compared to normal online wallets. You will need 2 devices to use this feature. Your normal day to day device that is connected to the internet and a secondary device that is not connected to the internet (Your secondary device would need to be online once to download the ArcBit app. Afterwards keep the secondary device offline for maximum security). This feature allows you to authorize bitcoin payments from an offline device so that the keys to your bitcoins will never need to be stored on your online device. You can enable the cold wallet feature by going into advanced settings." = "冷钱包功能将允许您创建比普通在线钱包更安全的账户。您需要2个设备才能使用此功能。您连接到互联网的普通日常设备和不连接到互联网的辅助设备(您的辅助设备需要在线一次以下载ArcBit应用程序,然后请保持辅助设备永久性离线以获得最大的安全性)。 此功能允许您从离线设备授权比特币付款,这样比特币的密钥永远不需要存储在您的在线设备上。您可以在高级设置中启用冷钱包功能。"; "This account type can't see reusable address payments" = "此账户类型不能看到可重用地址的支付"; "This feature allows you to manually input a transaction ID and see if the corresponding transaction contains a reusable address payment to your reusable address. If so, then the funds will be added to your wallet. Normally the app will discover reusable address payments automatically for you, but if you believe a payment is missing you can use this feature." = "此功能可让您手工输入交易ID,以查看相应的交易是否包含支付到您的可重用地址的付款。如果包含,资金将被添加到您的钱包。通常情况下,应用程序将自动发现支付到您的可重用地址的付款,但如果您确信付款丢失了,您可以使用此功能。"; "To:" = "至:"; "Today" = "今天"; "Toggle Automatic Transaction Fee" = "切换动态交易费"; "Toggle ‘Enable Transaction Fee’" = "切换“启用交易费”"; "Toggle ’Enable advanced mode’" = "切换“启用高级模式”"; "Total:" = "总计:"; "Transaction %@ already accounted for." = "交易%@已入账。"; "Transaction %@ does not belong to this account." = "交易%@不属于此账户。"; "Transaction Fee" = "手续费"; "Transaction ID" = "交易ID"; "Transaction ID: %@" = "交易ID: %@"; "Transaction authorized" = "交易授权"; "Transaction confirmations" = "交易确认深度"; "Transaction fees impact how quickly the Bitcoin network will confirm your transactions. Higher fees means faster confirmation times. Default fee behavior can be configured in settings." = "交易费用影响比特币网络确认您的交易的速度。费用越高,确认越快。可以在设置中配置默认交易费规则。"; "Transaction is not a reusable address transaction." = "交易不是一个可重用地址交易。"; "Transaction needs to be authorized by an offline and air gap device. Send transaction to an offline device for authorization?" = "交易需要由离线辅设备授权。发送交易到离线辅设备进行授权?"; "Transaction needs to be passed back to your online device in order for the payment to be sent" = "交易需要传递回您的在线主设备,以便将付款交易发送到网络"; "Try Again" = "再试一次"; "Try our new cold wallet feature!" = "尝试我们的冷钱包特性!"; "URL does not contain an address." = "网址不包含地址。"; "Unable to get dynamic fees. Falling back on fixed transaction fee. (fee can be configured on review payment)" = "无法获得动态的费用,仍旧使用固定交易费(费用可以在审核支付时修改)。"; "Unarchive Account" = "解除封存账户"; "Unarchive address" = "解除封存地址"; "Unarchived address" = "未封存地址"; "Unconfirmed" = "未确认"; "Unencrypted" = "未加密"; "Use ArcBit on your browser to complement the mobile app. The web wallet has all the features that the mobile wallet has plus more!" = "在您的浏览器中使用ArcBit作为移动APP的补充!网页版钱包包含所有的移动钱包功能,以及一些额外的功能!"; "Use all funds" = "使用所有资金"; "View Account Address" = "查看账户地址"; "View Account Address In Web" = "在网页中查看账户地址"; "View Account Addresses" = "查看账户地址列表"; "View Account Private Key" = "查看账户私钥"; "View Account Public Key" = "查看账户公钥"; "View Achievements" = "查看成就"; "View Addresses" = "查看地址列表"; "View ArcBit Brain Wallet Details" = "查看ArcBit脑钱包详情"; "View ArcBit Web Wallet Details" = "查看ArcBit网页钱包详情"; "View History" = "查看历史"; "View Private Key" = "查看私钥"; "View Transaction In Web" = "在网页中查看交易"; "View account private key QR code" = "查看账户私钥二维码"; "View account public key QR code" = "查看账户公钥二维码"; "View address QR code" = "查看地址二维码"; "View address in web" = "在网络中查看地址"; "View in web" = "在网页中查看"; "View private key QR code" = "查看私钥二维码"; "Visit our home page" = "访问我们的主页"; "Wallet backup passphrase" = "钱包备份短语"; "Warning" = "警告"; "Welcome to ArcBit, a user only controlled Bitcoin wallet. Start using the app now by depositing your Bitcoins here." = "欢迎来到ArcBit——一款完全由用户控制的比特币钱包。将您的比特币转入ArcBit,现在就开始使用它!"; "Welcome!" = "欢迎!"; "What are Account/Extended Keys?" = "什么是账户/扩展密钥?"; "What are accounts?" = "什么是账户?"; "What are reusable addresses?" = "什么是可重用地址?"; "What are the benefits and advantages of Bitcoin?" = "比特币有什么好处?"; "What are transaction confirmations?" = "交易确认数是什么意思?"; "What is ArcBit's cold wallet feature?" = "ArcBit的冷钱包功能是什么?"; "What is Bitcoin?" = "什么是比特币?"; "What is a bitcoin wallet?" = "什么是比特币钱包?"; "What makes ArcBit different from other bitcoin wallets?" = "ArcBit与其他钱包的区别在哪里?"; "With an ArcBit cold wallet feature, you can create wallets and make payments offline without exposing your private keys to an internet connected device. This feature is great for storing large amounts of bitcoin or for the security conscious minded. Check out this feature in the cold wallet section in the side menu." = "使用ArcBit冷钱包功能,您可以创建钱包并离线付款,而不会将您的私钥暴露给连接到互联网的设备。这个功能非常适合存储大量的比特币或者对安全敏感的用户。请在侧边菜单的冷钱包部分查看此功能。"; "Write down backup passphrase" = "写下备份短语"; "Write down or memorize your 12 word wallet backup passphrase. You can view it by clicking \"Show backup passphrase\" in Settings. Your wallet backup passphrase is needed to recover your bitcoins." = "写下或记住您的12单词钱包备份短语。在设置中单击“显示备份短语”可以查看。您的钱包备份短语可以用来恢复您的比特币。"; "Write down the 12 word passphrase below and keep it safe. This passphrase alone can restore your entire wallets\' bitcoins (excluding imports)." = "记下下面的12单词短语,并妥善保管。这个短语可以恢复您的整个钱包的比特币(但不包括导入的)"; "Write down the 12 word passphrase below and keep it safe. This passphrase alone can restore your entire wallets\' bitcoins." = "记下下面的12单词短语,并妥善保管。这个短语可以恢复您的整个钱包的比特币。"; "Yes" = "是"; "You are making a payment to a reusable address. Make sure that the receiver can see the payment made to them. (All ArcBit reusable addresses are compatible with other ArcBit wallets)" = "您正在向可重用地址付款。确保接收方可以看到对他们的付款。(所有ArcBit可重用地址都与其他ArcBit钱包兼容)"; "You have %@, but %@ is needed. (This includes the transactions fee)" = "您有%@,但需要%@(包括交易费用)。"; "You must exit and kill this app in order for this to take effect." = "您必须退出并杀死此APP,以使此修改生效。"; "Your current wallet will be deleted. Your can restore your current wallet later with the wallet passphrase, but any imported accounts or addresses created in advanced mode cannot be recovered. Do you wish to continue?" = "您目前的钱包将被删除。您可以稍后使用钱包短语恢复您当前的钱包,但无法恢复在高级模式下创建的任何导入的账户或地址。您想继续吗?"; "Your iCloud backup was last saved on %@. Do you want to restore your wallet from iCloud or backup your local wallet to iCloud?" = "您的iCloud备份最后保存于%@。您要从iCloud还原您的钱包,还是将本地钱包备份到iCloud?"; "Your new transaction fee is too high" = "您的新交易费太高"; "Your wallet is now restored" = "您的钱包现在已恢复"; "\nAllow camera access in\n Settings->Privacy->Camera->%@" = "\n允许相机访问于\n 设置->隐私->相机->%@"; "\tArcBit Web Wallet is a Chrome extension. It has all the features of the mobile wallet plus more. Highlights include the ability to create multiple wallets instead of just one, and a new non-cumbersome way to generate wallets, store and spend bitcoins all from cold storage! ArcBit's new way to manage your cold storage bitcoins also offers a more compelling reason to use ArcBit's watch account feature. Now you can safely watch the balance of your cold storage bitcoins by enabling advance mode in ArcBit and importing your cold storage account public keys.\n\tUse ArcBit Web Wallet in whatever way you wish. You can create a new wallet, or you can input your current 12 word backup passphrase to manage the same bitcoins across different devices. Check out the ArcBit Web Wallet in the Chrome Web Store for more details!\n" = "\tArcBit网页钱包是一款Chrome扩展程序。它比手机钱包具备更多功能。亮点包括可以创建多个钱包,以一种新的简便方式来生成钱包,以及从冷钱包中存储和支付比特币!ArcBit的管理冷钱包的新方法也为使用ArcBit的监视账户提供了令人信服的理由。现在,您可以通过在ArcBit中启用高级模式并导入冷钱包账户公钥,安全地观察冷钱包的比特币余额。\n\t您可以以任何您希望的方式使用ArcBit网页钱包。您可以创建一个新的钱包,或者您也可以输入您当前的12单词备份短语,以管理不同设备上的相同比特币。更多详细信息,请参阅Chrome应用商店中的ArcBit网页钱包。\n"; "\tWith the Arcbit Brain Wallet you can safely spend your bitcoins without ever having your private keys be exposed to the internet. It can be use in conjunction with your Arcbit Wallet or as a stand alone wallet.\n" = "\t使用Arcbit脑钱包,您可以安全地支付您的比特币,而不必让您的私钥暴露在互联网。它可以与您的Arcbit钱包一起使用或作为独立的钱包使用。\n"; "iCloud Error: %@" = "iCloud错误: %@"; "iCloud backup found" = "找到iCloud备份"; "iCloud backup not found" = "未找到iCloud备份"; "iCloud backup will be lost. Are you sure you want to backup your local wallet to iCloud?" = "iCloud备份将丢失。您确定要将本地的钱包备份到iCloud吗?"; "Wallet backup passphrase will be shown" = "将显示钱包备份短语"; "Write down or memorize your wallet backup passphrase. If you lose your backup passphrase, your wallet cannot be recovered." = "记下或记住您的钱包备份短语。如果您丢失了备份短语,您的钱包将无法恢复。"; "I understand" = "我明白"; "iCloud support for ArcBit discontinued" = "iCloud对ArcBit的支持已经停止"; "iCloud support for ArcBit is being discontinued. If your backup passphrase has not been backed up already, please do so." = "iCloud即将停止对ArcBit的支持。如果您尚未备份您的备份短语,请备份之。"; "Reusable address payments are disabled until further notice." = "可重用地址付款被禁用,直至另行通知。"; ================================================ FILE: ArcBit/zh-Hant.lproj/LaunchScreen.strings ================================================ ================================================ FILE: ArcBit/zh-Hant.lproj/Localizable.strings ================================================ "" = ""; "%@ is not allowed to access the camera" = "%@不允許訪問相機"; "%@ servers not reachable." = "%@伺服器無法訪問。"; "%d/%d parts scanned." = "%d/%d個部分進行掃描。"; "%llu confirmations" = "%llu個確認深度"; "1 Confirmation" = "1個確認深度"; "A bitcoin address typically begins with a '1' or '3'. You can see the transactions and track the balance of an address, but you cannot spend from an imported address.\nYou can temporarily import this watch addresses private key to spend its bitcoins. Simply go the Send screen and select a watch address to spend from and you will be prompted to temporarily import your addresses' private key when you click 'Review Payment' in the Send screen. The private key will stay in memory until the app exits or until you remove it manually in the Accounts screen." = "比特幣位址通常以“1”或“3”開頭。 您可以查看交易並跟蹤位址的餘額,但無法從導入的地址消費。\n您可以暫時導入此監控位址私密金鑰以使用比特幣。 只需進入發送螢幕並選擇一個監控位址,然後在發送螢幕中點擊“審查付款”時,系統會提示您臨時導入位址的私密金鑰。 私密金鑰將保留在記憶體中,直到應用程式退出或直到您在“帳戶”螢幕中手動刪除它。"; "A bitcoin wallet is a software application that allows people to send, receive and manage their bitcoins.\nBe aware of how other bitcoin applications store your bitcoins' private keys, which are needed to spend your bitcoins.\nThere are generally three different ways applications can your store your bitcoins.\n1.\nThe banking model, where your bitcoin private keys are held for you by someone else.\n2.\nThe security box model, where your bitcoin private keys are stored encrypted on someone else’s servers.\n3.\nThe wallet model, where your bitcoin private keys are stored only on your device." = "比特幣錢包是一種允許人們發送、接收和管理比特幣的軟體應用程式。\n請注意其他比特幣應用程式如何存儲您的比特幣私密金鑰(這是比特幣支付所必需的)。\n對APP而言通常可以有三種不同的方式存儲您的比特幣私密金鑰。\n1.\n銀行模式,您的比特幣私密金鑰由其他人持有。\n2.\n安全箱模式,您的比特幣私密金鑰在其他人的伺服器上加密存儲。\n3.\n錢包模式,您的比特幣私密金鑰只存儲在您的設備上。"; "A private key begins with an 'L', 'K', or '5'.\nBIP 38 encrypted private keys can also be imported. They can either be imported encrypted or unencrypted. If you choose to import it encrypted, you will need to input the password each time you spend from your encrypted private key." = "私密金鑰以字元“L”,“K”或“5”打頭。\nBIP38加密的私密金鑰也可以導入。您可以將它們以加密或不加密方式導入。如果您選擇加密導入,則每次使用加密私密金鑰時都需要輸入密碼。"; "Account %@ imported" = "帳戶%@已導入"; "Account %lu" = "帳戶 %lu"; "Account 1" = "帳戶 1"; "Account ID" = "帳戶ID"; "Account ID: %u" = "帳戶ID: %u"; "Account Private Key" = "帳戶私密金鑰"; "Account Public Key" = "帳戶公開金鑰"; "Account private key does not match imported account public key" = "帳戶私密金鑰不匹配導入的帳戶公開金鑰"; "Account private key missing" = "帳戶私密金鑰缺失"; "Accounts" = "帳戶列表"; "Achievement List" = "成就列表"; "Achievements" = "成就"; "Actions" = "操作"; "Active Change Addresses" = "活躍的找零地址"; "Active Main Addresses" = "活躍的主地址"; "Add Contacts Entry" = "添加連絡人條目"; "Address" = "地址"; "Address ID " = "地址ID "; "Address ID: %lu" = "地址ID: %lu"; "Addresses" = "地址清單"; "Advanced Achievement List" = "高級成就列表"; "Advanced FAQ" = "高級常見問題"; "Advanced how To:" = "高級操作說明:"; "After a transaction is broadcast to the Bitcoin network, it may be included in a block that is published to the network. When that happens, it is said that the transaction has been mined at a depth of 1 block. With each subsequent block that is found, the number of blocks deep is increased by one. To be secure against double spending, a transaction should not be considered as confirmed until it is a certain number of blocks deep.\nA good rule of thumb is that 1 confirmation is good for small value amounts of bitcoins and a user should wait for more confirmations for larger value amounts.\nArcBit will display the confirmation number up until the 6th confirmation." = "在交易被廣播到比特幣網路之後,它可能被包含在發佈到網路的塊中。當這種情況發生時,意味著這筆交易已經被開採了1個區塊的深度。隨著後續區塊的陸續開採,此塊的深度逐步增加。為了防止雙重支出,交易在達到一定的區塊深度之前不應該被視為得到確認。\n一個較好的經驗法則是,對於小額支付,1個確認深度是合適的;對於較大的金額,用戶應該等待更多的確認深度。\nArcBit將顯示確認深度直到達到6個。"; "Amount:" = "數量:"; "An account is a collection of bitcoin addresses. With accounts, you will no longer have to manage bitcoin addresses directly anymore. Since address reuse results in a loss of privacy for people using Bitcoin, ArcBit’s HD wallet account system will automatically handle the cycling of bitcoin addresses for you. This ensures you don’t use the same bitcoin address more then once.\nEach account also has a reusable address. You can find it in your Receive screen. Swipe all the way to right on the QRCode in your Receive screen and you will find a reusable address.\nYou can create an unlimited amount of accounts with ArcBit. See the help section on how to create a new account in ArcBit." = "一個帳戶是若干比特幣位址的集合。有了帳戶,您將不再需要直接管理比特幣位址了。由於位址重用會導致比特幣用戶損失隱私,ArcBit的HD錢包帳戶系統將自動為您處理比特幣位址的迴圈。這可以確保您不會重複使用同一個比特幣位址。\n每個帳戶也有一個可重用位址。您可以在接收螢幕中找到它。在接收螢幕上一直滑動到最右側的二維碼,您將找到可重用地址。\n您可以使用ArcBit創建任意數量的帳戶。請參閱説明部分瞭解如何在ArcBit中創建新帳戶。"; "An account private key begins with the letters 'xprv'. You can see, spend and recover the transactions and bitcoins of an entire account from an account private key." = "一個帳號私密金鑰以字母“xprv”開頭。您可以通過帳戶私密金鑰查看、支付和恢復整個帳戶的交易和比特幣。"; "An account public key begins with the letters 'xpub'. You can see the transactions and bitcoins of an entire account from an account private key, with the exception of reusable address payments. Future releases will address this issue.\nYou can temporarily import the corresponding account private key for this accounts' account public key to spend your watch accounts' bitcoins. Simply go to the Send screen and select a watch account to spend from and you will be prompted to temporarily import your account's private key when you click 'Review Payment' on the Send screen. The private key will stay in memory until the app exits or until you remove it manually on the Accounts screen." = "帳號公開金鑰以字母“xpub”開頭。您可以通過帳戶私密金鑰查看整個帳戶的交易和比特幣,但可重用地址付款除外。未來的版本將解決這個問題。\n您可以臨時導入帳戶相對應的私密金鑰以支付您的監視帳戶中的比特幣。只需進入發送螢幕,選擇一個監視帳戶,然後在發送螢幕上按一下“審查付款”,此時系統會提示您臨時導入帳戶的私密金鑰。私密金鑰將保留在記憶體中,直到應用程式退出,或您在“帳戶”螢幕上手動刪除它。"; "ArcBit Brain Wallet" = "ArcBit腦錢包"; "ArcBit Web Wallet" = "ArcBit網頁錢包"; "ArcBit uses the the bitcoin wallet model (See the section ’What is a bitcoin wallet?’ to understand the 3 different security models of bitcoin software). However, if you use iCloud to backup your wallet, you will be using the security box model. It is recommended that you do not use iCloud and be responsible for your bitcoins yourself. For those who don’t want to remember a simple backup passphrase, iCloud backup is a good alternative." = "ArcBit使用比特幣錢包模型(請參閱“什麼是比特幣錢包”以瞭解比特幣軟體的三種不同安全模型)。但是,如果您使用iCloud備份您的錢包,則將使用安全箱模型。建議您不要使用iCloud並自行負責您的比特幣。對於那些不想記住簡單的備份短語的人來說,iCloud備份是一個不錯的選擇。"; "Archive Account" = "封存帳戶"; "Archive address" = "封存地址"; "Archived Accounts" = "封存帳戶列表"; "Archived Change Addresses" = "封存活躍的找零地址"; "Archived Cold Wallet Accounts" = "封存的冷錢包帳戶列表"; "Archived Imported Accounts" = "已封存的導入帳戶列表"; "Archived Imported Addresses" = "已封存的導入地址清單"; "Archived Imported Watch Accounts" = "已封存的導入的監視帳戶清單"; "Archived Imported Watch Addresses" = "已封存的導入的監視地址清單"; "Archived Main Addresses" = "已封存的主地址"; "Are you sure you want to archive account %@?" = "您確定要封存帳戶%@嗎?"; "Are you sure you want to archive address %@?" = "您確定要封存位址%@嗎?"; "Are you sure you want to delete this account?" = "您確定要刪除這個帳戶嗎?"; "Are you sure you want to unarchive account %@" = "您確定您想解除封存帳戶%@嗎?"; "Are you sure you want to unarchive address %@?" = "您確定您想解除封存位址%@嗎?"; "Authorize Cold Wallet Account Payment" = "授權冷錢包帳戶付款"; "Authorize Payment" = "授權付款"; "Backup Passphrase" = "備份短語"; "Backup wallet" = "備份錢包"; "Backup local wallet" = "備份本地錢包"; "Backup passphrase found in keychain" = "在鑰匙扣中找到備份短語"; "Bitcoin cuts out the middleman and allows you to send money anywhere in the world with an internet connection with minimum to zero fees." = "比特幣免去了中間商,並允許您通過互聯網向世界任何地方匯款,收費最低為零。"; "Bitcoin, uppercase 'B', is an online payment system invented in 2008 and released as open-source software in 2009 by a programmer named Satoshi Nakamoto. The system is decentralized and peer-to-peer allowing users to transact directly without needing an intermediary.\nBitcoin is also a platform of which other decentralized applications can be built upon. Bitcoin, lowercase 'b' is the currency unit that Bitcoin uses." = "比特幣(大寫字母'B')是一個線上支付系統。中本聰于2008年發明了該系統,並于2009年作為開源軟體發佈。 該系統是去中心化的、對等的,允許使用者直接進行交易,而不需要中間人。\n比特幣也是一個平臺,允許其他去中心化的應用程式建立在這個平臺上。 比特幣(小寫字母'b')是比特幣使用的貨幣單位。"; "Bitcoins can be purchased from various bitcoin exchanges. ArcBit is not a bitcoin exchange. ArcBit is a bitcoin wallet. After you purchase some bitcoins from an exchange, you can move it to a bitcoin wallet." = "您可以從很多比特幣交易所買到比特幣。 ArcBit不是比特幣交易所,而是比特幣錢包。您從交易所購買比特幣後,可以將其轉移到比特幣錢包。"; "Cancel" = "取消"; "Cannot archive your default account" = "不能封存默認帳戶"; "Cannot archive your one and only account" = "不能封存唯一的帳戶"; "Cannot create transactions with outputs less then %@" = "無法創建輸出小於%@的交易"; "Cannot decrypt iCloud backup wallet." = "無法解密iCloud備份錢包。"; "Cannot import reusable address" = "無法導入可重用地址"; "Change Address ID " = "找零地址ID "; "Change Automatic Transaction Fee" = "更改自動交易費"; "Change Block Explorer URL" = "更改區塊流覽器網址"; "Change Blockexplorer Type" = "更改區塊流覽器類型"; "Check out the ArcBit Brain Wallet" = "檢查ArcBit腦錢包"; "Check out the ArcBit Web Wallet" = "檢查ArcBit網頁錢包"; "Check out the ArcBit Web Wallet!" = "檢查ArcBit網頁錢包!"; "Checking Transaction" = "檢查交易"; "Clear account private key from memory" = "從記憶體中清除帳戶私密金鑰"; "Clear private key from memory" = "從記憶體中清除私密金鑰"; "Cleared from memory" = "已從記憶體中清除"; "Click an address" = "點擊一個位址"; "Click the button with the arrow" = "點擊帶有箭頭的按鍵"; "Click the plus button at the top right" = "點擊右上角的加號鍵"; "Click the ‘Contacts’ button" = "點擊“連絡人”按鍵"; "Click ‘Accounts’" = "點擊“帳戶”"; "Click ‘Advanced settings’" = "點擊“高級設置”"; "Click ‘Archive Account’" = "點擊“封存帳戶”"; "Click ‘Create New Account’" = "點擊“創建新帳戶”"; "Click ‘Delete’" = "點擊“刪除”"; "Click ‘Done’" = "點擊“完成”"; "Click ‘Edit Account Name’" = "點擊“編輯帳戶名稱”"; "Click ‘Edit’" = "點擊“編輯”"; "Click ‘Enable PIN Code’" = "點擊“啟用PIN代碼”"; "Click ‘History’" = "點擊“歷史”"; "Click ‘Import Account’" = "點擊“導入帳戶”"; "Click ‘Import Private Key’" = "點擊“導入私密金鑰”"; "Click ‘Import Watch Only Account’" = "點擊“導入監視帳戶”"; "Click ‘Import Watch Only Address’" = "點擊“導入監視位址”"; "Click ‘Label transaction’" = "點擊“給交易貼標籤”"; "Click ‘Restore Wallet’" = "點擊“恢復錢包”"; "Click ‘Restore’" = "點擊“恢復”"; "Click ‘Review Payment’" = "點擊“審查付款”"; "Click ‘Send’" = "點擊“發送”"; "Click ‘Set Transaction Fee’" = "點擊“設置交易費”"; "Click ‘Settings’" = "點擊“設置”"; "Click ‘Show Backup Passphrase’" = "點擊“顯示備份短語”"; "Click ‘View Addresses’" = "點擊“查看地址清單”"; "Click ‘View account private key QR code’" = "點擊“查看帳戶私密金鑰二維碼”"; "Click ‘View account public key QR code’" = "點擊“查看帳戶公開金鑰二維碼”"; "Click ‘View address QR code’" = "點擊“查看地址二維碼”"; "Click ‘View in web’" = "點擊“在網頁中查看”"; "Click ‘View private key QR code’" = "點擊“查看私密金鑰二維碼”"; "Click ‘blockexplorer API type’" = "點擊“區塊流覽器API類型”"; "Click ’Receive’" = "點擊“接收”"; "Close" = "關閉"; "Cold Wallet" = "冷錢包"; "Cold Wallet Accounts" = "冷錢包帳戶列表"; "Cold Wallet Accounts can't see reusable address payments, thus this accounts' reusable address is not available." = "冷錢包帳戶無法查看可重用地址付款,因而該帳戶的可重用地址不可用。"; "Cold Wallet Overview" = "冷錢包概述"; "Cold wallet private keys are not stored here and cannot be viewed" = "冷錢包私密金鑰不存儲在這裡,無法查看。"; "Complete" = "完成"; "Complete step 1" = "完成第1步"; "Confirm Payment" = "確認付款"; "Confirm Pin Code" = "確認PIN碼"; "Contacts" = "連絡人"; "Continue" = "繼續"; "Copied To clipboard" = "已複製到剪貼板"; "Copy" = "複製"; "Copy Transaction ID to Clipboard" = "複製交易ID到剪貼板"; "Create Cold Wallet" = "新建冷錢包"; "Create New Account" = "新建新帳戶"; "Create new contact" = "新建連絡人"; "Customize Fee" = "定制交易費"; "Decrypting" = "解密"; "Delete" = "刪除"; "Delete %@" = "刪除 %@"; "Delete Account" = "刪除帳戶"; "Delete Contact" = "刪除連絡人"; "Delete address" = "刪除地址"; "Do not use the QR code from here to receive bitcoins. Go to the Receive screen to get a QR code to receive bitcoins." = "不要使用這裡的二維碼接受比特幣。進入接收螢幕得到一個QR二維碼以接收比特幣。"; "Do you like using ArcBit?" = "您喜歡用ArcBit嗎?"; "Do you want to load and backup your current local wallet file?" = "您要載入和備份您當前的本地錢包檔嗎?"; "Do you want to load local wallet file?" = "要載入本地錢包檔嗎?"; "Do you want to restore from your backup passphrase or start a new wallet?" = "您想從您的備份短語恢復或啟動一個新的錢包?"; "Do you want to temporary import your account private key?" = "您想臨時導入帳戶私密金鑰?"; "Do you want to temporary import your private key?" = "您想臨時導入私密金鑰?"; "Don't remind me" = "不要提醒我"; "Done" = "完成"; "Each account has a public and private account key. Account keys should be kept secret as they are used to view the account's transactions and spend the account's bitcoins." = "每個帳戶都有一個公開金鑰和一個私密金鑰。帳戶公開金鑰和私密金鑰都應當妥善保管,因為它們可以被用來查看帳戶的交易,以及支付帳戶裡的比特幣。"; "Edit" = "編輯"; "Edit Account Name" = "編輯帳戶名稱"; "Edit Contact Name" = "編輯連絡人姓名"; "Edit Label" = "編輯標籤"; "Edit Transaction label" = "編輯交易標籤"; "Email Support" = "電子郵件支援"; "Enable PIN code in settings to better secure your wallet." = "在設置中啟用PIN碼以更好地保護您的錢包"; "Enable Pin Code" = "啟用PIN碼"; "Enable Transaction Fee" = "啟用交易費"; "Enable advanced mode" = "啟用高級模式"; "Encountered error creating transaction. Please try again." = "創建交易時遇到錯誤。請再試一次。"; "Encrypted" = "加密"; "Enter Label" = "輸入標籤"; "Enter PIN" = "輸入PIN碼"; "Enter a wallet backup passphrase to wipe the current wallet and start/restore another." = "輸入一個錢包備份短語以擦除當前的錢包,並啟用/還原另一個。"; "Enter account private key" = "輸入帳戶私密金鑰"; "Enter account public key" = "輸入帳戶公開金鑰"; "Enter address" = "輸入網址"; "Enter an account ID and click 'QR Code'. Then on your primary online device, enable Cold Wallet in settings. Then go to the Accounts screen and click 'Import Cold Wallet Account' and scan the Account Public Key QR Code. Afterwards use this cold wallet account as you would a normal account and deposit bitcoins into it. When you want to make a payment from a cold wallet account, go to the next section on the previous screen and follow the step by step instructions there." = "首先輸入一個帳戶ID並點擊“二維碼”。然後在您的線上主設備上,在設置中啟用冷錢包。然後進入帳戶螢幕,點擊“導入冷錢包帳戶”並掃描帳戶公開金鑰二維碼。之後像使用正常帳戶一樣使用這個冷錢包帳戶,並將比特幣存入其中。如果您想從冷錢包帳戶中支付,請轉至上一螢幕的下一部分,然後按照說明進行操作。"; "Enter backup passphrase" = "輸入備份短語"; "Enter passphrase for your iCloud backup wallet." = "為您的iCloud備份錢包輸入短語。"; "Enter password for encrypted private key" = "輸入加密私密金鑰的密碼"; "Enter the 12 word passphrase that belongs to the cold wallet account that you want to make a payment from. This is the passphrase that was used to generate your account public key that was generated on the 'Create Cold Wallet' section found on the previous screen." = "輸入您要從中付款的冷錢包帳戶所對應的12個單詞的短語。這是前面的螢幕中的“創建冷錢包”部分生成的12單詞短語,該短語被用來生成帳戶公開金鑰。"; "Error" = "錯誤"; "Error fetching Transaction." = "獲取交易時遇到錯誤"; "Error fetching unspent outputs. Try again later." = "獲取未使用的輸出時遇到錯誤。請稍後再試。"; "Error fetching unspent outputs. Try again." = "獲取未使用的輸出時遇到錯誤。請再試一次。"; "Error getting block height." = "獲取塊高度時遇到錯誤。"; "Error importing account" = "導入帳戶時出錯"; "Error loading wallet JSON file" = "載入錢包JSON檔時出錯"; "Explanation" = "說明"; "FAQ" = "常見問題"; "Fee:" = "費用:"; "Fill address field" = "填充地址域"; "Finished Passing Transaction Data" = "交易資料傳送已完成"; "First make sure you are using your secondary offline device for this screen (as mentioned in the overview on the previous screen). Click 'New Wallet' and write down or memorize the generated 12 word passphrase. This passphrase can recover and generate all your accounts and the bitcoins associated with it, so keep it safe and to yourself. Also instead of creating a new wallet, you can also input an existing 12 word passphrase that was generated here to create additional accounts." = "首先請確保您正在使用您的離線輔設備打開此螢幕(如前一螢幕中的概述所述)。點擊“新錢包”,記下或記住生成的12個單詞的短語。這個短語可以恢復並生成所有的帳戶、以及與之相關的比特幣,所以請務必注意保密。或者,有別於創建一個新的錢包,您也可以輸入這裡生成的12個單詞短語來創建額外的帳戶。"; "Follow us on Twitter" = "在推特上關注我們"; "From:" = "從:"; "Funds have been claimed already." = "資金已經被申報。"; "Funds imported" = "資金導入完畢"; "Go" = "出發"; "Go to the side menu" = "轉到側邊菜單"; "Have sender scan QR code" = "讓付款人掃描二維碼"; "Have sender send you payment" = "讓付款人向您發送付款"; "Help" = "幫助"; "Here are some features that no other mobile bitcoin wallet supports.\n- Reusable address support\n- Ability to import individual account (extended) keys\n- iCloud backup support\n- Over 150 local currencies supported" = "這裡有一些ArcBit獨有的功能。\n-可重用位址支持\n-能夠導入個人帳戶(擴展)金鑰\n- iCloud備份支持\n-支持150多種本地貨幣"; "Hierarchical Deterministic Wallet" = "分層確定性錢包"; "History" = "歷史"; "How To:" = "如何:"; "How do I get bitcoins?" = "如何獲得比特幣?"; "How does ArcBit Wallet work?" = "ArcBit錢包是如何工作的?"; "Import Account" = "導入帳戶"; "Import Cold Wallet Account" = "導入冷錢包帳戶"; "Import Feature" = "導入特性"; "Import Private Key" = "導入私密金鑰"; "Import Private/Encrypted Key" = "導入私有/加密金鑰"; "Import Watch Account" = "導入監視帳戶"; "Import Watch Address" = "導入監視位址"; "Import private key encrypted or unencrypted?" = "將私密金鑰導入為加密形式還是不加密形式?"; "Import with QR code" = "以二維碼導入"; "Import with text input" = "以文本輸入導入"; "Imported Account %@" = "導入的帳戶%@"; "Imported Accounts" = "導入的帳戶列表"; "Imported Address" = "導入的地址"; "Imported Addresses" = "導入的地址清單"; "Imported Cold Wallet Account %@" = "導入冷錢包帳戶%@"; "Imported Watch Account %@" = "導入監視帳戶%@"; "Imported Watch Accounts" = "導入的監視帳戶清單"; "Imported Watch Addresses" = "導入的監視地址清單"; "Imported Watch Only Accounts can't see reusable address payments, thus this accounts' reusable address is not available. If you want see the reusable address for this account, import the account private key that corresponds to this accounts public key." = "導入的監視帳戶清單無法看到可重用位址上的付款,因此此帳戶的可重用位址不可用。如果您想查看此帳戶的可重用位址,請導入與此帳戶對應的私密金鑰。"; "Importing Account" = "正在導入帳戶"; "Importing Cold Wallet Account" = "正在導入冷錢包帳戶"; "Importing a Private Key" = "正在導入私密金鑰"; "Importing a Watch Only Account" = "正在導入監視帳戶"; "Importing a Watch Only Address" = "正在導入監視位址"; "Importing an Account" = "正在導入帳戶"; "Importing an encrypted key will require you to input the password every time you want to send bitcoins from it." = "將金鑰導入為加密形式將會在您每次從中付款時要求輸入密碼。"; "In advanced mode, you can import bitcoin keys and addresses from other sources. You can import account private keys, account public keys, private keys, and addresses.\nPlease note that your 12 word passphrase cannot recover your bitcoins, so it is recommended that you backup imported keys and addresses separately." = "在高級模式下,您可以從其他來源導入比特幣金鑰和位址。您可以導入帳戶私密金鑰、帳戶公開金鑰、私密金鑰和地址。\n請注意,您的12個單詞短語無法恢復您以此種方式導入的比特幣,因此建議您單獨備份這些導入的金鑰和位址。"; "Incomplete" = "不完整"; "Incorrect passphrase, could not decrypt iCloud wallet backup." = "短語錯誤,無法解密iCloud錢包備份。"; "Input a bitcoin address" = "輸入一個比特幣位址"; "Input a label" = "輸入一個標籤"; "Input a new label" = "輸入一個新標籤"; "Input a recommended amount. Somewhere between %@ and %@ BTC" = "輸入一個推薦值(介於 %@ 和 %@ BTC之間)"; "Input amount" = "輸入數量"; "Input label" = "輸入標籤"; "Input new account name" = "輸入新帳戶名稱"; "Input transaction fee in bitcoins" = "輸入交易費(以比特幣為單位)"; "Instructions" = "指令"; "Insufficient Funds" = "資金不足"; "Insufficient Funds. Account balance is %@ when %@ is required." = "資金不足。帳戶餘額為%@,但需要%@。"; "Insufficient Funds. Account contains bitcoin dust. You can only send up to %@ for now." = "資金不足。帳戶內有比特幣灰塵。當前您最多可以支出%@。"; "Internal Wallet Data" = "內部錢包資料"; "Internal account transfer" = "內部轉帳"; "Invalid Address" = "無效的地址"; "Invalid Passphrase" = "無效短語"; "Invalid URL" = "無效的網址"; "Invalid account private key" = "無效帳戶私密金鑰"; "Invalid account public Key" = "無效帳戶公開金鑰"; "Invalid amount" = "無效數量"; "Invalid backup passphrase" = "無效的備份短語"; "Invalid private key" = "無效私密金鑰"; "Invalid scanned data" = "無效的掃描資料"; "Invalid transaction ID" = "無效的交易ID"; "It is not recommended that you manually manage private keys yourself. A leak of a private key can lead to the compromise of your accounts." = "我們不建議您自己手工管理私密金鑰。私密金鑰的洩漏可能會導致您的帳戶受到損害。"; "It is not recommended that you use a regular bitcoin address for multiple payments, but instead you should import a reusable address. Add address anyways?" = "我們不建議您使用常規的位址多次付款,建議您導入一個可重用的位址。仍然堅持添加地址嗎?"; "Label" = "標籤"; "Label Transaction" = "給交易加標籤"; "Local backup to wallet failed!" = "本地備份到錢包失敗了!"; "Local wallet will be lost. Are you sure you want to restore wallet from iCloud?" = "本地錢包將會丟失。您確定要從iCloud還原錢包嗎?"; "Maximum accounts reached" = "達到最大的帳戶數量"; "More" = "更多"; "Name" = "名稱"; "Network Error" = "網路錯誤"; "New Wallet" = "新建錢包"; "New addresses will be automatically generated and cycled for you as you use your current available addresses." = "您使用當前可用位址的時候將為您自動生成新位址並迴圈。"; "Next" = "下一個"; "No" = "沒有"; "None currently" = "目前沒有"; "Not now" = "不是現在"; "Now authorize the transaction on your air gap device. When you have done so, click continue on this device to scan the authorized transaction data and make your payment." = "現在,請在您的離線輔設備上授權此交易。然後,在此設備上按一下“繼續”以掃描授權的交易資料並進行付款。"; "OK" = "OK"; "On your primary online device, when you want to make a payment from a cold wallet account, simply do it as you normally would on a normal account. When you click 'Send' on the Review Payment screen, instead of the payment going out immediately, you will be prompted to pass the unauthorized transaction data. Then on your secondary offline device, within this screen click 'Scan' to import the transaction so it can be authorized." = "在您的線上主設備上,當您想通過冷錢包帳戶付款時,只需按照通常在正常帳戶中的方式進行付款。當您在“審核付款”螢幕上點擊“發送”時,系統會提示您傳遞未經授權的交易資料,而不是立即付款。然後在您的離線輔設備上,在此螢幕中按一下“掃描”以導入交易,以便完成對交易的授權。"; "Once the transaction has been authorized by completing the above two steps, pass the authorized transaction back to your primary online device to finalize your payment." = "一旦該交易已通過以上兩個步驟完成授權,將已授權交易送回線上主設備以最終完成支付。"; "Other Links" = "其它連結"; "Pass" = "通過"; "Passphrase" = "短語"; "Passphrase does not match the transaction" = "短語不匹配交易"; "Password" = "密碼"; "Payment Index: %lu" = "付款索引: %lu"; "Private key does not match address" = "私密金鑰不匹配地址"; "Private key missing" = "私密金鑰缺失"; "QR code" = "二維碼"; "Quit and re-enter app" = "退出並重新進入APP"; "Rate" = "比率"; "Rate us in the App Store!" = "在App商店給我們評分!"; "Receive" = "接收"; "Receive Payment" = "接收付款"; "Receive Payment From Reusable Address" = "從可重用位址接收付款"; "Remind me Later" = "稍後提醒我"; "Restore" = "恢復"; "Restore Wallet" = "恢復錢包"; "Restore from iCloud" = "從iCloud還原"; "Restoring Wallet" = "恢復錢包"; "Retry" = "重試"; "Reusable Address Payment Addresses" = "可重用地址付款地址"; "Reusable Address:" = "可重用地址:"; "Reusable Addresses" = "可重用地址清單"; "Review Payment" = "審查付款"; "Save" = "保存"; "Scan" = "掃描"; "Scan For Reusable Address Payment" = "掃描支付到可重用位址的付款"; "Scan QR Code" = "掃描二維碼"; "Scan for reusable address transaction" = "掃描可重用位址交易"; "Scan next part" = "掃描下一部分"; "Scroll down to the section ‘Account Actions’" = "向下滾動到“帳戶操作”部分"; "Select Account" = "選擇帳戶"; "Select and click a blockexplorer API" = "選擇並點擊一個區塊流覽API"; "Select and click a transaction" = "選擇並點擊一個交易"; "Select and click an account" = "選擇並點擊一個帳戶"; "Select and click an account to receive from" = "選擇並點擊一個帳戶以從中收款"; "Select and click an account to view it’s transaction history" = "選擇並點擊一個帳戶以查看其交易歷史記錄"; "Select and click an address" = "選擇並點擊一個位址"; "Send" = "發送"; "Send Payment" = "發送付款"; "Send To Address In Contacts" = "發送到連絡人的地址"; "Send authorized payment?" = "發送已授權的付款?"; "Sending" = "發送中"; "Sending payment to a reusable address might take longer to show up then a normal transaction with the blockchain.info API. You might have to wait until at least 1 confirmation for the transaction to show up. This is due to the limitations of the blockchain.info API. For reusable address payments to show up faster, configure your app to use the Insight API in advance settings." = "在使用blockchain.info API時,比起正常交易,發送款項到可重用位址可能需要較長時間才能顯示出來。您可能不得不至少等到該交易得到1次確認。這是由blockchain.info API的限制導致的。如果希望發送到可重用位址的款項能夠儘快地顯示,請在高級設置中配置您的應用程式使用Insight API。"; "Sent %@ to %@" = "已發送%@到%@"; "Set Transaction Fee in %@" = "在%@中設置交易費"; "Settings" = "設置"; "Some funds may be pending confirmation and cannot be spent yet. (Check your account history) Account only has a spendable balance of %@" = "一些資金尚處於待確認狀態,暫時不能使用。(請檢查您的帳戶歷史記錄)帳戶僅有%@的可支付餘額"; "Some people have compared bitcoin addresses to a bank routing number. It is a good analogy, however bitcoin addresses are public. So if you reuse the same bitcoin address for multiple payments like you would a routing number, people will be able to figure out how much bitcoin you have. Thus it is recommended that you only use one address per payment.\nThis causes usability issues making the user use a new address whenever receiving a payment is cumbersome.\nStealth/reusable addresses provides a better solution. When you give a sender a reusable address, the sender will derive a one time regular bitcoin address from the reusable address. Then the sender will send a payment to that regular bitcoin address. Now you can give many people just one reusable address and have them all send you payments without letting other people know how much bitcoin you have.\nA reusable address looks like this vJmxthatTBXibYe9aZavx18iAT9gyiJETGkhwPX2WbHQGuzX83YvQXynD2t8yHU4Xjfonu5x9m6B4yxquytFP1c2CRbVR9mecxesvE. A reusable address is a lot longer then a regular bitcoin address, it is 102 characters in length.\nReusable addresses are great, however there are no other mobile bitcoin wallets but ArcBit that supports reusable addresses for now. Which is why ArcBit supports receiving payments from both regular bitcoin addresses and reusable addresses.\nFor each account, you have one reusable address. You can find it on your Receive screen. Swipe all the way to right on the QRCode on your Receive screen and you will find a reusable address." = "有些人將比特幣位址與銀行路由號碼進行了比較。這是一個很好的比喻。不過,比特幣地址是公開的。因此,如果您重複使用相同的比特幣位址進行多次付款(就像您的路由號碼),則人們將能夠計算出您擁有多少比特幣。因此,建議您每個位址只收一次款。\n但是要求用戶在每次收款時換用新地址是很麻煩的,這會導致可用性問題。\n隱形地址/可重用地址提供了更好的解決方案。當您給付款人一個可重用位址時,付款人將從可重用位址得到一個一次性的比特幣位址。然後付款人會發送一個支付到該位址。現在您可以給許多人同一個可重用位址,讓他們都給您付款,而不會讓別人知道您有多少比特幣。\n一個可重用位址看起來像這樣:vJmxthatTBXibYe9aZavx18iAT9gyiJETGkhwPX2WbHQGuzX83YvQXynD2t8yHU4Xjfonu5x9m6B4yxquytFP1c2CRbVR9mecxesvE。可重用位址的長度為102個字元,比普通的比特幣位址長得多。\n可重用位址非常棒,但是到目前為止,除了ArcBit,還沒有其他比特幣手機錢包支持。這就是為什麼ArcBit支援從常規比特幣位址和可重用位址接收付款的原因。\n對於每個帳戶,您都有一個可重用位址。在接收螢幕上一直向右滑動二維碼,您就可以找到它。"; "Spending from a cold wallet account" = "從冷錢包帳戶支出"; "Start fresh" = "全新開始"; "Start/Restore Another Wallet" = "開始/恢復另一個錢包"; "Starting Change address ID:" = "找零地址ID:"; "Starting Receiving Address ID:" = "起始接收位址ID:"; "Step 1: Scan transaction to authorize" = "第1步:掃描交易授權"; "Step 2: Input 12 word backup passphrase" = "第2步:輸入12字備份短語"; "Step 3: Pass authorized transaction data" = "第3步:傳遞經過授權的交易資料"; "Steps" = "步驟"; "Success" = "成功"; "Swipe right on an address" = "在地址上向右滑動"; "Swipe to the right on the QR Code Image until you see the reusable address" = "在二維碼圖像上向右滑動,直到看到可重用地址"; "Temporarily import account private key" = "臨時導入帳戶私密金鑰"; "Temporarily import private key" = "臨時導入私密金鑰"; "The cold wallet feature will allow you to create accounts which offer better security compared to normal online wallets. You will need 2 devices to use this feature. Your normal day to day device that is connected to the internet and a secondary device that is not connected to the internet (Your secondary device would need to be online once to download the ArcBit app. Afterwards keep the secondary device offline for maximum security). This feature allows you to authorize bitcoin payments from an offline device so that the keys to your bitcoins will never need to be stored on your online device. Follow the step by step instructions by clicking the info buttons within the below sections." = "冷錢包功能將允許您創建比普通線上錢包更安全的帳戶。您需要2個設備才能使用此功能。您連接到互聯網的日常使用設備和不連接到互聯網的輔助設備(您的輔助設備需要線上一次以下載ArcBit應用程式,然後請保持輔助設備永久性離線以保證最大的安全性)。 此功能允許您從離線設備授權比特幣付款,這樣比特幣的金鑰永遠不需要存儲在您的線上設備上。點擊下面的部分中的資訊按鈕,按照分步指令的指引進行操作。"; "The cold wallet feature will allow you to create accounts which offer better security compared to normal online wallets. You will need 2 devices to use this feature. Your normal day to day device that is connected to the internet and a secondary device that is not connected to the internet (Your secondary device would need to be online once to download the ArcBit app. Afterwards keep the secondary device offline for maximum security). This feature allows you to authorize bitcoin payments from an offline device so that the keys to your bitcoins will never need to be stored on your online device. You can enable the cold wallet feature by going into advanced settings." = "冷錢包功能將允許您創建比普通線上錢包更安全的帳戶。您需要2個設備才能使用此功能。您連接到互聯網的普通日常設備和不連接到互聯網的輔助設備(您的輔助設備需要線上一次以下載ArcBit應用程式,然後請保持輔助設備永久性離線以獲得最大的安全性)。 此功能允許您從離線設備授權比特幣付款,這樣比特幣的金鑰永遠不需要存儲在您的線上設備上。您可以在高級設置中啟用冷錢包功能。"; "This account type can't see reusable address payments" = "此帳戶類型不能看到可重用位址的支付"; "This feature allows you to manually input a transaction ID and see if the corresponding transaction contains a reusable address payment to your reusable address. If so, then the funds will be added to your wallet. Normally the app will discover reusable address payments automatically for you, but if you believe a payment is missing you can use this feature." = "此功能可讓您手工輸入交易ID,以查看相應的交易是否包含支付到您的可重用位址的付款。如果包含,資金將被添加到您的錢包。通常情況下,應用程式將自動發現支付到您的可重用位址的付款,但如果您確信付款丟失了,您可以使用此功能。"; "To:" = "至:"; "Today" = "今天"; "Toggle Automatic Transaction Fee" = "切換動態交易費"; "Toggle ‘Enable Transaction Fee’" = "切換“啟用交易費”"; "Toggle ’Enable advanced mode’" = "切換“啟用高級模式”"; "Total:" = "總計:"; "Transaction %@ already accounted for." = "交易%@已入帳。"; "Transaction %@ does not belong to this account." = "交易%@不屬於此帳戶。"; "Transaction Fee" = "手續費"; "Transaction ID" = "交易ID"; "Transaction ID: %@" = "交易ID: %@"; "Transaction authorized" = "交易授權"; "Transaction confirmations" = "交易確認深度"; "Transaction fees impact how quickly the Bitcoin network will confirm your transactions. Higher fees means faster confirmation times. Default fee behavior can be configured in settings." = "交易費用影響比特幣網路確認您的交易的速度。費用越高,確認越快。可以在設置中配置預設交易費規則。"; "Transaction is not a reusable address transaction." = "交易不是一個可重用位址交易。"; "Transaction needs to be authorized by an offline and air gap device. Send transaction to an offline device for authorization?" = "交易需要由離線輔設備授權。發送交易到離線輔設備進行授權?"; "Transaction needs to be passed back to your online device in order for the payment to be sent" = "交易需要傳遞回您的線上主設備,以便發送付款"; "Try Again" = "再試一次"; "Try our new cold wallet feature!" = "嘗試我們的冷錢包功能!"; "URL does not contain an address." = "網址不包含位址。"; "Unable to get dynamic fees. Falling back on fixed transaction fee. (fee can be configured on review payment)" = "無法獲得動態的費用。仍舊使用固定交易費。(費用可以在審核支付時修改)"; "Unarchive Account" = "解除封存帳戶"; "Unarchive address" = "解除封存地址"; "Unarchived address" = "未封存地址"; "Unconfirmed" = "未確認"; "Unencrypted" = "未加密"; "Use ArcBit on your browser to complement the mobile app. The web wallet has all the features that the mobile wallet has plus more!" = "在您的流覽器中使用ArcBit作為移動APP的補充!網頁版錢包包含所有的移動錢包功能,以及一些額外的功能!"; "Use all funds" = "使用所有資金"; "View Account Address" = "查看帳戶地址"; "View Account Address In Web" = "在網頁中查看帳戶地址"; "View Account Addresses" = "查看帳戶地址清單"; "View Account Private Key" = "查看帳戶私密金鑰"; "View Account Public Key" = "查看帳戶公開金鑰"; "View Achievements" = "查看成就"; "View Addresses" = "查看地址清單"; "View ArcBit Brain Wallet Details" = "查看ArcBit腦錢包詳情"; "View ArcBit Web Wallet Details" = "查看ArcBit網頁錢包詳情"; "View History" = "查看歷史"; "View Private Key" = "查看私密金鑰"; "View Transaction In Web" = "在網頁中查看交易"; "View account private key QR code" = "查看帳戶私密金鑰二維碼"; "View account public key QR code" = "查看帳戶公開金鑰二維碼"; "View address QR code" = "查看地址二維碼"; "View address in web" = "在網路中查看位址"; "View in web" = "在網頁中查看"; "View private key QR code" = "查看私密金鑰二維碼"; "Visit our home page" = "訪問我們的主頁"; "Wallet backup passphrase" = "錢包備份短語"; "Warning" = "警告"; "Welcome to ArcBit, a user only controlled Bitcoin wallet. Start using the app now by depositing your Bitcoins here." = "歡迎來到ArcBit——一款完全由用戶控制的比特幣錢包。將您的比特幣轉入ArcBit,現在就開始使用它!"; "Welcome!" = "歡迎!"; "What are Account/Extended Keys?" = "什麼是帳戶/擴展金鑰?"; "What are accounts?" = "什麼是帳戶?"; "What are reusable addresses?" = "什麼是可重用地址?"; "What are the benefits and advantages of Bitcoin?" = "比特幣有什麼好處?"; "What are transaction confirmations?" = "交易確認數是什麼意思?"; "What is ArcBit's cold wallet feature?" = "ArcBit的冷錢包功能是什麼?"; "What is Bitcoin?" = "什麼是比特幣?"; "What is a bitcoin wallet?" = "什麼是比特幣錢包?"; "What makes ArcBit different from other bitcoin wallets?" = "ArcBit與其他錢包的區別在哪裡?"; "With an ArcBit cold wallet feature, you can create wallets and make payments offline without exposing your private keys to an internet connected device. This feature is great for storing large amounts of bitcoin or for the security conscious minded. Check out this feature in the cold wallet section in the side menu." = "使用ArcBit冷錢包功能,您可以創建錢包並離線付款,而不會將您的私密金鑰暴露給連接到互聯網的設備。這個功能非常適合存儲大量的比特幣或者對安全敏感的用戶。請在側邊功能表的冷錢包部分查看此功能。"; "Write down backup passphrase" = "寫下備份短語"; "Write down or memorize your 12 word wallet backup passphrase. You can view it by clicking \"Show backup passphrase\" in Settings. Your wallet backup passphrase is needed to recover your bitcoins." = "寫下或記住您的12單詞錢包備份短語。在設置中按一下“顯示備份短語”可以查看。您的錢包備份短語可以用來恢復您的比特幣。"; "Write down the 12 word passphrase below and keep it safe. This passphrase alone can restore your entire wallets\' bitcoins (excluding imports)." = "記下下麵的12單詞短語,並妥善保管。這個短語可以恢復您的整個錢包的比特幣(但不包括導入的)"; "Write down the 12 word passphrase below and keep it safe. This passphrase alone can restore your entire wallets\' bitcoins." = "記下下麵的12單詞短語,並妥善保管。這個短語可以恢復您的整個錢包的比特幣。"; "Yes" = "是"; "You are making a payment to a reusable address. Make sure that the receiver can see the payment made to them. (All ArcBit reusable addresses are compatible with other ArcBit wallets)" = "您正在向可重用地址付款。確保接收方可以看到對他們的付款。(所有ArcBit可重用地址都與其他ArcBit錢包相容)"; "You have %@, but %@ is needed. (This includes the transactions fee)" = "您有%@,但需要%@(包括交易費用)。"; "You must exit and kill this app in order for this to take effect." = "您必須退出並殺死此APP,以使此修改生效。"; "Your current wallet will be deleted. Your can restore your current wallet later with the wallet passphrase, but any imported accounts or addresses created in advanced mode cannot be recovered. Do you wish to continue?" = "您目前的錢包將被刪除。您可以稍後使用錢包短語恢復您當前的錢包,但無法恢復在高級模式下創建的任何導入的帳戶或位址。您想繼續嗎?"; "Your iCloud backup was last saved on %@. Do you want to restore your wallet from iCloud or backup your local wallet to iCloud?" = "您的iCloud備份最後保存於%@。您要從iCloud還原您的錢包,還是將本地錢包備份到iCloud?"; "Your new transaction fee is too high" = "您的新交易費太高"; "Your wallet is now restored" = "您的錢包現在已恢復"; "\nAllow camera access in\n Settings->Privacy->Camera->%@" = "\n允許相機訪問於\n 設置->隱私->相機->%@"; "\tArcBit Web Wallet is a Chrome extension. It has all the features of the mobile wallet plus more. Highlights include the ability to create multiple wallets instead of just one, and a new non-cumbersome way to generate wallets, store and spend bitcoins all from cold storage! ArcBit's new way to manage your cold storage bitcoins also offers a more compelling reason to use ArcBit's watch account feature. Now you can safely watch the balance of your cold storage bitcoins by enabling advance mode in ArcBit and importing your cold storage account public keys.\n\tUse ArcBit Web Wallet in whatever way you wish. You can create a new wallet, or you can input your current 12 word backup passphrase to manage the same bitcoins across different devices. Check out the ArcBit Web Wallet in the Chrome Web Store for more details!\n" = "\tArcBit網頁錢包是一款Chrome擴展程式。它比手機錢包具備更多功能。亮點包括可以創建多個錢包,以一種新的簡便方式來生成錢包,以及從冷錢包中存儲和支付比特幣!ArcBit的管理冷錢包的新方法也為使用ArcBit的監視帳戶提供了令人信服的理由。現在,您可以通過在ArcBit中啟用高級模式並導入冷錢包帳戶公開金鑰,安全地觀察冷錢包的比特幣餘額。\n\t您可以以任何您希望的方式使用ArcBit網頁錢包。您可以創建一個新的錢包,或者您也可以輸入您當前的12單詞備份短語,以管理不同設備上的相同比特幣。更多詳細資訊,請參閱Chrome應用商店中的ArcBit網頁錢包。\n"; "\tWith the Arcbit Brain Wallet you can safely spend your bitcoins without ever having your private keys be exposed to the internet. It can be use in conjunction with your Arcbit Wallet or as a stand alone wallet.\n" = "\t使用Arcbit腦錢包,您可以安全地支付您的比特幣,而不必讓您的私密金鑰暴露在互聯網。它可以與您的Arcbit錢包一起使用或作為獨立的錢包使用。\n"; "iCloud Error: %@" = "iCloud錯誤: %@"; "iCloud backup found" = "找到iCloud備份"; "iCloud backup not found" = "未找到iCloud備份"; "iCloud backup will be lost. Are you sure you want to backup your local wallet to iCloud?" = "iCloud備份將丟失。您確定要將本地的錢包備份到iCloud嗎?"; "Wallet backup passphrase will be shown" = "將顯示錢包備份短語"; "Write down or memorize your wallet backup passphrase. If you lose your backup passphrase, your wallet cannot be recovered." = "記下或記住您的錢包備份短語。如果您丟失了備份短語,您的錢包將無法恢復。"; "I understand" = "我明白"; "iCloud support for ArcBit discontinued" = "iCloud對ArcBit的支持已經停止"; "iCloud support for ArcBit is being discontinued. If your backup passphrase has not been backed up already, please do so." = "iCloud即將停止對ArcBit的支持。如果您尚未備份您的備份短語,請備份之。"; "Reusable address payments are disabled until further notice." = "可重用地址付款被禁用,直至另行通知。"; ================================================ FILE: ArcBit/zh-Hant.lproj/Main.strings ================================================ /* Class = "UILabel"; text = "Address ID 3:"; ObjectID = "023-0e-B8c"; */ "023-0e-B8c.text" = "Address ID 3:"; /* Class = "UITextView"; text = "xpub6CUHPUqLTj2pxbrab2gdy7XYKiAoy47PFRBkgf8YiBHD3UzJH6WqQSmLXAndZbFuJnU4tWgBWjRjSxyk6AHN5JpVsUMu4E9MwNCGgFNibDd"; ObjectID = "1BE-No-RbK"; */ "1BE-No-RbK.text" = "xpub6CUHPUqLTj2pxbrab2gdy7XYKiAoy47PFRBkgf8YiBHD3UzJH6WqQSmLXAndZbFuJnU4tWgBWjRjSxyk6AHN5JpVsUMu4E9MwNCGgFNibDd"; /* Class = "UILabel"; text = "Step 1: Scan transaction to authorize"; ObjectID = "1EF-AG-Tuv"; */ "1EF-AG-Tuv.text" = "Step 1: Scan transaction to authorize"; /* Class = "UITextField"; placeholder = "0"; ObjectID = "1SA-Gl-EbR"; */ "1SA-Gl-EbR.placeholder" = "0"; /* Class = "UILabel"; text = "Address ID 0:"; ObjectID = "1T8-1h-MPB"; */ "1T8-1h-MPB.text" = "Address ID 0:"; /* Class = "UILabel"; text = "Change Address ID 3:"; ObjectID = "2Ct-rU-oU0"; */ "2Ct-rU-oU0.text" = "Change Address ID 3:"; /* Class = "UILabel"; text = "Title"; ObjectID = "2WH-yM-ULb"; */ "2WH-yM-ULb.text" = "Title"; /* Class = "UILabel"; text = "BTC"; ObjectID = "3XA-yE-mb3"; */ "3XA-yE-mb3.text" = "BTC"; /* Class = "UINavigationItem"; title = "Cold Wallet"; ObjectID = "3YE-it-wRp"; */ "3YE-it-wRp.title" = "Cold Wallet"; /* Class = "UILabel"; text = "Address ID 1:"; ObjectID = "4Er-7p-LfQ"; */ "4Er-7p-LfQ.text" = "Address ID 1:"; /* Class = "UINavigationItem"; title = "Restore Wallet"; ObjectID = "4al-pX-1Gn"; */ "4al-pX-1Gn.title" = "Restore Wallet"; /* Class = "UITabBarItem"; title = "Send"; ObjectID = "4mb-2o-3A1"; */ "4mb-2o-3A1.title" = "Send"; /* Class = "UITextField"; text = "L5HJ8fiaSL2oGNUrgVcAnq284r9g43F6P2FsjidA7nwuhTDMEdds"; ObjectID = "4p4-5y-JUZ"; */ "4p4-5y-JUZ.text" = "L5HJ8fiaSL2oGNUrgVcAnq284r9g43F6P2FsjidA7nwuhTDMEdds"; /* Class = "UITabBarItem"; title = "Receive"; ObjectID = "4vC-pM-f1o"; */ "4vC-pM-f1o.title" = "Receive"; /* Class = "UINavigationItem"; title = "Links"; ObjectID = "4vj-uf-BUZ"; */ "4vj-uf-BUZ.title" = "Links"; /* Class = "UILabel"; text = "Amount:"; ObjectID = "59b-yN-GFc"; */ "59b-yN-GFc.text" = "Amount:"; /* Class = "UILabel"; text = "Account Name"; ObjectID = "5EJ-8r-ive"; */ "5EJ-8r-ive.text" = "Account Name"; /* Class = "UILabel"; text = "Master Seed Hex"; ObjectID = "6BY-dx-fLW"; */ "6BY-dx-fLW.text" = "Master Seed Hex"; /* Class = "UITextField"; placeholder = "address"; ObjectID = "6V1-g4-Kqz"; */ "6V1-g4-Kqz.placeholder" = "address"; /* Class = "UILabel"; text = "Fee:"; ObjectID = "6dK-Su-wIf"; */ "6dK-Su-wIf.text" = "Fee:"; /* Class = "UITextField"; text = "L5HJ8fiaSL2oGNUrgVcAnq284r9g43F6P2FsjidA7nwuhTDMEdds"; ObjectID = "6iR-L6-vEV"; */ "6iR-L6-vEV.text" = "L5HJ8fiaSL2oGNUrgVcAnq284r9g43F6P2FsjidA7nwuhTDMEdds"; /* Class = "UILabel"; text = "BTC"; ObjectID = "7FD-gV-OIx"; */ "7FD-gV-OIx.text" = "BTC"; /* Class = "UIButton"; normalTitle = "QR Code"; ObjectID = "7uk-nQ-Elq"; */ "7uk-nQ-Elq.normalTitle" = "QR Code"; /* Class = "UIButton"; normalTitle = "QR Code"; ObjectID = "8PH-F7-qh6"; */ "8PH-F7-qh6.normalTitle" = "QR Code"; /* Class = "UITextView"; text = "street ankle life mass ready viable worth renew stove panther immense camera"; ObjectID = "8T4-K7-DEy"; */ "8T4-K7-DEy.text" = "street ankle life mass ready viable worth renew stove panther immense camera"; /* Class = "UITextField"; text = "1Jhrq6Uxfku5sK9mf3QNwmPb1WPbKPQ9Yx"; ObjectID = "8bg-zJ-MLy"; */ "8bg-zJ-MLy.text" = "1Jhrq6Uxfku5sK9mf3QNwmPb1WPbKPQ9Yx"; /* Class = "UIButton"; normalTitle = "QR Code"; ObjectID = "98i-Oo-pHA"; */ "98i-Oo-pHA.normalTitle" = "QR Code"; /* Class = "UITextField"; text = "1Jhrq6Uxfku5sK9mf3QNwmPb1WPbKPQ9Yx"; ObjectID = "9Hg-Kd-50y"; */ "9Hg-Kd-50y.text" = "1Jhrq6Uxfku5sK9mf3QNwmPb1WPbKPQ9Yx"; /* Class = "UILabel"; text = "Step 2: Input 12 word backup passphrase"; ObjectID = "9sL-6M-fQO"; */ "9sL-6M-fQO.text" = "Step 2: Input 12 word backup passphrase"; /* Class = "UILabel"; text = "Label"; ObjectID = "AHj-6Y-pf6"; */ "AHj-6Y-pf6.text" = "Label"; /* Class = "UINavigationItem"; title = "Root View Controller"; ObjectID = "AUN-mh-2Qy"; */ "AUN-mh-2Qy.title" = "Root View Controller"; /* Class = "UILabel"; text = "Incomplete"; ObjectID = "BDz-V2-QTc"; */ "BDz-V2-QTc.text" = "Incomplete"; /* Class = "UILabel"; text = "Wallet backup passphrase"; ObjectID = "C32-6S-BC5"; */ "C32-6S-BC5.text" = "Wallet backup passphrase"; /* Class = "UIButton"; normalTitle = "QR Code"; ObjectID = "CKf-rh-o2A"; */ "CKf-rh-o2A.normalTitle" = "QR Code"; /* Class = "UITextView"; text = "xpub6CUHPUqLTj2pxbrab2gdy7XYKiAoy47PFRBkgf8YiBHD3UzJH6WqQSmLXAndZbFuJnU4tWgBWjRjSxyk6AHN5JpVsUMu4E9MwNCGgFNibDd"; ObjectID = "CZC-KE-fwt"; */ "CZC-KE-fwt.text" = "xpub6CUHPUqLTj2pxbrab2gdy7XYKiAoy47PFRBkgf8YiBHD3UzJH6WqQSmLXAndZbFuJnU4tWgBWjRjSxyk6AHN5JpVsUMu4E9MwNCGgFNibDd"; /* Class = "UITextField"; text = "L5HJ8fiaSL2oGNUrgVcAnq284r9g43F6P2FsjidA7nwuhTDMEdds"; ObjectID = "CyY-nD-niX"; */ "CyY-nD-niX.text" = "L5HJ8fiaSL2oGNUrgVcAnq284r9g43F6P2FsjidA7nwuhTDMEdds"; /* Class = "UILabel"; text = "Change Address ID 2:"; ObjectID = "DHC-ME-zNy"; */ "DHC-ME-zNy.text" = "Change Address ID 2:"; /* Class = "UILabel"; text = "Change Address ID 1:"; ObjectID = "DfB-wQ-pzV"; */ "DfB-wQ-pzV.text" = "Change Address ID 1:"; /* Class = "UILabel"; text = "USD"; ObjectID = "DlS-FL-u3H"; */ "DlS-FL-u3H.text" = "USD"; /* Class = "UIButton"; normalTitle = "QR Code"; ObjectID = "EWj-Ul-aJK"; */ "EWj-Ul-aJK.normalTitle" = "QR Code"; /* Class = "UILabel"; text = "Enter a wallet backup passphrase to wipe the current wallet and start/restore another."; ObjectID = "End-6K-7BJ"; */ "End-6K-7BJ.text" = "Enter a wallet backup passphrase to wipe the current wallet and start/restore another."; /* Class = "UITextField"; text = "1Jhrq6Uxfku5sK9mf3QNwmPb1WPbKPQ9Yx"; ObjectID = "GIx-ls-zRM"; */ "GIx-ls-zRM.text" = "1Jhrq6Uxfku5sK9mf3QNwmPb1WPbKPQ9Yx"; /* Class = "UILabel"; text = "Account Name"; ObjectID = "GKZ-gr-jad"; */ "GKZ-gr-jad.text" = "Account Name"; /* Class = "UIButton"; normalTitle = "QR Code"; ObjectID = "GKc-CM-keO"; */ "GKc-CM-keO.normalTitle" = "QR Code"; /* Class = "UIButton"; normalTitle = "QR Code"; ObjectID = "GXm-xv-dei"; */ "GXm-xv-dei.normalTitle" = "QR Code"; /* Class = "UITextField"; text = "1Jhrq6Uxfku5sK9mf3QNwmPb1WPbKPQ9Yx"; ObjectID = "GaU-ZY-4ye"; */ "GaU-ZY-4ye.text" = "1Jhrq6Uxfku5sK9mf3QNwmPb1WPbKPQ9Yx"; /* Class = "UITextField"; text = "L5HJ8fiaSL2oGNUrgVcAnq284r9g43F6P2FsjidA7nwuhTDMEdds"; ObjectID = "Gev-Fd-cCI"; */ "Gev-Fd-cCI.text" = "L5HJ8fiaSL2oGNUrgVcAnq284r9g43F6P2FsjidA7nwuhTDMEdds"; /* Class = "UILabel"; text = "Address ID 2:"; ObjectID = "Gz1-7B-U19"; */ "Gz1-7B-U19.text" = "Address ID 2:"; /* Class = "UILabel"; text = "description"; ObjectID = "HDn-9v-o1a"; */ "HDn-9v-o1a.text" = "description"; /* Class = "UILabel"; text = "Title"; ObjectID = "J2q-P7-aOq"; */ "J2q-P7-aOq.text" = "Title"; /* Class = "UILabel"; text = "Label"; ObjectID = "JHD-rx-W8M"; */ "JHD-rx-W8M.text" = "Label"; /* Class = "UILabel"; text = "Address ID 4:"; ObjectID = "JMG-AI-3du"; */ "JMG-AI-3du.text" = "Address ID 4:"; /* Class = "UITextField"; text = "L5HJ8fiaSL2oGNUrgVcAnq284r9g43F6P2FsjidA7nwuhTDMEdds"; ObjectID = "Joe-Ag-Cvs"; */ "Joe-Ag-Cvs.text" = "L5HJ8fiaSL2oGNUrgVcAnq284r9g43F6P2FsjidA7nwuhTDMEdds"; /* Class = "UITextField"; text = "1Jhrq6Uxfku5sK9mf3QNwmPb1WPbKPQ9Yx"; ObjectID = "Jrq-7q-biA"; */ "Jrq-7q-biA.text" = "1Jhrq6Uxfku5sK9mf3QNwmPb1WPbKPQ9Yx"; /* Class = "UIButton"; normalTitle = "Contacts"; ObjectID = "JvL-Cg-kNK"; */ "JvL-Cg-kNK.normalTitle" = "Contacts"; /* Class = "UITabBarItem"; title = "Send"; ObjectID = "KIz-U8-UaX"; */ "KIz-U8-UaX.title" = "Send"; /* Class = "UIButton"; normalTitle = "Button"; ObjectID = "Koj-wy-VOb"; */ "Koj-wy-VOb.normalTitle" = "Button"; /* Class = "UIButton"; normalTitle = "QR Code"; ObjectID = "LU1-2p-LAR"; */ "LU1-2p-LAR.normalTitle" = "QR Code"; /* Class = "UILabel"; text = "100 Confimations"; ObjectID = "Lf7-5m-ufO"; */ "Lf7-5m-ufO.text" = "100 Confimations"; /* Class = "UITextField"; text = "1Jhrq6Uxfku5sK9mf3QNwmPb1WPbKPQ9Yx"; ObjectID = "LlW-tc-qEY"; */ "LlW-tc-qEY.text" = "1Jhrq6Uxfku5sK9mf3QNwmPb1WPbKPQ9Yx"; /* Class = "UITextView"; text = "xpub6CUHPUqLTj2pxbrab2gdy7XYKiAoy47PFRBkgf8YiBHD3UzJH6WqQSmLXAndZbFuJnU4tWgBWjRjSxyk6AHN5JpVsUMu4E9MwNCGgFNibDd"; ObjectID = "M9f-im-InR"; */ "M9f-im-InR.text" = "xpub6CUHPUqLTj2pxbrab2gdy7XYKiAoy47PFRBkgf8YiBHD3UzJH6WqQSmLXAndZbFuJnU4tWgBWjRjSxyk6AHN5JpVsUMu4E9MwNCGgFNibDd"; /* Class = "UILabel"; text = "Account Public Key:"; ObjectID = "Mit-XW-AMq"; */ "Mit-XW-AMq.text" = "Account Public Key:"; /* Class = "UITextView"; text = "f23324e18c237876f96d2064abbc26399cc3cd34d503cc6f2b00a9403b003dcad2cf7642e4e3311cf2e5de32307d88b116ac743f0b02fc4c2afe0e30dcd843f6"; ObjectID = "N96-Pu-vSQ"; */ "N96-Pu-vSQ.text" = "f23324e18c237876f96d2064abbc26399cc3cd34d503cc6f2b00a9403b003dcad2cf7642e4e3311cf2e5de32307d88b116ac743f0b02fc4c2afe0e30dcd843f6"; /* Class = "UILabel"; text = "1000000.00 USD"; ObjectID = "NGS-uf-qMF"; */ "NGS-uf-qMF.text" = "1000000.00 USD"; /* Class = "UILabel"; text = "1000000.00"; ObjectID = "NfN-kQ-zfd"; */ "NfN-kQ-zfd.text" = "1000000.00"; /* Class = "UILabel"; text = "From:"; ObjectID = "OX8-v3-aou"; */ "OX8-v3-aou.text" = "From:"; /* Class = "UIButton"; normalTitle = "QR Code"; ObjectID = "Oey-so-vuX"; */ "Oey-so-vuX.normalTitle" = "QR Code"; /* Class = "UILabel"; text = "Label"; ObjectID = "P5j-13-Jpg"; */ "P5j-13-Jpg.text" = "Label"; /* Class = "UILabel"; text = "Incomplete"; ObjectID = "P8S-SZ-g5V"; */ "P8S-SZ-g5V.text" = "Incomplete"; /* Class = "UILabel"; text = "Label"; ObjectID = "P8X-aW-lhi"; */ "P8X-aW-lhi.text" = "Label"; /* Class = "UITextField"; text = "L5HJ8fiaSL2oGNUrgVcAnq284r9g43F6P2FsjidA7nwuhTDMEdds"; ObjectID = "PfF-lM-vHP"; */ "PfF-lM-vHP.text" = "L5HJ8fiaSL2oGNUrgVcAnq284r9g43F6P2FsjidA7nwuhTDMEdds"; /* Class = "UILabel"; text = "12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX "; ObjectID = "PqG-LI-SgL"; */ "PqG-LI-SgL.text" = "12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX "; /* Class = "UILabel"; text = "Step 3: Pass authorized transaction data"; ObjectID = "Qhi-3j-THU"; */ "Qhi-3j-THU.text" = "Step 3: Pass authorized transaction data"; /* Class = "UILabel"; text = "date"; ObjectID = "RAj-d0-yzE"; */ "RAj-d0-yzE.text" = "date"; /* Class = "UILabel"; text = "Starting Change Address ID:"; ObjectID = "RgE-uK-H2P"; */ "RgE-uK-H2P.text" = "Starting Change Address ID:"; /* Class = "UITextField"; placeholder = "0"; ObjectID = "SPM-HC-T68"; */ "SPM-HC-T68.placeholder" = "0"; /* Class = "UITextField"; text = "0"; ObjectID = "SPM-HC-T68"; */ "SPM-HC-T68.text" = "0"; /* Class = "UILabel"; text = "Account ID:"; ObjectID = "SVW-aE-eKJ"; */ "SVW-aE-eKJ.text" = "Account ID:"; /* Class = "UIButton"; normalTitle = "QR Code"; ObjectID = "SVk-vu-qqu"; */ "SVk-vu-qqu.normalTitle" = "QR Code"; /* Class = "UILabel"; text = "1,000,000.12345678"; ObjectID = "UKQ-n4-f8s"; */ "UKQ-n4-f8s.text" = "1,000,000.12345678"; /* Class = "UILabel"; text = "From:"; ObjectID = "VQ0-4Z-YAo"; */ "VQ0-4Z-YAo.text" = "From:"; /* Class = "UILabel"; text = "1000000.00"; ObjectID = "Va3-CW-hze"; */ "Va3-CW-hze.text" = "1000000.00"; /* Class = "UITextField"; text = "L5HJ8fiaSL2oGNUrgVcAnq284r9g43F6P2FsjidA7nwuhTDMEdds"; ObjectID = "Vzj-if-ieW"; */ "Vzj-if-ieW.text" = "L5HJ8fiaSL2oGNUrgVcAnq284r9g43F6P2FsjidA7nwuhTDMEdds"; /* Class = "UIButton"; normalTitle = "New Wallet"; ObjectID = "W0Y-6g-j1G"; */ "W0Y-6g-j1G.normalTitle" = "New Wallet"; /* Class = "UINavigationItem"; title = "Manage Accounts"; ObjectID = "WPt-OD-Zry"; */ "WPt-OD-Zry.title" = "Manage Accounts"; /* Class = "UITextField"; text = "L5HJ8fiaSL2oGNUrgVcAnq284r9g43F6P2FsjidA7nwuhTDMEdds"; ObjectID = "Wgu-Mq-F3I"; */ "Wgu-Mq-F3I.text" = "L5HJ8fiaSL2oGNUrgVcAnq284r9g43F6P2FsjidA7nwuhTDMEdds"; /* Class = "UISegmentedControl"; Whz-q3-zSQ.segmentTitles[0] = "Mnemonic"; ObjectID = "Whz-q3-zSQ"; */ "Whz-q3-zSQ.segmentTitles[0]" = "Mnemonic"; /* Class = "UISegmentedControl"; Whz-q3-zSQ.segmentTitles[1] = "Account Private Key"; ObjectID = "Whz-q3-zSQ"; */ "Whz-q3-zSQ.segmentTitles[1]" = "Account Private Key"; /* Class = "UILabel"; text = "vJmwhHhMNevDQh188gSeHd2xxxYGBQmnVuMY2yG2MmVTC31UWN5s3vaM3xsM2Q1bUremdK1W7eNVgPg1BnvbTyQuDtMKAYJanahvse"; ObjectID = "Wyl-JC-mUk"; */ "Wyl-JC-mUk.text" = "vJmwhHhMNevDQh188gSeHd2xxxYGBQmnVuMY2yG2MmVTC31UWN5s3vaM3xsM2Q1bUremdK1W7eNVgPg1BnvbTyQuDtMKAYJanahvse"; /* Class = "UIButton"; normalTitle = "Send"; ObjectID = "XID-Gh-hGj"; */ "XID-Gh-hGj.normalTitle" = "Send"; /* Class = "UIButton"; normalTitle = "QR Code"; ObjectID = "Xc5-q5-LZT"; */ "Xc5-q5-LZT.normalTitle" = "QR Code"; /* Class = "UILabel"; text = "Backup Passphrase:"; ObjectID = "Y01-kf-oMW"; */ "Y01-kf-oMW.text" = "Backup Passphrase:"; /* Class = "UILabel"; text = "Account Private Key:"; ObjectID = "YTO-L1-1HN"; */ "YTO-L1-1HN.text" = "Account Private Key:"; /* Class = "UITextView"; text = "Loading..."; ObjectID = "YlR-nU-l4e"; */ "YlR-nU-l4e.text" = "Loading..."; /* Class = "UIButton"; normalTitle = "QR Code"; ObjectID = "Znf-pR-Mih"; */ "Znf-pR-Mih.normalTitle" = "QR Code"; /* Class = "UILabel"; text = "Change Address ID 4:"; ObjectID = "aDd-oS-yZx"; */ "aDd-oS-yZx.text" = "Change Address ID 4:"; /* Class = "UIButton"; normalTitle = "Scan"; ObjectID = "aFx-Fr-jWj"; */ "aFx-Fr-jWj.normalTitle" = "Scan"; /* Class = "UITextField"; placeholder = "0"; ObjectID = "aY3-3U-T31"; */ "aY3-3U-T31.placeholder" = "0"; /* Class = "UITextField"; text = "0"; ObjectID = "aY3-3U-T31"; */ "aY3-3U-T31.text" = "0"; /* Class = "UILabel"; text = "Backup Passphrase:"; ObjectID = "aYW-ME-0t8"; */ "aYW-ME-0t8.text" = "Backup Passphrase:"; /* Class = "UILabel"; text = "Change Address ID 0:"; ObjectID = "aog-r6-zfJ"; */ "aog-r6-zfJ.text" = "Change Address ID 0:"; /* Class = "UIButton"; normalTitle = "Review Payment"; ObjectID = "b97-9w-wKT"; */ "b97-9w-wKT.normalTitle" = "Review Payment"; /* Class = "UINavigationItem"; title = "Send"; ObjectID = "bCV-xP-8V6"; */ "bCV-xP-8V6.title" = "Send"; /* Class = "UITextField"; text = "1Jhrq6Uxfku5sK9mf3QNwmPb1WPbKPQ9Yx"; ObjectID = "bQd-mI-uH9"; */ "bQd-mI-uH9.text" = "1Jhrq6Uxfku5sK9mf3QNwmPb1WPbKPQ9Yx"; /* Class = "UIButton"; normalTitle = "QR Code"; ObjectID = "bUQ-jb-flc"; */ "bUQ-jb-flc.normalTitle" = "QR Code"; /* Class = "UILabel"; text = "100000.12345678 BTC"; ObjectID = "bc0-yo-6Ao"; */ "bc0-yo-6Ao.text" = "100000.12345678 BTC"; /* Class = "UIButton"; normalTitle = "Customize Fee"; ObjectID = "bta-AA-vWW"; */ "bta-AA-vWW.normalTitle" = "Customize Fee"; /* Class = "UILabel"; text = "Total:"; ObjectID = "cV8-uv-FpY"; */ "cV8-uv-FpY.text" = "Total:"; /* Class = "UILabel"; text = "Account Name"; ObjectID = "cmA-Xp-Fwi"; */ "cmA-Xp-Fwi.text" = "Account Name"; /* Class = "UITextField"; text = "L5HJ8fiaSL2oGNUrgVcAnq284r9g43F6P2FsjidA7nwuhTDMEdds"; ObjectID = "czu-k8-dQ3"; */ "czu-k8-dQ3.text" = "L5HJ8fiaSL2oGNUrgVcAnq284r9g43F6P2FsjidA7nwuhTDMEdds"; /* Class = "UILabel"; text = "To:"; ObjectID = "dLL-tj-JB2"; */ "dLL-tj-JB2.text" = "To:"; /* Class = "UILabel"; text = "Write down the 12 word passphrase below and keep it safe. You can restore your wallet with this single passphrase."; ObjectID = "evC-L0-0Dc"; */ "evC-L0-0Dc.text" = "Write down the 12 word passphrase below and keep it safe. You can restore your wallet with this single passphrase."; /* Class = "UITextField"; text = "1Jhrq6Uxfku5sK9mf3QNwmPb1WPbKPQ9Yx"; ObjectID = "f2d-GA-xFX"; */ "f2d-GA-xFX.text" = "1Jhrq6Uxfku5sK9mf3QNwmPb1WPbKPQ9Yx"; /* Class = "UIButton"; normalTitle = "QR Code"; ObjectID = "gHg-xR-i65"; */ "gHg-xR-i65.normalTitle" = "QR Code"; /* Class = "UIButton"; normalTitle = "QR Code"; ObjectID = "gw0-28-gSi"; */ "gw0-28-gSi.normalTitle" = "QR Code"; /* Class = "UITextView"; text = "street ankle life mass ready viable worth renew stove panther immense camera"; ObjectID = "h73-3F-Pi3"; */ "h73-3F-Pi3.text" = "street ankle life mass ready viable worth renew stove panther immense camera"; /* Class = "UITabBarItem"; title = "Receive"; ObjectID = "hWO-u0-HoS"; */ "hWO-u0-HoS.title" = "Receive"; /* Class = "UILabel"; text = "Account Private Key:"; ObjectID = "hvV-BT-VFx"; */ "hvV-BT-VFx.text" = "Account Private Key:"; /* Class = "UILabel"; text = "Label"; ObjectID = "i0i-6T-FsV"; */ "i0i-6T-FsV.text" = "Label"; /* Class = "UIButton"; normalTitle = "Scan QR"; ObjectID = "i9k-eB-RT8"; */ "i9k-eB-RT8.normalTitle" = "Scan QR"; /* Class = "UIButton"; normalTitle = "QR Code"; ObjectID = "iHQ-HC-h7I"; */ "iHQ-HC-h7I.normalTitle" = "QR Code"; /* Class = "UIButton"; normalTitle = "QR Code"; ObjectID = "iQv-V2-g9F"; */ "iQv-V2-g9F.normalTitle" = "QR Code"; /* Class = "UILabel"; text = "1000000.00"; ObjectID = "ict-FH-day"; */ "ict-FH-day.text" = "1000000.00"; /* Class = "UIButton"; normalTitle = "New Wallet"; ObjectID = "imI-wQ-7jn"; */ "imI-wQ-7jn.normalTitle" = "New Wallet"; /* Class = "UITextField"; placeholder = "0"; ObjectID = "ixE-Ze-q3A"; */ "ixE-Ze-q3A.placeholder" = "0"; /* Class = "UILabel"; text = "1,000,000.12345678"; ObjectID = "jQ2-VE-bsm"; */ "jQ2-VE-bsm.text" = "1,000,000.12345678"; /* Class = "UINavigationItem"; title = "History"; ObjectID = "kF7-DP-jfw"; */ "kF7-DP-jfw.title" = "History"; /* Class = "UIButton"; normalTitle = "Show QR"; ObjectID = "kND-lh-fVh"; */ "kND-lh-fVh.normalTitle" = "Show QR"; /* Class = "UIButton"; normalTitle = "QR Code"; ObjectID = "kOc-Zv-Dw2"; */ "kOc-Zv-Dw2.normalTitle" = "QR Code"; /* Class = "UITextField"; placeholder = "0"; ObjectID = "l4f-HC-Yrb"; */ "l4f-HC-Yrb.placeholder" = "0"; /* Class = "UITextField"; text = "0"; ObjectID = "l4f-HC-Yrb"; */ "l4f-HC-Yrb.text" = "0"; /* Class = "UITextField"; text = "1Jhrq6Uxfku5sK9mf3QNwmPb1WPbKPQ9Yx"; ObjectID = "lXL-Uj-u1w"; */ "lXL-Uj-u1w.text" = "1Jhrq6Uxfku5sK9mf3QNwmPb1WPbKPQ9Yx"; /* Class = "UITextField"; text = "1Jhrq6Uxfku5sK9mf3QNwmPb1WPbKPQ9Yx"; ObjectID = "ln6-WA-vdK"; */ "ln6-WA-vdK.text" = "1Jhrq6Uxfku5sK9mf3QNwmPb1WPbKPQ9Yx"; /* Class = "UILabel"; text = "Account ID:"; ObjectID = "lpn-HN-00K"; */ "lpn-HN-00K.text" = "Account ID:"; /* Class = "UINavigationItem"; title = "Help"; ObjectID = "m13-Co-pa3"; */ "m13-Co-pa3.title" = "Help"; /* Class = "UITextView"; text = "xpub6CUHPUqLTj2pxbrab2gdy7XYKiAoy47PFRBkgf8YiBHD3UzJH6WqQSmLXAndZbFuJnU4tWgBWjRjSxyk6AHN5JpVsUMu4E9MwNCGgFNibDd"; ObjectID = "nS4-qx-Uvm"; */ "nS4-qx-Uvm.text" = "xpub6CUHPUqLTj2pxbrab2gdy7XYKiAoy47PFRBkgf8YiBHD3UzJH6WqQSmLXAndZbFuJnU4tWgBWjRjSxyk6AHN5JpVsUMu4E9MwNCGgFNibDd"; /* Class = "UIButton"; normalTitle = "Pass"; ObjectID = "noM-hf-cWa"; */ "noM-hf-cWa.normalTitle" = "Pass"; /* Class = "UITextField"; placeholder = "0"; ObjectID = "o2h-Yp-N8m"; */ "o2h-Yp-N8m.placeholder" = "0"; /* Class = "UITextField"; text = "0"; ObjectID = "o2h-Yp-N8m"; */ "o2h-Yp-N8m.text" = "0"; /* Class = "UIButton"; normalTitle = "QR Code"; ObjectID = "o3w-pQ-fEA"; */ "o3w-pQ-fEA.normalTitle" = "QR Code"; /* Class = "UINavigationItem"; title = "Contacts"; ObjectID = "ob3-Xc-rR5"; */ "ob3-Xc-rR5.title" = "Contacts"; /* Class = "UINavigationItem"; title = "Confirm Payment"; ObjectID = "or3-6l-X5f"; */ "or3-6l-X5f.title" = "Confirm Payment"; /* Class = "UITextView"; text = "nice muse brush descend pattern focus bleed college sneak calm leap flight"; ObjectID = "p18-DP-CtY"; */ "p18-DP-CtY.text" = "nice muse brush descend pattern focus bleed college sneak calm leap flight"; /* Class = "UIButton"; normalTitle = "QR Code"; ObjectID = "p52-sH-YUa"; */ "p52-sH-YUa.normalTitle" = "QR Code"; /* Class = "UIButton"; normalTitle = "QR Code"; ObjectID = "pDU-E2-HeJ"; */ "pDU-E2-HeJ.normalTitle" = "QR Code"; /* Class = "UIButton"; normalTitle = "Button"; ObjectID = "q2V-qX-91M"; */ "q2V-qX-91M.normalTitle" = "Button"; /* Class = "UILabel"; text = "Account Name"; ObjectID = "qKO-FH-M15"; */ "qKO-FH-M15.text" = "Account Name"; /* Class = "UITextField"; placeholder = "0"; ObjectID = "r4G-2d-mhz"; */ "r4G-2d-mhz.placeholder" = "0"; /* Class = "UITextField"; text = "0"; ObjectID = "r4G-2d-mhz"; */ "r4G-2d-mhz.text" = "0"; /* Class = "UIButton"; normalTitle = "QR Code"; ObjectID = "r5V-Be-iHY"; */ "r5V-Be-iHY.normalTitle" = "QR Code"; /* Class = "UITextField"; text = "L5HJ8fiaSL2oGNUrgVcAnq284r9g43F6P2FsjidA7nwuhTDMEdds"; ObjectID = "rAM-gb-eOa"; */ "rAM-gb-eOa.text" = "L5HJ8fiaSL2oGNUrgVcAnq284r9g43F6P2FsjidA7nwuhTDMEdds"; /* Class = "UITextView"; text = "street ankle life mass ready viable worth renew stove panther immense camera"; ObjectID = "rEp-Hf-iwp"; */ "rEp-Hf-iwp.text" = "street ankle life mass ready viable worth renew stove panther immense camera"; /* Class = "UIButton"; normalTitle = "QR Code"; ObjectID = "tZh-nZ-RvU"; */ "tZh-nZ-RvU.normalTitle" = "QR Code"; /* Class = "UILabel"; text = "From:"; ObjectID = "u6a-ff-Ab7"; */ "u6a-ff-Ab7.text" = "From:"; /* Class = "UILabel"; text = "Starting Receiving Address ID:"; ObjectID = "wHc-Lm-EHM"; */ "wHc-Lm-EHM.text" = "Starting Receiving Address ID:"; /* Class = "UINavigationItem"; title = "Receive"; ObjectID = "wZG-Zf-k4F"; */ "wZG-Zf-k4F.title" = "Receive"; /* Class = "UILabel"; text = "The master seed hex is derived from the passphrase. If you want to import Bitcoins into another wallet, and if that other wallets don't use BIP39, then it's possible to import the Bitcoins using the master seed hex as long as they support BIP44."; ObjectID = "wjz-SH-FCO"; */ "wjz-SH-FCO.text" = "The master seed hex is derived from the passphrase. If you want to import Bitcoins into another wallet, and if that other wallets don't use BIP39, then it's possible to import the Bitcoins using the master seed hex as long as they support BIP44."; /* Class = "UILabel"; text = "Account Public Key:"; ObjectID = "xa4-b8-Sm0"; */ "xa4-b8-Sm0.text" = "Account Public Key:"; /* Class = "UILabel"; text = "To:"; ObjectID = "xuQ-JE-0IT"; */ "xuQ-JE-0IT.text" = "To:"; /* Class = "UILabel"; text = "Amount:"; ObjectID = "yRL-oX-vHz"; */ "yRL-oX-vHz.text" = "Amount:"; /* Class = "UILabel"; text = "From:"; ObjectID = "yUe-8I-uLU"; */ "yUe-8I-uLU.text" = "From:"; /* Class = "UILabel"; text = "Account ID:"; ObjectID = "zQb-q1-edR"; */ "zQb-q1-edR.text" = "Account ID:"; /* Class = "UINavigationItem"; title = "Passphrase"; ObjectID = "zdP-16-weC"; */ "zdP-16-weC.title" = "Passphrase"; ================================================ FILE: ArcBit.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 371609F6191B495389E4EF29 /* libPods-ArcBit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7C8CDAAD6F71CF30CCD5BBD6 /* libPods-ArcBit.a */; }; C004134A1ACEFAF200505F31 /* TLOperationsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C00413491ACEFAF200505F31 /* TLOperationsManager.swift */; }; C004134B1ACEFCB700505F31 /* TLOperationsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C00413491ACEFAF200505F31 /* TLOperationsManager.swift */; }; C01681A21DB0B1D9004D5FD7 /* TLBrainWalletViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C01681A11DB0B1D9004D5FD7 /* TLBrainWalletViewController.swift */; }; C01681AC1DBD9ED8004D5FD7 /* TLColdWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C01681AB1DBD9ED8004D5FD7 /* TLColdWallet.swift */; }; C0378C931AF4A1850083206F /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0378C921AF4A1850083206F /* AssetsLibrary.framework */; }; C0378C951AF4A18A0083206F /* CoreText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0378C941AF4A18A0083206F /* CoreText.framework */; }; C0378C971AF4A18F0083206F /* QuickLook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0378C961AF4A18F0083206F /* QuickLook.framework */; }; C03ECBF71B54A104005D4B0F /* TLStringLocalized.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0BD70051B4E2C4F00A5788D /* TLStringLocalized.swift */; }; C046DD5F1D6E271000B39580 /* TLReviewPaymentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C046DD5E1D6E271000B39580 /* TLReviewPaymentViewController.swift */; }; C05C398B1AD0710B00AADE55 /* TLCloudDocumentSyncWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = C05C398A1AD0710B00AADE55 /* TLCloudDocumentSyncWrapper.m */; }; C05C398C1AD0712B00AADE55 /* TLCloudDocumentSyncWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = C05C398A1AD0710B00AADE55 /* TLCloudDocumentSyncWrapper.m */; }; C05F82E01EF5D13E0016F0FC /* SocketAckEmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82C81EF5D13E0016F0FC /* SocketAckEmitter.swift */; }; C05F82E11EF5D13E0016F0FC /* SocketAckManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82C91EF5D13E0016F0FC /* SocketAckManager.swift */; }; C05F82E21EF5D13E0016F0FC /* SocketAnyEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82CA1EF5D13E0016F0FC /* SocketAnyEvent.swift */; }; C05F82E31EF5D13E0016F0FC /* SocketClientManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82CB1EF5D13E0016F0FC /* SocketClientManager.swift */; }; C05F82E41EF5D13E0016F0FC /* SocketEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82CC1EF5D13E0016F0FC /* SocketEngine.swift */; }; C05F82E51EF5D13E0016F0FC /* SocketEngineClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82CD1EF5D13E0016F0FC /* SocketEngineClient.swift */; }; C05F82E61EF5D13E0016F0FC /* SocketEnginePacketType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82CE1EF5D13E0016F0FC /* SocketEnginePacketType.swift */; }; C05F82E71EF5D13E0016F0FC /* SocketEnginePollable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82CF1EF5D13E0016F0FC /* SocketEnginePollable.swift */; }; C05F82E81EF5D13E0016F0FC /* SocketEngineSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82D01EF5D13E0016F0FC /* SocketEngineSpec.swift */; }; C05F82E91EF5D13E0016F0FC /* SocketEngineWebsocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82D11EF5D13E0016F0FC /* SocketEngineWebsocket.swift */; }; C05F82EA1EF5D13E0016F0FC /* SocketEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82D21EF5D13E0016F0FC /* SocketEventHandler.swift */; }; C05F82EB1EF5D13E0016F0FC /* SocketExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82D31EF5D13E0016F0FC /* SocketExtensions.swift */; }; C05F82EC1EF5D13E0016F0FC /* SocketIOClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82D41EF5D13E0016F0FC /* SocketIOClient.swift */; }; C05F82ED1EF5D13E0016F0FC /* SocketIOClientConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82D51EF5D13E0016F0FC /* SocketIOClientConfiguration.swift */; }; C05F82EE1EF5D13E0016F0FC /* SocketIOClientOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82D61EF5D13E0016F0FC /* SocketIOClientOption.swift */; }; C05F82EF1EF5D13E0016F0FC /* SocketIOClientSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82D71EF5D13E0016F0FC /* SocketIOClientSpec.swift */; }; C05F82F01EF5D13E0016F0FC /* SocketIOClientStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82D81EF5D13E0016F0FC /* SocketIOClientStatus.swift */; }; C05F82F11EF5D13E0016F0FC /* SocketLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82D91EF5D13E0016F0FC /* SocketLogger.swift */; }; C05F82F21EF5D13E0016F0FC /* SocketPacket.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82DA1EF5D13E0016F0FC /* SocketPacket.swift */; }; C05F82F31EF5D13E0016F0FC /* SocketParsable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82DB1EF5D13E0016F0FC /* SocketParsable.swift */; }; C05F82F41EF5D13E0016F0FC /* SocketStringReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82DC1EF5D13E0016F0FC /* SocketStringReader.swift */; }; C05F82F51EF5D13E0016F0FC /* SocketTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82DD1EF5D13E0016F0FC /* SocketTypes.swift */; }; C05F82F61EF5D13E0016F0FC /* SSLSecurity.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82DE1EF5D13E0016F0FC /* SSLSecurity.swift */; }; C05F82F71EF5D13E0016F0FC /* WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82DF1EF5D13E0016F0FC /* WebSocket.swift */; }; C05F82F81EF5D1420016F0FC /* SocketAckEmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82C81EF5D13E0016F0FC /* SocketAckEmitter.swift */; }; C05F82F91EF5D1450016F0FC /* SocketAckManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82C91EF5D13E0016F0FC /* SocketAckManager.swift */; }; C05F82FA1EF5D1470016F0FC /* SocketAnyEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82CA1EF5D13E0016F0FC /* SocketAnyEvent.swift */; }; C05F82FB1EF5D1490016F0FC /* SocketClientManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82CB1EF5D13E0016F0FC /* SocketClientManager.swift */; }; C05F82FC1EF5D14B0016F0FC /* SocketEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82CC1EF5D13E0016F0FC /* SocketEngine.swift */; }; C05F82FD1EF5D14C0016F0FC /* SocketEngineClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82CD1EF5D13E0016F0FC /* SocketEngineClient.swift */; }; C05F82FE1EF5D14E0016F0FC /* SocketEnginePacketType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82CE1EF5D13E0016F0FC /* SocketEnginePacketType.swift */; }; C05F82FF1EF5D1500016F0FC /* SocketEnginePollable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82CF1EF5D13E0016F0FC /* SocketEnginePollable.swift */; }; C05F83001EF5D1530016F0FC /* SocketEngineSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82D01EF5D13E0016F0FC /* SocketEngineSpec.swift */; }; C05F83011EF5D1550016F0FC /* SocketEngineWebsocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82D11EF5D13E0016F0FC /* SocketEngineWebsocket.swift */; }; C05F83021EF5D1570016F0FC /* SocketEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82D21EF5D13E0016F0FC /* SocketEventHandler.swift */; }; C05F83031EF5D1580016F0FC /* SocketExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82D31EF5D13E0016F0FC /* SocketExtensions.swift */; }; C05F83041EF5D15B0016F0FC /* SocketIOClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82D41EF5D13E0016F0FC /* SocketIOClient.swift */; }; C05F83051EF5D15C0016F0FC /* SocketIOClientConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82D51EF5D13E0016F0FC /* SocketIOClientConfiguration.swift */; }; C05F83061EF5D15F0016F0FC /* SocketIOClientOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82D61EF5D13E0016F0FC /* SocketIOClientOption.swift */; }; C05F83071EF5D1620016F0FC /* SocketIOClientSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82D71EF5D13E0016F0FC /* SocketIOClientSpec.swift */; }; C05F83081EF5D1640016F0FC /* SocketIOClientStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82D81EF5D13E0016F0FC /* SocketIOClientStatus.swift */; }; C05F83091EF5D1660016F0FC /* SocketLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82D91EF5D13E0016F0FC /* SocketLogger.swift */; }; C05F830A1EF5D1680016F0FC /* SocketPacket.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82DA1EF5D13E0016F0FC /* SocketPacket.swift */; }; C05F830B1EF5D16A0016F0FC /* SocketParsable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82DB1EF5D13E0016F0FC /* SocketParsable.swift */; }; C05F830C1EF5D16D0016F0FC /* SocketStringReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82DC1EF5D13E0016F0FC /* SocketStringReader.swift */; }; C05F830D1EF5D16F0016F0FC /* SocketTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82DD1EF5D13E0016F0FC /* SocketTypes.swift */; }; C05F830E1EF5D1710016F0FC /* SSLSecurity.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82DE1EF5D13E0016F0FC /* SSLSecurity.swift */; }; C05F830F1EF5D1730016F0FC /* WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F82DF1EF5D13E0016F0FC /* WebSocket.swift */; }; C0607FCD1AEEB3CD007203F8 /* TLStealthServerConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0607FCC1AEEB3CD007203F8 /* TLStealthServerConfig.swift */; }; C0607FCE1AEEB716007203F8 /* TLStealthServerConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0607FCC1AEEB3CD007203F8 /* TLStealthServerConfig.swift */; }; C0649CA31E256B52007E3965 /* TLAccountTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0649CA01E256B52007E3965 /* TLAccountTableViewCell.swift */; }; C0649CA41E256B52007E3965 /* TLAddressTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0649CA11E256B52007E3965 /* TLAddressTableViewCell.swift */; }; C0649CA51E256B52007E3965 /* TLTransactionTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0649CA21E256B52007E3965 /* TLTransactionTableViewCell.swift */; }; C0649CA71E256E16007E3965 /* TLDisplayStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0649CA61E256E16007E3965 /* TLDisplayStrings.swift */; }; C0649CA81E25969D007E3965 /* TLDisplayStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0649CA61E256E16007E3965 /* TLDisplayStrings.swift */; }; C09D416C1DC9B05200E2D09A /* TLColdWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C01681AB1DBD9ED8004D5FD7 /* TLColdWallet.swift */; }; C0A18F941ABF51C200BED648 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A18F931ABF51C200BED648 /* AppDelegate.swift */; }; C0A18F9B1ABF51C200BED648 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C0A18F9A1ABF51C200BED648 /* Images.xcassets */; }; C0A18F9E1ABF51C200BED648 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = C0A18F9C1ABF51C200BED648 /* LaunchScreen.xib */; }; C0A18FAA1ABF51C200BED648 /* ArcBitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A18FA91ABF51C200BED648 /* ArcBitTests.swift */; }; C0A1909E1ABF53A100BED648 /* arrow-right7-original.png in Resources */ = {isa = PBXBuildFile; fileRef = C0A18FB81ABF53A100BED648 /* arrow-right7-original.png */; }; C0A1909F1ABF53A100BED648 /* arrow-right7.png in Resources */ = {isa = PBXBuildFile; fileRef = C0A18FB91ABF53A100BED648 /* arrow-right7.png */; }; C0A190A01ABF53A100BED648 /* download2.png in Resources */ = {isa = PBXBuildFile; fileRef = C0A18FBA1ABF53A100BED648 /* download2.png */; }; C0A190A11ABF53A100BED648 /* book.png in Resources */ = {isa = PBXBuildFile; fileRef = C0A18FBC1ABF53A100BED648 /* book.png */; }; C0A190A21ABF53A100BED648 /* book@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = C0A18FBD1ABF53A100BED648 /* book@2x.png */; }; C0A190A31ABF53A100BED648 /* book@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = C0A18FBE1ABF53A100BED648 /* book@3x.png */; }; C0A190A41ABF53A100BED648 /* data.png in Resources */ = {isa = PBXBuildFile; fileRef = C0A18FBF1ABF53A100BED648 /* data.png */; }; C0A190A51ABF53A100BED648 /* data@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = C0A18FC01ABF53A100BED648 /* data@2x.png */; }; C0A190A61ABF53A100BED648 /* data@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = C0A18FC11ABF53A100BED648 /* data@3x.png */; }; C0A190A71ABF53A100BED648 /* download.png in Resources */ = {isa = PBXBuildFile; fileRef = C0A18FC21ABF53A100BED648 /* download.png */; }; C0A190A81ABF53A100BED648 /* download@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = C0A18FC31ABF53A100BED648 /* download@2x.png */; }; C0A190A91ABF53A100BED648 /* download@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = C0A18FC41ABF53A100BED648 /* download@3x.png */; }; C0A190AA1ABF53A100BED648 /* list.png in Resources */ = {isa = PBXBuildFile; fileRef = C0A18FC51ABF53A100BED648 /* list.png */; }; C0A190AB1ABF53A100BED648 /* list@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = C0A18FC61ABF53A100BED648 /* list@2x.png */; }; C0A190AC1ABF53A100BED648 /* list@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = C0A18FC71ABF53A100BED648 /* list@3x.png */; }; C0A190AD1ABF53A100BED648 /* newspaper-alt.png in Resources */ = {isa = PBXBuildFile; fileRef = C0A18FC81ABF53A100BED648 /* newspaper-alt.png */; }; C0A190AE1ABF53A100BED648 /* newspaper-alt@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = C0A18FC91ABF53A100BED648 /* newspaper-alt@2x.png */; }; C0A190AF1ABF53A100BED648 /* newspaper-alt@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = C0A18FCA1ABF53A100BED648 /* newspaper-alt@3x.png */; }; C0A190B01ABF53A100BED648 /* settings.png in Resources */ = {isa = PBXBuildFile; fileRef = C0A18FCB1ABF53A100BED648 /* settings.png */; }; C0A190B11ABF53A100BED648 /* settings@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = C0A18FCC1ABF53A100BED648 /* settings@2x.png */; }; C0A190B21ABF53A100BED648 /* settings@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = C0A18FCD1ABF53A100BED648 /* settings@3x.png */; }; C0A190B31ABF53A100BED648 /* upload.png in Resources */ = {isa = PBXBuildFile; fileRef = C0A18FCE1ABF53A100BED648 /* upload.png */; }; C0A190B41ABF53A100BED648 /* upload@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = C0A18FCF1ABF53A100BED648 /* upload@2x.png */; }; C0A190B51ABF53A100BED648 /* upload@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = C0A18FD01ABF53A100BED648 /* upload@3x.png */; }; C0A190B61ABF53A100BED648 /* upload2.png in Resources */ = {isa = PBXBuildFile; fileRef = C0A18FD11ABF53A100BED648 /* upload2.png */; }; C0A190C11ABF53A100BED648 /* BRKey+BIP38.m in Sources */ = {isa = PBXBuildFile; fileRef = C0A18FE41ABF53A100BED648 /* BRKey+BIP38.m */; }; C0A190C21ABF53A100BED648 /* BRKey.m in Sources */ = {isa = PBXBuildFile; fileRef = C0A18FE61ABF53A100BED648 /* BRKey.m */; }; C0A190C31ABF53A100BED648 /* BRTransaction.m in Sources */ = {isa = PBXBuildFile; fileRef = C0A18FE91ABF53A100BED648 /* BRTransaction.m */; }; C0A190C41ABF53A100BED648 /* NSData+Bitcoin.m in Sources */ = {isa = PBXBuildFile; fileRef = C0A18FEC1ABF53A100BED648 /* NSData+Bitcoin.m */; }; C0A190C51ABF53A100BED648 /* NSData+Hash.m in Sources */ = {isa = PBXBuildFile; fileRef = C0A18FEE1ABF53A100BED648 /* NSData+Hash.m */; }; C0A190C61ABF53A100BED648 /* NSMutableData+Bitcoin.m in Sources */ = {isa = PBXBuildFile; fileRef = C0A18FF01ABF53A100BED648 /* NSMutableData+Bitcoin.m */; }; C0A190C71ABF53A100BED648 /* NSString+Base58.m in Sources */ = {isa = PBXBuildFile; fileRef = C0A18FF21ABF53A100BED648 /* NSString+Base58.m */; }; C0A190C81ABF53A100BED648 /* CustomIOS7AlertView.m in Sources */ = {isa = PBXBuildFile; fileRef = C0A18FF51ABF53A100BED648 /* CustomIOS7AlertView.m */; }; C0A190DB1ABF53A100BED648 /* iToast.m in Sources */ = {isa = PBXBuildFile; fileRef = C0A190221ABF53A100BED648 /* iToast.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; C0A190DC1ABF53A100BED648 /* JNKeychain.m in Sources */ = {isa = PBXBuildFile; fileRef = C0A190251ABF53A100BED648 /* JNKeychain.m */; }; C0A190DD1ABF53A100BED648 /* KeychainItemWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = C0A190281ABF53A100BED648 /* KeychainItemWrapper.m */; }; C0A190DE1ABF53A100BED648 /* LTHPasscodeViewController.strings in Resources */ = {isa = PBXBuildFile; fileRef = C0A1902A1ABF53A100BED648 /* LTHPasscodeViewController.strings */; }; C0A190DF1ABF53A100BED648 /* LTHKeychainUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = C0A190351ABF53A100BED648 /* LTHKeychainUtils.m */; }; C0A190E01ABF53A100BED648 /* LTHPasscodeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C0A190371ABF53A100BED648 /* LTHPasscodeViewController.m */; }; C0A190E11ABF53A100BED648 /* NSDate-Utilities.m in Sources */ = {isa = PBXBuildFile; fileRef = C0A1903A1ABF53A100BED648 /* NSDate-Utilities.m */; }; C0A190E21ABF53A100BED648 /* DataMatrix.mm in Sources */ = {isa = PBXBuildFile; fileRef = C0A1903D1ABF53A100BED648 /* DataMatrix.mm */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; C0A190E31ABF53A100BED648 /* QR_Encode.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C0A1903E1ABF53A100BED648 /* QR_Encode.cpp */; }; C0A190E41ABF53A100BED648 /* QRCodeEncoderDemoViewController.mm in Sources */ = {isa = PBXBuildFile; fileRef = C0A190411ABF53A100BED648 /* QRCodeEncoderDemoViewController.mm */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; C0A190E51ABF53A100BED648 /* QREncoder.mm in Sources */ = {isa = PBXBuildFile; fileRef = C0A190451ABF53A100BED648 /* QREncoder.mm */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; C0A190E91ABF53A100BED648 /* UIAlertConrtoller+Blocks.m in Sources */ = {isa = PBXBuildFile; fileRef = C0A190501ABF53A100BED648 /* UIAlertConrtoller+Blocks.m */; }; C0A190EA1ABF53A100BED648 /* UINavigationBar+FixedHeightWhenStatusBarHidden.m in Sources */ = {isa = PBXBuildFile; fileRef = C0A190541ABF53A100BED648 /* UINavigationBar+FixedHeightWhenStatusBarHidden.m */; }; C0A190EC1ABF53A100BED648 /* InAppSettings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = C0A190561ABF53A100BED648 /* InAppSettings.bundle */; }; C0A190EE1ABF53A100BED648 /* TLAccountObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A190591ABF53A100BED648 /* TLAccountObject.swift */; }; C0A190EF1ABF53A100BED648 /* TLAccounts.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A1905A1ABF53A100BED648 /* TLAccounts.swift */; }; C0A190F01ABF53A100BED648 /* TLBlockchainStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A1905B1ABF53A100BED648 /* TLBlockchainStatus.swift */; }; C0A190F11ABF53A100BED648 /* TLCoin.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A1905C1ABF53A100BED648 /* TLCoin.swift */; }; C0A190F21ABF53A100BED648 /* TLCoreBitcoinWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A1905D1ABF53A100BED648 /* TLCoreBitcoinWrapper.swift */; }; C0A190F31ABF53A100BED648 /* TLHDWalletWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A1905E1ABF53A100BED648 /* TLHDWalletWrapper.swift */; }; C0A190F41ABF53A100BED648 /* TLImportedAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A1905F1ABF53A100BED648 /* TLImportedAddress.swift */; }; C0A190F51ABF53A100BED648 /* TLImportedAddresses.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A190601ABF53A100BED648 /* TLImportedAddresses.swift */; }; C0A190F61ABF53A100BED648 /* TLSelectedObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A190611ABF53A100BED648 /* TLSelectedObject.swift */; }; C0A190F71ABF53A100BED648 /* TLSendFormData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A190621ABF53A100BED648 /* TLSendFormData.swift */; }; C0A190F81ABF53A100BED648 /* TLSpaghettiGodSend.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A190631ABF53A100BED648 /* TLSpaghettiGodSend.swift */; }; C0A190F91ABF53A100BED648 /* TLStealthAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A190641ABF53A100BED648 /* TLStealthAddress.swift */; }; C0A190FA1ABF53A100BED648 /* TLStealthWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A190651ABF53A100BED648 /* TLStealthWallet.swift */; }; C0A190FB1ABF53A100BED648 /* TLSuggestions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A190661ABF53A100BED648 /* TLSuggestions.swift */; }; C0A190FC1ABF53A100BED648 /* TLTxObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A190671ABF53A100BED648 /* TLTxObject.swift */; }; C0A190FD1ABF53A100BED648 /* TLWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A190681ABF53A100BED648 /* TLWallet.swift */; }; C0A190FE1ABF53A100BED648 /* TLWalletJson.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A190691ABF53A100BED648 /* TLWalletJson.swift */; }; C0A190FF1ABF53A100BED648 /* TLWalletUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A1906A1ABF53A100BED648 /* TLWalletUtils.swift */; }; C0A191181ABF53A100BED648 /* UINavigationController+StatusBarStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A190861ABF53A100BED648 /* UINavigationController+StatusBarStyle.swift */; }; C0A191191ABF53A100BED648 /* UIView+FormScroll.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A190871ABF53A100BED648 /* UIView+FormScroll.swift */; }; C0A1911A1ABF53A100BED648 /* UIViewController+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A190881ABF53A100BED648 /* UIViewController+Extras.swift */; }; C0A1911B1ABF53A100BED648 /* UIViewController+Style.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A190891ABF53A100BED648 /* UIViewController+Style.swift */; }; C0A1911C1ABF53A100BED648 /* TLAccountsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A1908B1ABF53A100BED648 /* TLAccountsViewController.swift */; }; C0A1911D1ABF53A100BED648 /* TLAchievementsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A1908C1ABF53A100BED648 /* TLAchievementsViewController.swift */; }; C0A1911E1ABF53A100BED648 /* TLAddressBookViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A1908D1ABF53A100BED648 /* TLAddressBookViewController.swift */; }; C0A1911F1ABF53A100BED648 /* TLAddressListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A1908E1ABF53A100BED648 /* TLAddressListViewController.swift */; }; C0A191201ABF53A100BED648 /* TLRestoreWalletViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A1908F1ABF53A100BED648 /* TLRestoreWalletViewController.swift */; }; C0A191211ABF53A100BED648 /* TLHelpViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A190901ABF53A100BED648 /* TLHelpViewController.swift */; }; C0A191221ABF53A100BED648 /* TLHistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A190911ABF53A100BED648 /* TLHistoryViewController.swift */; }; C0A191231ABF53A100BED648 /* TLInstructionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A190921ABF53A100BED648 /* TLInstructionsViewController.swift */; }; C0A191241ABF53A100BED648 /* TLManageAccountsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A190931ABF53A100BED648 /* TLManageAccountsViewController.swift */; }; C0A191251ABF53A100BED648 /* TLMenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A190941ABF53A100BED648 /* TLMenuViewController.swift */; }; C0A191261ABF53A100BED648 /* TLPassPhraseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A190951ABF53A100BED648 /* TLPassPhraseViewController.swift */; }; C0A191271ABF53A100BED648 /* TLPreloadViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A190961ABF53A100BED648 /* TLPreloadViewController.swift */; }; C0A191281ABF53A100BED648 /* TLQRCodeScannerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A190971ABF53A100BED648 /* TLQRCodeScannerViewController.swift */; }; C0A191291ABF53A100BED648 /* TLReceiveViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A190981ABF53A100BED648 /* TLReceiveViewController.swift */; }; C0A1912A1ABF53A100BED648 /* TLSendViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A190991ABF53A100BED648 /* TLSendViewController.swift */; }; C0A1912B1ABF53A100BED648 /* TLSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A1909A1ABF53A100BED648 /* TLSettingsViewController.swift */; }; C0A1912C1ABF53A100BED648 /* TLTextViewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A1909B1ABF53A100BED648 /* TLTextViewViewController.swift */; }; C0A192D31ABF6EB200BED648 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0A192D21ABF6EB200BED648 /* CoreGraphics.framework */; }; C0A192D51ABF6EB900BED648 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0A192D41ABF6EB900BED648 /* QuartzCore.framework */; }; C0A192D71ABF6EC200BED648 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0A192D61ABF6EC200BED648 /* AVFoundation.framework */; }; C0A192D91ABF6EC900BED648 /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0A192D81ABF6EC900BED648 /* CoreMedia.framework */; }; C0A192DB1ABF6ED500BED648 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0A192DA1ABF6ED500BED648 /* CFNetwork.framework */; }; C0A192DD1ABF6EDC00BED648 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0A192DC1ABF6EDC00BED648 /* MobileCoreServices.framework */; }; C0A192DF1ABF6EE800BED648 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0A192DE1ABF6EE800BED648 /* SystemConfiguration.framework */; }; C0A192E11ABF6EEE00BED648 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0A192E01ABF6EEE00BED648 /* Security.framework */; }; C0A192E31ABF6EF500BED648 /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0A192E21ABF6EF500BED648 /* MessageUI.framework */; }; C0A192E51ABF6F0500BED648 /* libicucore.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = C0A192E41ABF6F0500BED648 /* libicucore.dylib */; }; C0A192E81ABF707400BED648 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C0A192E61ABF707400BED648 /* Main.storyboard */; }; C0A192EC1ABF742D00BED648 /* TLAchievements.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A192E91ABF742D00BED648 /* TLAchievements.swift */; }; C0A192ED1ABF742D00BED648 /* TLAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A192EA1ABF742D00BED648 /* TLAnalytics.swift */; }; C0A192EE1ABF742D00BED648 /* TLPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A192EB1ABF742D00BED648 /* TLPreferences.swift */; }; C0A192F71ABF744300BED648 /* TLMacros.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A192EF1ABF744300BED648 /* TLMacros.swift */; }; C0A192F81ABF744300BED648 /* TLUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A192F01ABF744300BED648 /* TLUtils.swift */; }; C0A192F91ABF744300BED648 /* TLColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A192F11ABF744300BED648 /* TLColors.swift */; }; C0A192FA1ABF744300BED648 /* TLHUDWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A192F21ABF744300BED648 /* TLHUDWrapper.swift */; }; C0A192FB1ABF744300BED648 /* TLNotificationEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A192F31ABF744300BED648 /* TLNotificationEvents.swift */; }; C0A192FD1ABF744300BED648 /* TLPrompts.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A192F51ABF744300BED648 /* TLPrompts.swift */; }; C0A192FE1ABF744300BED648 /* TLQRImageModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A192F61ABF744300BED648 /* TLQRImageModal.swift */; }; C0A193091ABF744E00BED648 /* TLBitcoinListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A193001ABF744E00BED648 /* TLBitcoinListener.swift */; }; C0A1930A1ABF744E00BED648 /* TLBlockchainAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A193011ABF744E00BED648 /* TLBlockchainAPI.swift */; }; C0A1930B1ABF744E00BED648 /* TLBlockExplorerAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A193021ABF744E00BED648 /* TLBlockExplorerAPI.swift */; }; C0A1930D1ABF744E00BED648 /* TLExchangeRate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A193041ABF744E00BED648 /* TLExchangeRate.swift */; }; C0A1930E1ABF744E00BED648 /* TLInsightAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A193051ABF744E00BED648 /* TLInsightAPI.swift */; }; C0A1930F1ABF744E00BED648 /* TLNetworking.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A193061ABF744E00BED648 /* TLNetworking.swift */; }; C0A193101ABF744E00BED648 /* TLStealthServerAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A193071ABF744E00BED648 /* TLStealthServerAPI.swift */; }; C0A193111ABF744E00BED648 /* TLStealthWebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A193081ABF744E00BED648 /* TLStealthWebSocket.swift */; }; C0A1931A1ABF751800BED648 /* BreadWalletTests.m in Sources */ = {isa = PBXBuildFile; fileRef = C0A193191ABF751800BED648 /* BreadWalletTests.m */; }; C0A1931C1ABF81CB00BED648 /* TLStealthAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A190641ABF53A100BED648 /* TLStealthAddress.swift */; }; C0A1931D1ABF81D100BED648 /* TLWalletJson.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A190691ABF53A100BED648 /* TLWalletJson.swift */; }; C0A1931E1ABF81DD00BED648 /* TLMacros.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A192EF1ABF744300BED648 /* TLMacros.swift */; }; C0A1931F1ABF822000BED648 /* TLHDWalletWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A1905E1ABF53A100BED648 /* TLHDWalletWrapper.swift */; }; C0A193201ABF824800BED648 /* TLWalletUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A1906A1ABF53A100BED648 /* TLWalletUtils.swift */; }; C0A193211ABF825700BED648 /* TLCoreBitcoinWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A1905D1ABF53A100BED648 /* TLCoreBitcoinWrapper.swift */; }; C0A193221ABF829800BED648 /* TLAchievements.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A192E91ABF742D00BED648 /* TLAchievements.swift */; }; C0A193231ABF829A00BED648 /* TLAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A192EA1ABF742D00BED648 /* TLAnalytics.swift */; }; C0A193241ABF829C00BED648 /* TLPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A192EB1ABF742D00BED648 /* TLPreferences.swift */; }; C0A193251ABF829E00BED648 /* TLAccountObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A190591ABF53A100BED648 /* TLAccountObject.swift */; }; C0A193261ABF82A100BED648 /* TLAccounts.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A1905A1ABF53A100BED648 /* TLAccounts.swift */; }; C0A193271ABF82A300BED648 /* TLBlockchainStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A1905B1ABF53A100BED648 /* TLBlockchainStatus.swift */; }; C0A193281ABF82A500BED648 /* TLCoin.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A1905C1ABF53A100BED648 /* TLCoin.swift */; }; C0A193291ABF82AA00BED648 /* TLImportedAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A1905F1ABF53A100BED648 /* TLImportedAddress.swift */; }; C0A1932A1ABF82AC00BED648 /* TLImportedAddresses.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A190601ABF53A100BED648 /* TLImportedAddresses.swift */; }; C0A1932B1ABF82AF00BED648 /* TLSelectedObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A190611ABF53A100BED648 /* TLSelectedObject.swift */; }; C0A1932C1ABF82B100BED648 /* TLSendFormData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A190621ABF53A100BED648 /* TLSendFormData.swift */; }; C0A1932D1ABF82B300BED648 /* TLSpaghettiGodSend.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A190631ABF53A100BED648 /* TLSpaghettiGodSend.swift */; }; C0A1932E1ABF82B700BED648 /* TLStealthWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A190651ABF53A100BED648 /* TLStealthWallet.swift */; }; C0A1932F1ABF82B900BED648 /* TLSuggestions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A190661ABF53A100BED648 /* TLSuggestions.swift */; }; C0A193301ABF82BB00BED648 /* TLTxObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A190671ABF53A100BED648 /* TLTxObject.swift */; }; C0A193311ABF82BD00BED648 /* TLWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A190681ABF53A100BED648 /* TLWallet.swift */; }; C0A193321ABF82E200BED648 /* TLBitcoinListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A193001ABF744E00BED648 /* TLBitcoinListener.swift */; }; C0A193331ABF82E400BED648 /* TLBlockchainAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A193011ABF744E00BED648 /* TLBlockchainAPI.swift */; }; C0A193341ABF82E600BED648 /* TLBlockExplorerAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A193021ABF744E00BED648 /* TLBlockExplorerAPI.swift */; }; C0A193361ABF82EA00BED648 /* TLExchangeRate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A193041ABF744E00BED648 /* TLExchangeRate.swift */; }; C0A193371ABF82EC00BED648 /* TLInsightAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A193051ABF744E00BED648 /* TLInsightAPI.swift */; }; C0A193381ABF82EF00BED648 /* TLNetworking.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A193061ABF744E00BED648 /* TLNetworking.swift */; }; C0A193391ABF82F100BED648 /* TLStealthWebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A193081ABF744E00BED648 /* TLStealthWebSocket.swift */; }; C0A1933A1ABF82F300BED648 /* TLStealthServerAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A193071ABF744E00BED648 /* TLStealthServerAPI.swift */; }; C0A1933E1ABF830000BED648 /* TLNotificationEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A192F31ABF744300BED648 /* TLNotificationEvents.swift */; }; C0A6AD751C42E80F00B8C22B /* TLLinksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A6AD741C42E80F00B8C22B /* TLLinksViewController.swift */; }; C0AB0A331AC52A8400B456DB /* TLPushTxAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0AB0A321AC52A8400B456DB /* TLPushTxAPI.swift */; }; C0AD2C591AC3DE3200FEF982 /* TLBlockrAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0AD2C581AC3DE3200FEF982 /* TLBlockrAPI.swift */; }; C0AD2C5A1AC3DED100FEF982 /* TLBlockrAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0AD2C581AC3DE3200FEF982 /* TLBlockrAPI.swift */; }; C0AD2C5B1AC4DF7700FEF982 /* TLUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A192F01ABF744300BED648 /* TLUtils.swift */; }; C0AE41B11F788BD700682CDE /* IASKAppSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C0AE417E1F788BD700682CDE /* IASKAppSettingsViewController.m */; }; C0AE41B21F788BD700682CDE /* IASKAppSettingsWebViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C0AE41801F788BD700682CDE /* IASKAppSettingsWebViewController.m */; }; C0AE41B31F788BD700682CDE /* IASKMultipleValueSelection.m in Sources */ = {isa = PBXBuildFile; fileRef = C0AE41821F788BD700682CDE /* IASKMultipleValueSelection.m */; }; C0AE41B41F788BD700682CDE /* IASKSpecifierValuesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C0AE41841F788BD700682CDE /* IASKSpecifierValuesViewController.m */; }; C0AE41B51F788BD700682CDE /* IASKSettingsReader.m in Sources */ = {isa = PBXBuildFile; fileRef = C0AE41881F788BD700682CDE /* IASKSettingsReader.m */; }; C0AE41B61F788BD700682CDE /* IASKSettingsStore.m in Sources */ = {isa = PBXBuildFile; fileRef = C0AE418A1F788BD700682CDE /* IASKSettingsStore.m */; }; C0AE41B71F788BD700682CDE /* IASKSettingsStoreFile.m in Sources */ = {isa = PBXBuildFile; fileRef = C0AE418C1F788BD700682CDE /* IASKSettingsStoreFile.m */; }; C0AE41B81F788BD700682CDE /* IASKSettingsStoreUserDefaults.m in Sources */ = {isa = PBXBuildFile; fileRef = C0AE418E1F788BD700682CDE /* IASKSettingsStoreUserDefaults.m */; }; C0AE41B91F788BD700682CDE /* IASKSpecifier.m in Sources */ = {isa = PBXBuildFile; fileRef = C0AE41901F788BD700682CDE /* IASKSpecifier.m */; }; C0AE41BA1F788BD700682CDE /* IASKLocalizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C0AE41921F788BD700682CDE /* IASKLocalizable.strings */; }; C0AE41BB1F788BD700682CDE /* IASKPSSliderSpecifierViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C0AE41A41F788BD700682CDE /* IASKPSSliderSpecifierViewCell.m */; }; C0AE41BC1F788BD700682CDE /* IASKPSTextFieldSpecifierViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C0AE41A61F788BD700682CDE /* IASKPSTextFieldSpecifierViewCell.m */; }; C0AE41BD1F788BD700682CDE /* IASKSlider.m in Sources */ = {isa = PBXBuildFile; fileRef = C0AE41A81F788BD700682CDE /* IASKSlider.m */; }; C0AE41BE1F788BD700682CDE /* IASKSwitch.m in Sources */ = {isa = PBXBuildFile; fileRef = C0AE41AA1F788BD700682CDE /* IASKSwitch.m */; }; C0AE41BF1F788BD700682CDE /* IASKTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = C0AE41AC1F788BD700682CDE /* IASKTextField.m */; }; C0AE41C01F788BD700682CDE /* IASKTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = C0AE41AE1F788BD700682CDE /* IASKTextView.m */; }; C0AE41C11F788BD700682CDE /* IASKTextViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C0AE41B01F788BD700682CDE /* IASKTextViewCell.m */; }; C0B4E7891AFAAEA200DC9B65 /* SRWebSocket+Helpers.m in Sources */ = {isa = PBXBuildFile; fileRef = C0B4E7881AFAAEA200DC9B65 /* SRWebSocket+Helpers.m */; }; C0BD70011B4E2BE300A5788D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C0BD70031B4E2BE300A5788D /* Localizable.strings */; }; C0BD70061B4E2C4F00A5788D /* TLStringLocalized.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0BD70051B4E2C4F00A5788D /* TLStringLocalized.swift */; }; C0C1BE781AF92CB500B705C4 /* base64.c in Sources */ = {isa = PBXBuildFile; fileRef = C0C1BE711AF92CB500B705C4 /* base64.c */; }; C0C1BE791AF92CB500B705C4 /* NSData+SRB64Additions.m in Sources */ = {isa = PBXBuildFile; fileRef = C0C1BE741AF92CB500B705C4 /* NSData+SRB64Additions.m */; }; C0C1BE7A1AF92CB500B705C4 /* SRWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = C0C1BE771AF92CB500B705C4 /* SRWebSocket.m */; }; C0C3BDEC1AC0F7AA001AC6CD /* TLWallet+Stealth.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C3BDEB1AC0F7AA001AC6CD /* TLWallet+Stealth.swift */; }; C0C3BDED1AC11BD2001AC6CD /* TLWallet+Stealth.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C3BDEB1AC0F7AA001AC6CD /* TLWallet+Stealth.swift */; }; C0C61C5A1AF15BEF00F9F71F /* TransitionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C61C591AF15BEF00F9F71F /* TransitionDelegate.swift */; }; C0C61C5E1AF15E6100F9F71F /* TransparentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C61C5D1AF15E6100F9F71F /* TransparentViewController.swift */; }; C0D20A921D5963FF008F70F2 /* TLTxFeeAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0D20A911D5963FF008F70F2 /* TLTxFeeAPI.swift */; }; C0D20A931D5966AB008F70F2 /* TLTxFeeAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0D20A911D5963FF008F70F2 /* TLTxFeeAPI.swift */; }; C0D5A6141DD8FA1300849E2C /* TLAdvancedNewWalletTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0D5A60D1DD8FA1300849E2C /* TLAdvancedNewWalletTableViewCell.swift */; }; C0D5A6151DD8FA1300849E2C /* TLColdWalletSelectWayTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0D5A60E1DD8FA1300849E2C /* TLColdWalletSelectWayTableViewCell.swift */; }; C0D5A6161DD8FA1300849E2C /* TLNewWalletTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0D5A60F1DD8FA1300849E2C /* TLNewWalletTableViewCell.swift */; }; C0D5A6171DD8FA1300849E2C /* TLInputColdWalletKeyTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0D5A6111DD8FA1300849E2C /* TLInputColdWalletKeyTableViewCell.swift */; }; C0D5A6181DD8FA1300849E2C /* TLPassSignedTxTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0D5A6121DD8FA1300849E2C /* TLPassSignedTxTableViewCell.swift */; }; C0D5A6191DD8FA1300849E2C /* TLScanUnsignedTxTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0D5A6131DD8FA1300849E2C /* TLScanUnsignedTxTableViewCell.swift */; }; C0D7B2DB1B16FDA10096E599 /* live.cer in Resources */ = {isa = PBXBuildFile; fileRef = C0D7B2DA1B16FDA10096E599 /* live.cer */; }; C0D8A36B1DAA126700497D31 /* TLColdWalletViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0D8A36A1DAA126700497D31 /* TLColdWalletViewController.swift */; }; C0D8A36D1DAA16C900497D31 /* TLCreateColdWalletViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0D8A36C1DAA16C900497D31 /* TLCreateColdWalletViewController.swift */; }; C0D8A36F1DAA183D00497D31 /* TLAuthorizeColdWalletPaymentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0D8A36E1DAA183D00497D31 /* TLAuthorizeColdWalletPaymentViewController.swift */; }; C0E5393D1AD454FD00651FE4 /* TLHelpDoc.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0E5393C1AD454FD00651FE4 /* TLHelpDoc.swift */; }; C0E5393E1AD4555C00651FE4 /* TLHelpDoc.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0E5393C1AD454FD00651FE4 /* TLHelpDoc.swift */; }; C0EE6D6A1B1B7E6000982692 /* 360X80logo.png in Resources */ = {isa = PBXBuildFile; fileRef = C0EE6D681B1B7E6000982692 /* 360X80logo.png */; }; C0EE6D6B1B1B7E6000982692 /* 1200X1200logo.png in Resources */ = {isa = PBXBuildFile; fileRef = C0EE6D691B1B7E6000982692 /* 1200X1200logo.png */; }; C0F278C81C41EA1A0013DC1A /* TLCurrencyFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0F278C71C41EA1A0013DC1A /* TLCurrencyFormat.swift */; }; C0F278C91C41EAF90013DC1A /* TLCurrencyFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0F278C71C41EA1A0013DC1A /* TLCurrencyFormat.swift */; }; F338F9561B794D1D00C02F74 /* TLWalletPassphrase.swift in Sources */ = {isa = PBXBuildFile; fileRef = F338F9551B794D1D00C02F74 /* TLWalletPassphrase.swift */; }; F338F9581B79718900C02F74 /* TLCrypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = F338F9571B79718900C02F74 /* TLCrypto.swift */; }; F34D5C601B7A2C0100DF37DC /* TLCrypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = F338F9571B79718900C02F74 /* TLCrypto.swift */; }; F37072461B8F570D0026505D /* TLWalletConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = F37072451B8F570D0026505D /* TLWalletConfig.swift */; }; F37072481B8F57810026505D /* TLWalletConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = F37072451B8F570D0026505D /* TLWalletConfig.swift */; }; F3BC360F1B8681A300CBB3F7 /* TLWalletPassphrase.swift in Sources */ = {isa = PBXBuildFile; fileRef = F338F9551B794D1D00C02F74 /* TLWalletPassphrase.swift */; }; F3BC36111B8681FF00CBB3F7 /* TLUpdateAppData.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BC36101B8681FF00CBB3F7 /* TLUpdateAppData.swift */; }; F3BC36121B8681FF00CBB3F7 /* TLUpdateAppData.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BC36101B8681FF00CBB3F7 /* TLUpdateAppData.swift */; }; F3DAAD661BB1D9EC000488F0 /* TLWalletJSONKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3DAAD651BB1D9EC000488F0 /* TLWalletJSONKeys.swift */; }; F3F1651B1BB1DB7F00F1293A /* TLWalletJSONKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3DAAD651BB1D9EC000488F0 /* TLWalletJSONKeys.swift */; }; F7E43EEB86A2A43FC05335C4 /* libPods-ArcBitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BABE2B037811FC35D025CD09 /* libPods-ArcBitTests.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ C0A18FA41ABF51C200BED648 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C0A18F861ABF51C200BED648 /* Project object */; proxyType = 1; remoteGlobalIDString = C0A18F8D1ABF51C200BED648; remoteInfo = ArcBit; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 173E65BB79838FECABC37B10 /* Pods-ArcBitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ArcBitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-ArcBitTests/Pods-ArcBitTests.release.xcconfig"; sourceTree = ""; }; 3C7ACA9C0834D6AF891F762C /* Pods-ArcBitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ArcBitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ArcBitTests/Pods-ArcBitTests.debug.xcconfig"; sourceTree = ""; }; 7C8CDAAD6F71CF30CCD5BBD6 /* libPods-ArcBit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ArcBit.a"; sourceTree = BUILT_PRODUCTS_DIR; }; AA36D9E0C4AFE7E2A43F3C79 /* Pods-ArcBit.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ArcBit.release.xcconfig"; path = "Pods/Target Support Files/Pods-ArcBit/Pods-ArcBit.release.xcconfig"; sourceTree = ""; }; BABE2B037811FC35D025CD09 /* libPods-ArcBitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ArcBitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; C00413491ACEFAF200505F31 /* TLOperationsManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLOperationsManager.swift; sourceTree = ""; }; C00E61361FB18C65003AD828 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; C01681A11DB0B1D9004D5FD7 /* TLBrainWalletViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLBrainWalletViewController.swift; sourceTree = ""; }; C01681AB1DBD9ED8004D5FD7 /* TLColdWallet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLColdWallet.swift; sourceTree = ""; }; C01A489E1B4F9E6E004D7FA0 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Main.strings; sourceTree = ""; }; C02D3B111B4A45F500B3B7F0 /* ArcBit copy-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "ArcBit copy-Info.plist"; path = "/Users/timothylee/git/arcbit-ios/ArcBit copy-Info.plist"; sourceTree = ""; }; C0378C921AF4A1850083206F /* AssetsLibrary.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AssetsLibrary.framework; path = System/Library/Frameworks/AssetsLibrary.framework; sourceTree = SDKROOT; }; C0378C941AF4A18A0083206F /* CoreText.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreText.framework; path = System/Library/Frameworks/CoreText.framework; sourceTree = SDKROOT; }; C0378C961AF4A18F0083206F /* QuickLook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuickLook.framework; path = System/Library/Frameworks/QuickLook.framework; sourceTree = SDKROOT; }; C046DD5E1D6E271000B39580 /* TLReviewPaymentViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLReviewPaymentViewController.swift; sourceTree = ""; }; C04A50E91B1792A8001FCE4E /* ArcBit.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = ArcBit.entitlements; sourceTree = ""; }; C05C39891AD0710B00AADE55 /* TLCloudDocumentSyncWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TLCloudDocumentSyncWrapper.h; sourceTree = ""; }; C05C398A1AD0710B00AADE55 /* TLCloudDocumentSyncWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TLCloudDocumentSyncWrapper.m; sourceTree = ""; }; C05F82C81EF5D13E0016F0FC /* SocketAckEmitter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketAckEmitter.swift; sourceTree = ""; }; C05F82C91EF5D13E0016F0FC /* SocketAckManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketAckManager.swift; sourceTree = ""; }; C05F82CA1EF5D13E0016F0FC /* SocketAnyEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketAnyEvent.swift; sourceTree = ""; }; C05F82CB1EF5D13E0016F0FC /* SocketClientManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketClientManager.swift; sourceTree = ""; }; C05F82CC1EF5D13E0016F0FC /* SocketEngine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketEngine.swift; sourceTree = ""; }; C05F82CD1EF5D13E0016F0FC /* SocketEngineClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketEngineClient.swift; sourceTree = ""; }; C05F82CE1EF5D13E0016F0FC /* SocketEnginePacketType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketEnginePacketType.swift; sourceTree = ""; }; C05F82CF1EF5D13E0016F0FC /* SocketEnginePollable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketEnginePollable.swift; sourceTree = ""; }; C05F82D01EF5D13E0016F0FC /* SocketEngineSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketEngineSpec.swift; sourceTree = ""; }; C05F82D11EF5D13E0016F0FC /* SocketEngineWebsocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketEngineWebsocket.swift; sourceTree = ""; }; C05F82D21EF5D13E0016F0FC /* SocketEventHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketEventHandler.swift; sourceTree = ""; }; C05F82D31EF5D13E0016F0FC /* SocketExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketExtensions.swift; sourceTree = ""; }; C05F82D41EF5D13E0016F0FC /* SocketIOClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketIOClient.swift; sourceTree = ""; }; C05F82D51EF5D13E0016F0FC /* SocketIOClientConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketIOClientConfiguration.swift; sourceTree = ""; }; C05F82D61EF5D13E0016F0FC /* SocketIOClientOption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketIOClientOption.swift; sourceTree = ""; }; C05F82D71EF5D13E0016F0FC /* SocketIOClientSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketIOClientSpec.swift; sourceTree = ""; }; C05F82D81EF5D13E0016F0FC /* SocketIOClientStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketIOClientStatus.swift; sourceTree = ""; }; C05F82D91EF5D13E0016F0FC /* SocketLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketLogger.swift; sourceTree = ""; }; C05F82DA1EF5D13E0016F0FC /* SocketPacket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketPacket.swift; sourceTree = ""; }; C05F82DB1EF5D13E0016F0FC /* SocketParsable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketParsable.swift; sourceTree = ""; }; C05F82DC1EF5D13E0016F0FC /* SocketStringReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketStringReader.swift; sourceTree = ""; }; C05F82DD1EF5D13E0016F0FC /* SocketTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketTypes.swift; sourceTree = ""; }; C05F82DE1EF5D13E0016F0FC /* SSLSecurity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SSLSecurity.swift; sourceTree = ""; }; C05F82DF1EF5D13E0016F0FC /* WebSocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebSocket.swift; sourceTree = ""; }; C0607FCC1AEEB3CD007203F8 /* TLStealthServerConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLStealthServerConfig.swift; sourceTree = ""; }; C0649CA01E256B52007E3965 /* TLAccountTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLAccountTableViewCell.swift; sourceTree = ""; }; C0649CA11E256B52007E3965 /* TLAddressTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLAddressTableViewCell.swift; sourceTree = ""; }; C0649CA21E256B52007E3965 /* TLTransactionTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLTransactionTableViewCell.swift; sourceTree = ""; }; C0649CA61E256E16007E3965 /* TLDisplayStrings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLDisplayStrings.swift; sourceTree = ""; }; C0920A971FA82DB6008A0745 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Main.strings"; sourceTree = ""; }; C0920A981FA82DB6008A0745 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/LaunchScreen.strings"; sourceTree = ""; }; C0920A991FA82DB6008A0745 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/IASKLocalizable.strings"; sourceTree = ""; }; C0920A9A1FA82DB6008A0745 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/LTHPasscodeViewController.strings"; sourceTree = ""; }; C0920A9B1FA82DB6008A0745 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = ""; }; C0A18F8E1ABF51C200BED648 /* ArcBit.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ArcBit.app; sourceTree = BUILT_PRODUCTS_DIR; }; C0A18F921ABF51C200BED648 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; C0A18F931ABF51C200BED648 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; C0A18F9A1ABF51C200BED648 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; C0A18F9D1ABF51C200BED648 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; C0A18FA31ABF51C200BED648 /* ArcBitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ArcBitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; C0A18FA81ABF51C200BED648 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; C0A18FA91ABF51C200BED648 /* ArcBitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArcBitTests.swift; sourceTree = ""; }; C0A18FB81ABF53A100BED648 /* arrow-right7-original.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "arrow-right7-original.png"; sourceTree = ""; }; C0A18FB91ABF53A100BED648 /* arrow-right7.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "arrow-right7.png"; sourceTree = ""; }; C0A18FBA1ABF53A100BED648 /* download2.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = download2.png; sourceTree = ""; }; C0A18FBC1ABF53A100BED648 /* book.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = book.png; sourceTree = ""; }; C0A18FBD1ABF53A100BED648 /* book@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "book@2x.png"; sourceTree = ""; }; C0A18FBE1ABF53A100BED648 /* book@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "book@3x.png"; sourceTree = ""; }; C0A18FBF1ABF53A100BED648 /* data.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = data.png; sourceTree = ""; }; C0A18FC01ABF53A100BED648 /* data@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "data@2x.png"; sourceTree = ""; }; C0A18FC11ABF53A100BED648 /* data@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "data@3x.png"; sourceTree = ""; }; C0A18FC21ABF53A100BED648 /* download.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = download.png; sourceTree = ""; }; C0A18FC31ABF53A100BED648 /* download@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "download@2x.png"; sourceTree = ""; }; C0A18FC41ABF53A100BED648 /* download@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "download@3x.png"; sourceTree = ""; }; C0A18FC51ABF53A100BED648 /* list.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = list.png; sourceTree = ""; }; C0A18FC61ABF53A100BED648 /* list@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "list@2x.png"; sourceTree = ""; }; C0A18FC71ABF53A100BED648 /* list@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "list@3x.png"; sourceTree = ""; }; C0A18FC81ABF53A100BED648 /* newspaper-alt.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "newspaper-alt.png"; sourceTree = ""; }; C0A18FC91ABF53A100BED648 /* newspaper-alt@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "newspaper-alt@2x.png"; sourceTree = ""; }; C0A18FCA1ABF53A100BED648 /* newspaper-alt@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "newspaper-alt@3x.png"; sourceTree = ""; }; C0A18FCB1ABF53A100BED648 /* settings.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = settings.png; sourceTree = ""; }; C0A18FCC1ABF53A100BED648 /* settings@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "settings@2x.png"; sourceTree = ""; }; C0A18FCD1ABF53A100BED648 /* settings@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "settings@3x.png"; sourceTree = ""; }; C0A18FCE1ABF53A100BED648 /* upload.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = upload.png; sourceTree = ""; }; C0A18FCF1ABF53A100BED648 /* upload@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "upload@2x.png"; sourceTree = ""; }; C0A18FD01ABF53A100BED648 /* upload@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "upload@3x.png"; sourceTree = ""; }; C0A18FD11ABF53A100BED648 /* upload2.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = upload2.png; sourceTree = ""; }; C0A18FE31ABF53A100BED648 /* BRKey+BIP38.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "BRKey+BIP38.h"; sourceTree = ""; }; C0A18FE41ABF53A100BED648 /* BRKey+BIP38.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "BRKey+BIP38.m"; sourceTree = ""; }; C0A18FE51ABF53A100BED648 /* BRKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BRKey.h; sourceTree = ""; }; C0A18FE61ABF53A100BED648 /* BRKey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BRKey.m; sourceTree = ""; }; C0A18FE81ABF53A100BED648 /* BRTransaction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BRTransaction.h; sourceTree = ""; }; C0A18FE91ABF53A100BED648 /* BRTransaction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BRTransaction.m; sourceTree = ""; }; C0A18FEA1ABF53A100BED648 /* ccMemory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ccMemory.h; sourceTree = ""; }; C0A18FEB1ABF53A100BED648 /* NSData+Bitcoin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+Bitcoin.h"; sourceTree = ""; }; C0A18FEC1ABF53A100BED648 /* NSData+Bitcoin.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+Bitcoin.m"; sourceTree = ""; }; C0A18FED1ABF53A100BED648 /* NSData+Hash.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+Hash.h"; sourceTree = ""; }; C0A18FEE1ABF53A100BED648 /* NSData+Hash.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+Hash.m"; sourceTree = ""; }; C0A18FEF1ABF53A100BED648 /* NSMutableData+Bitcoin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSMutableData+Bitcoin.h"; sourceTree = ""; }; C0A18FF01ABF53A100BED648 /* NSMutableData+Bitcoin.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSMutableData+Bitcoin.m"; sourceTree = ""; }; C0A18FF11ABF53A100BED648 /* NSString+Base58.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+Base58.h"; sourceTree = ""; }; C0A18FF21ABF53A100BED648 /* NSString+Base58.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+Base58.m"; sourceTree = ""; }; C0A18FF41ABF53A100BED648 /* CustomIOS7AlertView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CustomIOS7AlertView.h; sourceTree = ""; }; C0A18FF51ABF53A100BED648 /* CustomIOS7AlertView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CustomIOS7AlertView.m; sourceTree = ""; }; C0A190211ABF53A100BED648 /* iToast.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = iToast.h; sourceTree = ""; }; C0A190221ABF53A100BED648 /* iToast.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = iToast.m; sourceTree = ""; }; C0A190241ABF53A100BED648 /* JNKeychain.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JNKeychain.h; sourceTree = ""; }; C0A190251ABF53A100BED648 /* JNKeychain.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JNKeychain.m; sourceTree = ""; }; C0A190271ABF53A100BED648 /* KeychainItemWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KeychainItemWrapper.h; sourceTree = ""; }; C0A190281ABF53A100BED648 /* KeychainItemWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KeychainItemWrapper.m; sourceTree = ""; }; C0A1902B1ABF53A100BED648 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/LTHPasscodeViewController.strings; sourceTree = ""; }; C0A1902C1ABF53A100BED648 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/LTHPasscodeViewController.strings; sourceTree = ""; }; C0A1902D1ABF53A100BED648 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/LTHPasscodeViewController.strings; sourceTree = ""; }; C0A1902E1ABF53A100BED648 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/LTHPasscodeViewController.strings; sourceTree = ""; }; C0A1902F1ABF53A100BED648 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/LTHPasscodeViewController.strings; sourceTree = ""; }; C0A190301ABF53A100BED648 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/LTHPasscodeViewController.strings; sourceTree = ""; }; C0A190311ABF53A100BED648 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/LTHPasscodeViewController.strings; sourceTree = ""; }; C0A190321ABF53A100BED648 /* zh-Hans-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans-CN"; path = "zh-Hans-CN.lproj/LTHPasscodeViewController.strings"; sourceTree = ""; }; C0A190341ABF53A100BED648 /* LTHKeychainUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LTHKeychainUtils.h; sourceTree = ""; }; C0A190351ABF53A100BED648 /* LTHKeychainUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LTHKeychainUtils.m; sourceTree = ""; }; C0A190361ABF53A100BED648 /* LTHPasscodeViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LTHPasscodeViewController.h; sourceTree = ""; }; C0A190371ABF53A100BED648 /* LTHPasscodeViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LTHPasscodeViewController.m; sourceTree = ""; }; C0A190391ABF53A100BED648 /* NSDate-Utilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDate-Utilities.h"; sourceTree = ""; }; C0A1903A1ABF53A100BED648 /* NSDate-Utilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDate-Utilities.m"; sourceTree = ""; }; C0A1903C1ABF53A100BED648 /* DataMatrix.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DataMatrix.h; sourceTree = ""; }; C0A1903D1ABF53A100BED648 /* DataMatrix.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DataMatrix.mm; sourceTree = ""; }; C0A1903E1ABF53A100BED648 /* QR_Encode.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = QR_Encode.cpp; sourceTree = ""; }; C0A1903F1ABF53A100BED648 /* QR_Encode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QR_Encode.h; sourceTree = ""; }; C0A190401ABF53A100BED648 /* QRCodeEncoderDemoViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QRCodeEncoderDemoViewController.h; sourceTree = ""; }; C0A190411ABF53A100BED648 /* QRCodeEncoderDemoViewController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = QRCodeEncoderDemoViewController.mm; sourceTree = ""; }; C0A190421ABF53A100BED648 /* QRCodeEncoderObjectiveCAtGithub-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "QRCodeEncoderObjectiveCAtGithub-Prefix.pch"; sourceTree = ""; }; C0A190431ABF53A100BED648 /* QREncoder-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "QREncoder-Prefix.pch"; sourceTree = ""; }; C0A190441ABF53A100BED648 /* QREncoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QREncoder.h; sourceTree = ""; }; C0A190451ABF53A100BED648 /* QREncoder.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = QREncoder.mm; sourceTree = ""; }; C0A190501ABF53A100BED648 /* UIAlertConrtoller+Blocks.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIAlertConrtoller+Blocks.m"; sourceTree = ""; }; C0A190511ABF53A100BED648 /* UIAlertController+Blocks.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIAlertController+Blocks.h"; sourceTree = ""; }; C0A190531ABF53A100BED648 /* UINavigationBar+FixedHeightWhenStatusBarHidden.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UINavigationBar+FixedHeightWhenStatusBarHidden.h"; sourceTree = ""; }; C0A190541ABF53A100BED648 /* UINavigationBar+FixedHeightWhenStatusBarHidden.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UINavigationBar+FixedHeightWhenStatusBarHidden.m"; sourceTree = ""; }; C0A190561ABF53A100BED648 /* InAppSettings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = InAppSettings.bundle; sourceTree = ""; }; C0A190591ABF53A100BED648 /* TLAccountObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLAccountObject.swift; sourceTree = ""; }; C0A1905A1ABF53A100BED648 /* TLAccounts.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLAccounts.swift; sourceTree = ""; }; C0A1905B1ABF53A100BED648 /* TLBlockchainStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLBlockchainStatus.swift; sourceTree = ""; }; C0A1905C1ABF53A100BED648 /* TLCoin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLCoin.swift; sourceTree = ""; }; C0A1905D1ABF53A100BED648 /* TLCoreBitcoinWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLCoreBitcoinWrapper.swift; sourceTree = ""; }; C0A1905E1ABF53A100BED648 /* TLHDWalletWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLHDWalletWrapper.swift; sourceTree = ""; }; C0A1905F1ABF53A100BED648 /* TLImportedAddress.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLImportedAddress.swift; sourceTree = ""; }; C0A190601ABF53A100BED648 /* TLImportedAddresses.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLImportedAddresses.swift; sourceTree = ""; }; C0A190611ABF53A100BED648 /* TLSelectedObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLSelectedObject.swift; sourceTree = ""; }; C0A190621ABF53A100BED648 /* TLSendFormData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLSendFormData.swift; sourceTree = ""; }; C0A190631ABF53A100BED648 /* TLSpaghettiGodSend.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLSpaghettiGodSend.swift; sourceTree = ""; }; C0A190641ABF53A100BED648 /* TLStealthAddress.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLStealthAddress.swift; sourceTree = ""; }; C0A190651ABF53A100BED648 /* TLStealthWallet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLStealthWallet.swift; sourceTree = ""; }; C0A190661ABF53A100BED648 /* TLSuggestions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLSuggestions.swift; sourceTree = ""; }; C0A190671ABF53A100BED648 /* TLTxObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLTxObject.swift; sourceTree = ""; }; C0A190681ABF53A100BED648 /* TLWallet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLWallet.swift; sourceTree = ""; }; C0A190691ABF53A100BED648 /* TLWalletJson.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = TLWalletJson.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; C0A1906A1ABF53A100BED648 /* TLWalletUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLWalletUtils.swift; sourceTree = ""; }; C0A190861ABF53A100BED648 /* UINavigationController+StatusBarStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UINavigationController+StatusBarStyle.swift"; sourceTree = ""; }; C0A190871ABF53A100BED648 /* UIView+FormScroll.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+FormScroll.swift"; sourceTree = ""; }; C0A190881ABF53A100BED648 /* UIViewController+Extras.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIViewController+Extras.swift"; sourceTree = ""; }; C0A190891ABF53A100BED648 /* UIViewController+Style.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIViewController+Style.swift"; sourceTree = ""; }; C0A1908B1ABF53A100BED648 /* TLAccountsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLAccountsViewController.swift; sourceTree = ""; }; C0A1908C1ABF53A100BED648 /* TLAchievementsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLAchievementsViewController.swift; sourceTree = ""; }; C0A1908D1ABF53A100BED648 /* TLAddressBookViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLAddressBookViewController.swift; sourceTree = ""; }; C0A1908E1ABF53A100BED648 /* TLAddressListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLAddressListViewController.swift; sourceTree = ""; }; C0A1908F1ABF53A100BED648 /* TLRestoreWalletViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLRestoreWalletViewController.swift; sourceTree = ""; }; C0A190901ABF53A100BED648 /* TLHelpViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLHelpViewController.swift; sourceTree = ""; }; C0A190911ABF53A100BED648 /* TLHistoryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLHistoryViewController.swift; sourceTree = ""; }; C0A190921ABF53A100BED648 /* TLInstructionsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLInstructionsViewController.swift; sourceTree = ""; }; C0A190931ABF53A100BED648 /* TLManageAccountsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLManageAccountsViewController.swift; sourceTree = ""; }; C0A190941ABF53A100BED648 /* TLMenuViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLMenuViewController.swift; sourceTree = ""; }; C0A190951ABF53A100BED648 /* TLPassPhraseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLPassPhraseViewController.swift; sourceTree = ""; }; C0A190961ABF53A100BED648 /* TLPreloadViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = TLPreloadViewController.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; C0A190971ABF53A100BED648 /* TLQRCodeScannerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLQRCodeScannerViewController.swift; sourceTree = ""; }; C0A190981ABF53A100BED648 /* TLReceiveViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLReceiveViewController.swift; sourceTree = ""; }; C0A190991ABF53A100BED648 /* TLSendViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLSendViewController.swift; sourceTree = ""; }; C0A1909A1ABF53A100BED648 /* TLSettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLSettingsViewController.swift; sourceTree = ""; }; C0A1909B1ABF53A100BED648 /* TLTextViewViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLTextViewViewController.swift; sourceTree = ""; }; C0A1912D1ABF540000BED648 /* ArcBit-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ArcBit-Bridging-Header.h"; sourceTree = ""; }; C0A192D21ABF6EB200BED648 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; C0A192D41ABF6EB900BED648 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; C0A192D61ABF6EC200BED648 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; C0A192D81ABF6EC900BED648 /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; }; C0A192DA1ABF6ED500BED648 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; C0A192DC1ABF6EDC00BED648 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; C0A192DE1ABF6EE800BED648 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; C0A192E01ABF6EEE00BED648 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; C0A192E21ABF6EF500BED648 /* MessageUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MessageUI.framework; path = System/Library/Frameworks/MessageUI.framework; sourceTree = SDKROOT; }; C0A192E41ABF6F0500BED648 /* libicucore.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libicucore.dylib; path = usr/lib/libicucore.dylib; sourceTree = SDKROOT; }; C0A192E71ABF707400BED648 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; C0A192E91ABF742D00BED648 /* TLAchievements.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLAchievements.swift; sourceTree = ""; }; C0A192EA1ABF742D00BED648 /* TLAnalytics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLAnalytics.swift; sourceTree = ""; }; C0A192EB1ABF742D00BED648 /* TLPreferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = TLPreferences.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; C0A192EF1ABF744300BED648 /* TLMacros.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLMacros.swift; sourceTree = ""; }; C0A192F01ABF744300BED648 /* TLUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLUtils.swift; sourceTree = ""; }; C0A192F11ABF744300BED648 /* TLColors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLColors.swift; sourceTree = ""; }; C0A192F21ABF744300BED648 /* TLHUDWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLHUDWrapper.swift; sourceTree = ""; }; C0A192F31ABF744300BED648 /* TLNotificationEvents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLNotificationEvents.swift; sourceTree = ""; }; C0A192F51ABF744300BED648 /* TLPrompts.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLPrompts.swift; sourceTree = ""; }; C0A192F61ABF744300BED648 /* TLQRImageModal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLQRImageModal.swift; sourceTree = ""; }; C0A193001ABF744E00BED648 /* TLBitcoinListener.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLBitcoinListener.swift; sourceTree = ""; }; C0A193011ABF744E00BED648 /* TLBlockchainAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLBlockchainAPI.swift; sourceTree = ""; }; C0A193021ABF744E00BED648 /* TLBlockExplorerAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLBlockExplorerAPI.swift; sourceTree = ""; }; C0A193041ABF744E00BED648 /* TLExchangeRate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLExchangeRate.swift; sourceTree = ""; }; C0A193051ABF744E00BED648 /* TLInsightAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLInsightAPI.swift; sourceTree = ""; }; C0A193061ABF744E00BED648 /* TLNetworking.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLNetworking.swift; sourceTree = ""; }; C0A193071ABF744E00BED648 /* TLStealthServerAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLStealthServerAPI.swift; sourceTree = ""; }; C0A193081ABF744E00BED648 /* TLStealthWebSocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLStealthWebSocket.swift; sourceTree = ""; }; C0A193181ABF751700BED648 /* ArcBitTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ArcBitTests-Bridging-Header.h"; sourceTree = ""; }; C0A193191ABF751800BED648 /* BreadWalletTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BreadWalletTests.m; sourceTree = ""; }; C0A6AD741C42E80F00B8C22B /* TLLinksViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLLinksViewController.swift; sourceTree = ""; }; C0AB0A321AC52A8400B456DB /* TLPushTxAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLPushTxAPI.swift; sourceTree = ""; }; C0AD2C581AC3DE3200FEF982 /* TLBlockrAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLBlockrAPI.swift; sourceTree = ""; }; C0AE417D1F788BD700682CDE /* IASKAppSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKAppSettingsViewController.h; sourceTree = ""; }; C0AE417E1F788BD700682CDE /* IASKAppSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKAppSettingsViewController.m; sourceTree = ""; }; C0AE417F1F788BD700682CDE /* IASKAppSettingsWebViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKAppSettingsWebViewController.h; sourceTree = ""; }; C0AE41801F788BD700682CDE /* IASKAppSettingsWebViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKAppSettingsWebViewController.m; sourceTree = ""; }; C0AE41811F788BD700682CDE /* IASKMultipleValueSelection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKMultipleValueSelection.h; sourceTree = ""; }; C0AE41821F788BD700682CDE /* IASKMultipleValueSelection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKMultipleValueSelection.m; sourceTree = ""; }; C0AE41831F788BD700682CDE /* IASKSpecifierValuesViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKSpecifierValuesViewController.h; sourceTree = ""; }; C0AE41841F788BD700682CDE /* IASKSpecifierValuesViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKSpecifierValuesViewController.m; sourceTree = ""; }; C0AE41851F788BD700682CDE /* IASKViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKViewController.h; sourceTree = ""; }; C0AE41871F788BD700682CDE /* IASKSettingsReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKSettingsReader.h; sourceTree = ""; }; C0AE41881F788BD700682CDE /* IASKSettingsReader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKSettingsReader.m; sourceTree = ""; }; C0AE41891F788BD700682CDE /* IASKSettingsStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKSettingsStore.h; sourceTree = ""; }; C0AE418A1F788BD700682CDE /* IASKSettingsStore.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKSettingsStore.m; sourceTree = ""; }; C0AE418B1F788BD700682CDE /* IASKSettingsStoreFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKSettingsStoreFile.h; sourceTree = ""; }; C0AE418C1F788BD700682CDE /* IASKSettingsStoreFile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKSettingsStoreFile.m; sourceTree = ""; }; C0AE418D1F788BD700682CDE /* IASKSettingsStoreUserDefaults.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKSettingsStoreUserDefaults.h; sourceTree = ""; }; C0AE418E1F788BD700682CDE /* IASKSettingsStoreUserDefaults.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKSettingsStoreUserDefaults.m; sourceTree = ""; }; C0AE418F1F788BD700682CDE /* IASKSpecifier.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKSpecifier.h; sourceTree = ""; }; C0AE41901F788BD700682CDE /* IASKSpecifier.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKSpecifier.m; sourceTree = ""; }; C0AE41931F788BD700682CDE /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/IASKLocalizable.strings; sourceTree = ""; }; C0AE41941F788BD700682CDE /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/IASKLocalizable.strings; sourceTree = ""; }; C0AE41951F788BD700682CDE /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/IASKLocalizable.strings; sourceTree = ""; }; C0AE41961F788BD700682CDE /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/IASKLocalizable.strings; sourceTree = ""; }; C0AE41971F788BD700682CDE /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/IASKLocalizable.strings; sourceTree = ""; }; C0AE41981F788BD700682CDE /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/IASKLocalizable.strings; sourceTree = ""; }; C0AE41991F788BD700682CDE /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/IASKLocalizable.strings; sourceTree = ""; }; C0AE419A1F788BD700682CDE /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/IASKLocalizable.strings; sourceTree = ""; }; C0AE419B1F788BD700682CDE /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/IASKLocalizable.strings; sourceTree = ""; }; C0AE419C1F788BD700682CDE /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/IASKLocalizable.strings"; sourceTree = ""; }; C0AE419D1F788BD700682CDE /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/IASKLocalizable.strings; sourceTree = ""; }; C0AE419E1F788BD700682CDE /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/IASKLocalizable.strings; sourceTree = ""; }; C0AE419F1F788BD700682CDE /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/IASKLocalizable.strings; sourceTree = ""; }; C0AE41A01F788BD700682CDE /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/IASKLocalizable.strings; sourceTree = ""; }; C0AE41A11F788BD700682CDE /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/IASKLocalizable.strings; sourceTree = ""; }; C0AE41A31F788BD700682CDE /* IASKPSSliderSpecifierViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKPSSliderSpecifierViewCell.h; sourceTree = ""; }; C0AE41A41F788BD700682CDE /* IASKPSSliderSpecifierViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKPSSliderSpecifierViewCell.m; sourceTree = ""; }; C0AE41A51F788BD700682CDE /* IASKPSTextFieldSpecifierViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKPSTextFieldSpecifierViewCell.h; sourceTree = ""; }; C0AE41A61F788BD700682CDE /* IASKPSTextFieldSpecifierViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKPSTextFieldSpecifierViewCell.m; sourceTree = ""; }; C0AE41A71F788BD700682CDE /* IASKSlider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKSlider.h; sourceTree = ""; }; C0AE41A81F788BD700682CDE /* IASKSlider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKSlider.m; sourceTree = ""; }; C0AE41A91F788BD700682CDE /* IASKSwitch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKSwitch.h; sourceTree = ""; }; C0AE41AA1F788BD700682CDE /* IASKSwitch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKSwitch.m; sourceTree = ""; }; C0AE41AB1F788BD700682CDE /* IASKTextField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKTextField.h; sourceTree = ""; }; C0AE41AC1F788BD700682CDE /* IASKTextField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKTextField.m; sourceTree = ""; }; C0AE41AD1F788BD700682CDE /* IASKTextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKTextView.h; sourceTree = ""; }; C0AE41AE1F788BD700682CDE /* IASKTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKTextView.m; sourceTree = ""; }; C0AE41AF1F788BD700682CDE /* IASKTextViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKTextViewCell.h; sourceTree = ""; }; C0AE41B01F788BD700682CDE /* IASKTextViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKTextViewCell.m; sourceTree = ""; }; C0B4E7871AFAAEA200DC9B65 /* SRWebSocket+Helpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SRWebSocket+Helpers.h"; sourceTree = ""; }; C0B4E7881AFAAEA200DC9B65 /* SRWebSocket+Helpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "SRWebSocket+Helpers.m"; sourceTree = ""; }; C0B56BC11F51F30B00E4CA87 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; C0B56BC21F51F6BC00E4CA87 /* zh-Hans-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans-CN"; path = "zh-Hans-CN.lproj/Localizable.strings"; sourceTree = ""; }; C0BD70021B4E2BE300A5788D /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; C0BD70041B4E2BE600A5788D /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; C0BD70051B4E2C4F00A5788D /* TLStringLocalized.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLStringLocalized.swift; sourceTree = ""; }; C0C1BE711AF92CB500B705C4 /* base64.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = base64.c; sourceTree = ""; }; C0C1BE721AF92CB500B705C4 /* base64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = base64.h; sourceTree = ""; }; C0C1BE731AF92CB500B705C4 /* NSData+SRB64Additions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+SRB64Additions.h"; sourceTree = ""; }; C0C1BE741AF92CB500B705C4 /* NSData+SRB64Additions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+SRB64Additions.m"; sourceTree = ""; }; C0C1BE751AF92CB500B705C4 /* SocketRocket-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SocketRocket-Prefix.pch"; sourceTree = ""; }; C0C1BE761AF92CB500B705C4 /* SRWebSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRWebSocket.h; sourceTree = ""; }; C0C1BE771AF92CB500B705C4 /* SRWebSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRWebSocket.m; sourceTree = ""; }; C0C3BDEB1AC0F7AA001AC6CD /* TLWallet+Stealth.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TLWallet+Stealth.swift"; sourceTree = ""; }; C0C61C591AF15BEF00F9F71F /* TransitionDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransitionDelegate.swift; sourceTree = ""; }; C0C61C5D1AF15E6100F9F71F /* TransparentViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransparentViewController.swift; sourceTree = ""; }; C0D20A911D5963FF008F70F2 /* TLTxFeeAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLTxFeeAPI.swift; sourceTree = ""; }; C0D5A60D1DD8FA1300849E2C /* TLAdvancedNewWalletTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLAdvancedNewWalletTableViewCell.swift; sourceTree = ""; }; C0D5A60E1DD8FA1300849E2C /* TLColdWalletSelectWayTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLColdWalletSelectWayTableViewCell.swift; sourceTree = ""; }; C0D5A60F1DD8FA1300849E2C /* TLNewWalletTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLNewWalletTableViewCell.swift; sourceTree = ""; }; C0D5A6111DD8FA1300849E2C /* TLInputColdWalletKeyTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLInputColdWalletKeyTableViewCell.swift; sourceTree = ""; }; C0D5A6121DD8FA1300849E2C /* TLPassSignedTxTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLPassSignedTxTableViewCell.swift; sourceTree = ""; }; C0D5A6131DD8FA1300849E2C /* TLScanUnsignedTxTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLScanUnsignedTxTableViewCell.swift; sourceTree = ""; }; C0D7B2DA1B16FDA10096E599 /* live.cer */ = {isa = PBXFileReference; lastKnownFileType = file; path = live.cer; sourceTree = ""; }; C0D8A36A1DAA126700497D31 /* TLColdWalletViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLColdWalletViewController.swift; sourceTree = ""; }; C0D8A36C1DAA16C900497D31 /* TLCreateColdWalletViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLCreateColdWalletViewController.swift; sourceTree = ""; }; C0D8A36E1DAA183D00497D31 /* TLAuthorizeColdWalletPaymentViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLAuthorizeColdWalletPaymentViewController.swift; sourceTree = ""; }; C0E5393C1AD454FD00651FE4 /* TLHelpDoc.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLHelpDoc.swift; sourceTree = ""; }; C0EE6D681B1B7E6000982692 /* 360X80logo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = 360X80logo.png; sourceTree = ""; }; C0EE6D691B1B7E6000982692 /* 1200X1200logo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = 1200X1200logo.png; sourceTree = ""; }; C0F278C71C41EA1A0013DC1A /* TLCurrencyFormat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLCurrencyFormat.swift; sourceTree = ""; }; C4D7E88E9BE3FC3994E59767 /* Pods-ArcBit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ArcBit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ArcBit/Pods-ArcBit.debug.xcconfig"; sourceTree = ""; }; F338F9551B794D1D00C02F74 /* TLWalletPassphrase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLWalletPassphrase.swift; sourceTree = ""; }; F338F9571B79718900C02F74 /* TLCrypto.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLCrypto.swift; sourceTree = ""; }; F37072451B8F570D0026505D /* TLWalletConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLWalletConfig.swift; sourceTree = ""; }; F3BC36101B8681FF00CBB3F7 /* TLUpdateAppData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLUpdateAppData.swift; sourceTree = ""; }; F3DAAD651BB1D9EC000488F0 /* TLWalletJSONKeys.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLWalletJSONKeys.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ C0A18F8B1ABF51C200BED648 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( C0378C971AF4A18F0083206F /* QuickLook.framework in Frameworks */, C0378C951AF4A18A0083206F /* CoreText.framework in Frameworks */, C0378C931AF4A1850083206F /* AssetsLibrary.framework in Frameworks */, C0A192E51ABF6F0500BED648 /* libicucore.dylib in Frameworks */, C0A192E31ABF6EF500BED648 /* MessageUI.framework in Frameworks */, C0A192E11ABF6EEE00BED648 /* Security.framework in Frameworks */, C0A192DF1ABF6EE800BED648 /* SystemConfiguration.framework in Frameworks */, C0A192DD1ABF6EDC00BED648 /* MobileCoreServices.framework in Frameworks */, C0A192DB1ABF6ED500BED648 /* CFNetwork.framework in Frameworks */, C0A192D91ABF6EC900BED648 /* CoreMedia.framework in Frameworks */, C0A192D71ABF6EC200BED648 /* AVFoundation.framework in Frameworks */, C0A192D51ABF6EB900BED648 /* QuartzCore.framework in Frameworks */, C0A192D31ABF6EB200BED648 /* CoreGraphics.framework in Frameworks */, 371609F6191B495389E4EF29 /* libPods-ArcBit.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; C0A18FA01ABF51C200BED648 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( F7E43EEB86A2A43FC05335C4 /* libPods-ArcBitTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 15CDECF35E9C56BC58C8CBCC /* Pods */ = { isa = PBXGroup; children = ( C4D7E88E9BE3FC3994E59767 /* Pods-ArcBit.debug.xcconfig */, AA36D9E0C4AFE7E2A43F3C79 /* Pods-ArcBit.release.xcconfig */, 3C7ACA9C0834D6AF891F762C /* Pods-ArcBitTests.debug.xcconfig */, 173E65BB79838FECABC37B10 /* Pods-ArcBitTests.release.xcconfig */, ); name = Pods; sourceTree = ""; }; 65EAF1BBE7F17CF5F70A560B /* Frameworks */ = { isa = PBXGroup; children = ( C0378C961AF4A18F0083206F /* QuickLook.framework */, C0378C941AF4A18A0083206F /* CoreText.framework */, C0378C921AF4A1850083206F /* AssetsLibrary.framework */, C0A192E41ABF6F0500BED648 /* libicucore.dylib */, C0A192E21ABF6EF500BED648 /* MessageUI.framework */, C0A192E01ABF6EEE00BED648 /* Security.framework */, C0A192DE1ABF6EE800BED648 /* SystemConfiguration.framework */, C0A192DC1ABF6EDC00BED648 /* MobileCoreServices.framework */, C0A192DA1ABF6ED500BED648 /* CFNetwork.framework */, C0A192D81ABF6EC900BED648 /* CoreMedia.framework */, C0A192D61ABF6EC200BED648 /* AVFoundation.framework */, C0A192D41ABF6EB900BED648 /* QuartzCore.framework */, C0A192D21ABF6EB200BED648 /* CoreGraphics.framework */, 7C8CDAAD6F71CF30CCD5BBD6 /* libPods-ArcBit.a */, BABE2B037811FC35D025CD09 /* libPods-ArcBitTests.a */, ); name = Frameworks; sourceTree = ""; }; C041DA131AE76BAA003D74C9 /* certs */ = { isa = PBXGroup; children = ( C0D7B2DA1B16FDA10096E599 /* live.cer */, ); path = certs; sourceTree = ""; }; C05C39881AD0710B00AADE55 /* TLCloudDocumentSyncWrapper */ = { isa = PBXGroup; children = ( C05C39891AD0710B00AADE55 /* TLCloudDocumentSyncWrapper.h */, C05C398A1AD0710B00AADE55 /* TLCloudDocumentSyncWrapper.m */, ); path = TLCloudDocumentSyncWrapper; sourceTree = ""; }; C05F82C61EF5D13E0016F0FC /* socket.io-client-swift-10.0.0 */ = { isa = PBXGroup; children = ( C05F82C71EF5D13E0016F0FC /* Source */, ); path = "socket.io-client-swift-10.0.0"; sourceTree = ""; }; C05F82C71EF5D13E0016F0FC /* Source */ = { isa = PBXGroup; children = ( C05F82C81EF5D13E0016F0FC /* SocketAckEmitter.swift */, C05F82C91EF5D13E0016F0FC /* SocketAckManager.swift */, C05F82CA1EF5D13E0016F0FC /* SocketAnyEvent.swift */, C05F82CB1EF5D13E0016F0FC /* SocketClientManager.swift */, C05F82CC1EF5D13E0016F0FC /* SocketEngine.swift */, C05F82CD1EF5D13E0016F0FC /* SocketEngineClient.swift */, C05F82CE1EF5D13E0016F0FC /* SocketEnginePacketType.swift */, C05F82CF1EF5D13E0016F0FC /* SocketEnginePollable.swift */, C05F82D01EF5D13E0016F0FC /* SocketEngineSpec.swift */, C05F82D11EF5D13E0016F0FC /* SocketEngineWebsocket.swift */, C05F82D21EF5D13E0016F0FC /* SocketEventHandler.swift */, C05F82D31EF5D13E0016F0FC /* SocketExtensions.swift */, C05F82D41EF5D13E0016F0FC /* SocketIOClient.swift */, C05F82D51EF5D13E0016F0FC /* SocketIOClientConfiguration.swift */, C05F82D61EF5D13E0016F0FC /* SocketIOClientOption.swift */, C05F82D71EF5D13E0016F0FC /* SocketIOClientSpec.swift */, C05F82D81EF5D13E0016F0FC /* SocketIOClientStatus.swift */, C05F82D91EF5D13E0016F0FC /* SocketLogger.swift */, C05F82DA1EF5D13E0016F0FC /* SocketPacket.swift */, C05F82DB1EF5D13E0016F0FC /* SocketParsable.swift */, C05F82DC1EF5D13E0016F0FC /* SocketStringReader.swift */, C05F82DD1EF5D13E0016F0FC /* SocketTypes.swift */, C05F82DE1EF5D13E0016F0FC /* SSLSecurity.swift */, C05F82DF1EF5D13E0016F0FC /* WebSocket.swift */, ); path = Source; sourceTree = ""; }; C0649C9F1E256B52007E3965 /* walletTableViewCells */ = { isa = PBXGroup; children = ( C0649CA01E256B52007E3965 /* TLAccountTableViewCell.swift */, C0649CA11E256B52007E3965 /* TLAddressTableViewCell.swift */, C0649CA21E256B52007E3965 /* TLTransactionTableViewCell.swift */, ); path = walletTableViewCells; sourceTree = ""; }; C0A18F851ABF51C200BED648 = { isa = PBXGroup; children = ( C0A18F901ABF51C200BED648 /* ArcBit */, C0A18FA61ABF51C200BED648 /* ArcBitTests */, C0A18F8F1ABF51C200BED648 /* Products */, 65EAF1BBE7F17CF5F70A560B /* Frameworks */, 15CDECF35E9C56BC58C8CBCC /* Pods */, ); sourceTree = ""; }; C0A18F8F1ABF51C200BED648 /* Products */ = { isa = PBXGroup; children = ( C0A18F8E1ABF51C200BED648 /* ArcBit.app */, C0A18FA31ABF51C200BED648 /* ArcBitTests.xctest */, ); name = Products; sourceTree = ""; }; C0A18F901ABF51C200BED648 /* ArcBit */ = { isa = PBXGroup; children = ( C04A50E91B1792A8001FCE4E /* ArcBit.entitlements */, C0A1908A1ABF53A100BED648 /* viewControllers */, C0A190581ABF53A100BED648 /* model */, C0A192FF1ABF744E00BED648 /* APIs */, C0A190851ABF53A100BED648 /* utils */, C0A18F931ABF51C200BED648 /* AppDelegate.swift */, C0A1912D1ABF540000BED648 /* ArcBit-Bridging-Header.h */, C0A190561ABF53A100BED648 /* InAppSettings.bundle */, C0A18F9A1ABF51C200BED648 /* Images.xcassets */, C0A192E61ABF707400BED648 /* Main.storyboard */, C0A18F9C1ABF51C200BED648 /* LaunchScreen.xib */, C0A18FDE1ABF53A100BED648 /* External */, C0A18FB31ABF53A100BED648 /* Assets */, C0A18F911ABF51C200BED648 /* Supporting Files */, ); path = ArcBit; sourceTree = ""; }; C0A18F911ABF51C200BED648 /* Supporting Files */ = { isa = PBXGroup; children = ( C02D3B111B4A45F500B3B7F0 /* ArcBit copy-Info.plist */, C0A18F921ABF51C200BED648 /* Info.plist */, C0BD70031B4E2BE300A5788D /* Localizable.strings */, ); name = "Supporting Files"; sourceTree = ""; }; C0A18FA61ABF51C200BED648 /* ArcBitTests */ = { isa = PBXGroup; children = ( C0A18FA91ABF51C200BED648 /* ArcBitTests.swift */, C0A18FA71ABF51C200BED648 /* Supporting Files */, C0A193191ABF751800BED648 /* BreadWalletTests.m */, C0A193181ABF751700BED648 /* ArcBitTests-Bridging-Header.h */, ); path = ArcBitTests; sourceTree = ""; }; C0A18FA71ABF51C200BED648 /* Supporting Files */ = { isa = PBXGroup; children = ( C0A18FA81ABF51C200BED648 /* Info.plist */, ); name = "Supporting Files"; sourceTree = ""; }; C0A18FB31ABF53A100BED648 /* Assets */ = { isa = PBXGroup; children = ( C041DA131AE76BAA003D74C9 /* certs */, C0A18FB41ABF53A100BED648 /* images */, ); path = Assets; sourceTree = ""; }; C0A18FB41ABF53A100BED648 /* images */ = { isa = PBXGroup; children = ( C0A18FB71ABF53A100BED648 /* imageIcons */, C0A18FD21ABF53A100BED648 /* logo */, ); path = images; sourceTree = ""; }; C0A18FB71ABF53A100BED648 /* imageIcons */ = { isa = PBXGroup; children = ( C0A18FB81ABF53A100BED648 /* arrow-right7-original.png */, C0A18FB91ABF53A100BED648 /* arrow-right7.png */, C0A18FBA1ABF53A100BED648 /* download2.png */, C0A18FBB1ABF53A100BED648 /* imageWithDiffSizes */, C0A18FD11ABF53A100BED648 /* upload2.png */, ); path = imageIcons; sourceTree = ""; }; C0A18FBB1ABF53A100BED648 /* imageWithDiffSizes */ = { isa = PBXGroup; children = ( C0A18FBC1ABF53A100BED648 /* book.png */, C0A18FBD1ABF53A100BED648 /* book@2x.png */, C0A18FBE1ABF53A100BED648 /* book@3x.png */, C0A18FBF1ABF53A100BED648 /* data.png */, C0A18FC01ABF53A100BED648 /* data@2x.png */, C0A18FC11ABF53A100BED648 /* data@3x.png */, C0A18FC21ABF53A100BED648 /* download.png */, C0A18FC31ABF53A100BED648 /* download@2x.png */, C0A18FC41ABF53A100BED648 /* download@3x.png */, C0A18FC51ABF53A100BED648 /* list.png */, C0A18FC61ABF53A100BED648 /* list@2x.png */, C0A18FC71ABF53A100BED648 /* list@3x.png */, C0A18FC81ABF53A100BED648 /* newspaper-alt.png */, C0A18FC91ABF53A100BED648 /* newspaper-alt@2x.png */, C0A18FCA1ABF53A100BED648 /* newspaper-alt@3x.png */, C0A18FCB1ABF53A100BED648 /* settings.png */, C0A18FCC1ABF53A100BED648 /* settings@2x.png */, C0A18FCD1ABF53A100BED648 /* settings@3x.png */, C0A18FCE1ABF53A100BED648 /* upload.png */, C0A18FCF1ABF53A100BED648 /* upload@2x.png */, C0A18FD01ABF53A100BED648 /* upload@3x.png */, ); path = imageWithDiffSizes; sourceTree = ""; }; C0A18FD21ABF53A100BED648 /* logo */ = { isa = PBXGroup; children = ( C0EE6D681B1B7E6000982692 /* 360X80logo.png */, C0EE6D691B1B7E6000982692 /* 1200X1200logo.png */, ); path = logo; sourceTree = ""; }; C0A18FDE1ABF53A100BED648 /* External */ = { isa = PBXGroup; children = ( C0AE417B1F788BD700682CDE /* InAppSettingsKit */, C05F82C61EF5D13E0016F0FC /* socket.io-client-swift-10.0.0 */, C0B4E7861AFAAEA200DC9B65 /* MySocketRocketExtras */, C0C1BE701AF92CB500B705C4 /* SocketRocket */, C05C39881AD0710B00AADE55 /* TLCloudDocumentSyncWrapper */, C0A18FDF1ABF53A100BED648 /* BreadWalletClassesV0.5 */, C0A18FF31ABF53A100BED648 /* CustomIOS7AlertView */, C0A190201ABF53A100BED648 /* iToast */, C0A190231ABF53A100BED648 /* JNKeychain-master */, C0A190261ABF53A100BED648 /* KeychainItemWrapper */, C0A190291ABF53A100BED648 /* Localizations */, C0A190331ABF53A100BED648 /* LTHPasscodeViewController3.50 */, C0A190381ABF53A100BED648 /* NSDate-Extensions */, C0A1903B1ABF53A100BED648 /* QRCodeEncoderObjectiveCAtGithub */, C0A1904E1ABF53A100BED648 /* SWRevealViewController */, C0A1904F1ABF53A100BED648 /* UIAlertController+Blocks */, C0A190521ABF53A100BED648 /* UINavigationBar-FixedHeightWhenStatusBarHidden-master */, ); path = External; sourceTree = ""; }; C0A18FDF1ABF53A100BED648 /* BreadWalletClassesV0.5 */ = { isa = PBXGroup; children = ( C0A18FE31ABF53A100BED648 /* BRKey+BIP38.h */, C0A18FE41ABF53A100BED648 /* BRKey+BIP38.m */, C0A18FE51ABF53A100BED648 /* BRKey.h */, C0A18FE61ABF53A100BED648 /* BRKey.m */, C0A18FE81ABF53A100BED648 /* BRTransaction.h */, C0A18FE91ABF53A100BED648 /* BRTransaction.m */, C0A18FEA1ABF53A100BED648 /* ccMemory.h */, C0A18FEB1ABF53A100BED648 /* NSData+Bitcoin.h */, C0A18FEC1ABF53A100BED648 /* NSData+Bitcoin.m */, C0A18FED1ABF53A100BED648 /* NSData+Hash.h */, C0A18FEE1ABF53A100BED648 /* NSData+Hash.m */, C0A18FEF1ABF53A100BED648 /* NSMutableData+Bitcoin.h */, C0A18FF01ABF53A100BED648 /* NSMutableData+Bitcoin.m */, C0A18FF11ABF53A100BED648 /* NSString+Base58.h */, C0A18FF21ABF53A100BED648 /* NSString+Base58.m */, ); path = BreadWalletClassesV0.5; sourceTree = ""; }; C0A18FF31ABF53A100BED648 /* CustomIOS7AlertView */ = { isa = PBXGroup; children = ( C0A18FF41ABF53A100BED648 /* CustomIOS7AlertView.h */, C0A18FF51ABF53A100BED648 /* CustomIOS7AlertView.m */, ); path = CustomIOS7AlertView; sourceTree = ""; }; C0A190201ABF53A100BED648 /* iToast */ = { isa = PBXGroup; children = ( C0A190211ABF53A100BED648 /* iToast.h */, C0A190221ABF53A100BED648 /* iToast.m */, ); path = iToast; sourceTree = ""; }; C0A190231ABF53A100BED648 /* JNKeychain-master */ = { isa = PBXGroup; children = ( C0A190241ABF53A100BED648 /* JNKeychain.h */, C0A190251ABF53A100BED648 /* JNKeychain.m */, ); path = "JNKeychain-master"; sourceTree = ""; }; C0A190261ABF53A100BED648 /* KeychainItemWrapper */ = { isa = PBXGroup; children = ( C0A190271ABF53A100BED648 /* KeychainItemWrapper.h */, C0A190281ABF53A100BED648 /* KeychainItemWrapper.m */, ); path = KeychainItemWrapper; sourceTree = ""; }; C0A190291ABF53A100BED648 /* Localizations */ = { isa = PBXGroup; children = ( C0A1902A1ABF53A100BED648 /* LTHPasscodeViewController.strings */, ); path = Localizations; sourceTree = ""; }; C0A190331ABF53A100BED648 /* LTHPasscodeViewController3.50 */ = { isa = PBXGroup; children = ( C0A190341ABF53A100BED648 /* LTHKeychainUtils.h */, C0A190351ABF53A100BED648 /* LTHKeychainUtils.m */, C0A190361ABF53A100BED648 /* LTHPasscodeViewController.h */, C0A190371ABF53A100BED648 /* LTHPasscodeViewController.m */, ); path = LTHPasscodeViewController3.50; sourceTree = ""; }; C0A190381ABF53A100BED648 /* NSDate-Extensions */ = { isa = PBXGroup; children = ( C0A190391ABF53A100BED648 /* NSDate-Utilities.h */, C0A1903A1ABF53A100BED648 /* NSDate-Utilities.m */, ); path = "NSDate-Extensions"; sourceTree = ""; }; C0A1903B1ABF53A100BED648 /* QRCodeEncoderObjectiveCAtGithub */ = { isa = PBXGroup; children = ( C0A1903C1ABF53A100BED648 /* DataMatrix.h */, C0A1903D1ABF53A100BED648 /* DataMatrix.mm */, C0A1903E1ABF53A100BED648 /* QR_Encode.cpp */, C0A1903F1ABF53A100BED648 /* QR_Encode.h */, C0A190401ABF53A100BED648 /* QRCodeEncoderDemoViewController.h */, C0A190411ABF53A100BED648 /* QRCodeEncoderDemoViewController.mm */, C0A190421ABF53A100BED648 /* QRCodeEncoderObjectiveCAtGithub-Prefix.pch */, C0A190431ABF53A100BED648 /* QREncoder-Prefix.pch */, C0A190441ABF53A100BED648 /* QREncoder.h */, C0A190451ABF53A100BED648 /* QREncoder.mm */, ); path = QRCodeEncoderObjectiveCAtGithub; sourceTree = ""; }; C0A1904E1ABF53A100BED648 /* SWRevealViewController */ = { isa = PBXGroup; children = ( ); path = SWRevealViewController; sourceTree = ""; }; C0A1904F1ABF53A100BED648 /* UIAlertController+Blocks */ = { isa = PBXGroup; children = ( C0A190501ABF53A100BED648 /* UIAlertConrtoller+Blocks.m */, C0A190511ABF53A100BED648 /* UIAlertController+Blocks.h */, ); path = "UIAlertController+Blocks"; sourceTree = ""; }; C0A190521ABF53A100BED648 /* UINavigationBar-FixedHeightWhenStatusBarHidden-master */ = { isa = PBXGroup; children = ( C0A190531ABF53A100BED648 /* UINavigationBar+FixedHeightWhenStatusBarHidden.h */, C0A190541ABF53A100BED648 /* UINavigationBar+FixedHeightWhenStatusBarHidden.m */, ); path = "UINavigationBar-FixedHeightWhenStatusBarHidden-master"; sourceTree = ""; }; C0A190581ABF53A100BED648 /* model */ = { isa = PBXGroup; children = ( C0649CA61E256E16007E3965 /* TLDisplayStrings.swift */, C0A192E91ABF742D00BED648 /* TLAchievements.swift */, C0A192EA1ABF742D00BED648 /* TLAnalytics.swift */, C0A192EB1ABF742D00BED648 /* TLPreferences.swift */, C0A190591ABF53A100BED648 /* TLAccountObject.swift */, C00413491ACEFAF200505F31 /* TLOperationsManager.swift */, C0A1905A1ABF53A100BED648 /* TLAccounts.swift */, C0A1905B1ABF53A100BED648 /* TLBlockchainStatus.swift */, C0A1905C1ABF53A100BED648 /* TLCoin.swift */, C0A1905D1ABF53A100BED648 /* TLCoreBitcoinWrapper.swift */, C0A1905E1ABF53A100BED648 /* TLHDWalletWrapper.swift */, C0A1905F1ABF53A100BED648 /* TLImportedAddress.swift */, C0A190601ABF53A100BED648 /* TLImportedAddresses.swift */, C0A190611ABF53A100BED648 /* TLSelectedObject.swift */, C0A190621ABF53A100BED648 /* TLSendFormData.swift */, C0A190631ABF53A100BED648 /* TLSpaghettiGodSend.swift */, C0A190641ABF53A100BED648 /* TLStealthAddress.swift */, C0A190651ABF53A100BED648 /* TLStealthWallet.swift */, C0A190661ABF53A100BED648 /* TLSuggestions.swift */, C0A190671ABF53A100BED648 /* TLTxObject.swift */, C0A190681ABF53A100BED648 /* TLWallet.swift */, C0C3BDEB1AC0F7AA001AC6CD /* TLWallet+Stealth.swift */, C0A190691ABF53A100BED648 /* TLWalletJson.swift */, F3DAAD651BB1D9EC000488F0 /* TLWalletJSONKeys.swift */, C0A1906A1ABF53A100BED648 /* TLWalletUtils.swift */, C0F278C71C41EA1A0013DC1A /* TLCurrencyFormat.swift */, C0E5393C1AD454FD00651FE4 /* TLHelpDoc.swift */, F338F9551B794D1D00C02F74 /* TLWalletPassphrase.swift */, F338F9571B79718900C02F74 /* TLCrypto.swift */, F37072451B8F570D0026505D /* TLWalletConfig.swift */, C01681AB1DBD9ED8004D5FD7 /* TLColdWallet.swift */, ); path = model; sourceTree = ""; }; C0A190851ABF53A100BED648 /* utils */ = { isa = PBXGroup; children = ( C0BD70051B4E2C4F00A5788D /* TLStringLocalized.swift */, C0A192EF1ABF744300BED648 /* TLMacros.swift */, C0A192F01ABF744300BED648 /* TLUtils.swift */, C0A192F11ABF744300BED648 /* TLColors.swift */, C0A192F21ABF744300BED648 /* TLHUDWrapper.swift */, C0A192F31ABF744300BED648 /* TLNotificationEvents.swift */, C0A192F51ABF744300BED648 /* TLPrompts.swift */, C0A192F61ABF744300BED648 /* TLQRImageModal.swift */, C0A190861ABF53A100BED648 /* UINavigationController+StatusBarStyle.swift */, C0A190871ABF53A100BED648 /* UIView+FormScroll.swift */, C0A190881ABF53A100BED648 /* UIViewController+Extras.swift */, C0A190891ABF53A100BED648 /* UIViewController+Style.swift */, C0C61C591AF15BEF00F9F71F /* TransitionDelegate.swift */, F3BC36101B8681FF00CBB3F7 /* TLUpdateAppData.swift */, ); path = utils; sourceTree = ""; }; C0A1908A1ABF53A100BED648 /* viewControllers */ = { isa = PBXGroup; children = ( C0D5A60B1DD8FA1300849E2C /* tableViewCells */, C0D8A36E1DAA183D00497D31 /* TLAuthorizeColdWalletPaymentViewController.swift */, C0D8A36C1DAA16C900497D31 /* TLCreateColdWalletViewController.swift */, C01681A11DB0B1D9004D5FD7 /* TLBrainWalletViewController.swift */, C0D8A36A1DAA126700497D31 /* TLColdWalletViewController.swift */, C0A1908B1ABF53A100BED648 /* TLAccountsViewController.swift */, C0A1908C1ABF53A100BED648 /* TLAchievementsViewController.swift */, C0A1908D1ABF53A100BED648 /* TLAddressBookViewController.swift */, C0A1908E1ABF53A100BED648 /* TLAddressListViewController.swift */, C0A1908F1ABF53A100BED648 /* TLRestoreWalletViewController.swift */, C0A190901ABF53A100BED648 /* TLHelpViewController.swift */, C0A6AD741C42E80F00B8C22B /* TLLinksViewController.swift */, C0A190911ABF53A100BED648 /* TLHistoryViewController.swift */, C0A190921ABF53A100BED648 /* TLInstructionsViewController.swift */, C0A190931ABF53A100BED648 /* TLManageAccountsViewController.swift */, C0A190941ABF53A100BED648 /* TLMenuViewController.swift */, C0A190961ABF53A100BED648 /* TLPreloadViewController.swift */, C0A190951ABF53A100BED648 /* TLPassPhraseViewController.swift */, C046DD5E1D6E271000B39580 /* TLReviewPaymentViewController.swift */, C0A190971ABF53A100BED648 /* TLQRCodeScannerViewController.swift */, C0A190981ABF53A100BED648 /* TLReceiveViewController.swift */, C0A190991ABF53A100BED648 /* TLSendViewController.swift */, C0A1909A1ABF53A100BED648 /* TLSettingsViewController.swift */, C0A1909B1ABF53A100BED648 /* TLTextViewViewController.swift */, C0C61C5D1AF15E6100F9F71F /* TransparentViewController.swift */, ); path = viewControllers; sourceTree = ""; }; C0A192FF1ABF744E00BED648 /* APIs */ = { isa = PBXGroup; children = ( C0D20A911D5963FF008F70F2 /* TLTxFeeAPI.swift */, C0A193001ABF744E00BED648 /* TLBitcoinListener.swift */, C0A193011ABF744E00BED648 /* TLBlockchainAPI.swift */, C0A193021ABF744E00BED648 /* TLBlockExplorerAPI.swift */, C0A193041ABF744E00BED648 /* TLExchangeRate.swift */, C0A193051ABF744E00BED648 /* TLInsightAPI.swift */, C0AD2C581AC3DE3200FEF982 /* TLBlockrAPI.swift */, C0A193061ABF744E00BED648 /* TLNetworking.swift */, C0A193071ABF744E00BED648 /* TLStealthServerAPI.swift */, C0A193081ABF744E00BED648 /* TLStealthWebSocket.swift */, C0AB0A321AC52A8400B456DB /* TLPushTxAPI.swift */, C0607FCC1AEEB3CD007203F8 /* TLStealthServerConfig.swift */, ); path = APIs; sourceTree = ""; }; C0AE417B1F788BD700682CDE /* InAppSettingsKit */ = { isa = PBXGroup; children = ( C0AE417C1F788BD700682CDE /* Controllers */, C0AE41861F788BD700682CDE /* Models */, C0AE41911F788BD700682CDE /* Resources */, C0AE41A21F788BD700682CDE /* Views */, ); path = InAppSettingsKit; sourceTree = ""; }; C0AE417C1F788BD700682CDE /* Controllers */ = { isa = PBXGroup; children = ( C0AE417D1F788BD700682CDE /* IASKAppSettingsViewController.h */, C0AE417E1F788BD700682CDE /* IASKAppSettingsViewController.m */, C0AE417F1F788BD700682CDE /* IASKAppSettingsWebViewController.h */, C0AE41801F788BD700682CDE /* IASKAppSettingsWebViewController.m */, C0AE41811F788BD700682CDE /* IASKMultipleValueSelection.h */, C0AE41821F788BD700682CDE /* IASKMultipleValueSelection.m */, C0AE41831F788BD700682CDE /* IASKSpecifierValuesViewController.h */, C0AE41841F788BD700682CDE /* IASKSpecifierValuesViewController.m */, C0AE41851F788BD700682CDE /* IASKViewController.h */, ); path = Controllers; sourceTree = ""; }; C0AE41861F788BD700682CDE /* Models */ = { isa = PBXGroup; children = ( C0AE41871F788BD700682CDE /* IASKSettingsReader.h */, C0AE41881F788BD700682CDE /* IASKSettingsReader.m */, C0AE41891F788BD700682CDE /* IASKSettingsStore.h */, C0AE418A1F788BD700682CDE /* IASKSettingsStore.m */, C0AE418B1F788BD700682CDE /* IASKSettingsStoreFile.h */, C0AE418C1F788BD700682CDE /* IASKSettingsStoreFile.m */, C0AE418D1F788BD700682CDE /* IASKSettingsStoreUserDefaults.h */, C0AE418E1F788BD700682CDE /* IASKSettingsStoreUserDefaults.m */, C0AE418F1F788BD700682CDE /* IASKSpecifier.h */, C0AE41901F788BD700682CDE /* IASKSpecifier.m */, ); path = Models; sourceTree = ""; }; C0AE41911F788BD700682CDE /* Resources */ = { isa = PBXGroup; children = ( C0AE41921F788BD700682CDE /* IASKLocalizable.strings */, ); path = Resources; sourceTree = ""; }; C0AE41A21F788BD700682CDE /* Views */ = { isa = PBXGroup; children = ( C0AE41A31F788BD700682CDE /* IASKPSSliderSpecifierViewCell.h */, C0AE41A41F788BD700682CDE /* IASKPSSliderSpecifierViewCell.m */, C0AE41A51F788BD700682CDE /* IASKPSTextFieldSpecifierViewCell.h */, C0AE41A61F788BD700682CDE /* IASKPSTextFieldSpecifierViewCell.m */, C0AE41A71F788BD700682CDE /* IASKSlider.h */, C0AE41A81F788BD700682CDE /* IASKSlider.m */, C0AE41A91F788BD700682CDE /* IASKSwitch.h */, C0AE41AA1F788BD700682CDE /* IASKSwitch.m */, C0AE41AB1F788BD700682CDE /* IASKTextField.h */, C0AE41AC1F788BD700682CDE /* IASKTextField.m */, C0AE41AD1F788BD700682CDE /* IASKTextView.h */, C0AE41AE1F788BD700682CDE /* IASKTextView.m */, C0AE41AF1F788BD700682CDE /* IASKTextViewCell.h */, C0AE41B01F788BD700682CDE /* IASKTextViewCell.m */, ); path = Views; sourceTree = ""; }; C0B4E7861AFAAEA200DC9B65 /* MySocketRocketExtras */ = { isa = PBXGroup; children = ( C0B4E7871AFAAEA200DC9B65 /* SRWebSocket+Helpers.h */, C0B4E7881AFAAEA200DC9B65 /* SRWebSocket+Helpers.m */, ); path = MySocketRocketExtras; sourceTree = ""; }; C0C1BE701AF92CB500B705C4 /* SocketRocket */ = { isa = PBXGroup; children = ( C0C1BE711AF92CB500B705C4 /* base64.c */, C0C1BE721AF92CB500B705C4 /* base64.h */, C0C1BE731AF92CB500B705C4 /* NSData+SRB64Additions.h */, C0C1BE741AF92CB500B705C4 /* NSData+SRB64Additions.m */, C0C1BE751AF92CB500B705C4 /* SocketRocket-Prefix.pch */, C0C1BE761AF92CB500B705C4 /* SRWebSocket.h */, C0C1BE771AF92CB500B705C4 /* SRWebSocket.m */, ); path = SocketRocket; sourceTree = ""; }; C0D5A60B1DD8FA1300849E2C /* tableViewCells */ = { isa = PBXGroup; children = ( C0649C9F1E256B52007E3965 /* walletTableViewCells */, C0D5A60C1DD8FA1300849E2C /* createColdWalletTableViewCells */, C0D5A6101DD8FA1300849E2C /* spendColdWalletTableViewCells */, ); path = tableViewCells; sourceTree = ""; }; C0D5A60C1DD8FA1300849E2C /* createColdWalletTableViewCells */ = { isa = PBXGroup; children = ( C0D5A60D1DD8FA1300849E2C /* TLAdvancedNewWalletTableViewCell.swift */, C0D5A60E1DD8FA1300849E2C /* TLColdWalletSelectWayTableViewCell.swift */, C0D5A60F1DD8FA1300849E2C /* TLNewWalletTableViewCell.swift */, ); path = createColdWalletTableViewCells; sourceTree = ""; }; C0D5A6101DD8FA1300849E2C /* spendColdWalletTableViewCells */ = { isa = PBXGroup; children = ( C0D5A6111DD8FA1300849E2C /* TLInputColdWalletKeyTableViewCell.swift */, C0D5A6121DD8FA1300849E2C /* TLPassSignedTxTableViewCell.swift */, C0D5A6131DD8FA1300849E2C /* TLScanUnsignedTxTableViewCell.swift */, ); path = spendColdWalletTableViewCells; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ C0A18F8D1ABF51C200BED648 /* ArcBit */ = { isa = PBXNativeTarget; buildConfigurationList = C0A18FAD1ABF51C200BED648 /* Build configuration list for PBXNativeTarget "ArcBit" */; buildPhases = ( 172F38C0899F2CAA3997D54E /* [CP] Check Pods Manifest.lock */, C0A18F8A1ABF51C200BED648 /* Sources */, C0A18F8B1ABF51C200BED648 /* Frameworks */, C0A18F8C1ABF51C200BED648 /* Resources */, C0878BE81AF4A11B002A4F5A /* ShellScript */, 6ABB43AFCFDF0A2994449960 /* [CP] Embed Pods Frameworks */, B6B36DF9BC602A37C9711298 /* [CP] Copy Pods Resources */, C0698B451CFB7D4900115062 /* ShellScript */, ); buildRules = ( ); dependencies = ( ); name = ArcBit; productName = ArcBit; productReference = C0A18F8E1ABF51C200BED648 /* ArcBit.app */; productType = "com.apple.product-type.application"; }; C0A18FA21ABF51C200BED648 /* ArcBitTests */ = { isa = PBXNativeTarget; buildConfigurationList = C0A18FB01ABF51C200BED648 /* Build configuration list for PBXNativeTarget "ArcBitTests" */; buildPhases = ( 782EBA77421757A4C888700E /* [CP] Check Pods Manifest.lock */, C0A18F9F1ABF51C200BED648 /* Sources */, C0A18FA01ABF51C200BED648 /* Frameworks */, C0A18FA11ABF51C200BED648 /* Resources */, 73FC12304A7AE0663DF98D3A /* [CP] Embed Pods Frameworks */, 2AE1D86853126B8686B821A5 /* [CP] Copy Pods Resources */, ); buildRules = ( ); dependencies = ( C0A18FA51ABF51C200BED648 /* PBXTargetDependency */, ); name = ArcBitTests; productName = ArcBitTests; productReference = C0A18FA31ABF51C200BED648 /* ArcBitTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ C0A18F861ABF51C200BED648 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftMigration = 0700; LastSwiftUpdateCheck = 0700; LastUpgradeCheck = 0700; ORGANIZATIONNAME = ArcBit; TargetAttributes = { C0A18F8D1ABF51C200BED648 = { CreatedOnToolsVersion = 6.2; DevelopmentTeam = 2VMT9RED95; LastSwiftMigration = 0800; SystemCapabilities = { com.apple.iCloud = { enabled = 0; }; }; }; C0A18FA21ABF51C200BED648 = { CreatedOnToolsVersion = 6.2; LastSwiftMigration = 0800; TestTargetID = C0A18F8D1ABF51C200BED648; }; }; }; buildConfigurationList = C0A18F891ABF51C200BED648 /* Build configuration list for PBXProject "ArcBit" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, Base, de, es, fr, ja, ro, ru, "zh-Hans-CN", el, it, nl, "pt-PT", pt, sv, th, tr, "zh-Hant", ); mainGroup = C0A18F851ABF51C200BED648; productRefGroup = C0A18F8F1ABF51C200BED648 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( C0A18F8D1ABF51C200BED648 /* ArcBit */, C0A18FA21ABF51C200BED648 /* ArcBitTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ C0A18F8C1ABF51C200BED648 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( C0A190B01ABF53A100BED648 /* settings.png in Resources */, C0A190A51ABF53A100BED648 /* data@2x.png in Resources */, C0A190AD1ABF53A100BED648 /* newspaper-alt.png in Resources */, C0A190A11ABF53A100BED648 /* book.png in Resources */, C0EE6D6B1B1B7E6000982692 /* 1200X1200logo.png in Resources */, C0A190A81ABF53A100BED648 /* download@2x.png in Resources */, C0A190AE1ABF53A100BED648 /* newspaper-alt@2x.png in Resources */, C0EE6D6A1B1B7E6000982692 /* 360X80logo.png in Resources */, C0A190DE1ABF53A100BED648 /* LTHPasscodeViewController.strings in Resources */, C0A190A01ABF53A100BED648 /* download2.png in Resources */, C0A190A91ABF53A100BED648 /* download@3x.png in Resources */, C0A18F9E1ABF51C200BED648 /* LaunchScreen.xib in Resources */, C0BD70011B4E2BE300A5788D /* Localizable.strings in Resources */, C0A190B41ABF53A100BED648 /* upload@2x.png in Resources */, C0A190A41ABF53A100BED648 /* data.png in Resources */, C0AE41BA1F788BD700682CDE /* IASKLocalizable.strings in Resources */, C0D7B2DB1B16FDA10096E599 /* live.cer in Resources */, C0A190AC1ABF53A100BED648 /* list@3x.png in Resources */, C0A190A61ABF53A100BED648 /* data@3x.png in Resources */, C0A190B61ABF53A100BED648 /* upload2.png in Resources */, C0A190B51ABF53A100BED648 /* upload@3x.png in Resources */, C0A1909F1ABF53A100BED648 /* arrow-right7.png in Resources */, C0A190B31ABF53A100BED648 /* upload.png in Resources */, C0A190AA1ABF53A100BED648 /* list.png in Resources */, C0A190A31ABF53A100BED648 /* book@3x.png in Resources */, C0A18F9B1ABF51C200BED648 /* Images.xcassets in Resources */, C0A190A71ABF53A100BED648 /* download.png in Resources */, C0A190AF1ABF53A100BED648 /* newspaper-alt@3x.png in Resources */, C0A190EC1ABF53A100BED648 /* InAppSettings.bundle in Resources */, C0A190B11ABF53A100BED648 /* settings@2x.png in Resources */, C0A192E81ABF707400BED648 /* Main.storyboard in Resources */, C0A190AB1ABF53A100BED648 /* list@2x.png in Resources */, C0A1909E1ABF53A100BED648 /* arrow-right7-original.png in Resources */, C0A190B21ABF53A100BED648 /* settings@3x.png in Resources */, C0A190A21ABF53A100BED648 /* book@2x.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; C0A18FA11ABF51C200BED648 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 172F38C0899F2CAA3997D54E /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-ArcBit-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 2AE1D86853126B8686B821A5 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "[CP] Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ArcBitTests/Pods-ArcBitTests-resources.sh\"\n"; showEnvVarsInLog = 0; }; 6ABB43AFCFDF0A2994449960 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ArcBit/Pods-ArcBit-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 73FC12304A7AE0663DF98D3A /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ArcBitTests/Pods-ArcBitTests-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 782EBA77421757A4C888700E /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-ArcBitTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; B6B36DF9BC602A37C9711298 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "[CP] Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ArcBit/Pods-ArcBit-resources.sh\"\n"; showEnvVarsInLog = 0; }; C0698B451CFB7D4900115062 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Fabric/run\" 24ecfd2fbad3162671908399854ab3e9ddba0710 fdc29481dd35a8aa6ec132c294cff185a7211174fff8b1803540ff4e64d5b24a"; }; C0878BE81AF4A11B002A4F5A /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "FILE=\"${SRCROOT}/HockeySDK-iOS/BuildAgent\"\nif [ -f \"$FILE\" ]; then\n\"$FILE\"\nfi"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ C0A18F8A1ABF51C200BED648 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( C01681AC1DBD9ED8004D5FD7 /* TLColdWallet.swift in Sources */, C0AE41BB1F788BD700682CDE /* IASKPSSliderSpecifierViewCell.m in Sources */, C0C1BE781AF92CB500B705C4 /* base64.c in Sources */, C05F82E81EF5D13E0016F0FC /* SocketEngineSpec.swift in Sources */, C0A190F81ABF53A100BED648 /* TLSpaghettiGodSend.swift in Sources */, C0BD70061B4E2C4F00A5788D /* TLStringLocalized.swift in Sources */, C0A192ED1ABF742D00BED648 /* TLAnalytics.swift in Sources */, C05F82E41EF5D13E0016F0FC /* SocketEngine.swift in Sources */, F37072461B8F570D0026505D /* TLWalletConfig.swift in Sources */, C0D8A36D1DAA16C900497D31 /* TLCreateColdWalletViewController.swift in Sources */, C0A191251ABF53A100BED648 /* TLMenuViewController.swift in Sources */, C05F82EA1EF5D13E0016F0FC /* SocketEventHandler.swift in Sources */, C05C398B1AD0710B00AADE55 /* TLCloudDocumentSyncWrapper.m in Sources */, C0A190FD1ABF53A100BED648 /* TLWallet.swift in Sources */, C0A190DB1ABF53A100BED648 /* iToast.m in Sources */, F338F9561B794D1D00C02F74 /* TLWalletPassphrase.swift in Sources */, C0E5393D1AD454FD00651FE4 /* TLHelpDoc.swift in Sources */, C0AE41C01F788BD700682CDE /* IASKTextView.m in Sources */, C0AE41B51F788BD700682CDE /* IASKSettingsReader.m in Sources */, C0A190F41ABF53A100BED648 /* TLImportedAddress.swift in Sources */, C0A1930D1ABF744E00BED648 /* TLExchangeRate.swift in Sources */, C05F82F31EF5D13E0016F0FC /* SocketParsable.swift in Sources */, C05F82E31EF5D13E0016F0FC /* SocketClientManager.swift in Sources */, C0A192F71ABF744300BED648 /* TLMacros.swift in Sources */, C0A190E41ABF53A100BED648 /* QRCodeEncoderDemoViewController.mm in Sources */, C0A190E21ABF53A100BED648 /* DataMatrix.mm in Sources */, C0AE41BF1F788BD700682CDE /* IASKTextField.m in Sources */, C05F82EB1EF5D13E0016F0FC /* SocketExtensions.swift in Sources */, C0A190FA1ABF53A100BED648 /* TLStealthWallet.swift in Sources */, C0C61C5A1AF15BEF00F9F71F /* TransitionDelegate.swift in Sources */, C0AE41B81F788BD700682CDE /* IASKSettingsStoreUserDefaults.m in Sources */, C0AE41B71F788BD700682CDE /* IASKSettingsStoreFile.m in Sources */, C0C3BDEC1AC0F7AA001AC6CD /* TLWallet+Stealth.swift in Sources */, C0A190F61ABF53A100BED648 /* TLSelectedObject.swift in Sources */, C0A190FC1ABF53A100BED648 /* TLTxObject.swift in Sources */, C0A192FD1ABF744300BED648 /* TLPrompts.swift in Sources */, C0649CA41E256B52007E3965 /* TLAddressTableViewCell.swift in Sources */, C0A190C31ABF53A100BED648 /* BRTransaction.m in Sources */, C0A191221ABF53A100BED648 /* TLHistoryViewController.swift in Sources */, C0D5A6161DD8FA1300849E2C /* TLNewWalletTableViewCell.swift in Sources */, C0A190FF1ABF53A100BED648 /* TLWalletUtils.swift in Sources */, C0AE41C11F788BD700682CDE /* IASKTextViewCell.m in Sources */, C0A1930F1ABF744E00BED648 /* TLNetworking.swift in Sources */, C05F82F61EF5D13E0016F0FC /* SSLSecurity.swift in Sources */, C0AE41BC1F788BD700682CDE /* IASKPSTextFieldSpecifierViewCell.m in Sources */, C0A190FE1ABF53A100BED648 /* TLWalletJson.swift in Sources */, C0AB0A331AC52A8400B456DB /* TLPushTxAPI.swift in Sources */, C0AE41B61F788BD700682CDE /* IASKSettingsStore.m in Sources */, C0D8A36B1DAA126700497D31 /* TLColdWalletViewController.swift in Sources */, C0A190C41ABF53A100BED648 /* NSData+Bitcoin.m in Sources */, C0A191201ABF53A100BED648 /* TLRestoreWalletViewController.swift in Sources */, C0D5A6191DD8FA1300849E2C /* TLScanUnsignedTxTableViewCell.swift in Sources */, C05F82F21EF5D13E0016F0FC /* SocketPacket.swift in Sources */, C0AE41B21F788BD700682CDE /* IASKAppSettingsWebViewController.m in Sources */, C0A190F31ABF53A100BED648 /* TLHDWalletWrapper.swift in Sources */, C05F82E91EF5D13E0016F0FC /* SocketEngineWebsocket.swift in Sources */, C0649CA31E256B52007E3965 /* TLAccountTableViewCell.swift in Sources */, C05F82EC1EF5D13E0016F0FC /* SocketIOClient.swift in Sources */, C0D5A6141DD8FA1300849E2C /* TLAdvancedNewWalletTableViewCell.swift in Sources */, C05F82E21EF5D13E0016F0FC /* SocketAnyEvent.swift in Sources */, C0A1930A1ABF744E00BED648 /* TLBlockchainAPI.swift in Sources */, C05F82EE1EF5D13E0016F0FC /* SocketIOClientOption.swift in Sources */, C0A190DC1ABF53A100BED648 /* JNKeychain.m in Sources */, C0A190E11ABF53A100BED648 /* NSDate-Utilities.m in Sources */, C0D20A921D5963FF008F70F2 /* TLTxFeeAPI.swift in Sources */, C0D8A36F1DAA183D00497D31 /* TLAuthorizeColdWalletPaymentViewController.swift in Sources */, C05F82E01EF5D13E0016F0FC /* SocketAckEmitter.swift in Sources */, C0A192F81ABF744300BED648 /* TLUtils.swift in Sources */, C01681A21DB0B1D9004D5FD7 /* TLBrainWalletViewController.swift in Sources */, C0A190DD1ABF53A100BED648 /* KeychainItemWrapper.m in Sources */, C0A192FE1ABF744300BED648 /* TLQRImageModal.swift in Sources */, C0A1911F1ABF53A100BED648 /* TLAddressListViewController.swift in Sources */, C05F82F71EF5D13E0016F0FC /* WebSocket.swift in Sources */, C0A1911C1ABF53A100BED648 /* TLAccountsViewController.swift in Sources */, C0A1911A1ABF53A100BED648 /* UIViewController+Extras.swift in Sources */, C0A191191ABF53A100BED648 /* UIView+FormScroll.swift in Sources */, C0A190C51ABF53A100BED648 /* NSData+Hash.m in Sources */, C0A190C71ABF53A100BED648 /* NSString+Base58.m in Sources */, C0A1912B1ABF53A100BED648 /* TLSettingsViewController.swift in Sources */, C0C1BE791AF92CB500B705C4 /* NSData+SRB64Additions.m in Sources */, C0A1912A1ABF53A100BED648 /* TLSendViewController.swift in Sources */, C0A1930B1ABF744E00BED648 /* TLBlockExplorerAPI.swift in Sources */, C0A190F01ABF53A100BED648 /* TLBlockchainStatus.swift in Sources */, C0A190E01ABF53A100BED648 /* LTHPasscodeViewController.m in Sources */, C0A190C61ABF53A100BED648 /* NSMutableData+Bitcoin.m in Sources */, C046DD5F1D6E271000B39580 /* TLReviewPaymentViewController.swift in Sources */, C0A190F11ABF53A100BED648 /* TLCoin.swift in Sources */, C0A1911B1ABF53A100BED648 /* UIViewController+Style.swift in Sources */, F3BC36111B8681FF00CBB3F7 /* TLUpdateAppData.swift in Sources */, C0AD2C591AC3DE3200FEF982 /* TLBlockrAPI.swift in Sources */, C0A18F941ABF51C200BED648 /* AppDelegate.swift in Sources */, C004134A1ACEFAF200505F31 /* TLOperationsManager.swift in Sources */, C0A190C11ABF53A100BED648 /* BRKey+BIP38.m in Sources */, C0A191261ABF53A100BED648 /* TLPassPhraseViewController.swift in Sources */, C0A193091ABF744E00BED648 /* TLBitcoinListener.swift in Sources */, C0649CA71E256E16007E3965 /* TLDisplayStrings.swift in Sources */, C05F82EF1EF5D13E0016F0FC /* SocketIOClientSpec.swift in Sources */, C0A192F91ABF744300BED648 /* TLColors.swift in Sources */, C05F82ED1EF5D13E0016F0FC /* SocketIOClientConfiguration.swift in Sources */, C05F82E11EF5D13E0016F0FC /* SocketAckManager.swift in Sources */, C0A190E31ABF53A100BED648 /* QR_Encode.cpp in Sources */, C0AE41BD1F788BD700682CDE /* IASKSlider.m in Sources */, C0A6AD751C42E80F00B8C22B /* TLLinksViewController.swift in Sources */, C0A190E51ABF53A100BED648 /* QREncoder.mm in Sources */, C0A191281ABF53A100BED648 /* TLQRCodeScannerViewController.swift in Sources */, C0A192EC1ABF742D00BED648 /* TLAchievements.swift in Sources */, C0A191211ABF53A100BED648 /* TLHelpViewController.swift in Sources */, C0F278C81C41EA1A0013DC1A /* TLCurrencyFormat.swift in Sources */, C0D5A6151DD8FA1300849E2C /* TLColdWalletSelectWayTableViewCell.swift in Sources */, C0A190EF1ABF53A100BED648 /* TLAccounts.swift in Sources */, C0A190F21ABF53A100BED648 /* TLCoreBitcoinWrapper.swift in Sources */, C0A190EA1ABF53A100BED648 /* UINavigationBar+FixedHeightWhenStatusBarHidden.m in Sources */, C0A193111ABF744E00BED648 /* TLStealthWebSocket.swift in Sources */, C05F82E61EF5D13E0016F0FC /* SocketEnginePacketType.swift in Sources */, C0A1930E1ABF744E00BED648 /* TLInsightAPI.swift in Sources */, F338F9581B79718900C02F74 /* TLCrypto.swift in Sources */, C0A191181ABF53A100BED648 /* UINavigationController+StatusBarStyle.swift in Sources */, C0A190C81ABF53A100BED648 /* CustomIOS7AlertView.m in Sources */, C0A1912C1ABF53A100BED648 /* TLTextViewViewController.swift in Sources */, C0A1911D1ABF53A100BED648 /* TLAchievementsViewController.swift in Sources */, C0A192FA1ABF744300BED648 /* TLHUDWrapper.swift in Sources */, C0A192EE1ABF742D00BED648 /* TLPreferences.swift in Sources */, C0C1BE7A1AF92CB500B705C4 /* SRWebSocket.m in Sources */, C0A191231ABF53A100BED648 /* TLInstructionsViewController.swift in Sources */, C0A191271ABF53A100BED648 /* TLPreloadViewController.swift in Sources */, C0A190F91ABF53A100BED648 /* TLStealthAddress.swift in Sources */, C0AE41B31F788BD700682CDE /* IASKMultipleValueSelection.m in Sources */, C0A193101ABF744E00BED648 /* TLStealthServerAPI.swift in Sources */, C0AE41BE1F788BD700682CDE /* IASKSwitch.m in Sources */, C0B4E7891AFAAEA200DC9B65 /* SRWebSocket+Helpers.m in Sources */, C0AE41B91F788BD700682CDE /* IASKSpecifier.m in Sources */, C0A190C21ABF53A100BED648 /* BRKey.m in Sources */, C0A190EE1ABF53A100BED648 /* TLAccountObject.swift in Sources */, C05F82F51EF5D13E0016F0FC /* SocketTypes.swift in Sources */, C0D5A6171DD8FA1300849E2C /* TLInputColdWalletKeyTableViewCell.swift in Sources */, C0A191241ABF53A100BED648 /* TLManageAccountsViewController.swift in Sources */, C0A190E91ABF53A100BED648 /* UIAlertConrtoller+Blocks.m in Sources */, C0A191291ABF53A100BED648 /* TLReceiveViewController.swift in Sources */, C0C61C5E1AF15E6100F9F71F /* TransparentViewController.swift in Sources */, C0A192FB1ABF744300BED648 /* TLNotificationEvents.swift in Sources */, C05F82F11EF5D13E0016F0FC /* SocketLogger.swift in Sources */, C05F82F41EF5D13E0016F0FC /* SocketStringReader.swift in Sources */, C0D5A6181DD8FA1300849E2C /* TLPassSignedTxTableViewCell.swift in Sources */, C05F82E71EF5D13E0016F0FC /* SocketEnginePollable.swift in Sources */, C0649CA51E256B52007E3965 /* TLTransactionTableViewCell.swift in Sources */, F3DAAD661BB1D9EC000488F0 /* TLWalletJSONKeys.swift in Sources */, C05F82E51EF5D13E0016F0FC /* SocketEngineClient.swift in Sources */, C0AE41B41F788BD700682CDE /* IASKSpecifierValuesViewController.m in Sources */, C0A1911E1ABF53A100BED648 /* TLAddressBookViewController.swift in Sources */, C0A190F51ABF53A100BED648 /* TLImportedAddresses.swift in Sources */, C05F82F01EF5D13E0016F0FC /* SocketIOClientStatus.swift in Sources */, C0607FCD1AEEB3CD007203F8 /* TLStealthServerConfig.swift in Sources */, C0AE41B11F788BD700682CDE /* IASKAppSettingsViewController.m in Sources */, C0A190DF1ABF53A100BED648 /* LTHKeychainUtils.m in Sources */, C0A190FB1ABF53A100BED648 /* TLSuggestions.swift in Sources */, C0A190F71ABF53A100BED648 /* TLSendFormData.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; C0A18F9F1ABF51C200BED648 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( C0A193281ABF82A500BED648 /* TLCoin.swift in Sources */, C05F830E1EF5D1710016F0FC /* SSLSecurity.swift in Sources */, C05F82FC1EF5D14B0016F0FC /* SocketEngine.swift in Sources */, C0A193221ABF829800BED648 /* TLAchievements.swift in Sources */, C0A1931D1ABF81D100BED648 /* TLWalletJson.swift in Sources */, C05F82F81EF5D1420016F0FC /* SocketAckEmitter.swift in Sources */, C0A1931E1ABF81DD00BED648 /* TLMacros.swift in Sources */, C0A193271ABF82A300BED648 /* TLBlockchainStatus.swift in Sources */, C0A193201ABF824800BED648 /* TLWalletUtils.swift in Sources */, C0A193361ABF82EA00BED648 /* TLExchangeRate.swift in Sources */, C004134B1ACEFCB700505F31 /* TLOperationsManager.swift in Sources */, C05F830D1EF5D16F0016F0FC /* SocketTypes.swift in Sources */, C0A193321ABF82E200BED648 /* TLBitcoinListener.swift in Sources */, C0A1932A1ABF82AC00BED648 /* TLImportedAddresses.swift in Sources */, C05F83001EF5D1530016F0FC /* SocketEngineSpec.swift in Sources */, C05F830A1EF5D1680016F0FC /* SocketPacket.swift in Sources */, C05F82FB1EF5D1490016F0FC /* SocketClientManager.swift in Sources */, F34D5C601B7A2C0100DF37DC /* TLCrypto.swift in Sources */, C0A193331ABF82E400BED648 /* TLBlockchainAPI.swift in Sources */, F3F1651B1BB1DB7F00F1293A /* TLWalletJSONKeys.swift in Sources */, C03ECBF71B54A104005D4B0F /* TLStringLocalized.swift in Sources */, C05F83041EF5D15B0016F0FC /* SocketIOClient.swift in Sources */, C05F830B1EF5D16A0016F0FC /* SocketParsable.swift in Sources */, C0D20A931D5966AB008F70F2 /* TLTxFeeAPI.swift in Sources */, C0649CA81E25969D007E3965 /* TLDisplayStrings.swift in Sources */, C0A1932B1ABF82AF00BED648 /* TLSelectedObject.swift in Sources */, C05F83071EF5D1620016F0FC /* SocketIOClientSpec.swift in Sources */, C09D416C1DC9B05200E2D09A /* TLColdWallet.swift in Sources */, C0A1932D1ABF82B300BED648 /* TLSpaghettiGodSend.swift in Sources */, C05F83061EF5D15F0016F0FC /* SocketIOClientOption.swift in Sources */, C0F278C91C41EAF90013DC1A /* TLCurrencyFormat.swift in Sources */, C05F83011EF5D1550016F0FC /* SocketEngineWebsocket.swift in Sources */, C0A1933A1ABF82F300BED648 /* TLStealthServerAPI.swift in Sources */, C0A193251ABF829E00BED648 /* TLAccountObject.swift in Sources */, C05F83051EF5D15C0016F0FC /* SocketIOClientConfiguration.swift in Sources */, C0607FCE1AEEB716007203F8 /* TLStealthServerConfig.swift in Sources */, C0A193301ABF82BB00BED648 /* TLTxObject.swift in Sources */, C0A193391ABF82F100BED648 /* TLStealthWebSocket.swift in Sources */, C0E5393E1AD4555C00651FE4 /* TLHelpDoc.swift in Sources */, C0A1931F1ABF822000BED648 /* TLHDWalletWrapper.swift in Sources */, C0A18FAA1ABF51C200BED648 /* ArcBitTests.swift in Sources */, C0A193241ABF829C00BED648 /* TLPreferences.swift in Sources */, C05F830C1EF5D16D0016F0FC /* SocketStringReader.swift in Sources */, C0A193381ABF82EF00BED648 /* TLNetworking.swift in Sources */, C0A1932E1ABF82B700BED648 /* TLStealthWallet.swift in Sources */, F3BC360F1B8681A300CBB3F7 /* TLWalletPassphrase.swift in Sources */, F37072481B8F57810026505D /* TLWalletConfig.swift in Sources */, C05F83091EF5D1660016F0FC /* SocketLogger.swift in Sources */, C05F82FA1EF5D1470016F0FC /* SocketAnyEvent.swift in Sources */, C05F830F1EF5D1730016F0FC /* WebSocket.swift in Sources */, C0C3BDED1AC11BD2001AC6CD /* TLWallet+Stealth.swift in Sources */, C0A1931C1ABF81CB00BED648 /* TLStealthAddress.swift in Sources */, C0A1931A1ABF751800BED648 /* BreadWalletTests.m in Sources */, C05F83021EF5D1570016F0FC /* SocketEventHandler.swift in Sources */, F3BC36121B8681FF00CBB3F7 /* TLUpdateAppData.swift in Sources */, C05F82FF1EF5D1500016F0FC /* SocketEnginePollable.swift in Sources */, C0A193291ABF82AA00BED648 /* TLImportedAddress.swift in Sources */, C0A193341ABF82E600BED648 /* TLBlockExplorerAPI.swift in Sources */, C05F82FE1EF5D14E0016F0FC /* SocketEnginePacketType.swift in Sources */, C05F82F91EF5D1450016F0FC /* SocketAckManager.swift in Sources */, C0AD2C5A1AC3DED100FEF982 /* TLBlockrAPI.swift in Sources */, C0A1933E1ABF830000BED648 /* TLNotificationEvents.swift in Sources */, C0A193311ABF82BD00BED648 /* TLWallet.swift in Sources */, C0A193261ABF82A100BED648 /* TLAccounts.swift in Sources */, C05C398C1AD0712B00AADE55 /* TLCloudDocumentSyncWrapper.m in Sources */, C05F82FD1EF5D14C0016F0FC /* SocketEngineClient.swift in Sources */, C0A193231ABF829A00BED648 /* TLAnalytics.swift in Sources */, C05F83081EF5D1640016F0FC /* SocketIOClientStatus.swift in Sources */, C0A1932F1ABF82B900BED648 /* TLSuggestions.swift in Sources */, C0A1932C1ABF82B100BED648 /* TLSendFormData.swift in Sources */, C05F83031EF5D1580016F0FC /* SocketExtensions.swift in Sources */, C0AD2C5B1AC4DF7700FEF982 /* TLUtils.swift in Sources */, C0A193211ABF825700BED648 /* TLCoreBitcoinWrapper.swift in Sources */, C0A193371ABF82EC00BED648 /* TLInsightAPI.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ C0A18FA51ABF51C200BED648 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = C0A18F8D1ABF51C200BED648 /* ArcBit */; targetProxy = C0A18FA41ABF51C200BED648 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ C0A18F9C1ABF51C200BED648 /* LaunchScreen.xib */ = { isa = PBXVariantGroup; children = ( C0A18F9D1ABF51C200BED648 /* Base */, C0920A981FA82DB6008A0745 /* zh-Hant */, ); name = LaunchScreen.xib; sourceTree = ""; }; C0A1902A1ABF53A100BED648 /* LTHPasscodeViewController.strings */ = { isa = PBXVariantGroup; children = ( C0A1902B1ABF53A100BED648 /* de */, C0A1902C1ABF53A100BED648 /* en */, C0A1902D1ABF53A100BED648 /* es */, C0A1902E1ABF53A100BED648 /* fr */, C0A1902F1ABF53A100BED648 /* ja */, C0A190301ABF53A100BED648 /* ro */, C0A190311ABF53A100BED648 /* ru */, C0A190321ABF53A100BED648 /* zh-Hans-CN */, C0920A9A1FA82DB6008A0745 /* zh-Hant */, ); name = LTHPasscodeViewController.strings; sourceTree = ""; }; C0A192E61ABF707400BED648 /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( C0A192E71ABF707400BED648 /* Base */, C01A489E1B4F9E6E004D7FA0 /* ru */, C0920A971FA82DB6008A0745 /* zh-Hant */, ); name = Main.storyboard; sourceTree = ""; }; C0AE41921F788BD700682CDE /* IASKLocalizable.strings */ = { isa = PBXVariantGroup; children = ( C0AE41931F788BD700682CDE /* Base */, C0AE41941F788BD700682CDE /* de */, C0AE41951F788BD700682CDE /* el */, C0AE41961F788BD700682CDE /* en */, C0AE41971F788BD700682CDE /* es */, C0AE41981F788BD700682CDE /* fr */, C0AE41991F788BD700682CDE /* it */, C0AE419A1F788BD700682CDE /* ja */, C0AE419B1F788BD700682CDE /* nl */, C0AE419C1F788BD700682CDE /* pt-PT */, C0AE419D1F788BD700682CDE /* pt */, C0AE419E1F788BD700682CDE /* ru */, C0AE419F1F788BD700682CDE /* sv */, C0AE41A01F788BD700682CDE /* th */, C0AE41A11F788BD700682CDE /* tr */, C0920A991FA82DB6008A0745 /* zh-Hant */, ); name = IASKLocalizable.strings; sourceTree = ""; }; C0BD70031B4E2BE300A5788D /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( C0BD70021B4E2BE300A5788D /* Base */, C0BD70041B4E2BE600A5788D /* ru */, C0B56BC11F51F30B00E4CA87 /* es */, C0B56BC21F51F6BC00E4CA87 /* zh-Hans-CN */, C0920A9B1FA82DB6008A0745 /* zh-Hant */, C00E61361FB18C65003AD828 /* de */, ); name = Localizable.strings; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ C0A18FAB1ABF51C200BED648 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.2; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; C0A18FAC1ABF51C200BED648 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.2; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; }; name = Release; }; C0A18FAE1ABF51C200BED648 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = C4D7E88E9BE3FC3994E59767 /* Pods-ArcBit.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = ArcBit/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; OTHER_SWIFT_FLAGS = "-D DEBUG"; PRODUCT_BUNDLE_IDENTIFIER = "com.$(PRODUCT_NAME:rfc1034identifier).app"; PRODUCT_NAME = ArcBit; PROVISIONING_PROFILE = ""; SWIFT_OBJC_BRIDGING_HEADER = "ArcBit/ArcBit-Bridging-Header.h"; SWIFT_VERSION = 3.0; }; name = Debug; }; C0A18FAF1ABF51C200BED648 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = AA36D9E0C4AFE7E2A43F3C79 /* Pods-ArcBit.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = ArcBit/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = "com.$(PRODUCT_NAME:rfc1034identifier).app"; PRODUCT_NAME = ArcBit; PROVISIONING_PROFILE = ""; SWIFT_OBJC_BRIDGING_HEADER = "ArcBit/ArcBit-Bridging-Header.h"; SWIFT_VERSION = 3.0; }; name = Release; }; C0A18FB11ABF51C200BED648 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 3C7ACA9C0834D6AF891F762C /* Pods-ArcBitTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ENABLE_MODULES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(SDKROOT)/Developer/Library/Frameworks", "$(inherited)", ); GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); INFOPLIST_FILE = ArcBitTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "ArcBit.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = ArcBitTests; SWIFT_OBJC_BRIDGING_HEADER = "ArcBitTests/ArcBitTests-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 3.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ArcBit.app/ArcBit"; }; name = Debug; }; C0A18FB21ABF51C200BED648 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 173E65BB79838FECABC37B10 /* Pods-ArcBitTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ENABLE_MODULES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(SDKROOT)/Developer/Library/Frameworks", "$(inherited)", ); INFOPLIST_FILE = ArcBitTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "ArcBit.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = ArcBitTests; SWIFT_OBJC_BRIDGING_HEADER = "ArcBitTests/ArcBitTests-Bridging-Header.h"; SWIFT_VERSION = 3.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ArcBit.app/ArcBit"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ C0A18F891ABF51C200BED648 /* Build configuration list for PBXProject "ArcBit" */ = { isa = XCConfigurationList; buildConfigurations = ( C0A18FAB1ABF51C200BED648 /* Debug */, C0A18FAC1ABF51C200BED648 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; C0A18FAD1ABF51C200BED648 /* Build configuration list for PBXNativeTarget "ArcBit" */ = { isa = XCConfigurationList; buildConfigurations = ( C0A18FAE1ABF51C200BED648 /* Debug */, C0A18FAF1ABF51C200BED648 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; C0A18FB01ABF51C200BED648 /* Build configuration list for PBXNativeTarget "ArcBitTests" */ = { isa = XCConfigurationList; buildConfigurations = ( C0A18FB11ABF51C200BED648 /* Debug */, C0A18FB21ABF51C200BED648 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = C0A18F861ABF51C200BED648 /* Project object */; } ================================================ FILE: ArcBit.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: ArcBit.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: ArcBitTests/ArcBitTests-Bridging-Header.h ================================================ // // Use this file to import your target's public headers that you would like to expose to Swift. // // // Use this file to import your target's public headers that you would like to expose to Swift. // #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import "NSMutableData+Bitcoin.h" #import #import "UIAlertController+Blocks.h" #import #import "JNKeyChain.h" #import "MBProgressHUD.h" #import #import "RNCryptor/RNCryptor.h" #import "BRKey.h" #import "BRKey+BIP38.h" #import "NSString+Base58.h" #import #import "BRTransaction.h" #import "NSData+Hash.h" #import "QREncoder.h" #import "RNEncryptor.h" #import "RNDecryptor.h" #import "NSDate-Utilities.h" #import "RNCryptor.h" #import "iToast.h" #import "AFNetworking.h" #import "SRWebSocket.h" #import "SRWebSocket+Helpers.h" //#import "SIOSocket.h" #import "CustomIOS7AlertView.h" #import "ECSlidingConstants.h" #import "IASKAppSettingsViewController.h" #import "IASKSettingsReader.h" #import "LTHPasscodeViewController.h" ================================================ FILE: ArcBitTests/ArcBitTests.swift ================================================ // // ArcBitTests.swift // ArcBitTests // // Created by Tim Lee on 3/22/15. // Copyright (c) 2015 ArcBit. All rights reserved. // import UIKit import XCTest class ArcBitTests: XCTestCase { override func setUp() { super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. } override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. super.tearDown() } func testExample() { // This is an example of a functional test case. XCTAssert(true, "Pass") } func testPerformanceExample() { // This is an example of a performance test case. self.measure() { // Put the code you want to measure the time of here. } } func testSignature() { let privKey = "4e422fb1e5e1db6c1f6ab32a7706d368ceb385e7fab098e633c5c5949c3b97cd" let challenge = "0000000000000000104424c7eda87ebd4a690b9efa09abc0ec23f2ae4c64cc4e" let key = BTCKey(privateKey: BTCDataFromHex(privKey)) let signature = key?.signature(forMessage: challenge) NSLog("signature: %@", signature!.base64EncodedString(options: NSData.Base64EncodingOptions.lineLength64Characters)) XCTAssertTrue((key?.isValidSignature(signature, forMessage: challenge))!, "") } func testStealthAddress() { let expectedStealthAddress = "vJmujDzf2PyDEcLQEQWyzVNthLpRAXqTi3ZencThu2WCzrRNi64eFYJP6ZyPWj53hSZBKTcUAk8J5Mb8rZC4wvGn77Sj4Z3yP7zE69" let expectedScanPublicKey = "02a13daf6cc5ad7a1adcae59ff348a005247aa9e84453770d0e0ee96b894f8bbb1" let scanPrivateKey = "d63e1ca7e79bafd8fdc7e568c6b3fcf8a287ad328e80376e6582af2e69943eca" let expectedSpendPublicKey = "02c55695f16cd320fef70ff6f46601cdeed655d9198d555a533382fb81a8f6eab5" let spendPrivateKey = "c4054001795dd20c740d5d1389e080b424a9ff2ec9503aa3182369f4b71f00ac" let ephemeralPublicKey = "02d53b53c3cb7d6e8f4925e404ce40ec9edd81b0b03d49da950deb3c2240ca519a" let ephemeralPrivateKey = "dc406d598685e3400a7eff2d952d47f999de9f69d5ff1295302ad7314a2cf979" let paymentAddressPublicKey = "02da20a21ac1332edd5352306104f7a751b45e52bf4a41d4c350ccb890301d80e6" let paymentAddressPrivateKey = "775c912899b27ee8a1f944c0e2ac90e095f63893d39c3d66d0dd0a854b799eb5" let isTestNet = false let stealthAddress = TLStealthAddress.createStealthAddress(expectedScanPublicKey as NSString, spendPublicKey:expectedSpendPublicKey as NSString, isTestnet:isTestNet) NSLog("stealthAddress: %@", stealthAddress) XCTAssertTrue(stealthAddress == expectedStealthAddress) let publicKeys = TLStealthAddress.getScanPublicKeyAndSpendPublicKey(stealthAddress, isTestnet:isTestNet) let scanPublicKey = publicKeys.0 let spendPublicKey = publicKeys.1 XCTAssertTrue(scanPublicKey == expectedScanPublicKey, "scanPublicKey != scanPublicKey") XCTAssertTrue(spendPublicKey == expectedSpendPublicKey, "spendPublicKey != spendPublicKey") let nonce:UInt32 = 0xdeadbeef let stealthDataScriptAndPaymentAddress = TLStealthAddress.createDataScriptAndPaymentAddress(stealthAddress, ephemeralPrivateKey:ephemeralPrivateKey, nonce:nonce, isTestnet:isTestNet) let expectedStealthDataScript = String(format:"%02x%02x%02x%x%@", BTCOpcode.OP_RETURN.rawValue, TLStealthAddress.getStealthAddressMsgSize(), TLStealthAddress.getStealthAddressTransacionVersion(), nonce, ephemeralPublicKey) XCTAssertTrue(stealthDataScriptAndPaymentAddress.0 == expectedStealthDataScript) let key = BTCKey(publicKey:paymentAddressPublicKey.hexToData()) let paymentAddress = key?.address.base58String XCTAssertTrue(stealthDataScriptAndPaymentAddress.1 == paymentAddress) NSLog("stealthDataScript: %@", stealthDataScriptAndPaymentAddress.0) NSLog("paymentAddress: %@", stealthDataScriptAndPaymentAddress.1) let stealthDataScript = stealthDataScriptAndPaymentAddress.0 let publicKey = TLStealthAddress.getPaymentAddressPublicKeyFromScript(stealthDataScript, scanPrivateKey:scanPrivateKey, spendPublicKey:spendPublicKey) XCTAssertTrue(publicKey == paymentAddressPublicKey) let secret = TLStealthAddress.getPaymentAddressPrivateKeySecretFromScript(stealthDataScript, scanPrivateKey:scanPrivateKey, spendPrivateKey:spendPrivateKey) XCTAssertTrue(secret == paymentAddressPrivateKey) XCTAssertTrue(TLStealthAddress.isStealthAddress(expectedStealthAddress, isTestnet:false)) XCTAssertTrue(!TLStealthAddress.isStealthAddress(expectedStealthAddress, isTestnet:true)) } func testStealthAddress2() { let addr = "vJmujDzf2PyDEcLQEQWyzVNthLpRAXqTi3ZencThu2WCzrRNi64eFYJP6ZyPWj53hSZBKTcUAk8J5Mb8rZC4wvGn77Sj4Z3yP7zE69" let scanPublicKey = "02a13daf6cc5ad7a1adcae59ff348a005247aa9e84453770d0e0ee96b894f8bbb1" let scanPrivateKey = "d63e1ca7e79bafd8fdc7e568c6b3fcf8a287ad328e80376e6582af2e69943eca" let spendPublicKey = "02c55695f16cd320fef70ff6f46601cdeed655d9198d555a533382fb81a8f6eab5" let spendPrivateKey = "c4054001795dd20c740d5d1389e080b424a9ff2ec9503aa3182369f4b71f00ac" let ephemeralPublicKey = "02d53b53c3cb7d6e8f4925e404ce40ec9edd81b0b03d49da950deb3c2240ca519a" let ephemeralPrivateKey = "dc406d598685e3400a7eff2d952d47f999de9f69d5ff1295302ad7314a2cf979" let paymentAddressPublicKey = "02da20a21ac1332edd5352306104f7a751b45e52bf4a41d4c350ccb890301d80e6" let paymentAddressPrivateKey = "775c912899b27ee8a1f944c0e2ac90e095f63893d39c3d66d0dd0a854b799eb5" let stealthDataScript = "6a2606deadbeef02d53b53c3cb7d6e8f4925e404ce40ec9edd81b0b03d49da950deb3c2240ca519a" let publicKey = TLStealthAddress.getPaymentAddressPublicKeyFromScript(stealthDataScript, scanPrivateKey: scanPrivateKey, spendPublicKey: spendPublicKey) XCTAssertTrue(publicKey == paymentAddressPublicKey) NSLog("publicKey: %@", publicKey!) var key = BTCKey(publicKey:publicKey!.hexToData()) NSLog("address: %@", key!.address.base58String) XCTAssertTrue(key?.address.base58String == "1C6gQ79qKKG21AGCA9USKYWPvu6LzoPH5h") let secret = TLStealthAddress.getPaymentAddressPrivateKeySecretFromScript(stealthDataScript, scanPrivateKey:scanPrivateKey, spendPrivateKey:spendPrivateKey) NSLog("secret: %@", secret!) key = BTCKey(privateKey: BTCDataFromHex(secret)) key?.isPublicKeyCompressed = true NSLog("address: %@", key!.address.base58String) XCTAssertTrue(secret == paymentAddressPrivateKey) XCTAssertTrue(key?.address.base58String == "1C6gQ79qKKG21AGCA9USKYWPvu6LzoPH5h") let nonce:UInt32 = 0xdeadbeef let stealthDataScriptAndPaymentAddress = TLStealthAddress.createDataScriptAndPaymentAddress(addr, ephemeralPrivateKey: ephemeralPrivateKey, nonce: nonce, isTestnet: false) NSLog("stealthDataScript: %@", stealthDataScriptAndPaymentAddress.0) NSLog("paymentAddress: %@", stealthDataScriptAndPaymentAddress.1) } func testEncryptionAndDecryption() { NSLog("testEncryptionAndDecryption") var plainText = "test" let pbk = UInt32(2000) var cipherText = TLCrypto.encrypt(plainText, password:"pass", PBKDF2Iterations:pbk) var decryptedText = TLCrypto.decrypt(cipherText, password:"pass", PBKDF2Iterations:pbk) NSLog("decryptedText: %@", decryptedText!) XCTAssert(plainText == decryptedText) plainText = "test" cipherText = TLCrypto.encrypt("test", password:"pass") decryptedText = TLCrypto.decrypt(cipherText, password:"pass") XCTAssert(plainText == decryptedText) plainText = "test" cipherText = TLCrypto.encrypt("test", password:"pass1", PBKDF2Iterations:pbk) decryptedText = TLCrypto.decrypt(cipherText, password:"pass2", PBKDF2Iterations:pbk) XCTAssert(decryptedText == nil) plainText = "test" cipherText = TLCrypto.encrypt("test", password:"pass", PBKDF2Iterations:pbk) decryptedText = TLCrypto.decrypt(cipherText, password:"pass", PBKDF2Iterations:UInt32(1000)) XCTAssert(true) } func testHDWallet() { NSLog("testHDWallet") XCTAssertTrue(TLHDWalletWrapper.isValidExtendedPrivateKey("xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U")) XCTAssertTrue(!TLHDWalletWrapper.isValidExtendedPrivateKey("xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB")) XCTAssertTrue(TLHDWalletWrapper.isValidExtendedPublicKey("xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB")) XCTAssertTrue(!TLHDWalletWrapper.isValidExtendedPublicKey("xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U")) XCTAssertTrue(!TLHDWalletWrapper.isValidExtendedPrivateKey("I'm sorry, Dave. I'm afraid I can't do that")) XCTAssertTrue(!TLHDWalletWrapper.isValidExtendedPublicKey("I'm sorry, Dave. I'm afraid I can't do that")) XCTAssertTrue(!TLHDWalletWrapper.phraseIsValid("report age service frame aspect worry nature toward vendor jungle grit grit")) let backupPassphrase = "slogan lottery zone helmet fatigue rebuild solve best hint frown conduct ill" let masterHex = TLHDWalletWrapper.getMasterHex(backupPassphrase) XCTAssertTrue(TLHDWalletWrapper.phraseIsValid(backupPassphrase)) NSLog("masterHex: %@", masterHex) XCTAssertTrue(masterHex == "ae3ff5936bf70293eda11b5ea5ee9585fe9b22c9a80b610ee37251a22120e970c75a18bbd95219a0348c7dee40eeb44a4d2480900be8f931d0cf85203f9d94ce") let extendPrivKey = TLHDWalletWrapper.getExtendPrivKey(masterHex, accountIdx:0) NSLog("extendPrivKey: %@", extendPrivKey) XCTAssertTrue("xprv9z2LgaTwJsrjcHqwG9ZFManHWbiUQqwSMYdMvDN4Pr8i7sVf3x8Us9JSQ8FFCT8f7wBDzEVEhTFX3wJdNx2pchEZJ2HNTa4U7NKgM9uWoK6" == extendPrivKey) let extendPubKey = TLHDWalletWrapper.getExtendPubKey(extendPrivKey) NSLog("extendPubKey: %@", extendPubKey) XCTAssertTrue("xpub6D1h65zq9FR2pmvQNB6Fiij24dYxpJfHimYxibmfxBfgzfpobVSjQwcvFPr7pTATRisprc2YwYYWiysUEvJ1u9iuAQKMNsiLn2PPSrtVFt6" == extendPubKey) let walletConfig = TLWalletConfig(isTestnet: false) let mainAddressIndex0 = [0,0] let mainAddress0 = TLHDWalletWrapper.getAddress(extendPubKey, sequence:mainAddressIndex0 as NSArray, isTestnet:walletConfig.isTestnet) NSLog("mainAddress0: %@", mainAddress0) XCTAssertTrue("1K7fXZeeQydcUvbsfvkMSQmiacV5sKRYQz" == mainAddress0) TLHDWalletWrapper.getPrivateKey(extendPrivKey as NSString, sequence:mainAddressIndex0 as NSArray, isTestnet:walletConfig.isTestnet) let mainPrivKey0 = TLHDWalletWrapper.getPrivateKey(extendPrivKey as NSString, sequence:mainAddressIndex0 as NSArray, isTestnet:walletConfig.isTestnet) NSLog("mainPrivKey0: %@", mainPrivKey0) XCTAssertTrue("KwJhkmrjjg3AEX5gvccNAHCDcXnQLwzyZshnp5yK7vXz1mHKqDDq" == mainPrivKey0) let mainAddressIndex1 = [0,1] let mainAddress1 = TLHDWalletWrapper.getAddress(extendPubKey, sequence:mainAddressIndex1 as NSArray, isTestnet:walletConfig.isTestnet) NSLog("mainAddress1: %@", mainAddress1) XCTAssertTrue("12eQLjACXw6XwfGF9kqBwy9U7Se8qGoBuq" == mainAddress1) TLHDWalletWrapper.getPrivateKey(extendPrivKey as NSString, sequence:mainAddressIndex0 as NSArray, isTestnet:walletConfig.isTestnet) let mainPrivKey1 = TLHDWalletWrapper.getPrivateKey(extendPrivKey as NSString, sequence:mainAddressIndex1 as NSArray, isTestnet:walletConfig.isTestnet) NSLog("mainPrivKey1: %@", mainPrivKey1) XCTAssertTrue("KwpCsb3wBGk7E1M9EXcZWZhRoKBoZLNc63RsSP4YspUR53Ndefyr" == mainPrivKey1) let changeAddressIndex0 = [1,0] let changeAddress0 = TLHDWalletWrapper.getAddress(extendPubKey, sequence:changeAddressIndex0 as NSArray, isTestnet:walletConfig.isTestnet) NSLog("changeAddress0: %@", changeAddress0) XCTAssertTrue("1CvpGn9VxVY1nsWWL3MSWRYaBHdNkCDbmv" == changeAddress0) TLHDWalletWrapper.getPrivateKey(extendPrivKey as NSString, sequence:changeAddressIndex0 as NSArray, isTestnet:walletConfig.isTestnet) let changePrivKey0 = TLHDWalletWrapper.getPrivateKey(extendPrivKey as NSString, sequence:changeAddressIndex0 as NSArray, isTestnet:walletConfig.isTestnet) NSLog("changePrivKey0: %@", changePrivKey0) XCTAssertTrue("L33guNrQHMXdpFd9jpjo2mQzddwLUgUrNzK3KqAM83D9ZU1H5NDN" == changePrivKey0) let changeAddressIndex1 = [1,1] let changeAddress1 = TLHDWalletWrapper.getAddress(extendPubKey, sequence:changeAddressIndex1 as NSArray, isTestnet:walletConfig.isTestnet) NSLog("changeAddress1: %@", changeAddress1) XCTAssertTrue("17vnH8d1fBbjX7GZx727X2Y6dheaid2NUR" == changeAddress1) TLHDWalletWrapper.getPrivateKey(extendPrivKey as NSString, sequence:changeAddressIndex1 as NSArray, isTestnet:walletConfig.isTestnet) let changePrivKey1 = TLHDWalletWrapper.getPrivateKey(extendPrivKey as NSString, sequence:changeAddressIndex1 as NSArray, isTestnet:walletConfig.isTestnet) NSLog("changePrivKey1: %@", changePrivKey1) XCTAssertTrue("KwiMiFtWv1PXNN3zV67TC59tWJxPbeagMJU1SSr3uLssAC82UKhf" == changePrivKey1) } func testUtils() { NSLog("testUtils") let txid = "2c441ba4920f03f37866edb5647f2626b64f57ad98b0a8e011af07da0aefcec3" let txHash = TLWalletUtils.reverseHexString(txid) NSLog("txHash: %@", txHash) XCTAssertTrue(txHash == "c3ceef0ada07af11e0a8b098ad574fb626267f64b5ed6678f3030f92a41b442c") let address = TLCoreBitcoinWrapper.getAddressFromOutputScript("76a9147ab89f9fae3f8043dcee5f7b5467a0f0a6e2f7e188ac", isTestnet: false) NSLog("address: %@", address!) XCTAssertTrue(address == "1CBtcGivXmHQ8ZqdPgeMfcpQNJrqTrSAcG") } func testCreateSignedSerializeTransactionHex() { NSLog("testCreateSignedSerializeTransactionHex") let hash = TLWalletUtils.hexStringToData(TLWalletUtils.reverseHexString("935c6975aa65f95cb55616ace8c8bede83b010f7191c0a6d385be1c95992870d"))! let script = TLWalletUtils.hexStringToData("76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac")! let address = "1F3sAm6ZtwLAUnj7d38pGFxtP3RVEvtsbV" let privateKey = "L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1" let txHexAndTxHash = TLCoreBitcoinWrapper.createSignedSerializedTransactionHex([hash], inputIndexes:[0], inputScripts:[script], outputAddresses:[address], outputAmounts:[2500000], privateKeys:[privateKey], outputScripts:nil, isTestnet: false)! let txHex = txHexAndTxHash.object(forKey: "txHex") as! String let txHash = txHexAndTxHash.object(forKey: "txHash") as! String let txSize = txHexAndTxHash.object(forKey: "txSize") as! NSNumber NSLog("txHash: %@", txHash) NSLog("txHex: %@", txHex) NSLog("txSize: %@", txSize) XCTAssertTrue("121d274734c83488e2bd6a2a3a136823d6099bf5a3517f78931c3ed0b9a2c619" == txHash) XCTAssertTrue("01000000010d879259c9e15b386d0a1c19f710b083debec8e8ac1656b55cf965aa75695c93000000006b4830450221009ceebee12f7a6321e39e83a0d0f8ba3db33271439e98addbc2c8518e9dd4d4ab022061965b500a9b1dd154545df086c3cc44661265841c82a4db20c44304711f1a0a012103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdffffffff01a0252600000000001976a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac00000000" == txHex) XCTAssertTrue(txSize.uintValue == 193) } func testCreateSignedSerializedTransactionHexAndBIP69() { NSLog("testCreateSignedSerializedTransactionHexAndBIP69") var mockWalletPayload = NSMutableDictionary() mockWalletPayload.setObject("1", forKey: "version" as NSCopying) var wallets = NSMutableArray() var wallet = NSMutableDictionary() var imports = NSMutableDictionary() imports.setObject(NSMutableArray(), forKey: "imported_accounts" as NSCopying) imports.setObject(NSMutableArray(), forKey: "imported_private_keys" as NSCopying) imports.setObject(NSMutableArray(), forKey: "watch_only_accounts" as NSCopying) imports.setObject(NSMutableArray(), forKey: "watch_only_addresses" as NSCopying) var backupPassphrase = "slogan lottery zone helmet fatigue rebuild solve best hint frown conduct ill" let masterHex = TLHDWalletWrapper.getMasterHex(backupPassphrase) let walletConfig = TLWalletConfig(isTestnet: false) let extendPrivKey = TLHDWalletWrapper.getExtendPrivKey(masterHex, accountIdx:0) let extendPubKey = TLHDWalletWrapper.getExtendPubKey(extendPrivKey) let mainAddressIndex0 = [0,0] let mainAddress0 = TLHDWalletWrapper.getAddress(extendPubKey, sequence:mainAddressIndex0 as NSArray, isTestnet:walletConfig.isTestnet) let fromAddress = BTCAddress(base58String: mainAddress0) XCTAssertTrue("1K7fXZeeQydcUvbsfvkMSQmiacV5sKRYQz" == mainAddress0) let changeAddressIndex0 = [1,0] let changeAddress0 = TLHDWalletWrapper.getAddress(extendPubKey, sequence:changeAddressIndex0 as NSArray, isTestnet:walletConfig.isTestnet) XCTAssertTrue("1CvpGn9VxVY1nsWWL3MSWRYaBHdNkCDbmv" == changeAddress0) var hdWallets = NSMutableArray() var hdWallet = NSMutableDictionary() hdWallet.setObject(0, forKey: "current_account_id" as NSCopying) hdWallet.setObject(0, forKey: "master_hex" as NSCopying) hdWallet.setObject(1, forKey: "max_account_id_created" as NSCopying) hdWallet.setObject("default", forKey: "name" as NSCopying) hdWallet.setObject(backupPassphrase, forKey: "passphrase" as NSCopying) var accounts = NSMutableArray() var accountDict = NSMutableDictionary() accountDict.setObject(0, forKey: "account_idx" as NSCopying) accountDict.setObject(extendPubKey, forKey: "xpub" as NSCopying) accountDict.setObject(extendPrivKey, forKey: "xpriv" as NSCopying) var changeAdresses = NSMutableArray() var changeAdress = NSMutableDictionary() changeAdress.setObject(changeAddress0, forKey: "address" as NSCopying) changeAdress.setObject(0, forKey: "index" as NSCopying) changeAdress.setObject(1, forKey: "status" as NSCopying) changeAdresses.add(changeAdress) accountDict.setObject(changeAdresses, forKey: "change_addresses" as NSCopying) var mainAddresses = NSMutableArray() var mainAddress = NSMutableDictionary() mainAddress.setObject(mainAddress0, forKey: "address" as NSCopying) mainAddress.setObject(0, forKey: "index" as NSCopying) mainAddress.setObject(1, forKey: "status" as NSCopying) mainAddresses.add(mainAddress) accountDict.setObject(mainAddresses, forKey: "main_addresses" as NSCopying) accountDict.setObject(0, forKey: "min_change_address_vidx" as NSCopying) accountDict.setObject(0, forKey: "min_main_address_idx" as NSCopying) accountDict.setObject("Account 1", forKey: "name" as NSCopying) accountDict.setObject(0, forKey: "needs_recovering" as NSCopying) accountDict.setObject(1, forKey: "status" as NSCopying) var stealthAddresses = NSMutableArray() var stealthAddress = NSMutableDictionary() stealthAddress.setObject(0, forKey: "last_tx_time" as NSCopying) stealthAddress.setObject(NSMutableArray(), forKey: "payments" as NSCopying) stealthAddress.setObject("NOTUSED", forKey: "scan_key" as NSCopying) var servers = NSMutableDictionary() var watching = NSMutableDictionary() watching.setObject(1, forKey: "watching" as NSCopying) servers.setObject(watching, forKey: "www.arcbit.net" as NSCopying) stealthAddress.setObject(servers, forKey: "servers" as NSCopying) stealthAddress.setObject("NOTUSED", forKey: "spend_key" as NSCopying) stealthAddress.setObject("NOTUSED", forKey: "stealth_address" as NSCopying) stealthAddresses.add(stealthAddress) accountDict.setObject(stealthAddresses, forKey: "stealth_addresses" as NSCopying) accounts.add(accountDict) accountDict.setObject(extendPrivKey, forKey: "xprv" as NSCopying) accountDict.setObject(extendPubKey, forKey: "xpub" as NSCopying) hdWallet.setObject(accounts, forKey: "accounts" as NSCopying) hdWallets.add(hdWallet) wallet.setObject(hdWallets, forKey: "hd_wallets" as NSCopying) wallet.setObject(NSMutableArray(), forKey: "address_book" as NSCopying) wallet.setObject(imports, forKey: "imports" as NSCopying) wallet.setObject(NSMutableArray(), forKey: "tx_tags" as NSCopying) wallets.add(wallet) var payload = NSMutableDictionary() payload.setObject(wallets, forKey: "wallets" as NSCopying) mockWalletPayload.setObject(payload, forKey: "payload" as NSCopying) let appWallet = TLWallet(walletName: "Test Wallet", walletConfig: walletConfig) let godSend = TLSpaghettiGodSend(appWallet: appWallet) appWallet.loadWalletPayload(mockWalletPayload, masterHex:masterHex) let accountsArray = appWallet.getAccountObjectArray() let accountObject = accountsArray.object(at: 0) as! TLAccountObject godSend.setOnlyFromAccount(accountObject) let mockUnspentOutput = { (txid: String, value: UInt64, txOutputN: Int) -> NSDictionary in var unspentOutput = NSMutableDictionary() unspentOutput.setObject(TLWalletUtils.reverseHexString(txid), forKey: "tx_hash" as NSCopying) unspentOutput.setObject(txid, forKey: "tx_hash_big_endian" as NSCopying) unspentOutput.setObject(txOutputN, forKey: "tx_output_n" as NSCopying) unspentOutput.setObject(BTCScript(address: fromAddress).hex, forKey: "script" as NSCopying) unspentOutput.setObject(NSNumber(value: value as UInt64), forKey: "value" as NSCopying) unspentOutput.setObject(6, forKey: "confirmations" as NSCopying) return unspentOutput } func testCreateSignedSerializedTransactionHexAndBIP69_1() -> () { let feeAmount = TLCoin(bitcoinAmount: "0.00000", bitcoinDenomination: TLBitcoinDenomination.bitcoin) let toAddress = "1KAD5EnzzLtrSo2Da2G4zzD7uZrjk8zRAv" let toAddress2 = "1DZTzaBHUDM7T3QvUKBz4qXMRpkg8jsfB5" let toAmount = TLCoin(bitcoinAmount: "1", bitcoinDenomination: TLBitcoinDenomination.bitcoin) let toAmount2 = TLCoin(bitcoinAmount: "24", bitcoinDenomination: TLBitcoinDenomination.bitcoin) let txid0 = "35288d269cee1941eaebb2ea85e32b42cdb2b04284a56d8b14dcc3f5c65d6055" let txid1 = "35288d269cee1941eaebb2ea85e32b42cdb2b04284a56d8b14dcc3f5c65d6055" let unspentOutput0 = mockUnspentOutput(txid0, 100000000, 0) let unspentOutput1 = mockUnspentOutput(txid1, 2400000000, 1) func testCreateSignedSerializedTransactionHexAndBIP69_1_1() -> () { let toAddressesAndAmounts = [["address": toAddress, "amount": toAmount], ["address": toAddress2, "amount": toAmount2]] accountObject.unspentOutputs = NSMutableArray(capacity: 2) accountObject.unspentOutputs!.add(unspentOutput0) accountObject.unspentOutputs!.add(unspentOutput1) accountObject.stealthPaymentUnspentOutputs = NSMutableArray(capacity: 0) let ret = godSend.createSignedSerializedTransactionHex(toAddressesAndAmounts as NSArray, feeAmount: feeAmount, error: { (data: String?) in }) let txHexAndTxHash = ret.0 let realToAddresses = ret.1 let txHex = txHexAndTxHash!.object(forKey: "txHex") as! String let txHash = txHexAndTxHash!.object(forKey: "txHash") as! String let txSize = txHexAndTxHash!.object(forKey: "txSize") as! NSNumber XCTAssertTrue(txHash == "fbacfede55dc6a779782ba8fa22813860b7ef07d82c3abebb8f290b3141bf965") XCTAssertTrue(txHex == "010000000255605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d2835000000006a4730440220449b1f95687bf469fb954bcdbbc0ae362fe9bd6ba88c5b4dd227d9a5c37eb82a02203440bf6b4178786913a197344d0999a7d98d246099dcddf4bf9b24473a4e7a9a0121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff55605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d2835010000006a47304402201f6a4a87d0584157471210c1e126e64e52f565e950feb80045fc855829df3da4022059fd75fe51262aa7b7f214534357ed2786a9b3dcb12493112027711aebc8478a0121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff0200e1f505000000001976a914c73015fa62d972ebb3b241fe8c936657b13fabd788ac00180d8f000000001976a91489c55a3ca6676c9f7f260a6439c83249b747380288ac00000000") XCTAssertTrue(txSize.uintValue == 376) let transaction = BTCTransaction(hex: txHex) XCTAssertTrue(transaction?.inputs.count == 2) let input0 = transaction?.inputs[0] as! BTCTransactionInput XCTAssertTrue(input0.previousTransactionID == txid0) XCTAssertTrue(input0.outpoint.index == 0) let input1 = transaction?.inputs[1] as! BTCTransactionInput XCTAssertTrue(input1.previousTransactionID == txid1) XCTAssertTrue(input1.outpoint.index == 1) XCTAssertTrue(transaction?.outputs.count == 2) let output0 = transaction?.outputs[0] as! BTCTransactionOutput XCTAssertTrue(output0.script.hex == "76a914c73015fa62d972ebb3b241fe8c936657b13fabd788ac") XCTAssertTrue(output0.value == 100000000) XCTAssertTrue(output0.script.standardAddress.base58String == "1KAD5EnzzLtrSo2Da2G4zzD7uZrjk8zRAv") let output1 = transaction?.outputs[1] as! BTCTransactionOutput XCTAssertTrue(output1.script.hex == "76a91489c55a3ca6676c9f7f260a6439c83249b747380288ac") XCTAssertTrue(output1.value == 2400000000) XCTAssertTrue(output1.script.standardAddress.base58String == "1DZTzaBHUDM7T3QvUKBz4qXMRpkg8jsfB5") XCTAssertTrue(realToAddresses.count == 2) XCTAssertTrue(realToAddresses[0] == "1KAD5EnzzLtrSo2Da2G4zzD7uZrjk8zRAv") XCTAssertTrue(realToAddresses[1] == "1DZTzaBHUDM7T3QvUKBz4qXMRpkg8jsfB5") } func testCreateSignedSerializedTransactionHexAndBIP69_1_2() -> () { let toAddressesAndAmounts = [["address": toAddress2, "amount": toAmount2], ["address": toAddress, "amount": toAmount]] accountObject.unspentOutputs = NSMutableArray(capacity: 2) accountObject.unspentOutputs!.add(unspentOutput1) accountObject.unspentOutputs!.add(unspentOutput0) accountObject.stealthPaymentUnspentOutputs = NSMutableArray(capacity: 0) let ret = godSend.createSignedSerializedTransactionHex(toAddressesAndAmounts as NSArray, feeAmount: feeAmount, error: { (data: String?) in }) let txHexAndTxHash = ret.0 let realToAddresses = ret.1 let txHex = txHexAndTxHash!.object(forKey: "txHex") as! String let txHash = txHexAndTxHash!.object(forKey: "txHash") as! String let txSize = txHexAndTxHash!.object(forKey: "txSize") as! NSNumber XCTAssertTrue(txHash == "fbacfede55dc6a779782ba8fa22813860b7ef07d82c3abebb8f290b3141bf965") XCTAssertTrue(txHex == "010000000255605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d2835000000006a4730440220449b1f95687bf469fb954bcdbbc0ae362fe9bd6ba88c5b4dd227d9a5c37eb82a02203440bf6b4178786913a197344d0999a7d98d246099dcddf4bf9b24473a4e7a9a0121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff55605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d2835010000006a47304402201f6a4a87d0584157471210c1e126e64e52f565e950feb80045fc855829df3da4022059fd75fe51262aa7b7f214534357ed2786a9b3dcb12493112027711aebc8478a0121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff0200e1f505000000001976a914c73015fa62d972ebb3b241fe8c936657b13fabd788ac00180d8f000000001976a91489c55a3ca6676c9f7f260a6439c83249b747380288ac00000000") XCTAssertTrue(txSize.uintValue == 376) let transaction = BTCTransaction(hex: txHex) XCTAssertTrue(transaction?.inputs.count == 2) let input0 = transaction?.inputs[0] as! BTCTransactionInput XCTAssertTrue(input0.previousTransactionID == txid0) XCTAssertTrue(input0.outpoint.index == 0) let input1 = transaction?.inputs[1] as! BTCTransactionInput XCTAssertTrue(input1.previousTransactionID == txid1) XCTAssertTrue(input1.outpoint.index == 1) XCTAssertTrue(transaction?.outputs.count == 2) let output0 = transaction?.outputs[0] as! BTCTransactionOutput XCTAssertTrue(output0.script.hex == "76a914c73015fa62d972ebb3b241fe8c936657b13fabd788ac") XCTAssertTrue(output0.value == 100000000) XCTAssertTrue(output0.script.standardAddress.base58String == "1KAD5EnzzLtrSo2Da2G4zzD7uZrjk8zRAv") let output1 = transaction?.outputs[1] as! BTCTransactionOutput XCTAssertTrue(output1.script.hex == "76a91489c55a3ca6676c9f7f260a6439c83249b747380288ac") XCTAssertTrue(output1.value == 2400000000) XCTAssertTrue(output1.script.standardAddress.base58String == "1DZTzaBHUDM7T3QvUKBz4qXMRpkg8jsfB5") XCTAssertTrue(realToAddresses.count == 2) XCTAssertTrue(realToAddresses[0] == "1DZTzaBHUDM7T3QvUKBz4qXMRpkg8jsfB5") XCTAssertTrue(realToAddresses[1] == "1KAD5EnzzLtrSo2Da2G4zzD7uZrjk8zRAv") } testCreateSignedSerializedTransactionHexAndBIP69_1_1() testCreateSignedSerializedTransactionHexAndBIP69_1_2() } func testCreateSignedSerializedTransactionHexAndBIP69_2() -> () { let feeAmount = TLCoin(bitcoinAmount: "0.00002735", bitcoinDenomination: TLBitcoinDenomination.bitcoin) let toAddress = "17nFgS1YaDPnXKMPQkZVdNQqZnVqRgBwnZ" let toAddress2 = "19Nrc2Xm226xmSbeGZ1BVtX7DUm4oCx8Pm" let toAmount = TLCoin(bitcoinAmount: "4.00057456", bitcoinDenomination: TLBitcoinDenomination.bitcoin) let toAmount2 = TLCoin(bitcoinAmount: "400", bitcoinDenomination: TLBitcoinDenomination.bitcoin) let txid0 = "0e53ec5dfb2cb8a71fec32dc9a634a35b7e24799295ddd5278217822e0b31f57" let txid1 = "26aa6e6d8b9e49bb0630aac301db6757c02e3619feb4ee0eea81eb1672947024" let txid2 = "28e0fdd185542f2c6ea19030b0796051e7772b6026dd5ddccd7a2f93b73e6fc2" let txid3 = "381de9b9ae1a94d9c17f6a08ef9d341a5ce29e2e60c36a52d333ff6203e58d5d" let txid4 = "3b8b2f8efceb60ba78ca8bba206a137f14cb5ea4035e761ee204302d46b98de2" let txid5 = "402b2c02411720bf409eff60d05adad684f135838962823f3614cc657dd7bc0a" let txid6 = "54ffff182965ed0957dba1239c27164ace5a73c9b62a660c74b7b7f15ff61e7a" let txid7 = "643e5f4e66373a57251fb173151e838ccd27d279aca882997e005016bb53d5aa" let txid8 = "6c1d56f31b2de4bfc6aaea28396b333102b1f600da9c6d6149e96ca43f1102b1" let txid9 = "7a1de137cbafb5c70405455c49c5104ca3057a1f1243e6563bb9245c9c88c191" let txid10 = "7d037ceb2ee0dc03e82f17be7935d238b35d1deabf953a892a4507bfbeeb3ba4" let txid11 = "a5e899dddb28776ea9ddac0a502316d53a4a3fca607c72f66c470e0412e34086" let txid12 = "b4112b8f900a7ca0c8b0e7c4dfad35c6be5f6be46b3458974988e1cdb2fa61b8" let txid13 = "bafd65e3c7f3f9fdfdc1ddb026131b278c3be1af90a4a6ffa78c4658f9ec0c85" let txid14 = "de0411a1e97484a2804ff1dbde260ac19de841bebad1880c782941aca883b4e9" let txid15 = "f0a130a84912d03c1d284974f563c5949ac13f8342b8112edff52971599e6a45" let txid16 = "f320832a9d2e2452af63154bc687493484a0e7745ebd3aaf9ca19eb80834ad60" let unspentOutput0 = mockUnspentOutput(txid0, 2529937904, 0) let unspentOutput1 = mockUnspentOutput(txid1, 2521656792, 1) let unspentOutput2 = mockUnspentOutput(txid2, 2509683086, 0) let unspentOutput3 = mockUnspentOutput(txid3, 2506060377, 1) let unspentOutput4 = mockUnspentOutput(txid4, 2510645247, 0) let unspentOutput5 = mockUnspentOutput(txid5, 2502325820, 1) let unspentOutput6 = mockUnspentOutput(txid6, 2525953727, 1) let unspentOutput7 = mockUnspentOutput(txid7, 2507302856, 0) let unspentOutput8 = mockUnspentOutput(txid8, 2534185804, 1) let unspentOutput9 = mockUnspentOutput(txid9, 136219905, 0) let unspentOutput10 = mockUnspentOutput(txid10, 2502901118, 1) let unspentOutput11 = mockUnspentOutput(txid11, 2527569363, 0) let unspentOutput12 = mockUnspentOutput(txid12, 2516268302, 0) let unspentOutput13 = mockUnspentOutput(txid13, 2521794404, 0) let unspentOutput14 = mockUnspentOutput(txid14, 2520533680, 1) let unspentOutput15 = mockUnspentOutput(txid15, 2513840095, 0) let unspentOutput16 = mockUnspentOutput(txid16, 2513181711, 0) func testCreateSignedSerializedTransactionHexAndBIP69_2_1() -> () { let toAddressesAndAmounts = [["address": toAddress, "amount": toAmount], ["address": toAddress2, "amount": toAmount2]] accountObject.unspentOutputs = NSMutableArray(capacity: 17) accountObject.unspentOutputs!.add(unspentOutput0) accountObject.unspentOutputs!.add(unspentOutput1) accountObject.unspentOutputs!.add(unspentOutput2) accountObject.unspentOutputs!.add(unspentOutput3) accountObject.unspentOutputs!.add(unspentOutput4) accountObject.unspentOutputs!.add(unspentOutput5) accountObject.unspentOutputs!.add(unspentOutput6) accountObject.unspentOutputs!.add(unspentOutput7) accountObject.unspentOutputs!.add(unspentOutput8) accountObject.unspentOutputs!.add(unspentOutput9) accountObject.unspentOutputs!.add(unspentOutput10) accountObject.unspentOutputs!.add(unspentOutput11) accountObject.unspentOutputs!.add(unspentOutput12) accountObject.unspentOutputs!.add(unspentOutput13) accountObject.unspentOutputs!.add(unspentOutput14) accountObject.unspentOutputs!.add(unspentOutput15) accountObject.unspentOutputs!.add(unspentOutput16) accountObject.stealthPaymentUnspentOutputs = NSMutableArray(capacity: 0) let ret = godSend.createSignedSerializedTransactionHex(toAddressesAndAmounts as NSArray, feeAmount: feeAmount, error: { (data: String?) in }) let txHexAndTxHash = ret.0 let realToAddresses = ret.1 let txHex = txHexAndTxHash!.object(forKey: "txHex") as! String let txHash = txHexAndTxHash!.object(forKey: "txHash") as! String let txSize = txHexAndTxHash!.object(forKey: "txSize") as! NSNumber XCTAssertTrue(txHash == "0656add012962ef3bdd11eaf88347b78a2c4adb08fe8b95f79a8b8a4fe862132") XCTAssertTrue(txHex == "0100000011571fb3e02278217852dd5d299947e2b7354a639adc32ec1fa7b82cfb5dec530e000000006b483045022100b28348624779833117dc8ae73bcb649528ad6edf9d5b48018c4488dbc9b9fa3702201f8b0e1707bdfa3438d6c1353b62e3a01cb0b7b4ee5e7ef93e7b2f563ead66a30121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff2470947216eb81ea0eeeb4fe19362ec05767db01c3aa3006bb499e8b6d6eaa26010000006a4730440220679db98b1e5b17a57acc78e7271c357130fd8b6d8d2072880429d05630c5cc2802205fb88f764053185d610ae8041907bdb85f711a51f5602bb663b744e786fd78700121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffffc26f3eb7932f7acddc5ddd26602b77e7516079b03090a16e2c2f5485d1fde028000000006a47304402205031699fc96af02637f1ed7120c0e380f65370824f6af5cd37baf391f8188f73022026f5ba7a7f31fc3590f1e4dce50f12cc2122ce3fe30d187f11c3922ce3b22d0a0121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff5d8de50362ff33d3526ac3602e9ee25c1a349def086a7fc1d9941aaeb9e91d38010000006b483045022100fefda0743cc428b17e688c65d226e899af8b0d5a6f05d0944f9c67257fa5a15a02207baa0a95d88b98b0b669cab8342cc43bc992daf3be41c4ba40de77453ed3fb220121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffffe28db9462d3004e21e765e03a45ecb147f136a20ba8bca78ba60ebfc8e2f8b3b000000006b483045022100a54a4e0a3b476c855273a0aa6d97f5995e78a83cad59c28cda786b49a14f370602202cce0aadf128986b6448ad7f3288f95c9c5467ba01d16e94d807db587e481fe30121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff0abcd77d65cc14363f8262898335f184d6da5ad060ff9e40bf201741022c2b40010000006a4730440220636b2a05ef164457c9b8ee0f364c308a7ef8a0f5f7b01d6633ace40803a6fd7902205f052f39e940d2b8d797a5259ee35d0596a0dbfd199799722d95a895308bd1f10121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff7a1ef65ff1b7b7740c662ab6c9735ace4a16279c23a1db5709ed652918ffff54010000006b4830450221008e3f42e8e5d45712efe14c17ba199724e1d2bcaa2a459ede155b2df89d1b8c7902205260062b1eb6595a43180f0b40307467f4fe2f67138c2aed47d21dac739f4a770121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffffaad553bb1650007e9982a8ac79d227cd8c831e1573b11f25573a37664e5f3e64000000006a47304402204b3a8b40ea4bd092ce05ae5a55704d98ceee485b87e9d9bbc1dcc0956a2230bb022043b2660c1513b029038a3f2492c2d0d39b45c04a14b1143ede43abb6832d6f910121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffffb102113fa46ce949616d9cda00f6b10231336b3928eaaac6bfe42d1bf3561d6c010000006a4730440220651a1d62ba88ac05790bab2ead82483e99a748965cd8f1887c943c62c67786de022002bc57e36c7668c8e5d4c02e1baed3491b30d91e53633b807ca950b8bfad6fe90121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff91c1889c5c24b93b56e643121f7a05a34c10c5495c450504c7b5afcb37e11d7a000000006b483045022100ea05603d2944228bd2231354b1a6e6d106a803d7d52e8a290232b9769629c9a502204125264bb9d2cfd2db5455044d58a7b8ea8e0c7c88870def8c0fac2c559c5cfc0121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffffa43bebbebf07452a893a95bfea1d5db338d23579be172fe803dce02eeb7c037d010000006b483045022100fe8ce378ed72b0829805dde89cb16d2f6722f8b4128ee07f6ef064eaa4b607f702206400a9816e120e3e7427f4e83518b2822f010c219bcb758560ff280aae1cbe420121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff8640e312040e476cf6727c60ca3f4a3ad51623500aacdda96e7728dbdd99e8a5000000006a47304402203cce0a54d3314decb5adf1d9dbe5f887eb779daf6d4d2ce463435181da6cc72302206104213df446b9fc78d598c8425c82ca9b0952fab48a9edecb382ee5e68c11fe0121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffffb861fab2cde188499758346be46b5fbec635addfc4e7b0c8a07c0a908f2b11b4000000006a4730440220120d5d6672695c9ad3e72049da00be123bab74971386953b38409ff52989ce2502202b8702ba2d7fe90fc35aef52585c664865ae746d9e4242955b7ece22d89ad1ca0121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff850cecf958468ca7ffa6a490afe13b8c271b1326b0ddc1fdfdf9f3c7e365fdba000000006a4730440220282a14909d8ed766441c4766a574af0fc20e1b587e545e1943429670457b959602207e4c7503ae3c9de83865d0972fbb18cd7c9630023347d6eba45963f0670ef7e70121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffffe9b483a8ac4129780c88d1babe41e89dc10a26dedbf14f80a28474e9a11104de010000006b483045022100ec3744090e0603690319768ef234071410224230cc32e4731e50f9e8a05a6a5802203a1a8f7380e83fd1b61f4fd775aa6410d2e87d018b820aab4e98e1a8de0715f10121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff456a9e597129f5df2e11b842833fc19a94c563f57449281d3cd01249a830a1f0000000006a473044022060b06dcb2550beb9dd4181a45566ccf0ba41040d9003aa83c02fb63c4f9cbd8a02203c577c070c69382cfb05c74275590e3de6dcb77ce929b0d771f314ffa889f47c0121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff60ad3408b89ea19caf3abd5e74e7a084344987c64b1563af52242e9d2a8320f3000000006b483045022100d7b08a358ce19469d369765c38d1f17ffe66151f7c9cd85757d89d4f218a9d390220418f13a4b8eb41ca471901f1ba2b1677166f90127b65b7417ad5a62d76f0b8c80121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff027064d817000000001976a9144a5fba237213a062f6f57978f796390bdcf8d01588ac00902f50090000001976a9145be32612930b8323add2212a4ec03c1562084f8488ac00000000") XCTAssertTrue(txSize.uintValue == 2611) let transaction = BTCTransaction(hex: txHex) XCTAssertTrue(transaction?.inputs.count == 17) let input0 = transaction?.inputs[0] as! BTCTransactionInput XCTAssertTrue(input0.previousTransactionID == txid0) XCTAssertTrue(input0.outpoint.index == 0) let input1 = transaction?.inputs[1] as! BTCTransactionInput XCTAssertTrue(input1.previousTransactionID == txid1) XCTAssertTrue(input1.outpoint.index == 1) let input2 = transaction?.inputs[2] as! BTCTransactionInput XCTAssertTrue(input2.previousTransactionID == txid2) XCTAssertTrue(input2.outpoint.index == 0) let input3 = transaction?.inputs[3] as! BTCTransactionInput XCTAssertTrue(input3.previousTransactionID == txid3) XCTAssertTrue(input3.outpoint.index == 1) let input4 = transaction?.inputs[4] as! BTCTransactionInput XCTAssertTrue(input4.previousTransactionID == txid4) XCTAssertTrue(input4.outpoint.index == 0) let input5 = transaction?.inputs[5] as! BTCTransactionInput XCTAssertTrue(input5.previousTransactionID == txid5) XCTAssertTrue(input5.outpoint.index == 1) let input6 = transaction?.inputs[6] as! BTCTransactionInput XCTAssertTrue(input6.previousTransactionID == txid6) XCTAssertTrue(input6.outpoint.index == 1) let input7 = transaction?.inputs[7] as! BTCTransactionInput XCTAssertTrue(input7.previousTransactionID == txid7) XCTAssertTrue(input7.outpoint.index == 0) let input8 = transaction?.inputs[8] as! BTCTransactionInput XCTAssertTrue(input8.previousTransactionID == txid8) XCTAssertTrue(input8.outpoint.index == 1) let input9 = transaction?.inputs[9] as! BTCTransactionInput XCTAssertTrue(input9.previousTransactionID == txid9) XCTAssertTrue(input9.outpoint.index == 0) let input10 = transaction?.inputs[10] as! BTCTransactionInput XCTAssertTrue(input10.previousTransactionID == txid10) XCTAssertTrue(input10.outpoint.index == 1) let input11 = transaction?.inputs[11] as! BTCTransactionInput XCTAssertTrue(input11.previousTransactionID == txid11) XCTAssertTrue(input11.outpoint.index == 0) let input12 = transaction?.inputs[12] as! BTCTransactionInput XCTAssertTrue(input12.previousTransactionID == txid12) XCTAssertTrue(input12.outpoint.index == 0) let input13 = transaction?.inputs[13] as! BTCTransactionInput XCTAssertTrue(input13.previousTransactionID == txid13) XCTAssertTrue(input13.outpoint.index == 0) let input14 = transaction?.inputs[14] as! BTCTransactionInput XCTAssertTrue(input14.previousTransactionID == txid14) XCTAssertTrue(input14.outpoint.index == 1) let input15 = transaction?.inputs[15] as! BTCTransactionInput XCTAssertTrue(input15.previousTransactionID == txid15) XCTAssertTrue(input15.outpoint.index == 0) let input16 = transaction?.inputs[16] as! BTCTransactionInput XCTAssertTrue(input16.previousTransactionID == txid16) XCTAssertTrue(input16.outpoint.index == 0) XCTAssertTrue(transaction?.outputs.count == 2) let output0 = transaction?.outputs[0] as! BTCTransactionOutput XCTAssertTrue(output0.script.hex == "76a9144a5fba237213a062f6f57978f796390bdcf8d01588ac") XCTAssertTrue(output0.value == 400057456) XCTAssertTrue(output0.script.standardAddress.base58String == "17nFgS1YaDPnXKMPQkZVdNQqZnVqRgBwnZ") let output1 = transaction?.outputs[1] as! BTCTransactionOutput XCTAssertTrue(output1.script.hex == "76a9145be32612930b8323add2212a4ec03c1562084f8488ac") XCTAssertTrue(output1.value == 40000000000) XCTAssertTrue(output1.script.standardAddress.base58String == "19Nrc2Xm226xmSbeGZ1BVtX7DUm4oCx8Pm") XCTAssertTrue(realToAddresses.count == 2) XCTAssertTrue(realToAddresses[0] == "17nFgS1YaDPnXKMPQkZVdNQqZnVqRgBwnZ") XCTAssertTrue(realToAddresses[1] == "19Nrc2Xm226xmSbeGZ1BVtX7DUm4oCx8Pm") } func testCreateSignedSerializedTransactionHexAndBIP69_2_2() -> () { let toAddressesAndAmounts = [["address": toAddress2, "amount": toAmount2], ["address": toAddress, "amount": toAmount]] accountObject.unspentOutputs = NSMutableArray(capacity: 17) accountObject.unspentOutputs!.add(unspentOutput15) accountObject.unspentOutputs!.add(unspentOutput2) accountObject.unspentOutputs!.add(unspentOutput0) accountObject.unspentOutputs!.add(unspentOutput1) accountObject.unspentOutputs!.add(unspentOutput5) accountObject.unspentOutputs!.add(unspentOutput3) accountObject.unspentOutputs!.add(unspentOutput4) accountObject.unspentOutputs!.add(unspentOutput6) accountObject.unspentOutputs!.add(unspentOutput7) accountObject.unspentOutputs!.add(unspentOutput9) accountObject.unspentOutputs!.add(unspentOutput10) accountObject.unspentOutputs!.add(unspentOutput8) accountObject.unspentOutputs!.add(unspentOutput12) accountObject.unspentOutputs!.add(unspentOutput11) accountObject.unspentOutputs!.add(unspentOutput14) accountObject.unspentOutputs!.add(unspentOutput16) accountObject.unspentOutputs!.add(unspentOutput13) accountObject.stealthPaymentUnspentOutputs = NSMutableArray(capacity: 0) let ret = godSend.createSignedSerializedTransactionHex(toAddressesAndAmounts as NSArray, feeAmount: feeAmount, error: { (data: String?) in }) let txHexAndTxHash = ret.0 let realToAddresses = ret.1 let txHex = txHexAndTxHash!.object(forKey: "txHex") as! String let txHash = txHexAndTxHash!.object(forKey: "txHash") as! String let txSize = txHexAndTxHash!.object(forKey: "txSize") as! NSNumber XCTAssertTrue(txHash == "0656add012962ef3bdd11eaf88347b78a2c4adb08fe8b95f79a8b8a4fe862132") XCTAssertTrue(txHex == "0100000011571fb3e02278217852dd5d299947e2b7354a639adc32ec1fa7b82cfb5dec530e000000006b483045022100b28348624779833117dc8ae73bcb649528ad6edf9d5b48018c4488dbc9b9fa3702201f8b0e1707bdfa3438d6c1353b62e3a01cb0b7b4ee5e7ef93e7b2f563ead66a30121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff2470947216eb81ea0eeeb4fe19362ec05767db01c3aa3006bb499e8b6d6eaa26010000006a4730440220679db98b1e5b17a57acc78e7271c357130fd8b6d8d2072880429d05630c5cc2802205fb88f764053185d610ae8041907bdb85f711a51f5602bb663b744e786fd78700121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffffc26f3eb7932f7acddc5ddd26602b77e7516079b03090a16e2c2f5485d1fde028000000006a47304402205031699fc96af02637f1ed7120c0e380f65370824f6af5cd37baf391f8188f73022026f5ba7a7f31fc3590f1e4dce50f12cc2122ce3fe30d187f11c3922ce3b22d0a0121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff5d8de50362ff33d3526ac3602e9ee25c1a349def086a7fc1d9941aaeb9e91d38010000006b483045022100fefda0743cc428b17e688c65d226e899af8b0d5a6f05d0944f9c67257fa5a15a02207baa0a95d88b98b0b669cab8342cc43bc992daf3be41c4ba40de77453ed3fb220121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffffe28db9462d3004e21e765e03a45ecb147f136a20ba8bca78ba60ebfc8e2f8b3b000000006b483045022100a54a4e0a3b476c855273a0aa6d97f5995e78a83cad59c28cda786b49a14f370602202cce0aadf128986b6448ad7f3288f95c9c5467ba01d16e94d807db587e481fe30121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff0abcd77d65cc14363f8262898335f184d6da5ad060ff9e40bf201741022c2b40010000006a4730440220636b2a05ef164457c9b8ee0f364c308a7ef8a0f5f7b01d6633ace40803a6fd7902205f052f39e940d2b8d797a5259ee35d0596a0dbfd199799722d95a895308bd1f10121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff7a1ef65ff1b7b7740c662ab6c9735ace4a16279c23a1db5709ed652918ffff54010000006b4830450221008e3f42e8e5d45712efe14c17ba199724e1d2bcaa2a459ede155b2df89d1b8c7902205260062b1eb6595a43180f0b40307467f4fe2f67138c2aed47d21dac739f4a770121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffffaad553bb1650007e9982a8ac79d227cd8c831e1573b11f25573a37664e5f3e64000000006a47304402204b3a8b40ea4bd092ce05ae5a55704d98ceee485b87e9d9bbc1dcc0956a2230bb022043b2660c1513b029038a3f2492c2d0d39b45c04a14b1143ede43abb6832d6f910121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffffb102113fa46ce949616d9cda00f6b10231336b3928eaaac6bfe42d1bf3561d6c010000006a4730440220651a1d62ba88ac05790bab2ead82483e99a748965cd8f1887c943c62c67786de022002bc57e36c7668c8e5d4c02e1baed3491b30d91e53633b807ca950b8bfad6fe90121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff91c1889c5c24b93b56e643121f7a05a34c10c5495c450504c7b5afcb37e11d7a000000006b483045022100ea05603d2944228bd2231354b1a6e6d106a803d7d52e8a290232b9769629c9a502204125264bb9d2cfd2db5455044d58a7b8ea8e0c7c88870def8c0fac2c559c5cfc0121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffffa43bebbebf07452a893a95bfea1d5db338d23579be172fe803dce02eeb7c037d010000006b483045022100fe8ce378ed72b0829805dde89cb16d2f6722f8b4128ee07f6ef064eaa4b607f702206400a9816e120e3e7427f4e83518b2822f010c219bcb758560ff280aae1cbe420121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff8640e312040e476cf6727c60ca3f4a3ad51623500aacdda96e7728dbdd99e8a5000000006a47304402203cce0a54d3314decb5adf1d9dbe5f887eb779daf6d4d2ce463435181da6cc72302206104213df446b9fc78d598c8425c82ca9b0952fab48a9edecb382ee5e68c11fe0121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffffb861fab2cde188499758346be46b5fbec635addfc4e7b0c8a07c0a908f2b11b4000000006a4730440220120d5d6672695c9ad3e72049da00be123bab74971386953b38409ff52989ce2502202b8702ba2d7fe90fc35aef52585c664865ae746d9e4242955b7ece22d89ad1ca0121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff850cecf958468ca7ffa6a490afe13b8c271b1326b0ddc1fdfdf9f3c7e365fdba000000006a4730440220282a14909d8ed766441c4766a574af0fc20e1b587e545e1943429670457b959602207e4c7503ae3c9de83865d0972fbb18cd7c9630023347d6eba45963f0670ef7e70121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffffe9b483a8ac4129780c88d1babe41e89dc10a26dedbf14f80a28474e9a11104de010000006b483045022100ec3744090e0603690319768ef234071410224230cc32e4731e50f9e8a05a6a5802203a1a8f7380e83fd1b61f4fd775aa6410d2e87d018b820aab4e98e1a8de0715f10121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff456a9e597129f5df2e11b842833fc19a94c563f57449281d3cd01249a830a1f0000000006a473044022060b06dcb2550beb9dd4181a45566ccf0ba41040d9003aa83c02fb63c4f9cbd8a02203c577c070c69382cfb05c74275590e3de6dcb77ce929b0d771f314ffa889f47c0121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff60ad3408b89ea19caf3abd5e74e7a084344987c64b1563af52242e9d2a8320f3000000006b483045022100d7b08a358ce19469d369765c38d1f17ffe66151f7c9cd85757d89d4f218a9d390220418f13a4b8eb41ca471901f1ba2b1677166f90127b65b7417ad5a62d76f0b8c80121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff027064d817000000001976a9144a5fba237213a062f6f57978f796390bdcf8d01588ac00902f50090000001976a9145be32612930b8323add2212a4ec03c1562084f8488ac00000000") XCTAssertTrue(txSize.uintValue == 2611) let transaction = BTCTransaction(hex: txHex) XCTAssertTrue(transaction?.inputs.count == 17) let input0 = transaction?.inputs[0] as! BTCTransactionInput XCTAssertTrue(input0.previousTransactionID == txid0) XCTAssertTrue(input0.outpoint.index == 0) let input1 = transaction?.inputs[1] as! BTCTransactionInput XCTAssertTrue(input1.previousTransactionID == txid1) XCTAssertTrue(input1.outpoint.index == 1) let input2 = transaction?.inputs[2] as! BTCTransactionInput XCTAssertTrue(input2.previousTransactionID == txid2) XCTAssertTrue(input2.outpoint.index == 0) let input3 = transaction?.inputs[3] as! BTCTransactionInput XCTAssertTrue(input3.previousTransactionID == txid3) XCTAssertTrue(input3.outpoint.index == 1) let input4 = transaction?.inputs[4] as! BTCTransactionInput XCTAssertTrue(input4.previousTransactionID == txid4) XCTAssertTrue(input4.outpoint.index == 0) let input5 = transaction?.inputs[5] as! BTCTransactionInput XCTAssertTrue(input5.previousTransactionID == txid5) XCTAssertTrue(input5.outpoint.index == 1) let input6 = transaction?.inputs[6] as! BTCTransactionInput XCTAssertTrue(input6.previousTransactionID == txid6) XCTAssertTrue(input6.outpoint.index == 1) let input7 = transaction?.inputs[7] as! BTCTransactionInput XCTAssertTrue(input7.previousTransactionID == txid7) XCTAssertTrue(input7.outpoint.index == 0) let input8 = transaction?.inputs[8] as! BTCTransactionInput XCTAssertTrue(input8.previousTransactionID == txid8) XCTAssertTrue(input8.outpoint.index == 1) let input9 = transaction?.inputs[9] as! BTCTransactionInput XCTAssertTrue(input9.previousTransactionID == txid9) XCTAssertTrue(input9.outpoint.index == 0) let input10 = transaction?.inputs[10] as! BTCTransactionInput XCTAssertTrue(input10.previousTransactionID == txid10) XCTAssertTrue(input10.outpoint.index == 1) let input11 = transaction?.inputs[11] as! BTCTransactionInput XCTAssertTrue(input11.previousTransactionID == txid11) XCTAssertTrue(input11.outpoint.index == 0) let input12 = transaction?.inputs[12] as! BTCTransactionInput XCTAssertTrue(input12.previousTransactionID == txid12) XCTAssertTrue(input12.outpoint.index == 0) let input13 = transaction?.inputs[13] as! BTCTransactionInput XCTAssertTrue(input13.previousTransactionID == txid13) XCTAssertTrue(input13.outpoint.index == 0) let input14 = transaction?.inputs[14] as! BTCTransactionInput XCTAssertTrue(input14.previousTransactionID == txid14) XCTAssertTrue(input14.outpoint.index == 1) let input15 = transaction?.inputs[15] as! BTCTransactionInput XCTAssertTrue(input15.previousTransactionID == txid15) XCTAssertTrue(input15.outpoint.index == 0) let input16 = transaction?.inputs[16] as! BTCTransactionInput XCTAssertTrue(input16.previousTransactionID == txid16) XCTAssertTrue(input16.outpoint.index == 0) XCTAssertTrue(transaction?.outputs.count == 2) let output0 = transaction?.outputs[0] as! BTCTransactionOutput XCTAssertTrue(output0.script.hex == "76a9144a5fba237213a062f6f57978f796390bdcf8d01588ac") XCTAssertTrue(output0.value == 400057456) XCTAssertTrue(output0.script.standardAddress.base58String == "17nFgS1YaDPnXKMPQkZVdNQqZnVqRgBwnZ") let output1 = transaction?.outputs[1] as! BTCTransactionOutput XCTAssertTrue(output1.script.hex == "76a9145be32612930b8323add2212a4ec03c1562084f8488ac") XCTAssertTrue(output1.value == 40000000000) XCTAssertTrue(output1.script.standardAddress.base58String == "19Nrc2Xm226xmSbeGZ1BVtX7DUm4oCx8Pm") XCTAssertTrue(realToAddresses.count == 2) XCTAssertTrue(realToAddresses[0] == "19Nrc2Xm226xmSbeGZ1BVtX7DUm4oCx8Pm") XCTAssertTrue(realToAddresses[1] == "17nFgS1YaDPnXKMPQkZVdNQqZnVqRgBwnZ") } testCreateSignedSerializedTransactionHexAndBIP69_2_1() testCreateSignedSerializedTransactionHexAndBIP69_2_2() } func testCreateSignedSerializedTransactionHexAndBIP69_3() -> () { let feeAmount = TLCoin(bitcoinAmount: "0.00000", bitcoinDenomination: TLBitcoinDenomination.bitcoin) let toAddress = "1KAD5EnzzLtrSo2Da2G4zzD7uZrjk8zRAv" let toAddress2 = "vJmwhHhMNevDQh188gSeHd2xxxYGBQmnVuMY2yG2MmVTC31UWN5s3vaM3xsM2Q1bUremdK1W7eNVgPg1BnvbTyQuDtMKAYJanahvse" let toAmount = TLCoin(bitcoinAmount: "1", bitcoinDenomination: TLBitcoinDenomination.bitcoin) let toAmount2 = TLCoin(bitcoinAmount: "24", bitcoinDenomination: TLBitcoinDenomination.bitcoin) let txid0 = "35288d269cee1941eaebb2ea85e32b42cdb2b04284a56d8b14dcc3f5c65d6055" let txid1 = "35288d269cee1941eaebb2ea85e32b42cdb2b04284a56d8b14dcc3f5c65d6055" let unspentOutput0 = mockUnspentOutput(txid0, 100000000, 0) let unspentOutput1 = mockUnspentOutput(txid1, 2400000000, 1) let nonce:UInt32 = 123 let ephemeralPrivateKeyHex = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" func testCreateSignedSerializedTransactionHexAndBIP69_3_1() -> () { let toAddressesAndAmounts = [["address": toAddress, "amount": toAmount], ["address": toAddress2, "amount": toAmount2]] accountObject.unspentOutputs = NSMutableArray(capacity: 2) accountObject.unspentOutputs!.add(unspentOutput0) accountObject.unspentOutputs!.add(unspentOutput1) accountObject.stealthPaymentUnspentOutputs = NSMutableArray(capacity: 0) let ret = godSend.createSignedSerializedTransactionHex(toAddressesAndAmounts as NSArray, feeAmount: feeAmount, nonce: nonce, ephemeralPrivateKeyHex: ephemeralPrivateKeyHex, error: { (data: String?) in }) let txHexAndTxHash = ret.0 let realToAddresses = ret.1 let txHex = txHexAndTxHash!.object(forKey: "txHex") as! String let txHash = txHexAndTxHash!.object(forKey: "txHash") as! String let txSize = txHexAndTxHash!.object(forKey: "txSize") as! NSNumber XCTAssertTrue(txHash == "9debd8fa98772ef4110fc3eb07a0a172e7704a148708ada788b2b5560efd445f") XCTAssertTrue(txHex == "010000000255605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d2835000000006b483045022100bb8786c01153753ab524a4a40c4d0635489e6bd68ded28e63b06f661977fa9fc022055bcba9bd538a5bb2c375c9b76f64c8a5e65f8d609fe50413d65c71afa6d31c40121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff55605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d2835010000006a47304402202c4c09455e0fc246617575d335194253a98bfb516943b3c0f14bb40f2676717402200af78f6591c26fc34910f4e43bde46c24538e500c71a781e2fb359b425fbca8e0121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff030000000000000000286a26060000007b03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd00e1f505000000001976a914c73015fa62d972ebb3b241fe8c936657b13fabd788ac00180d8f000000001976a914d9bbccb1b996061b735b35841d90844c263fbc7388ac00000000") XCTAssertTrue(txSize.uintValue == 410) let transaction = BTCTransaction(hex: txHex) XCTAssertTrue(transaction?.inputs.count == 2) let input0 = transaction?.inputs[0] as! BTCTransactionInput XCTAssertTrue(input0.previousTransactionID == txid0) XCTAssertTrue(input0.outpoint.index == 0) let input1 = transaction?.inputs[1] as! BTCTransactionInput XCTAssertTrue(input1.previousTransactionID == txid1) XCTAssertTrue(input1.outpoint.index == 1) XCTAssertTrue(transaction?.outputs.count == 3) let output0 = transaction?.outputs[0] as! BTCTransactionOutput XCTAssertTrue(output0.script.hex == "6a26060000007b03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd") XCTAssertTrue(output0.value == 0) XCTAssertTrue(output0.script.standardAddress == nil) let output1 = transaction?.outputs[1] as! BTCTransactionOutput XCTAssertTrue(output1.script.hex == "76a914c73015fa62d972ebb3b241fe8c936657b13fabd788ac") XCTAssertTrue(output1.value == 100000000) XCTAssertTrue(output1.script.standardAddress.base58String == "1KAD5EnzzLtrSo2Da2G4zzD7uZrjk8zRAv") let output2 = transaction?.outputs[2] as! BTCTransactionOutput XCTAssertTrue(output2.script.hex == "76a914d9bbccb1b996061b735b35841d90844c263fbc7388ac") XCTAssertTrue(output2.value == 2400000000) XCTAssertTrue(output2.script.standardAddress.base58String == "1LrGcAw6WPFK4re5mt4MQfXj9xLeBYojRm") XCTAssertTrue(realToAddresses.count == 2) XCTAssertTrue(realToAddresses[0] == "1KAD5EnzzLtrSo2Da2G4zzD7uZrjk8zRAv") XCTAssertTrue(realToAddresses[1] == "1LrGcAw6WPFK4re5mt4MQfXj9xLeBYojRm") } func testCreateSignedSerializedTransactionHexAndBIP69_3_2() -> () { let toAddressesAndAmounts = [["address": toAddress2, "amount": toAmount2], ["address": toAddress, "amount": toAmount]] accountObject.unspentOutputs = NSMutableArray(capacity: 2) accountObject.unspentOutputs!.add(unspentOutput1) accountObject.unspentOutputs!.add(unspentOutput0) accountObject.stealthPaymentUnspentOutputs = NSMutableArray(capacity: 0) let ret = godSend.createSignedSerializedTransactionHex(toAddressesAndAmounts as NSArray, feeAmount: feeAmount, nonce: nonce, ephemeralPrivateKeyHex: ephemeralPrivateKeyHex, error: { (data: String?) in }) let txHexAndTxHash = ret.0 let realToAddresses = ret.1 let txHex = txHexAndTxHash!.object(forKey: "txHex") as! String let txHash = txHexAndTxHash!.object(forKey: "txHash") as! String let txSize = txHexAndTxHash!.object(forKey: "txSize") as! NSNumber XCTAssertTrue(txHash == "9debd8fa98772ef4110fc3eb07a0a172e7704a148708ada788b2b5560efd445f") XCTAssertTrue(txHex == "010000000255605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d2835000000006b483045022100bb8786c01153753ab524a4a40c4d0635489e6bd68ded28e63b06f661977fa9fc022055bcba9bd538a5bb2c375c9b76f64c8a5e65f8d609fe50413d65c71afa6d31c40121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff55605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d2835010000006a47304402202c4c09455e0fc246617575d335194253a98bfb516943b3c0f14bb40f2676717402200af78f6591c26fc34910f4e43bde46c24538e500c71a781e2fb359b425fbca8e0121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff030000000000000000286a26060000007b03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd00e1f505000000001976a914c73015fa62d972ebb3b241fe8c936657b13fabd788ac00180d8f000000001976a914d9bbccb1b996061b735b35841d90844c263fbc7388ac00000000") XCTAssertTrue(txSize.uintValue == 410) let transaction = BTCTransaction(hex: txHex) XCTAssertTrue(transaction?.inputs.count == 2) let input0 = transaction?.inputs[0] as! BTCTransactionInput XCTAssertTrue(input0.previousTransactionID == txid0) XCTAssertTrue(input0.outpoint.index == 0) let input1 = transaction?.inputs[1] as! BTCTransactionInput XCTAssertTrue(input1.previousTransactionID == txid1) XCTAssertTrue(input1.outpoint.index == 1) XCTAssertTrue(transaction?.outputs.count == 3) let output0 = transaction?.outputs[0] as! BTCTransactionOutput XCTAssertTrue(output0.script.hex == "6a26060000007b03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd") XCTAssertTrue(output0.value == 0) XCTAssertTrue(output0.script.standardAddress == nil) let output1 = transaction?.outputs[1] as! BTCTransactionOutput XCTAssertTrue(output1.script.hex == "76a914c73015fa62d972ebb3b241fe8c936657b13fabd788ac") XCTAssertTrue(output1.value == 100000000) XCTAssertTrue(output1.script.standardAddress.base58String == "1KAD5EnzzLtrSo2Da2G4zzD7uZrjk8zRAv") let output2 = transaction?.outputs[2] as! BTCTransactionOutput XCTAssertTrue(output2.script.hex == "76a914d9bbccb1b996061b735b35841d90844c263fbc7388ac") XCTAssertTrue(output2.value == 2400000000) XCTAssertTrue(output2.script.standardAddress.base58String == "1LrGcAw6WPFK4re5mt4MQfXj9xLeBYojRm") XCTAssertTrue(realToAddresses.count == 2) XCTAssertTrue(realToAddresses[0] == "1LrGcAw6WPFK4re5mt4MQfXj9xLeBYojRm") XCTAssertTrue(realToAddresses[1] == "1KAD5EnzzLtrSo2Da2G4zzD7uZrjk8zRAv") } testCreateSignedSerializedTransactionHexAndBIP69_3_1() testCreateSignedSerializedTransactionHexAndBIP69_3_2() } func testCreateSignedSerializedTransactionHexAndBIP69_4() -> () { let feeAmount = TLCoin(bitcoinAmount: "0.00002735", bitcoinDenomination: TLBitcoinDenomination.bitcoin) let toAddress = "vJmwhHhMNevDQh188gSeHd2xxxYGBQmnVuMY2yG2MmVTC31UWN5s3vaM3xsM2Q1bUremdK1W7eNVgPg1BnvbTyQuDtMKAYJanahvse" let toAddress2 = "19Nrc2Xm226xmSbeGZ1BVtX7DUm4oCx8Pm" let toAmount = TLCoin(bitcoinAmount: "4.00057456", bitcoinDenomination: TLBitcoinDenomination.bitcoin) let toAmount2 = TLCoin(bitcoinAmount: "400", bitcoinDenomination: TLBitcoinDenomination.bitcoin) let txid0 = "0e53ec5dfb2cb8a71fec32dc9a634a35b7e24799295ddd5278217822e0b31f57" let txid1 = "26aa6e6d8b9e49bb0630aac301db6757c02e3619feb4ee0eea81eb1672947024" let txid2 = "28e0fdd185542f2c6ea19030b0796051e7772b6026dd5ddccd7a2f93b73e6fc2" let txid3 = "381de9b9ae1a94d9c17f6a08ef9d341a5ce29e2e60c36a52d333ff6203e58d5d" let txid4 = "3b8b2f8efceb60ba78ca8bba206a137f14cb5ea4035e761ee204302d46b98de2" let txid5 = "402b2c02411720bf409eff60d05adad684f135838962823f3614cc657dd7bc0a" let txid6 = "54ffff182965ed0957dba1239c27164ace5a73c9b62a660c74b7b7f15ff61e7a" let txid7 = "643e5f4e66373a57251fb173151e838ccd27d279aca882997e005016bb53d5aa" let txid8 = "6c1d56f31b2de4bfc6aaea28396b333102b1f600da9c6d6149e96ca43f1102b1" let txid9 = "7a1de137cbafb5c70405455c49c5104ca3057a1f1243e6563bb9245c9c88c191" let txid10 = "7d037ceb2ee0dc03e82f17be7935d238b35d1deabf953a892a4507bfbeeb3ba4" let txid11 = "a5e899dddb28776ea9ddac0a502316d53a4a3fca607c72f66c470e0412e34086" let txid12 = "b4112b8f900a7ca0c8b0e7c4dfad35c6be5f6be46b3458974988e1cdb2fa61b8" let txid13 = "bafd65e3c7f3f9fdfdc1ddb026131b278c3be1af90a4a6ffa78c4658f9ec0c85" let txid14 = "de0411a1e97484a2804ff1dbde260ac19de841bebad1880c782941aca883b4e9" let txid15 = "f0a130a84912d03c1d284974f563c5949ac13f8342b8112edff52971599e6a45" let txid16 = "f320832a9d2e2452af63154bc687493484a0e7745ebd3aaf9ca19eb80834ad60" let unspentOutput0 = mockUnspentOutput(txid0, 2529937904, 0) let unspentOutput1 = mockUnspentOutput(txid1, 2521656792, 1) let unspentOutput2 = mockUnspentOutput(txid2, 2509683086, 0) let unspentOutput3 = mockUnspentOutput(txid3, 2506060377, 1) let unspentOutput4 = mockUnspentOutput(txid4, 2510645247, 0) let unspentOutput5 = mockUnspentOutput(txid5, 2502325820, 1) let unspentOutput6 = mockUnspentOutput(txid6, 2525953727, 1) let unspentOutput7 = mockUnspentOutput(txid7, 2507302856, 0) let unspentOutput8 = mockUnspentOutput(txid8, 2534185804, 1) let unspentOutput9 = mockUnspentOutput(txid9, 136219905, 0) let unspentOutput10 = mockUnspentOutput(txid10, 2502901118, 1) let unspentOutput11 = mockUnspentOutput(txid11, 2527569363, 0) let unspentOutput12 = mockUnspentOutput(txid12, 2516268302, 0) let unspentOutput13 = mockUnspentOutput(txid13, 2521794404, 0) let unspentOutput14 = mockUnspentOutput(txid14, 2520533680, 1) let unspentOutput15 = mockUnspentOutput(txid15, 2513840095, 0) let unspentOutput16 = mockUnspentOutput(txid16, 2513181711, 0) let nonce:UInt32 = 123 let ephemeralPrivateKeyHex = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" func testCreateSignedSerializedTransactionHexAndBIP69_4_1() -> () { let toAddressesAndAmounts = [["address": toAddress, "amount": toAmount], ["address": toAddress2, "amount": toAmount2]] accountObject.unspentOutputs = NSMutableArray(capacity: 17) accountObject.unspentOutputs!.add(unspentOutput0) accountObject.unspentOutputs!.add(unspentOutput1) accountObject.unspentOutputs!.add(unspentOutput2) accountObject.unspentOutputs!.add(unspentOutput3) accountObject.unspentOutputs!.add(unspentOutput4) accountObject.unspentOutputs!.add(unspentOutput5) accountObject.unspentOutputs!.add(unspentOutput6) accountObject.unspentOutputs!.add(unspentOutput7) accountObject.unspentOutputs!.add(unspentOutput8) accountObject.unspentOutputs!.add(unspentOutput9) accountObject.unspentOutputs!.add(unspentOutput10) accountObject.unspentOutputs!.add(unspentOutput11) accountObject.unspentOutputs!.add(unspentOutput12) accountObject.unspentOutputs!.add(unspentOutput13) accountObject.unspentOutputs!.add(unspentOutput14) accountObject.unspentOutputs!.add(unspentOutput15) accountObject.unspentOutputs!.add(unspentOutput16) accountObject.stealthPaymentUnspentOutputs = NSMutableArray(capacity: 0) let ret = godSend.createSignedSerializedTransactionHex(toAddressesAndAmounts as NSArray, feeAmount: feeAmount, nonce: nonce, ephemeralPrivateKeyHex: ephemeralPrivateKeyHex, error: { (data: String?) in }) let txHexAndTxHash = ret.0 let realToAddresses = ret.1 let txHex = txHexAndTxHash!.object(forKey: "txHex") as! String let txHash = txHexAndTxHash!.object(forKey: "txHash") as! String let txSize = txHexAndTxHash!.object(forKey: "txSize") as! NSNumber XCTAssertTrue(txHash == "b982687699c5bbd6ee36b157c3b34b3d3370945e68b63c987cdf880dbe475706") XCTAssertTrue(txHex == "0100000011571fb3e02278217852dd5d299947e2b7354a639adc32ec1fa7b82cfb5dec530e000000006b483045022100d9e6a6677e63574fd5216957f0652334acf64343192064c4f19c5c8daad1f796022041cbcc403865f92b2804e2d04cfa165dd42bd75c247055c626901507479f923c0121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff2470947216eb81ea0eeeb4fe19362ec05767db01c3aa3006bb499e8b6d6eaa26010000006a47304402204a6451764251502cfdcac44deab397e538e5c33fdf354116bcf3dd8088b47c450220345f69761d82e03e88dce29f37e6bccdffce78e3b640d089a377079950006fba0121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffffc26f3eb7932f7acddc5ddd26602b77e7516079b03090a16e2c2f5485d1fde028000000006b48304502210080577b722d775c9ab9acba7f90b6ee0187395c65824c52ee96a83d9582b27761022063aafc98452e62ee85d99082c96d9dae4071ed0b5f822a4ab211428336e937440121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff5d8de50362ff33d3526ac3602e9ee25c1a349def086a7fc1d9941aaeb9e91d38010000006b483045022100a2279a85d58b05822dbc1ba9cb4c22a9efaf4e3e2d0aaf4c140f6232b90339cf02202e88942afcc0defc3839e764e7358e5065e9bcc6a437f3a1f9e60a912e5cd0180121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffffe28db9462d3004e21e765e03a45ecb147f136a20ba8bca78ba60ebfc8e2f8b3b000000006b483045022100d431504d890b2acdc45f618ddc53c2a7accb01d9273afbaa31d5beb71c9bb4de02200797a199a0783d16397152db1159fc8594946ca876ef3586f06be36afc0915230121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff0abcd77d65cc14363f8262898335f184d6da5ad060ff9e40bf201741022c2b40010000006b4830450221008424d7c4bc369a735b92c0f367f5bead679bc82b77ac3ea527002a795299e5cd02200bd017178c46caf204cd3283daed2539a525051e3a73f10f23175d4a90a6d21e0121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff7a1ef65ff1b7b7740c662ab6c9735ace4a16279c23a1db5709ed652918ffff54010000006b483045022100bec61f4a8aa3ed122f02663d162ea8d06b65730a1400bb58586783c4155c4ecc022037ba1f6434685252902ca095317299e9facb634216e94677e444182c15d4b8dc0121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffffaad553bb1650007e9982a8ac79d227cd8c831e1573b11f25573a37664e5f3e64000000006b483045022100b090ff8248aa3f6ea9026861ecbf91e60859801d04fbc4ad54eb3f7497a482c90220128a6dcb1d2d17033aa3e002fcec7fa54b0f817f56abf14d5d37574f2dcf2d1d0121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffffb102113fa46ce949616d9cda00f6b10231336b3928eaaac6bfe42d1bf3561d6c010000006b483045022100cb8f6d41cb664bee9fe86417e6b6b61452fdcc7c652fe66c46cddae49a678fcb02201f8c040f0a034602015ad2cbf4e6c058077366633c7d3fd5df626de03a091e7b0121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff91c1889c5c24b93b56e643121f7a05a34c10c5495c450504c7b5afcb37e11d7a000000006a47304402200cd323984290d2ef6d7ad01942102ea0cceee9b897103ef385719c6f2b57963702201d9dd8c3ea68ea02b6a3ee4a19c18f899d8b02da549f914dd917489c067e7e8e0121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffffa43bebbebf07452a893a95bfea1d5db338d23579be172fe803dce02eeb7c037d010000006b483045022100be493ef5d839eea19d68e8a3a037fc2f7eb41655d511169a4a8b653ab9d86ca30220416cb1f8dfc83a322de2617e007521dbd408d5ef776193400351c046cbde3d780121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff8640e312040e476cf6727c60ca3f4a3ad51623500aacdda96e7728dbdd99e8a5000000006a47304402200b746b555bf44674ca15ba71ca751719311244f3ba0a5a492fe685fdf7a95dcf0220357f17f4af7a322ca18fc65ddd87580e75bb9988023a11ab00b2f4243c7b6b150121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffffb861fab2cde188499758346be46b5fbec635addfc4e7b0c8a07c0a908f2b11b4000000006b4830450221008c0b600801fed1af9c9400daf9c345f27837670a7acd0f1dcdbbbeb7925bad1f022017ef87eabf09308b2f11e63dcb15c4007a907b78afcad4931f9129355ed57b390121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff850cecf958468ca7ffa6a490afe13b8c271b1326b0ddc1fdfdf9f3c7e365fdba000000006b483045022100bcfe32a695abde4c66996b9d38b6be73a70abfcbe09fcc564f4aa1e0c51fd93b0220579cb2061627efbf9ce1748284f9ecedbe72ccdbc9010cb673e64f2aa58c51d90121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffffe9b483a8ac4129780c88d1babe41e89dc10a26dedbf14f80a28474e9a11104de010000006a47304402204eeedb2a870d7c1f9aa74a9edc166eda1d80a63c39c5d964c9c4b92db14c1bdd02207e70e0d5740835419f82c23580fdeed491ab872c2fe8a51b4f03fdb817ebfe850121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff456a9e597129f5df2e11b842833fc19a94c563f57449281d3cd01249a830a1f0000000006b483045022100c3c3a47e694c6e9c1d43ae89bfe97a387c45e17d99f79707a6a4df006f5561240220573c40748cc42c45038ac718963eb6385c75216b70249dfb9f8f9d67ae569b9a0121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff60ad3408b89ea19caf3abd5e74e7a084344987c64b1563af52242e9d2a8320f3000000006a47304402202744a81ba331f89bc0f39c2eb241460a279347e070df57c60148dd7c6ae1778102200616c24bf72cd82a49e0419634388bed2516e4b95dc68dd846851742e15f3cec0121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff030000000000000000286a26060000007b03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd7064d817000000001976a914d9bbccb1b996061b735b35841d90844c263fbc7388ac00902f50090000001976a9145be32612930b8323add2212a4ec03c1562084f8488ac00000000") XCTAssertTrue(txSize.uintValue == 2645) let transaction = BTCTransaction(hex: txHex) XCTAssertTrue(transaction?.inputs.count == 17) let input0 = transaction?.inputs[0] as! BTCTransactionInput XCTAssertTrue(input0.previousTransactionID == txid0) XCTAssertTrue(input0.outpoint.index == 0) let input1 = transaction?.inputs[1] as! BTCTransactionInput XCTAssertTrue(input1.previousTransactionID == txid1) XCTAssertTrue(input1.outpoint.index == 1) let input2 = transaction?.inputs[2] as! BTCTransactionInput XCTAssertTrue(input2.previousTransactionID == txid2) XCTAssertTrue(input2.outpoint.index == 0) let input3 = transaction?.inputs[3] as! BTCTransactionInput XCTAssertTrue(input3.previousTransactionID == txid3) XCTAssertTrue(input3.outpoint.index == 1) let input4 = transaction?.inputs[4] as! BTCTransactionInput XCTAssertTrue(input4.previousTransactionID == txid4) XCTAssertTrue(input4.outpoint.index == 0) let input5 = transaction?.inputs[5] as! BTCTransactionInput XCTAssertTrue(input5.previousTransactionID == txid5) XCTAssertTrue(input5.outpoint.index == 1) let input6 = transaction?.inputs[6] as! BTCTransactionInput XCTAssertTrue(input6.previousTransactionID == txid6) XCTAssertTrue(input6.outpoint.index == 1) let input7 = transaction?.inputs[7] as! BTCTransactionInput XCTAssertTrue(input7.previousTransactionID == txid7) XCTAssertTrue(input7.outpoint.index == 0) let input8 = transaction?.inputs[8] as! BTCTransactionInput XCTAssertTrue(input8.previousTransactionID == txid8) XCTAssertTrue(input8.outpoint.index == 1) let input9 = transaction?.inputs[9] as! BTCTransactionInput XCTAssertTrue(input9.previousTransactionID == txid9) XCTAssertTrue(input9.outpoint.index == 0) let input10 = transaction?.inputs[10] as! BTCTransactionInput XCTAssertTrue(input10.previousTransactionID == txid10) XCTAssertTrue(input10.outpoint.index == 1) let input11 = transaction?.inputs[11] as! BTCTransactionInput XCTAssertTrue(input11.previousTransactionID == txid11) XCTAssertTrue(input11.outpoint.index == 0) let input12 = transaction?.inputs[12] as! BTCTransactionInput XCTAssertTrue(input12.previousTransactionID == txid12) XCTAssertTrue(input12.outpoint.index == 0) let input13 = transaction?.inputs[13] as! BTCTransactionInput XCTAssertTrue(input13.previousTransactionID == txid13) XCTAssertTrue(input13.outpoint.index == 0) let input14 = transaction?.inputs[14] as! BTCTransactionInput XCTAssertTrue(input14.previousTransactionID == txid14) XCTAssertTrue(input14.outpoint.index == 1) let input15 = transaction?.inputs[15] as! BTCTransactionInput XCTAssertTrue(input15.previousTransactionID == txid15) XCTAssertTrue(input15.outpoint.index == 0) let input16 = transaction?.inputs[16] as! BTCTransactionInput XCTAssertTrue(input16.previousTransactionID == txid16) XCTAssertTrue(input16.outpoint.index == 0) XCTAssertTrue(transaction?.outputs.count == 3) let output0 = transaction?.outputs[0] as! BTCTransactionOutput XCTAssertTrue(output0.script.hex == "6a26060000007b03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd") XCTAssertTrue(output0.value == 0) XCTAssertTrue(output0.script.standardAddress == nil) let output1 = transaction?.outputs[1] as! BTCTransactionOutput XCTAssertTrue(output1.script.hex == "76a914d9bbccb1b996061b735b35841d90844c263fbc7388ac") XCTAssertTrue(output1.value == 400057456) XCTAssertTrue(output1.script.standardAddress.base58String == "1LrGcAw6WPFK4re5mt4MQfXj9xLeBYojRm") let output2 = transaction?.outputs[2] as! BTCTransactionOutput XCTAssertTrue(output2.script.hex == "76a9145be32612930b8323add2212a4ec03c1562084f8488ac") XCTAssertTrue(output2.value == 40000000000) XCTAssertTrue(output2.script.standardAddress.base58String == "19Nrc2Xm226xmSbeGZ1BVtX7DUm4oCx8Pm") XCTAssertTrue(realToAddresses.count == 2) XCTAssertTrue(realToAddresses[0] == "1LrGcAw6WPFK4re5mt4MQfXj9xLeBYojRm") XCTAssertTrue(realToAddresses[1] == "19Nrc2Xm226xmSbeGZ1BVtX7DUm4oCx8Pm") } func testCreateSignedSerializedTransactionHexAndBIP69_4_2() -> () { let toAddressesAndAmounts = [["address": toAddress2, "amount": toAmount2], ["address": toAddress, "amount": toAmount]] accountObject.unspentOutputs = NSMutableArray(capacity: 17) accountObject.unspentOutputs!.add(unspentOutput5) accountObject.unspentOutputs!.add(unspentOutput2) accountObject.unspentOutputs!.add(unspentOutput15) accountObject.unspentOutputs!.add(unspentOutput0) accountObject.unspentOutputs!.add(unspentOutput1) accountObject.unspentOutputs!.add(unspentOutput6) accountObject.unspentOutputs!.add(unspentOutput4) accountObject.unspentOutputs!.add(unspentOutput7) accountObject.unspentOutputs!.add(unspentOutput3) accountObject.unspentOutputs!.add(unspentOutput13) accountObject.unspentOutputs!.add(unspentOutput9) accountObject.unspentOutputs!.add(unspentOutput10) accountObject.unspentOutputs!.add(unspentOutput8) accountObject.unspentOutputs!.add(unspentOutput11) accountObject.unspentOutputs!.add(unspentOutput14) accountObject.unspentOutputs!.add(unspentOutput12) accountObject.unspentOutputs!.add(unspentOutput16) accountObject.stealthPaymentUnspentOutputs = NSMutableArray(capacity: 0) let ret = godSend.createSignedSerializedTransactionHex(toAddressesAndAmounts as NSArray, feeAmount: feeAmount, nonce: nonce, ephemeralPrivateKeyHex: ephemeralPrivateKeyHex, error: { (data: String?) in }) let txHexAndTxHash = ret.0 let realToAddresses = ret.1 let txHex = txHexAndTxHash!.object(forKey: "txHex") as! String let txHash = txHexAndTxHash!.object(forKey: "txHash") as! String let txSize = txHexAndTxHash!.object(forKey: "txSize") as! NSNumber XCTAssertTrue(txHash == "b982687699c5bbd6ee36b157c3b34b3d3370945e68b63c987cdf880dbe475706") XCTAssertTrue(txHex == "0100000011571fb3e02278217852dd5d299947e2b7354a639adc32ec1fa7b82cfb5dec530e000000006b483045022100d9e6a6677e63574fd5216957f0652334acf64343192064c4f19c5c8daad1f796022041cbcc403865f92b2804e2d04cfa165dd42bd75c247055c626901507479f923c0121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff2470947216eb81ea0eeeb4fe19362ec05767db01c3aa3006bb499e8b6d6eaa26010000006a47304402204a6451764251502cfdcac44deab397e538e5c33fdf354116bcf3dd8088b47c450220345f69761d82e03e88dce29f37e6bccdffce78e3b640d089a377079950006fba0121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffffc26f3eb7932f7acddc5ddd26602b77e7516079b03090a16e2c2f5485d1fde028000000006b48304502210080577b722d775c9ab9acba7f90b6ee0187395c65824c52ee96a83d9582b27761022063aafc98452e62ee85d99082c96d9dae4071ed0b5f822a4ab211428336e937440121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff5d8de50362ff33d3526ac3602e9ee25c1a349def086a7fc1d9941aaeb9e91d38010000006b483045022100a2279a85d58b05822dbc1ba9cb4c22a9efaf4e3e2d0aaf4c140f6232b90339cf02202e88942afcc0defc3839e764e7358e5065e9bcc6a437f3a1f9e60a912e5cd0180121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffffe28db9462d3004e21e765e03a45ecb147f136a20ba8bca78ba60ebfc8e2f8b3b000000006b483045022100d431504d890b2acdc45f618ddc53c2a7accb01d9273afbaa31d5beb71c9bb4de02200797a199a0783d16397152db1159fc8594946ca876ef3586f06be36afc0915230121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff0abcd77d65cc14363f8262898335f184d6da5ad060ff9e40bf201741022c2b40010000006b4830450221008424d7c4bc369a735b92c0f367f5bead679bc82b77ac3ea527002a795299e5cd02200bd017178c46caf204cd3283daed2539a525051e3a73f10f23175d4a90a6d21e0121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff7a1ef65ff1b7b7740c662ab6c9735ace4a16279c23a1db5709ed652918ffff54010000006b483045022100bec61f4a8aa3ed122f02663d162ea8d06b65730a1400bb58586783c4155c4ecc022037ba1f6434685252902ca095317299e9facb634216e94677e444182c15d4b8dc0121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffffaad553bb1650007e9982a8ac79d227cd8c831e1573b11f25573a37664e5f3e64000000006b483045022100b090ff8248aa3f6ea9026861ecbf91e60859801d04fbc4ad54eb3f7497a482c90220128a6dcb1d2d17033aa3e002fcec7fa54b0f817f56abf14d5d37574f2dcf2d1d0121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffffb102113fa46ce949616d9cda00f6b10231336b3928eaaac6bfe42d1bf3561d6c010000006b483045022100cb8f6d41cb664bee9fe86417e6b6b61452fdcc7c652fe66c46cddae49a678fcb02201f8c040f0a034602015ad2cbf4e6c058077366633c7d3fd5df626de03a091e7b0121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff91c1889c5c24b93b56e643121f7a05a34c10c5495c450504c7b5afcb37e11d7a000000006a47304402200cd323984290d2ef6d7ad01942102ea0cceee9b897103ef385719c6f2b57963702201d9dd8c3ea68ea02b6a3ee4a19c18f899d8b02da549f914dd917489c067e7e8e0121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffffa43bebbebf07452a893a95bfea1d5db338d23579be172fe803dce02eeb7c037d010000006b483045022100be493ef5d839eea19d68e8a3a037fc2f7eb41655d511169a4a8b653ab9d86ca30220416cb1f8dfc83a322de2617e007521dbd408d5ef776193400351c046cbde3d780121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff8640e312040e476cf6727c60ca3f4a3ad51623500aacdda96e7728dbdd99e8a5000000006a47304402200b746b555bf44674ca15ba71ca751719311244f3ba0a5a492fe685fdf7a95dcf0220357f17f4af7a322ca18fc65ddd87580e75bb9988023a11ab00b2f4243c7b6b150121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffffb861fab2cde188499758346be46b5fbec635addfc4e7b0c8a07c0a908f2b11b4000000006b4830450221008c0b600801fed1af9c9400daf9c345f27837670a7acd0f1dcdbbbeb7925bad1f022017ef87eabf09308b2f11e63dcb15c4007a907b78afcad4931f9129355ed57b390121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff850cecf958468ca7ffa6a490afe13b8c271b1326b0ddc1fdfdf9f3c7e365fdba000000006b483045022100bcfe32a695abde4c66996b9d38b6be73a70abfcbe09fcc564f4aa1e0c51fd93b0220579cb2061627efbf9ce1748284f9ecedbe72ccdbc9010cb673e64f2aa58c51d90121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffffe9b483a8ac4129780c88d1babe41e89dc10a26dedbf14f80a28474e9a11104de010000006a47304402204eeedb2a870d7c1f9aa74a9edc166eda1d80a63c39c5d964c9c4b92db14c1bdd02207e70e0d5740835419f82c23580fdeed491ab872c2fe8a51b4f03fdb817ebfe850121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff456a9e597129f5df2e11b842833fc19a94c563f57449281d3cd01249a830a1f0000000006b483045022100c3c3a47e694c6e9c1d43ae89bfe97a387c45e17d99f79707a6a4df006f5561240220573c40748cc42c45038ac718963eb6385c75216b70249dfb9f8f9d67ae569b9a0121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff60ad3408b89ea19caf3abd5e74e7a084344987c64b1563af52242e9d2a8320f3000000006a47304402202744a81ba331f89bc0f39c2eb241460a279347e070df57c60148dd7c6ae1778102200616c24bf72cd82a49e0419634388bed2516e4b95dc68dd846851742e15f3cec0121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff030000000000000000286a26060000007b03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd7064d817000000001976a914d9bbccb1b996061b735b35841d90844c263fbc7388ac00902f50090000001976a9145be32612930b8323add2212a4ec03c1562084f8488ac00000000") XCTAssertTrue(txSize.uintValue == 2645) let transaction = BTCTransaction(hex: txHex) XCTAssertTrue(transaction?.inputs.count == 17) let input0 = transaction?.inputs[0] as! BTCTransactionInput XCTAssertTrue(input0.previousTransactionID == txid0) XCTAssertTrue(input0.outpoint.index == 0) let input1 = transaction?.inputs[1] as! BTCTransactionInput XCTAssertTrue(input1.previousTransactionID == txid1) XCTAssertTrue(input1.outpoint.index == 1) let input2 = transaction?.inputs[2] as! BTCTransactionInput XCTAssertTrue(input2.previousTransactionID == txid2) XCTAssertTrue(input2.outpoint.index == 0) let input3 = transaction?.inputs[3] as! BTCTransactionInput XCTAssertTrue(input3.previousTransactionID == txid3) XCTAssertTrue(input3.outpoint.index == 1) let input4 = transaction?.inputs[4] as! BTCTransactionInput XCTAssertTrue(input4.previousTransactionID == txid4) XCTAssertTrue(input4.outpoint.index == 0) let input5 = transaction?.inputs[5] as! BTCTransactionInput XCTAssertTrue(input5.previousTransactionID == txid5) XCTAssertTrue(input5.outpoint.index == 1) let input6 = transaction?.inputs[6] as! BTCTransactionInput XCTAssertTrue(input6.previousTransactionID == txid6) XCTAssertTrue(input6.outpoint.index == 1) let input7 = transaction?.inputs[7] as! BTCTransactionInput XCTAssertTrue(input7.previousTransactionID == txid7) XCTAssertTrue(input7.outpoint.index == 0) let input8 = transaction?.inputs[8] as! BTCTransactionInput XCTAssertTrue(input8.previousTransactionID == txid8) XCTAssertTrue(input8.outpoint.index == 1) let input9 = transaction?.inputs[9] as! BTCTransactionInput XCTAssertTrue(input9.previousTransactionID == txid9) XCTAssertTrue(input9.outpoint.index == 0) let input10 = transaction?.inputs[10] as! BTCTransactionInput XCTAssertTrue(input10.previousTransactionID == txid10) XCTAssertTrue(input10.outpoint.index == 1) let input11 = transaction?.inputs[11] as! BTCTransactionInput XCTAssertTrue(input11.previousTransactionID == txid11) XCTAssertTrue(input11.outpoint.index == 0) let input12 = transaction?.inputs[12] as! BTCTransactionInput XCTAssertTrue(input12.previousTransactionID == txid12) XCTAssertTrue(input12.outpoint.index == 0) let input13 = transaction?.inputs[13] as! BTCTransactionInput XCTAssertTrue(input13.previousTransactionID == txid13) XCTAssertTrue(input13.outpoint.index == 0) let input14 = transaction?.inputs[14] as! BTCTransactionInput XCTAssertTrue(input14.previousTransactionID == txid14) XCTAssertTrue(input14.outpoint.index == 1) let input15 = transaction?.inputs[15] as! BTCTransactionInput XCTAssertTrue(input15.previousTransactionID == txid15) XCTAssertTrue(input15.outpoint.index == 0) let input16 = transaction?.inputs[16] as! BTCTransactionInput XCTAssertTrue(input16.previousTransactionID == txid16) XCTAssertTrue(input16.outpoint.index == 0) XCTAssertTrue(transaction?.outputs.count == 3) let output0 = transaction?.outputs[0] as! BTCTransactionOutput XCTAssertTrue(output0.script.hex == "6a26060000007b03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd") XCTAssertTrue(output0.value == 0) XCTAssertTrue(output0.script.standardAddress == nil) let output1 = transaction?.outputs[1] as! BTCTransactionOutput XCTAssertTrue(output1.script.hex == "76a914d9bbccb1b996061b735b35841d90844c263fbc7388ac") XCTAssertTrue(output1.value == 400057456) XCTAssertTrue(output1.script.standardAddress.base58String == "1LrGcAw6WPFK4re5mt4MQfXj9xLeBYojRm") let output2 = transaction?.outputs[2] as! BTCTransactionOutput XCTAssertTrue(output2.script.hex == "76a9145be32612930b8323add2212a4ec03c1562084f8488ac") XCTAssertTrue(output2.value == 40000000000) XCTAssertTrue(output2.script.standardAddress.base58String == "19Nrc2Xm226xmSbeGZ1BVtX7DUm4oCx8Pm") XCTAssertTrue(realToAddresses.count == 2) XCTAssertTrue(realToAddresses[0] == "19Nrc2Xm226xmSbeGZ1BVtX7DUm4oCx8Pm") XCTAssertTrue(realToAddresses[1] == "1LrGcAw6WPFK4re5mt4MQfXj9xLeBYojRm") } testCreateSignedSerializedTransactionHexAndBIP69_4_1() testCreateSignedSerializedTransactionHexAndBIP69_4_2() } func testCreateSignedSerializedTransactionHexAndBIP69_5() -> () { let feeAmount = TLCoin(bitcoinAmount: "0.00000", bitcoinDenomination: TLBitcoinDenomination.bitcoin) let toAddress = "1KAD5EnzzLtrSo2Da2G4zzD7uZrjk8zRAv" let toAmount = TLCoin(bitcoinAmount: "8", bitcoinDenomination: TLBitcoinDenomination.bitcoin) let txid0 = "35288d269cee1941eaebb2ea85e32b42cdb2b04284a56d8b14dcc3f5c65d6055" let txid1 = "35288d269cee1941eaebb2ea85e32b42cdb2b04284a56d8b14dcc3f5c65d6055" let unspentOutput0 = mockUnspentOutput(txid0, 700000000, 0) let unspentOutput1 = mockUnspentOutput(txid1, 1000000000, 1) func testCreateSignedSerializedTransactionHexAndBIP69_5_1() -> () { let toAddressesAndAmounts = [["address": toAddress, "amount": toAmount]] accountObject.unspentOutputs = NSMutableArray(capacity: 2) accountObject.unspentOutputs!.add(unspentOutput0) accountObject.unspentOutputs!.add(unspentOutput1) accountObject.stealthPaymentUnspentOutputs = NSMutableArray(capacity: 0) let ret = godSend.createSignedSerializedTransactionHex(toAddressesAndAmounts as NSArray, feeAmount: feeAmount, error: { (data: String?) in }) let txHexAndTxHash = ret.0 let realToAddresses = ret.1 let txHex = txHexAndTxHash!.object(forKey: "txHex") as! String let txHash = txHexAndTxHash!.object(forKey: "txHash") as! String let txSize = txHexAndTxHash!.object(forKey: "txSize") as! NSNumber XCTAssertTrue(txHash == "7993558323324a61028e592f8e1421ec131d48ecba09627645d7c2aec49b838e") XCTAssertTrue(txHex == "010000000255605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d2835000000006a473044022044fb6ce5ce9ae0ef3d381f749612a993d98bf6c293e1e6bbc73979c0c7d7f88a0220749e495896ca230272d0d6e93a53019e2479b4829f09d574c26e21b5ef614da80121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff55605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d2835010000006a473044022020573f136e62c66ba6130b1fbe7fb87ba8cfbfd9af89227fe10a878a189c3569022043f1fcf6b88cd6befee91899ae92905074fe48ef3f82206638752db6bd90201a0121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff020008af2f000000001976a914c73015fa62d972ebb3b241fe8c936657b13fabd788ac00e9a435000000001976a91482d6e3eb4cb25dfd325b4af06948d3a2e064a5f788ac00000000") XCTAssertTrue(txSize.uintValue == 376) let transaction = BTCTransaction(hex: txHex) XCTAssertTrue(transaction?.inputs.count == 2) let input0 = transaction?.inputs[0] as! BTCTransactionInput XCTAssertTrue(input0.previousTransactionID == txid0) XCTAssertTrue(input0.outpoint.index == 0) let input1 = transaction?.inputs[1] as! BTCTransactionInput XCTAssertTrue(input1.previousTransactionID == txid1) XCTAssertTrue(input1.outpoint.index == 1) XCTAssertTrue(transaction?.outputs.count == 2) let output0 = transaction?.outputs[0] as! BTCTransactionOutput XCTAssertTrue(output0.script.hex == "76a914c73015fa62d972ebb3b241fe8c936657b13fabd788ac") XCTAssertTrue(output0.value == 800000000) XCTAssertTrue(output0.script.standardAddress.base58String == "1KAD5EnzzLtrSo2Da2G4zzD7uZrjk8zRAv") let output1 = transaction?.outputs[1] as! BTCTransactionOutput XCTAssertTrue(output1.script.hex == "76a91482d6e3eb4cb25dfd325b4af06948d3a2e064a5f788ac") XCTAssertTrue(output1.value == 900000000) XCTAssertTrue(output1.script.standardAddress.base58String == changeAddress0) XCTAssertTrue(realToAddresses.count == 1) XCTAssertTrue(realToAddresses[0] == "1KAD5EnzzLtrSo2Da2G4zzD7uZrjk8zRAv") } func testCreateSignedSerializedTransactionHexAndBIP69_5_2() -> () { let toAddressesAndAmounts = [["address": toAddress, "amount": toAmount]] accountObject.unspentOutputs = NSMutableArray(capacity: 2) accountObject.unspentOutputs!.add(unspentOutput1) accountObject.unspentOutputs!.add(unspentOutput0) accountObject.stealthPaymentUnspentOutputs = NSMutableArray(capacity: 0) let ret = godSend.createSignedSerializedTransactionHex(toAddressesAndAmounts as NSArray, feeAmount: feeAmount, error: { (data: String?) in }) let txHexAndTxHash = ret.0 let realToAddresses = ret.1 let txHex = txHexAndTxHash!.object(forKey: "txHex") as! String let txHash = txHexAndTxHash!.object(forKey: "txHash") as! String let txSize = txHexAndTxHash!.object(forKey: "txSize") as! NSNumber XCTAssertTrue(txHash == "9d332311d0a172ef2f875fc76ac261ddac4debbd86cbf0711d9c86a5024423dd") XCTAssertTrue(txHex == "010000000155605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d2835010000006b483045022100b5a727e693ddc88a13e50513252a3d508757a6bfbd2f4b9b369f37fac41c28c00220392dbb2f4d99c4efd1d346513e695163b1e1564399d4cc1a9f5779b04a5f03aa0121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff0200c2eb0b000000001976a91482d6e3eb4cb25dfd325b4af06948d3a2e064a5f788ac0008af2f000000001976a914c73015fa62d972ebb3b241fe8c936657b13fabd788ac00000000") XCTAssertTrue(txSize.uintValue == 227) let transaction = BTCTransaction(hex: txHex) XCTAssertTrue(transaction?.inputs.count == 1) let input0 = transaction?.inputs[0] as! BTCTransactionInput XCTAssertTrue(input0.previousTransactionID == txid1) XCTAssertTrue(input0.outpoint.index == 1) XCTAssertTrue(transaction?.outputs.count == 2) let output0 = transaction?.outputs[0] as! BTCTransactionOutput XCTAssertTrue(output0.script.hex == "76a91482d6e3eb4cb25dfd325b4af06948d3a2e064a5f788ac") XCTAssertTrue(output0.value == 200000000) XCTAssertTrue(output0.script.standardAddress.base58String == changeAddress0) let output1 = transaction?.outputs[1] as! BTCTransactionOutput XCTAssertTrue(output1.script.hex == "76a914c73015fa62d972ebb3b241fe8c936657b13fabd788ac") XCTAssertTrue(output1.value == 800000000) XCTAssertTrue(output1.script.standardAddress.base58String == "1KAD5EnzzLtrSo2Da2G4zzD7uZrjk8zRAv") XCTAssertTrue(realToAddresses.count == 1) XCTAssertTrue(realToAddresses[0] == "1KAD5EnzzLtrSo2Da2G4zzD7uZrjk8zRAv") } testCreateSignedSerializedTransactionHexAndBIP69_5_1() testCreateSignedSerializedTransactionHexAndBIP69_5_2() } func testCreateSignedSerializedTransactionHexAndBIP69_6() -> () { let feeAmount = TLCoin(bitcoinAmount: "0.00000", bitcoinDenomination: TLBitcoinDenomination.bitcoin) let toAddress = "1KAD5EnzzLtrSo2Da2G4zzD7uZrjk8zRAv" let toAddress2 = "1DZTzaBHUDM7T3QvUKBz4qXMRpkg8jsfB5" let toAmount = TLCoin(bitcoinAmount: "1", bitcoinDenomination: TLBitcoinDenomination.bitcoin) let toAmount2 = TLCoin(bitcoinAmount: "1", bitcoinDenomination: TLBitcoinDenomination.bitcoin) let txid0 = "35288d269cee1941eaebb2ea85e32b42cdb2b04284a56d8b14dcc3f5c65d6055" let txid1 = "35288d269cee1941eaebb2ea85e32b42cdb2b04284a56d8b14dcc3f5c65d6055" let unspentOutput0 = mockUnspentOutput(txid0, 100000000, 0) let unspentOutput1 = mockUnspentOutput(txid1, 100000000, 1) func testCreateSignedSerializedTransactionHexAndBIP69_6_1() -> () { let toAddressesAndAmounts = [["address": toAddress, "amount": toAmount], ["address": toAddress2, "amount": toAmount2]] accountObject.unspentOutputs = NSMutableArray(capacity: 2) accountObject.unspentOutputs!.add(unspentOutput0) accountObject.unspentOutputs!.add(unspentOutput1) accountObject.stealthPaymentUnspentOutputs = NSMutableArray(capacity: 0) let ret = godSend.createSignedSerializedTransactionHex(toAddressesAndAmounts as NSArray, feeAmount: feeAmount, error: { (data: String?) in }) let txHexAndTxHash = ret.0 let realToAddresses = ret.1 let txHex = txHexAndTxHash!.object(forKey: "txHex") as! String let txHash = txHexAndTxHash!.object(forKey: "txHash") as! String let txSize = txHexAndTxHash!.object(forKey: "txSize") as! NSNumber XCTAssertTrue(txHash == "1b27e859e51c272c6fa539e8579649cf0ba3d6ac560c38e3d93d83edd85adedc") XCTAssertTrue(txHex == "010000000255605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d2835000000006b483045022100a24aec6b79e3907be855490f4e9e4a7c28c67181b707df50599e1b7381f578810220275c84a766f8088e92de8e01de291ce7edc50a7dc8e8afa28741fe80c0ab91860121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff55605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d2835010000006a473044022014ed6ef24ff1048d29ec8b2ed8602299e85b4a39a98df7cc3f0ea02db11a345c022008d955f96e52fc85d2fe0d42c6f3c4b04fc6b43ef87978c4a01c10afb59041a40121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff0200e1f505000000001976a91489c55a3ca6676c9f7f260a6439c83249b747380288ac00e1f505000000001976a914c73015fa62d972ebb3b241fe8c936657b13fabd788ac00000000") XCTAssertTrue(txSize.uintValue == 376) let transaction = BTCTransaction(hex: txHex) XCTAssertTrue(transaction?.inputs.count == 2) let input0 = transaction?.inputs[0] as! BTCTransactionInput XCTAssertTrue(input0.previousTransactionID == txid0) XCTAssertTrue(input0.outpoint.index == 0) let input1 = transaction?.inputs[1] as! BTCTransactionInput XCTAssertTrue(input1.previousTransactionID == txid1) XCTAssertTrue(input1.outpoint.index == 1) XCTAssertTrue(transaction?.outputs.count == 2) let output0 = transaction?.outputs[0] as! BTCTransactionOutput XCTAssertTrue(output0.script.hex == "76a91489c55a3ca6676c9f7f260a6439c83249b747380288ac") XCTAssertTrue(output0.value == 100000000) XCTAssertTrue(output0.script.standardAddress.base58String == "1DZTzaBHUDM7T3QvUKBz4qXMRpkg8jsfB5") let output1 = transaction?.outputs[1] as! BTCTransactionOutput XCTAssertTrue(output1.script.hex == "76a914c73015fa62d972ebb3b241fe8c936657b13fabd788ac") XCTAssertTrue(output1.value == 100000000) XCTAssertTrue(output1.script.standardAddress.base58String == "1KAD5EnzzLtrSo2Da2G4zzD7uZrjk8zRAv") XCTAssertTrue(realToAddresses.count == 2) XCTAssertTrue(realToAddresses[0] == "1KAD5EnzzLtrSo2Da2G4zzD7uZrjk8zRAv") XCTAssertTrue(realToAddresses[1] == "1DZTzaBHUDM7T3QvUKBz4qXMRpkg8jsfB5") } testCreateSignedSerializedTransactionHexAndBIP69_6_1() } func testColdWallet_1() -> () { let feeAmount = TLCoin(bitcoinAmount: "0.00000", bitcoinDenomination: TLBitcoinDenomination.bitcoin) let toAddress = "1KAD5EnzzLtrSo2Da2G4zzD7uZrjk8zRAv" let toAddress2 = "1DZTzaBHUDM7T3QvUKBz4qXMRpkg8jsfB5" let toAmount = TLCoin(bitcoinAmount: "1", bitcoinDenomination: TLBitcoinDenomination.bitcoin) let toAmount2 = TLCoin(bitcoinAmount: "24", bitcoinDenomination: TLBitcoinDenomination.bitcoin) let txid0 = "35288d269cee1941eaebb2ea85e32b42cdb2b04284a56d8b14dcc3f5c65d6055" let txid1 = "35288d269cee1941eaebb2ea85e32b42cdb2b04284a56d8b14dcc3f5c65d6055" let unspentOutput0 = mockUnspentOutput(txid0, 100000000, 0) let unspentOutput1 = mockUnspentOutput(txid1, 2400000000, 1) func testColdWallet_1_1() -> () { let toAddressesAndAmounts = [["address": toAddress, "amount": toAmount], ["address": toAddress2, "amount": toAmount2]] accountObject.unspentOutputs = NSMutableArray(capacity: 2) accountObject.unspentOutputs!.add(unspentOutput0) accountObject.unspentOutputs!.add(unspentOutput1) accountObject.stealthPaymentUnspentOutputs = NSMutableArray(capacity: 0) let ret = godSend.createSignedSerializedTransactionHex(toAddressesAndAmounts as NSArray, feeAmount: feeAmount, signTx: false, error: { (data: String?) in }) let txHexAndTxHash = ret.0 let realToAddresses = ret.1 let txInputsAccountHDIdxes = ret.2 let unSignedTx = txHexAndTxHash!.object(forKey: "txHex") as! String let inputScripts = txHexAndTxHash!.object(forKey: "inputScripts") as! NSArray let unsignedTxAirGapDataBase64 = TLColdWallet.createSerializedUnsignedTxAipGapData(unSignedTx, extendedPublicKey: extendPubKey, inputScripts: inputScripts, txInputsAccountHDIdxes: txInputsAccountHDIdxes!) NSLog("testColdWallet_1_1 unsignedTxAirGapDataBase64 \(unsignedTxAirGapDataBase64)"); XCTAssertTrue(unsignedTxAirGapDataBase64 == "eyJ2IjoiMSIsImFjY291bnRfcHVibGljX2tleSI6InhwdWI2RDFoNjV6cTlGUjJwbXZRTkI2RmlpajI0ZFl4cEpmSGltWXhpYm1meEJmZ3pmcG9iVlNqUXdjdkZQcjdwVEFUUmlzcHJjMll3WVlXaXlzVUV2SjF1OWl1QVFLTU5zaUxuMlBQU3J0VkZ0NiIsInVuc2lnbmVkX3R4X2Jhc2U2NCI6IkFRQUFBQUpWWUYzRzljUGNGSXR0cFlSQ3NMTE5RaXZqaGVxeTYrcEJHZTZjSm8wb05RQUFBQUFBXC9cL1wvXC9cLzFWZ1hjYjF3OXdVaTIybGhFS3dzczFDSytPRjZyTHI2a0VaN3B3bWpTZzFBUUFBQUFEXC9cL1wvXC9cL0FnRGg5UVVBQUFBQUdYYXBGTWN3RmZwaTJYTHJzN0pCXC9veVRabGV4UDZ2WGlLd0FHQTJQQUFBQUFCbDJxUlNKeFZvOHBtZHNuMzhtQ21RNXlESkp0MGM0QW9pc0FBQUFBQT09IiwidHhfaW5wdXRzX2FjY291bnRfaGRfaWR4ZXMiOlt7ImlzX2NoYW5nZSI6ZmFsc2UsImlkeCI6MH0seyJpc19jaGFuZ2UiOmZhbHNlLCJpZHgiOjB9XSwiaW5wdXRfc2NyaXB0cyI6WyI3NmE5MTRjNmI0ZWJhOTcyYzIyN2FjMDQ1OGRiYTk1MWI0ODEyMzFlNGQ1ZmQ3ODhhYyIsIjc2YTkxNGM2YjRlYmE5NzJjMjI3YWMwNDU4ZGJhOTUxYjQ4MTIzMWU0ZDVmZDc4OGFjIl19") let unsignedTxAirGapDataBase64PartsArray = TLColdWallet.splitStringToArray(unsignedTxAirGapDataBase64!) //Pass unsigned tx here ---------------------------------------------------------------------------------------- var passedUnsignedTxairGapDataBase64 = "" for unsignedTxAirGapDataBase64Part in unsignedTxAirGapDataBase64PartsArray { let ret = TLColdWallet.parseScannedPart(unsignedTxAirGapDataBase64Part) let dataPart = ret.0 //let partNumber = ret.1 // unused in test //let totalParts = ret.2 // unused in test passedUnsignedTxairGapDataBase64 += dataPart } XCTAssertTrue(passedUnsignedTxairGapDataBase64 == unsignedTxAirGapDataBase64) do { let serializedSignedAipGapData = try TLColdWallet.createSerializedSignedTxAipGapData(passedUnsignedTxairGapDataBase64, mnemonicOrExtendedPrivateKey: backupPassphrase, isTestnet: false) NSLog("testColdWallet_1_1 serializedSignedAipGapData \(serializedSignedAipGapData)"); XCTAssertTrue(serializedSignedAipGapData == "eyJ0eEhleCI6IjAxMDAwMDAwMDI1NTYwNWRjNmY1YzNkYzE0OGI2ZGE1ODQ0MmIwYjJjZDQyMmJlMzg1ZWFiMmViZWE0MTE5ZWU5YzI2OGQyODM1MDAwMDAwMDA2YTQ3MzA0NDAyMjA0NDliMWY5NTY4N2JmNDY5ZmI5NTRiY2RiYmMwYWUzNjJmZTliZDZiYTg4YzViNGRkMjI3ZDlhNWMzN2ViODJhMDIyMDM0NDBiZjZiNDE3ODc4NjkxM2ExOTczNDRkMDk5OWE3ZDk4ZDI0NjA5OWRjZGRmNGJmOWIyNDQ3M2E0ZTdhOWEwMTIxMDI3ZWNiYTllYmM0Njk5ZGY3ZjU1N2M0ZTE4MTkyZWZiNWM5N2IxZmY0ZWNkY2ViYjRlMjFiYjdkMWZlZDIyMDNhZmZmZmZmZmY1NTYwNWRjNmY1YzNkYzE0OGI2ZGE1ODQ0MmIwYjJjZDQyMmJlMzg1ZWFiMmViZWE0MTE5ZWU5YzI2OGQyODM1MDEwMDAwMDA2YTQ3MzA0NDAyMjAxZjZhNGE4N2QwNTg0MTU3NDcxMjEwYzFlMTI2ZTY0ZTUyZjU2NWU5NTBmZWI4MDA0NWZjODU1ODI5ZGYzZGE0MDIyMDU5ZmQ3NWZlNTEyNjJhYTdiN2YyMTQ1MzQzNTdlZDI3ODZhOWIzZGNiMTI0OTMxMTIwMjc3MTFhZWJjODQ3OGEwMTIxMDI3ZWNiYTllYmM0Njk5ZGY3ZjU1N2M0ZTE4MTkyZWZiNWM5N2IxZmY0ZWNkY2ViYjRlMjFiYjdkMWZlZDIyMDNhZmZmZmZmZmYwMjAwZTFmNTA1MDAwMDAwMDAxOTc2YTkxNGM3MzAxNWZhNjJkOTcyZWJiM2IyNDFmZThjOTM2NjU3YjEzZmFiZDc4OGFjMDAxODBkOGYwMDAwMDAwMDE5NzZhOTE0ODljNTVhM2NhNjY3NmM5ZjdmMjYwYTY0MzljODMyNDliNzQ3MzgwMjg4YWMwMDAwMDAwMCIsInR4SGFzaCI6ImZiYWNmZWRlNTVkYzZhNzc5NzgyYmE4ZmEyMjgxMzg2MGI3ZWYwN2Q4MmMzYWJlYmI4ZjI5MGIzMTQxYmY5NjUiLCJ0eFNpemUiOjM3Nn0=") let signedTxAirGapDataBase64PartsArray = TLColdWallet.splitStringToArray(serializedSignedAipGapData!) //Pass signed tx here ---------------------------------------------------------------------------------------- var passedSignedTxairGapDataBase64 = "" for signedTxAirGapDataBase64Part in signedTxAirGapDataBase64PartsArray { let ret = TLColdWallet.parseScannedPart(signedTxAirGapDataBase64Part) let dataPart = ret.0 //let partNumber = ret.1 // unused in test //let totalParts = ret.2 // unused in test passedSignedTxairGapDataBase64 += dataPart } XCTAssertTrue(passedSignedTxairGapDataBase64 == serializedSignedAipGapData) let signedTxData = TLColdWallet.getSignedTxData(passedSignedTxairGapDataBase64) let txHex = signedTxData!["txHex"] as! String let txHash = signedTxData!["txHash"] as! String let txSize = signedTxData!["txSize"] as! NSNumber XCTAssertTrue(txHash == "fbacfede55dc6a779782ba8fa22813860b7ef07d82c3abebb8f290b3141bf965") XCTAssertTrue(txHex == "010000000255605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d2835000000006a4730440220449b1f95687bf469fb954bcdbbc0ae362fe9bd6ba88c5b4dd227d9a5c37eb82a02203440bf6b4178786913a197344d0999a7d98d246099dcddf4bf9b24473a4e7a9a0121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff55605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d2835010000006a47304402201f6a4a87d0584157471210c1e126e64e52f565e950feb80045fc855829df3da4022059fd75fe51262aa7b7f214534357ed2786a9b3dcb12493112027711aebc8478a0121027ecba9ebc4699df7f557c4e18192efb5c97b1ff4ecdcebb4e21bb7d1fed2203affffffff0200e1f505000000001976a914c73015fa62d972ebb3b241fe8c936657b13fabd788ac00180d8f000000001976a91489c55a3ca6676c9f7f260a6439c83249b747380288ac00000000") XCTAssertTrue(txSize.uintValue == 376) let transaction = BTCTransaction(hex: txHex) XCTAssertTrue(transaction?.inputs.count == 2) let input0 = transaction?.inputs[0] as! BTCTransactionInput XCTAssertTrue(input0.previousTransactionID == txid0) XCTAssertTrue(input0.outpoint.index == 0) let input1 = transaction?.inputs[1] as! BTCTransactionInput XCTAssertTrue(input1.previousTransactionID == txid1) XCTAssertTrue(input1.outpoint.index == 1) XCTAssertTrue(transaction?.outputs.count == 2) let output0 = transaction?.outputs[0] as! BTCTransactionOutput XCTAssertTrue(output0.script.hex == "76a914c73015fa62d972ebb3b241fe8c936657b13fabd788ac") XCTAssertTrue(output0.value == 100000000) XCTAssertTrue(output0.script.standardAddress.base58String == "1KAD5EnzzLtrSo2Da2G4zzD7uZrjk8zRAv") let output1 = transaction?.outputs[1] as! BTCTransactionOutput XCTAssertTrue(output1.script.hex == "76a91489c55a3ca6676c9f7f260a6439c83249b747380288ac") XCTAssertTrue(output1.value == 2400000000) XCTAssertTrue(output1.script.standardAddress.base58String == "1DZTzaBHUDM7T3QvUKBz4qXMRpkg8jsfB5") XCTAssertTrue(realToAddresses.count == 2) XCTAssertTrue(realToAddresses[0] == "1KAD5EnzzLtrSo2Da2G4zzD7uZrjk8zRAv") XCTAssertTrue(realToAddresses[1] == "1DZTzaBHUDM7T3QvUKBz4qXMRpkg8jsfB5") } catch TLColdWallet.TLColdWalletError.InvalidKey(let error) { } catch TLColdWallet.TLColdWalletError.MisMatchExtendedPublicKey(let error) { } catch { } } testColdWallet_1_1() } testCreateSignedSerializedTransactionHexAndBIP69_1() testCreateSignedSerializedTransactionHexAndBIP69_2() testCreateSignedSerializedTransactionHexAndBIP69_3() testCreateSignedSerializedTransactionHexAndBIP69_4() testCreateSignedSerializedTransactionHexAndBIP69_5() testCreateSignedSerializedTransactionHexAndBIP69_6() testColdWallet_1() } func testCoin() { var coin:TLCoin coin = TLCurrencyFormat.bitcoinAmountStringToCoin("0.0") XCTAssertTrue(coin.toUInt64() == 0) coin = TLCurrencyFormat.bitcoinAmountStringToCoin("0") XCTAssertTrue(coin.toUInt64() == 0) coin = TLCurrencyFormat.bitcoinAmountStringToCoin("0.00000001") XCTAssertTrue(coin.toUInt64() == 1) //coin = TLCurrencyFormat.bitcoinAmountStringToCoin("0.000000011") //XCTAssertTrue(coin.toUInt64() == 1) //coin = TLCurrencyFormat.bitcoinAmountStringToCoin("0.000000015") //XCTAssertTrue(coin.toUInt64() == 2) //coin = TLCurrencyFormat.bitcoinAmountStringToCoin("0.000000019") //XCTAssertTrue(coin.toUInt64() == 2) coin = TLCurrencyFormat.bitcoinAmountStringToCoin("0.1") XCTAssertTrue(coin.toUInt64() == 10000000) coin = TLCurrencyFormat.bitcoinAmountStringToCoin(".99999998") XCTAssertTrue(coin.toUInt64() == 99999998) //coin = TLCurrencyFormat.bitcoinAmountStringToCoin(".999999985") //XCTAssertTrue(coin.toUInt64() == 99999998) //coin = TLCurrencyFormat.bitcoinAmountStringToCoin(".999999986") //XCTAssertTrue(coin.toUInt64() == 99999999) //coin = TLCurrencyFormat.bitcoinAmountStringToCoin(".999999989") //XCTAssertTrue(coin.toUInt64() == 99999999) coin = TLCurrencyFormat.bitcoinAmountStringToCoin("0.99999999") XCTAssertTrue(coin.toUInt64() == 99999999) coin = TLCurrencyFormat.bitcoinAmountStringToCoin(".99999999") XCTAssertTrue(coin.toUInt64() == 99999999) coin = TLCurrencyFormat.bitcoinAmountStringToCoin("1.00000000") XCTAssertTrue(coin.toUInt64() == 100000000) coin = TLCurrencyFormat.bitcoinAmountStringToCoin("1") XCTAssertTrue(coin.toUInt64() == 100000000) coin = TLCurrencyFormat.bitcoinAmountStringToCoin("1.00000001") XCTAssertTrue(coin.toUInt64() == 100000001) coin = TLCurrencyFormat.bitcoinAmountStringToCoin("1.99999998") XCTAssertTrue(coin.toUInt64() == 199999998) coin = TLCurrencyFormat.bitcoinAmountStringToCoin("1.99999999") XCTAssertTrue(coin.toUInt64() == 199999999) coin = TLCurrencyFormat.bitcoinAmountStringToCoin("2.0") XCTAssertTrue(coin.toUInt64() == 200000000) coin = TLCurrencyFormat.bitcoinAmountStringToCoin("2.00000001") XCTAssertTrue(coin.toUInt64() == 200000001) coin = TLCurrencyFormat.bitcoinAmountStringToCoin("31,821.95320551") XCTAssertTrue(coin.toUInt64() == 3182195320551) coin = TLCurrencyFormat.bitcoinAmountStringToCoin("31821.95320551") XCTAssertTrue(coin.toUInt64() == 3182195320551) coin = TLCurrencyFormat.bitcoinAmountStringToCoin("21,000,000") XCTAssertTrue(coin.toUInt64() == 2100000000000000) coin = TLCurrencyFormat.bitcoinAmountStringToCoin("21000000") XCTAssertTrue(coin.toUInt64() == 2100000000000000) let spainLocale = Locale(identifier: "es_ES") coin = TLCurrencyFormat.bitcoinAmountStringToCoin("0,0", locale: spainLocale) XCTAssertTrue(coin.toUInt64() == 0) coin = TLCurrencyFormat.bitcoinAmountStringToCoin("0", locale: spainLocale) XCTAssertTrue(coin.toUInt64() == 0) coin = TLCurrencyFormat.bitcoinAmountStringToCoin("0,00000001", locale: spainLocale) XCTAssertTrue(coin.toUInt64() == 1) coin = TLCurrencyFormat.bitcoinAmountStringToCoin("0,1", locale: spainLocale) XCTAssertTrue(coin.toUInt64() == 10000000) coin = TLCurrencyFormat.bitcoinAmountStringToCoin(",99999998", locale: spainLocale) XCTAssertTrue(coin.toUInt64() == 99999998) coin = TLCurrencyFormat.bitcoinAmountStringToCoin("0,99999999", locale: spainLocale) XCTAssertTrue(coin.toUInt64() == 99999999) coin = TLCurrencyFormat.bitcoinAmountStringToCoin(",99999999", locale: spainLocale) XCTAssertTrue(coin.toUInt64() == 99999999) coin = TLCurrencyFormat.bitcoinAmountStringToCoin("1,00000000", locale: spainLocale) XCTAssertTrue(coin.toUInt64() == 100000000) coin = TLCurrencyFormat.bitcoinAmountStringToCoin("1", locale: spainLocale) XCTAssertTrue(coin.toUInt64() == 100000000) coin = TLCurrencyFormat.bitcoinAmountStringToCoin("1,00000001", locale: spainLocale) XCTAssertTrue(coin.toUInt64() == 100000001) coin = TLCurrencyFormat.bitcoinAmountStringToCoin("1,99999998", locale: spainLocale) XCTAssertTrue(coin.toUInt64() == 199999998) coin = TLCurrencyFormat.bitcoinAmountStringToCoin("1,99999999", locale: spainLocale) XCTAssertTrue(coin.toUInt64() == 199999999) coin = TLCurrencyFormat.bitcoinAmountStringToCoin("2,0", locale: spainLocale) XCTAssertTrue(coin.toUInt64() == 200000000) coin = TLCurrencyFormat.bitcoinAmountStringToCoin("2,00000001", locale: spainLocale) XCTAssertTrue(coin.toUInt64() == 200000001) coin = TLCurrencyFormat.bitcoinAmountStringToCoin("31.821,95320551", locale: spainLocale) XCTAssertTrue(coin.toUInt64() == 3182195320551) coin = TLCurrencyFormat.bitcoinAmountStringToCoin("31821,95320551", locale: spainLocale) XCTAssertTrue(coin.toUInt64() == 3182195320551) coin = TLCurrencyFormat.bitcoinAmountStringToCoin("21.000.000", locale: spainLocale) XCTAssertTrue(coin.toUInt64() == 2100000000000000) coin = TLCurrencyFormat.bitcoinAmountStringToCoin("21000000", locale: spainLocale) XCTAssertTrue(coin.toUInt64() == 2100000000000000) TLPreferences.setBitcoinDisplay("1") coin = TLCurrencyFormat.properBitcoinAmountStringToCoin("1000") XCTAssertTrue(coin.toUInt64() == 100000000) TLPreferences.setBitcoinDisplay("2") coin = TLCurrencyFormat.properBitcoinAmountStringToCoin("1000000") XCTAssertTrue(coin.toUInt64() == 100000000) TLPreferences.setBitcoinDisplay("0") } func testCoreBitcoinWrapper() { let outputScript = "76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac" var address = TLCoreBitcoinWrapper.getAddressFromOutputScript(outputScript, isTestnet: false) NSLog("address: %@", address!) XCTAssertTrue(address == "1F3sAm6ZtwLAUnj7d38pGFxtP3RVEvtsbV") address = TLCoreBitcoinWrapper.getAddressFromOutputScript(outputScript, isTestnet: true) NSLog("address: %@", address!) XCTAssertTrue(address == "muZpTpBYhxmRFuCjLc7C6BBDF32C8XVJUi") let secret = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" address = TLCoreBitcoinWrapper.getAddressFromSecret(secret, isTestnet: false) NSLog("address: %@", address!) XCTAssertTrue(address == "1F3sAm6ZtwLAUnj7d38pGFxtP3RVEvtsbV") address = TLCoreBitcoinWrapper.getAddressFromSecret(secret, isTestnet: true) NSLog("address: %@", address!) XCTAssertTrue(address == "muZpTpBYhxmRFuCjLc7C6BBDF32C8XVJUi") var privateKey:String privateKey = TLCoreBitcoinWrapper.privateKeyFromSecret(secret, isTestnet: false) NSLog("privateKey: %@", privateKey) XCTAssertTrue(privateKey == "L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1") privateKey = TLCoreBitcoinWrapper.privateKeyFromSecret(secret, isTestnet: true) NSLog("privateKey: %@", privateKey) XCTAssertTrue(privateKey == "cVDJUtDjdaM25yNVVDLLX3hcHUfth4c7tY3rSc4hy9e8ibtCuj6G") var pubKeyHash:String pubKeyHash = TLCoreBitcoinWrapper.getStandardPubKeyHashScriptFromAddress("1F3sAm6ZtwLAUnj7d38pGFxtP3RVEvtsbV", isTestnet: false) NSLog("pubKeyHash: %@", pubKeyHash) XCTAssertTrue(pubKeyHash == "76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac") pubKeyHash = TLCoreBitcoinWrapper.getStandardPubKeyHashScriptFromAddress("muZpTpBYhxmRFuCjLc7C6BBDF32C8XVJUi", isTestnet: true) NSLog("pubKeyHash: %@", pubKeyHash) XCTAssertTrue(pubKeyHash == "76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac") address = TLCoreBitcoinWrapper.getAddress("L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1", isTestnet: false) NSLog("address: %@", address!) XCTAssertTrue(address! == "1F3sAm6ZtwLAUnj7d38pGFxtP3RVEvtsbV") address = TLCoreBitcoinWrapper.getAddress("5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss", isTestnet: false) NSLog("address: %@", address!) XCTAssertTrue(address! == "1HZwkjkeaoZfTSaJxDw6aKkxp45agDiEzN") address = TLCoreBitcoinWrapper.getAddress("cVDJUtDjdaM25yNVVDLLX3hcHUfth4c7tY3rSc4hy9e8ibtCuj6G", isTestnet: true) NSLog("address: %@", address!) XCTAssertTrue(address! == "muZpTpBYhxmRFuCjLc7C6BBDF32C8XVJUi") address = TLCoreBitcoinWrapper.getAddress("93KCDD4LdP4BDTNBXrvKUCVES2jo9dAKKvhyWpNEMstuxDauHty", isTestnet: true) NSLog("address: %@", address!) XCTAssertTrue(address! == "mx5u3nqdPpzvEZ3vfnuUQEyHg3gHd8zrrH") let compressedPubKey = "03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd" address = TLCoreBitcoinWrapper.getAddressFromPublicKey(compressedPubKey, isTestnet: false) NSLog("address: %@", address!) XCTAssertTrue(address == "1F3sAm6ZtwLAUnj7d38pGFxtP3RVEvtsbV") address = TLCoreBitcoinWrapper.getAddressFromPublicKey(compressedPubKey, isTestnet: true) NSLog("address: %@", address!) XCTAssertTrue(address == "muZpTpBYhxmRFuCjLc7C6BBDF32C8XVJUi") let uncompressedPubKey = "04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235" address = TLCoreBitcoinWrapper.getAddressFromPublicKey(uncompressedPubKey, isTestnet: false) NSLog("address: %@", address!) XCTAssertTrue(address == "1HZwkjkeaoZfTSaJxDw6aKkxp45agDiEzN") address = TLCoreBitcoinWrapper.getAddressFromPublicKey(uncompressedPubKey, isTestnet: true) NSLog("address: %@", address!) XCTAssertTrue(address == "mx5u3nqdPpzvEZ3vfnuUQEyHg3gHd8zrrH") XCTAssertTrue(TLCoreBitcoinWrapper.isAddressVersion0("1F3sAm6ZtwLAUnj7d38pGFxtP3RVEvtsbV", isTestnet: false)) XCTAssertTrue(TLCoreBitcoinWrapper.isAddressVersion0("muZpTpBYhxmRFuCjLc7C6BBDF32C8XVJUi", isTestnet: true)) XCTAssertTrue(TLCoreBitcoinWrapper.isAddressVersion0("n2MLT38EuTYgK8GcDrL2JQqbVvkSCGGf6S", isTestnet: true)) XCTAssertTrue(!TLCoreBitcoinWrapper.isAddressVersion0("3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX", isTestnet: false)) XCTAssertTrue(!TLCoreBitcoinWrapper.isAddressVersion0("2MzQwSSnBHWHqSAqtTVQ6v47XtaisrJa1Vc", isTestnet: true)) // address = 13adjLZo3iUEZuQEeEAkyRvw2nKKrGLuKJ password = "murray rothbard" XCTAssertTrue(TLCoreBitcoinWrapper.isBIP38EncryptedKey("6PfRr3RtH3GKh7qcRUfEe5rAcFBBcKxJAvQWZPwpksfL6dxTpC9kqMctoE", isTestnet: false)) // address = n1nKsF2UvyPgG3QLYupR4mwv1fQwLEJf9b password = "murray rothbard" XCTAssertTrue(TLCoreBitcoinWrapper.isBIP38EncryptedKey("6PfSacWmYziVFFjHqiAHM9nvxsZDMBpDnPtWVQSNgSH9qpo1s1VCCWYEno", isTestnet: true)) XCTAssertTrue(TLCoreBitcoinWrapper.isValidAddress("1F3sAm6ZtwLAUnj7d38pGFxtP3RVEvtsbV", isTestnet: false)) XCTAssertTrue(TLCoreBitcoinWrapper.isValidAddress("muZpTpBYhxmRFuCjLc7C6BBDF32C8XVJUi", isTestnet: true)) XCTAssertTrue(TLCoreBitcoinWrapper.isValidAddress("3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX", isTestnet: false)) XCTAssertTrue(TLCoreBitcoinWrapper.isValidAddress("2MzQwSSnBHWHqSAqtTVQ6v47XtaisrJa1Vc", isTestnet: true)) XCTAssertTrue(TLCoreBitcoinWrapper.isValidAddress("vJmujDzf2PyDEcLQEQWyzVNthLpRAXqTi3ZencThu2WCzrRNi64eFYJP6ZyPWj53hSZBKTcUAk8J5Mb8rZC4wvGn77Sj4Z3yP7zE69", isTestnet: false)) XCTAssertTrue(TLCoreBitcoinWrapper.isValidAddress("waPUEHTatbqyM6RKtsbdCy63fqyjwW6ksSCi5KhD1NTGdYrvAgvSAneAqDooHxVzpMAx8nZLzZTnhAGM1WxpRFvvp9zF6wFuAA7dNW", isTestnet: true)) XCTAssertTrue(TLCoreBitcoinWrapper.isValidPrivateKey("L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1", isTestnet: false)) XCTAssertTrue(TLCoreBitcoinWrapper.isValidPrivateKey("cVDJUtDjdaM25yNVVDLLX3hcHUfth4c7tY3rSc4hy9e8ibtCuj6G", isTestnet: true)) } func testHDWalletWrapper() { let extendPrivKey = "xprv9z2LgaTwJsrjcHqwG9ZFManHWbiUQqwSMYdMvDN4Pr8i7sVf3x8Us9JSQ8FFCT8f7wBDzEVEhTFX3wJdNx2pchEZJ2HNTa4U7NKgM9uWoK6" let extendPubKey = "xpub6D1h65zq9FR2pmvQNB6Fiij24dYxpJfHimYxibmfxBfgzfpobVSjQwcvFPr7pTATRisprc2YwYYWiysUEvJ1u9iuAQKMNsiLn2PPSrtVFt6" let mainAddressIndex0 = [0,0] var mainAddress0:String var walletConfig = TLWalletConfig(isTestnet: false) mainAddress0 = TLHDWalletWrapper.getAddress(extendPubKey, sequence:mainAddressIndex0 as NSArray, isTestnet:walletConfig.isTestnet) NSLog("mainAddress0: %@", mainAddress0) XCTAssertTrue("1K7fXZeeQydcUvbsfvkMSQmiacV5sKRYQz" == mainAddress0) var mainPrivKey0:String mainPrivKey0 = TLHDWalletWrapper.getPrivateKey(extendPrivKey as NSString, sequence:mainAddressIndex0 as NSArray, isTestnet:walletConfig.isTestnet) NSLog("mainPrivKey0: %@", mainPrivKey0) XCTAssertTrue("KwJhkmrjjg3AEX5gvccNAHCDcXnQLwzyZshnp5yK7vXz1mHKqDDq" == mainPrivKey0) walletConfig = TLWalletConfig(isTestnet: true) mainAddress0 = TLHDWalletWrapper.getAddress(extendPubKey, sequence:mainAddressIndex0 as NSArray, isTestnet:walletConfig.isTestnet) NSLog("mainAddress0: %@", mainAddress0) XCTAssertTrue("mydcpcjdE14sG35VPVijGKz3Sc5nsbbeo7" == mainAddress0) mainPrivKey0 = TLHDWalletWrapper.getPrivateKey(extendPrivKey as NSString, sequence:mainAddressIndex0 as NSArray, isTestnet:walletConfig.isTestnet) NSLog("mainPrivKey0: %@", mainPrivKey0) XCTAssertTrue("cMfhDgrbAjjRPxYxK2RVXbhHEm5p1Q6fdurFvWRpd3BzGWQYiFw6" == mainPrivKey0) } } ================================================ FILE: ArcBitTests/BreadWalletTests.m ================================================ // // BreadWalletTests.m // ArcBit // // Created by Tim Lee on 3/22/15. // Copyright (c) 2015 ArcBit. All rights reserved. // #import #import #import #import #import #import #import "BRTransaction.h" #import "BRKey.h" #import "BRKey+BIP38.h" #import "NSMutableData+Bitcoin.h" #import "NSString+Base58.h" BOOL isTestnet = NO; @interface BreadWalletTests : XCTestCase @end @implementation BreadWalletTests - (void)setUp { [super setUp]; // Put setup code here. This method is called before the invocation of each test method in the class. } - (void)tearDown { // Put teardown code here. This method is called after the invocation of each test method in the class. [super tearDown]; } - (void)testExample { // This is an example of a functional test case. XCTAssert(YES, @"Pass"); } - (void)testPerformanceExample { // This is an example of a performance test case. [self measureBlock:^{ // Put the code you want to measure the time of here. }]; } #pragma mark - testBase58 /* - (void)testBase58 { // test bad input NSString *s = [NSString base58WithData:[@"#&$@*^(*#!^" base58ToData]]; XCTAssertTrue(s.length == 0, @"[NSString base58WithData:]"); s = [NSString base58WithData:[@"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" base58ToData]]; XCTAssertEqualObjects(@"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz", s, @"[NSString base58WithData:]"); } #pragma mark - testKey #if ! BITCOIN_TESTNET - (void)testKeyWithPrivateKey { XCTAssertFalse([@"S6c56bnXQiBjk9mqSYE7ykVQ7NzrRz" isValidBitcoinPrivateKey:isTestnet], @"[NSString+Base58 isValidBitcoinPrivateKey]"); XCTAssertTrue([@"S6c56bnXQiBjk9mqSYE7ykVQ7NzrRy" isValidBitcoinPrivateKey:isTestnet], @"[NSString+Base58 isValidBitcoinPrivateKey]"); // mini private key format BRKey *key = [BRKey keyWithPrivateKey:@"S6c56bnXQiBjk9mqSYE7ykVQ7NzrRy" isTestnet:isTestnet]; NSLog(@"privKey:S6c56bnXQiBjk9mqSYE7ykVQ7NzrRy = %@", key.address); XCTAssertEqualObjects(@"1CciesT23BNionJeXrbxmjc7ywfiyM4oLW", key.address, @"[BRKey keyWithPrivateKey:]"); XCTAssertTrue([@"SzavMBLoXU6kDrqtUVmffv" isValidBitcoinPrivateKey:isTestnet], @"[NSString+Base58 isValidBitcoinPrivateKey]"); // old mini private key format key = [BRKey keyWithPrivateKey:@"SzavMBLoXU6kDrqtUVmffv" isTestnet:isTestnet]; NSLog(@"privKey:SzavMBLoXU6kDrqtUVmffv = %@", key.address); XCTAssertEqualObjects(@"1CC3X2gu58d6wXUWMffpuzN9JAfTUWu4Kj", key.address, @"[BRKey keyWithPrivateKey:]"); // uncompressed private key key = [BRKey keyWithPrivateKey:@"5Kb8kLf9zgWQnogidDA76MzPL6TsZZY36hWXMssSzNydYXYB9KF" isTestnet:isTestnet]; NSLog(@"privKey:5Kb8kLf9zgWQnogidDA76MzPL6TsZZY36hWXMssSzNydYXYB9KF = %@", key.address); XCTAssertEqualObjects(@"1CC3X2gu58d6wXUWMffpuzN9JAfTUWu4Kj", key.address, @"[BRKey keyWithPrivateKey:]"); // uncompressed private key export NSLog(@"privKey = %@", key.privateKey); XCTAssertEqualObjects(@"5Kb8kLf9zgWQnogidDA76MzPL6TsZZY36hWXMssSzNydYXYB9KF", key.privateKey, @"[BRKey privateKey]"); // compressed private key key = [BRKey keyWithPrivateKey:@"KyvGbxRUoofdw3TNydWn2Z78dBHSy2odn1d3wXWN2o3SAtccFNJL" isTestnet:isTestnet]; NSLog(@"privKey:KyvGbxRUoofdw3TNydWn2Z78dBHSy2odn1d3wXWN2o3SAtccFNJL = %@", key.address); XCTAssertEqualObjects(@"1JMsC6fCtYWkTjPPdDrYX3we2aBrewuEM3", key.address, @"[BRKey keyWithPrivateKey:]"); // compressed private key export NSLog(@"privKey = %@", key.privateKey); XCTAssertEqualObjects(@"KyvGbxRUoofdw3TNydWn2Z78dBHSy2odn1d3wXWN2o3SAtccFNJL", key.privateKey, @"[BRKey privateKey]"); } #endif #pragma mark - testKeyWithBIP38Key #if ! BITCOIN_TESTNET && ! SKIP_BIP38 - (void)testKeyWithBIP38Key { NSString *intercode, *confcode, *privkey; BRKey *key; // non EC multiplied, uncompressed key = [BRKey keyWithBIP38Key:@"6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEbn2Nh2ZoGg" andPassphrase:@"TestingOneTwoThree" isTestnet:isTestnet]; NSLog(@"privKey = %@", key.privateKey); XCTAssertEqualObjects(@"5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR", key.privateKey, @"[BRKey keyWithBIP38Key:andPassphrase:]"); XCTAssertEqualObjects([key BIP38KeyWithPassphrase:@"TestingOneTwoThree" isTestnet:isTestnet], @"6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEbn2Nh2ZoGg", @"[BRKey BIP38KeyWithPassphrase:]"); key = [BRKey keyWithBIP38Key:@"6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTePPX1dWByq" andPassphrase:@"Satoshi" isTestnet:isTestnet]; NSLog(@"privKey = %@", key.privateKey); XCTAssertEqualObjects(@"5HtasZ6ofTHP6HCwTqTkLDuLQisYPah7aUnSKfC7h4hMUVw2gi5", key.privateKey, @"[BRKey keyWithBIP38Key:andPassphrase:]"); XCTAssertEqualObjects([key BIP38KeyWithPassphrase:@"Satoshi" isTestnet:isTestnet], @"6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTePPX1dWByq", @"[BRKey BIP38KeyWithPassphrase:]"); // non EC multiplied, compressed key = [BRKey keyWithBIP38Key:@"6PYNKZ1EAgYgmQfmNVamxyXVWHzK5s6DGhwP4J5o44cvXdoY7sRzhtpUeo" andPassphrase:@"TestingOneTwoThree" isTestnet:isTestnet]; NSLog(@"privKey = %@", key.privateKey); XCTAssertEqualObjects(@"L44B5gGEpqEDRS9vVPz7QT35jcBG2r3CZwSwQ4fCewXAhAhqGVpP", key.privateKey, @"[BRKey keyWithBIP38Key:andPassphrase:]"); XCTAssertEqualObjects([key BIP38KeyWithPassphrase:@"TestingOneTwoThree" isTestnet:isTestnet], @"6PYNKZ1EAgYgmQfmNVamxyXVWHzK5s6DGhwP4J5o44cvXdoY7sRzhtpUeo", @"[BRKey BIP38KeyWithPassphrase:]"); key = [BRKey keyWithBIP38Key:@"6PYLtMnXvfG3oJde97zRyLYFZCYizPU5T3LwgdYJz1fRhh16bU7u6PPmY7" andPassphrase:@"Satoshi" isTestnet:isTestnet]; NSLog(@"privKey = %@", key.privateKey); XCTAssertEqualObjects(@"KwYgW8gcxj1JWJXhPSu4Fqwzfhp5Yfi42mdYmMa4XqK7NJxXUSK7", key.privateKey, @"[BRKey keyWithBIP38Key:andPassphrase:]"); XCTAssertEqualObjects([key BIP38KeyWithPassphrase:@"Satoshi" isTestnet:isTestnet], @"6PYLtMnXvfG3oJde97zRyLYFZCYizPU5T3LwgdYJz1fRhh16bU7u6PPmY7", @"[BRKey BIP38KeyWithPassphrase:]"); // EC multiplied, uncompressed, no lot/sequence number key = [BRKey keyWithBIP38Key:@"6PfQu77ygVyJLZjfvMLyhLMQbYnu5uguoJJ4kMCLqWwPEdfpwANVS76gTX" andPassphrase:@"TestingOneTwoThree" isTestnet:isTestnet]; NSLog(@"privKey = %@", key.privateKey); XCTAssertEqualObjects(@"5K4caxezwjGCGfnoPTZ8tMcJBLB7Jvyjv4xxeacadhq8nLisLR2", key.privateKey, @"[BRKey keyWithBIP38Key:andPassphrase:]"); intercode = [BRKey BIP38IntermediateCodeWithSalt:0xa50dba6772cb9383ULL andPassphrase:@"TestingOneTwoThree"]; NSLog(@"intercode = %@", intercode); privkey = [BRKey BIP38KeyWithIntermediateCode:intercode seedb:@"99241d58245c883896f80843d2846672d7312e6195ca1a6c".hexToData compressed:NO confirmationCode:&confcode isTestnet:isTestnet]; NSLog(@"confcode = %@", confcode); XCTAssertEqualObjects(@"6PfQu77ygVyJLZjfvMLyhLMQbYnu5uguoJJ4kMCLqWwPEdfpwANVS76gTX", privkey, @"[BRKey BIP38KeyWithIntermediateCode:]"); XCTAssertTrue([BRKey confirmWithBIP38ConfirmationCode:confcode address:@"1PE6TQi6HTVNz5DLwB1LcpMBALubfuN2z2" passphrase:@"TestingOneTwoThree" isTestnet:isTestnet], @"[BRKey confirmWithBIP38ConfirmationCode:]"); key = [BRKey keyWithBIP38Key:@"6PfLGnQs6VZnrNpmVKfjotbnQuaJK4KZoPFrAjx1JMJUa1Ft8gnf5WxfKd" andPassphrase:@"Satoshi" isTestnet:isTestnet]; NSLog(@"privKey = %@", key.privateKey); XCTAssertEqualObjects(@"5KJ51SgxWaAYR13zd9ReMhJpwrcX47xTJh2D3fGPG9CM8vkv5sH", key.privateKey, @"[BRKey keyWithBIP38Key:andPassphrase:]"); intercode = [BRKey BIP38IntermediateCodeWithSalt:0x67010a9573418906ULL andPassphrase:@"Satoshi"]; NSLog(@"intercode = %@", intercode); privkey = [BRKey BIP38KeyWithIntermediateCode:intercode seedb:@"49111e301d94eab339ff9f6822ee99d9f49606db3b47a497".hexToData compressed:NO confirmationCode:&confcode isTestnet:isTestnet]; NSLog(@"confcode = %@", confcode); XCTAssertEqualObjects(@"6PfLGnQs6VZnrNpmVKfjotbnQuaJK4KZoPFrAjx1JMJUa1Ft8gnf5WxfKd", privkey, @"[BRKey BIP38KeyWithIntermediateCode:]"); XCTAssertTrue([BRKey confirmWithBIP38ConfirmationCode:confcode address:@"1CqzrtZC6mXSAhoxtFwVjz8LtwLJjDYU3V" passphrase:@"Satoshi" isTestnet:isTestnet], @"[BRKey confirmWithBIP38ConfirmationCode:]"); // EC multiplied, uncompressed, with lot/sequence number key = [BRKey keyWithBIP38Key:@"6PgNBNNzDkKdhkT6uJntUXwwzQV8Rr2tZcbkDcuC9DZRsS6AtHts4Ypo1j" andPassphrase:@"MOLON LABE" isTestnet:isTestnet]; NSLog(@"privKey = %@", key.privateKey); XCTAssertEqualObjects(@"5JLdxTtcTHcfYcmJsNVy1v2PMDx432JPoYcBTVVRHpPaxUrdtf8", key.privateKey, @"[BRKey keyWithBIP38Key:andPassphrase:]"); intercode = [BRKey BIP38IntermediateCodeWithLot:263183 sequence:1 salt:0x4fca5a97u passphrase:@"MOLON LABE"]; NSLog(@"intercode = %@", intercode); privkey = [BRKey BIP38KeyWithIntermediateCode:intercode seedb:@"87a13b07858fa753cd3ab3f1c5eafb5f12579b6c33c9a53f".hexToData compressed:NO confirmationCode:&confcode isTestnet:isTestnet]; NSLog(@"confcode = %@", confcode); XCTAssertEqualObjects(@"6PgNBNNzDkKdhkT6uJntUXwwzQV8Rr2tZcbkDcuC9DZRsS6AtHts4Ypo1j", privkey, @"[BRKey BIP38KeyWithIntermediateCode:]"); XCTAssertTrue([BRKey confirmWithBIP38ConfirmationCode:confcode address:@"1Jscj8ALrYu2y9TD8NrpvDBugPedmbj4Yh" passphrase:@"MOLON LABE" isTestnet:isTestnet], @"[BRKey confirmWithBIP38ConfirmationCode:]"); key = [BRKey keyWithBIP38Key:@"6PgGWtx25kUg8QWvwuJAgorN6k9FbE25rv5dMRwu5SKMnfpfVe5mar2ngH" andPassphrase:@"\u039c\u039f\u039b\u03a9\u039d \u039b\u0391\u0392\u0395" isTestnet:isTestnet]; NSLog(@"privKey = %@", key.privateKey); XCTAssertEqualObjects(@"5KMKKuUmAkiNbA3DazMQiLfDq47qs8MAEThm4yL8R2PhV1ov33D", key.privateKey, @"[BRKey keyWithBIP38Key:andPassphrase:]"); intercode = [BRKey BIP38IntermediateCodeWithLot:806938 sequence:1 salt:0xc40ea76fu passphrase:@"\u039c\u039f\u039b\u03a9\u039d \u039b\u0391\u0392\u0395"]; NSLog(@"intercode = %@", intercode); privkey = [BRKey BIP38KeyWithIntermediateCode:intercode seedb:@"03b06a1ea7f9219ae364560d7b985ab1fa27025aaa7e427a".hexToData compressed:NO confirmationCode:&confcode isTestnet:isTestnet]; NSLog(@"confcode = %@", confcode); XCTAssertEqualObjects(@"6PgGWtx25kUg8QWvwuJAgorN6k9FbE25rv5dMRwu5SKMnfpfVe5mar2ngH", privkey, @"[BRKey BIP38KeyWithIntermediateCode:]"); XCTAssertTrue([BRKey confirmWithBIP38ConfirmationCode:confcode address:@"1Lurmih3KruL4xDB5FmHof38yawNtP9oGf" passphrase:@"\u039c\u039f\u039b\u03a9\u039d \u039b\u0391\u0392\u0395" isTestnet:isTestnet], @"[BRKey confirmWithBIP38ConfirmationCode:]"); // password NFC unicode normalization test key = [BRKey keyWithBIP38Key:@"6PRW5o9FLp4gJDDVqJQKJFTpMvdsSGJxMYHtHaQBF3ooa8mwD69bapcDQn" andPassphrase:@"\u03D2\u0301\0\U00010400\U0001F4A9" isTestnet:isTestnet]; NSLog(@"privKey = %@", key.privateKey); XCTAssertEqualObjects(@"5Jajm8eQ22H3pGWLEVCXyvND8dQZhiQhoLJNKjYXk9roUFTMSZ4", key.privateKey, @"[BRKey keyWithBIP38Key:andPassphrase:]"); // incorrect password test key = [BRKey keyWithBIP38Key:@"6PRW5o9FLp4gJDDVqJQKJFTpMvdsSGJxMYHtHaQBF3ooa8mwD69bapcDQn" andPassphrase:@"foobar" isTestnet:isTestnet]; NSLog(@"privKey = %@", key.privateKey); XCTAssertNil(key, @"[BRKey keyWithBIP38Key:andPassphrase:]"); } #endif #pragma mark - testTransaction - (void)testTransaction { NSMutableData *hash = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH], *script = [NSMutableData data]; BRKey *k = [BRKey keyWithSecret:@"0000000000000000000000000000000000000000000000000000000000000001".hexToData compressed:YES isTestnet:isTestnet]; [script appendScriptPubKeyForAddress:k.address isTestnet:isTestnet]; BRTransaction *tx = [[BRTransaction alloc] initWithInputHashes:@[hash] inputIndexes:@[@0] inputScripts:@[script] outputAddresses:@[k.address, k.address] outputAmounts:@[@100000000, @4900000000] isTestnet:isTestnet]; [tx signWithPrivateKeys:@[k.privateKey] isTestnet:isTestnet]; XCTAssertTrue([tx isSigned], @"[BRTransaction signWithPrivateKeys:]"); NSUInteger height = [tx blockHeightUntilFreeForAmounts:@[@5000000000] withBlockHeights:@[@1]]; uint64_t priority = [tx priorityForAmounts:@[@5000000000] withAges:@[@(height - 1)]]; NSLog(@"height = %lu", (unsigned long)height); NSLog(@"priority = %llu", priority); XCTAssertTrue(priority >= TX_FREE_MIN_PRIORITY, @"[BRTransaction priorityForAmounts:withAges:]"); NSData *d = tx.data; tx = [BRTransaction transactionWithMessage:d isTestnet:isTestnet]; XCTAssertEqualObjects(d, tx.data, @"[BRTransaction transactionWithMessage:]"); tx = [[BRTransaction alloc] initWithInputHashes:@[hash, hash, hash, hash, hash, hash, hash, hash, hash, hash] inputIndexes:@[@0, @0,@0, @0, @0, @0, @0, @0, @0, @0] inputScripts:@[script, script, script, script, script, script, script, script, script, script] outputAddresses:@[k.address, k.address, k.address, k.address, k.address, k.address, k.address, k.address, k.address, k.address] outputAmounts:@[@1000000, @1000000, @1000000, @1000000, @1000000, @1000000, @1000000, @1000000, @1000000, @1000000] isTestnet:isTestnet]; [tx signWithPrivateKeys:@[k.privateKey] isTestnet:isTestnet]; XCTAssertTrue([tx isSigned], @"[BRTransaction signWithPrivateKeys:]"); height = [tx blockHeightUntilFreeForAmounts:@[@1000000, @1000000, @1000000, @1000000, @1000000, @1000000, @1000000, @1000000, @1000000, @1000000] withBlockHeights:@[@1, @2, @3, @4, @5, @6, @7, @8, @9, @10]]; priority = [tx priorityForAmounts:@[@1000000, @1000000, @1000000, @1000000, @1000000, @1000000, @1000000, @1000000, @1000000, @1000000] withAges:@[@(height - 1), @(height - 2), @(height - 3), @(height - 4), @(height - 5), @(height - 6), @(height - 7), @(height - 8), @(height - 9), @(height - 10)]]; NSLog(@"height = %lu", (unsigned long)height); NSLog(@"priority = %llu", priority); XCTAssertTrue(priority >= TX_FREE_MIN_PRIORITY, @"[BRTransaction priorityForAmounts:withAges:]"); d = tx.data; tx = [BRTransaction transactionWithMessage:d isTestnet:isTestnet]; XCTAssertEqualObjects(d, tx.data, @"[BRTransaction transactionWithMessage:]"); } //*/ @end ================================================ FILE: ArcBitTests/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 ================================================ FILE: Podfile ================================================ # Uncomment this line to define a global platform for your project # platform :ios, '7.0' # ignore all warnings from all pods inhibit_all_warnings! source 'https://github.com/CocoaPods/Specs.git' target 'ArcBit' do pod 'AFNetworking', '2.5.3', :inhibit_warnings => true pod 'CoreBitcoin', :podspec => 'https://raw.github.com/oleganza/CoreBitcoin/master/CoreBitcoin.podspec', :inhibit_warnings => true pod 'MBProgressHUD', '~> 0.9', :inhibit_warnings => true pod 'RNCryptor', '2.2', :inhibit_warnings => true pod 'ECSlidingViewController', '~> 2.0.3', :inhibit_warnings => true pod 'iCloudDocumentSync', :inhibit_warnings => true pod 'SwiftTryCatch', :inhibit_warnings => true pod 'Fabric' pod 'Crashlytics' end target 'ArcBitTests' do pod 'AFNetworking', '2.5.3', :inhibit_warnings => true pod 'CoreBitcoin', :podspec => 'https://raw.github.com/oleganza/CoreBitcoin/master/CoreBitcoin.podspec', :inhibit_warnings => true pod 'MBProgressHUD', '~> 0.9', :inhibit_warnings => true pod 'RNCryptor', '2.2', :inhibit_warnings => true pod 'ECSlidingViewController', '~> 2.0.3', :inhibit_warnings => true pod 'iCloudDocumentSync', :inhibit_warnings => true pod 'SwiftTryCatch', :inhibit_warnings => true pod 'Fabric' pod 'Crashlytics' end ================================================ FILE: Pods/AFNetworking/AFNetworking/AFHTTPRequestOperation.h ================================================ // AFHTTPRequestOperation.h // // Copyright (c) 2013-2015 AFNetworking (http://afnetworking.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import #import "AFURLConnectionOperation.h" /** `AFHTTPRequestOperation` is a subclass of `AFURLConnectionOperation` for requests using the HTTP or HTTPS protocols. It encapsulates the concept of acceptable status codes and content types, which determine the success or failure of a request. */ @interface AFHTTPRequestOperation : AFURLConnectionOperation ///------------------------------------------------ /// @name Getting HTTP URL Connection Information ///------------------------------------------------ /** The last HTTP response received by the operation's connection. */ @property (readonly, nonatomic, strong) NSHTTPURLResponse *response; /** Responses sent from the server in data tasks created with `dataTaskWithRequest:success:failure:` and run using the `GET` / `POST` / et al. convenience methods are automatically validated and serialized by the response serializer. By default, this property is set to an AFHTTPResponse serializer, which uses the raw data as its response object. The serializer validates the status code to be in the `2XX` range, denoting success. If the response serializer generates an error in `-responseObjectForResponse:data:error:`, the `failure` callback of the session task or request operation will be executed; otherwise, the `success` callback will be executed. @warning `responseSerializer` must not be `nil`. Setting a response serializer will clear out any cached value */ @property (nonatomic, strong) AFHTTPResponseSerializer * responseSerializer; /** An object constructed by the `responseSerializer` from the response and response data. Returns `nil` unless the operation `isFinished`, has a `response`, and has `responseData` with non-zero content length. If an error occurs during serialization, `nil` will be returned, and the `error` property will be populated with the serialization error. */ @property (readonly, nonatomic, strong) id responseObject; ///----------------------------------------------------------- /// @name Setting Completion Block Success / Failure Callbacks ///----------------------------------------------------------- /** Sets the `completionBlock` property with a block that executes either the specified success or failure block, depending on the state of the request on completion. If `error` returns a value, which can be caused by an unacceptable status code or content type, then `failure` is executed. Otherwise, `success` is executed. This method should be overridden in subclasses in order to specify the response object passed into the success block. @param success The block to be executed on the completion of a successful request. This block has no return value and takes two arguments: the receiver operation and the object constructed from the response data of the request. @param failure The block to be executed on the completion of an unsuccessful request. This block has no return value and takes two arguments: the receiver operation and the error that occurred during the request. */ - (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure; @end ================================================ FILE: Pods/AFNetworking/AFNetworking/AFHTTPRequestOperation.m ================================================ // AFHTTPRequestOperation.m // // Copyright (c) 2013-2015 AFNetworking (http://afnetworking.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import "AFHTTPRequestOperation.h" static dispatch_queue_t http_request_operation_processing_queue() { static dispatch_queue_t af_http_request_operation_processing_queue; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ af_http_request_operation_processing_queue = dispatch_queue_create("com.alamofire.networking.http-request.processing", DISPATCH_QUEUE_CONCURRENT); }); return af_http_request_operation_processing_queue; } static dispatch_group_t http_request_operation_completion_group() { static dispatch_group_t af_http_request_operation_completion_group; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ af_http_request_operation_completion_group = dispatch_group_create(); }); return af_http_request_operation_completion_group; } #pragma mark - @interface AFURLConnectionOperation () @property (readwrite, nonatomic, strong) NSURLRequest *request; @property (readwrite, nonatomic, strong) NSURLResponse *response; @end @interface AFHTTPRequestOperation () @property (readwrite, nonatomic, strong) NSHTTPURLResponse *response; @property (readwrite, nonatomic, strong) id responseObject; @property (readwrite, nonatomic, strong) NSError *responseSerializationError; @property (readwrite, nonatomic, strong) NSRecursiveLock *lock; @end @implementation AFHTTPRequestOperation @dynamic response; @dynamic lock; - (instancetype)initWithRequest:(NSURLRequest *)urlRequest { self = [super initWithRequest:urlRequest]; if (!self) { return nil; } self.responseSerializer = [AFHTTPResponseSerializer serializer]; return self; } - (void)setResponseSerializer:(AFHTTPResponseSerializer *)responseSerializer { NSParameterAssert(responseSerializer); [self.lock lock]; _responseSerializer = responseSerializer; self.responseObject = nil; self.responseSerializationError = nil; [self.lock unlock]; } - (id)responseObject { [self.lock lock]; if (!_responseObject && [self isFinished] && !self.error) { NSError *error = nil; self.responseObject = [self.responseSerializer responseObjectForResponse:self.response data:self.responseData error:&error]; if (error) { self.responseSerializationError = error; } } [self.lock unlock]; return _responseObject; } - (NSError *)error { if (_responseSerializationError) { return _responseSerializationError; } else { return [super error]; } } #pragma mark - AFHTTPRequestOperation - (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure { // completionBlock is manually nilled out in AFURLConnectionOperation to break the retain cycle. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-retain-cycles" #pragma clang diagnostic ignored "-Wgnu" self.completionBlock = ^{ if (self.completionGroup) { dispatch_group_enter(self.completionGroup); } dispatch_async(http_request_operation_processing_queue(), ^{ if (self.error) { if (failure) { dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{ failure(self, self.error); }); } } else { id responseObject = self.responseObject; if (self.error) { if (failure) { dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{ failure(self, self.error); }); } } else { if (success) { dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{ success(self, responseObject); }); } } } if (self.completionGroup) { dispatch_group_leave(self.completionGroup); } }); }; #pragma clang diagnostic pop } #pragma mark - AFURLRequestOperation - (void)pause { [super pause]; u_int64_t offset = 0; if ([self.outputStream propertyForKey:NSStreamFileCurrentOffsetKey]) { offset = [(NSNumber *)[self.outputStream propertyForKey:NSStreamFileCurrentOffsetKey] unsignedLongLongValue]; } else { offset = [(NSData *)[self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey] length]; } NSMutableURLRequest *mutableURLRequest = [self.request mutableCopy]; if ([self.response respondsToSelector:@selector(allHeaderFields)] && [[self.response allHeaderFields] valueForKey:@"ETag"]) { [mutableURLRequest setValue:[[self.response allHeaderFields] valueForKey:@"ETag"] forHTTPHeaderField:@"If-Range"]; } [mutableURLRequest setValue:[NSString stringWithFormat:@"bytes=%llu-", offset] forHTTPHeaderField:@"Range"]; self.request = mutableURLRequest; } #pragma mark - NSSecureCoding + (BOOL)supportsSecureCoding { return YES; } - (id)initWithCoder:(NSCoder *)decoder { self = [super initWithCoder:decoder]; if (!self) { return nil; } self.responseSerializer = [decoder decodeObjectOfClass:[AFHTTPResponseSerializer class] forKey:NSStringFromSelector(@selector(responseSerializer))]; return self; } - (void)encodeWithCoder:(NSCoder *)coder { [super encodeWithCoder:coder]; [coder encodeObject:self.responseSerializer forKey:NSStringFromSelector(@selector(responseSerializer))]; } #pragma mark - NSCopying - (id)copyWithZone:(NSZone *)zone { AFHTTPRequestOperation *operation = [super copyWithZone:zone]; operation.responseSerializer = [self.responseSerializer copyWithZone:zone]; operation.completionQueue = self.completionQueue; operation.completionGroup = self.completionGroup; return operation; } @end ================================================ FILE: Pods/AFNetworking/AFNetworking/AFHTTPRequestOperationManager.h ================================================ // AFHTTPRequestOperationManager.h // // Copyright (c) 2013-2015 AFNetworking (http://afnetworking.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import #import #import #if __IPHONE_OS_VERSION_MIN_REQUIRED #import #else #import #endif #import "AFHTTPRequestOperation.h" #import "AFURLResponseSerialization.h" #import "AFURLRequestSerialization.h" #import "AFSecurityPolicy.h" #import "AFNetworkReachabilityManager.h" #ifndef NS_DESIGNATED_INITIALIZER #if __has_attribute(objc_designated_initializer) #define NS_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) #else #define NS_DESIGNATED_INITIALIZER #endif #endif /** `AFHTTPRequestOperationManager` encapsulates the common patterns of communicating with a web application over HTTP, including request creation, response serialization, network reachability monitoring, and security, as well as request operation management. ## Subclassing Notes Developers targeting iOS 7 or Mac OS X 10.9 or later that deal extensively with a web service are encouraged to subclass `AFHTTPSessionManager`, providing a class method that returns a shared singleton object on which authentication and other configuration can be shared across the application. For developers targeting iOS 6 or Mac OS X 10.8 or earlier, `AFHTTPRequestOperationManager` may be used to similar effect. ## Methods to Override To change the behavior of all request operation construction for an `AFHTTPRequestOperationManager` subclass, override `HTTPRequestOperationWithRequest:success:failure`. ## Serialization Requests created by an HTTP client will contain default headers and encode parameters according to the `requestSerializer` property, which is an object conforming to ``. Responses received from the server are automatically validated and serialized by the `responseSerializers` property, which is an object conforming to `` ## URL Construction Using Relative Paths For HTTP convenience methods, the request serializer constructs URLs from the path relative to the `-baseURL`, using `NSURL +URLWithString:relativeToURL:`, when provided. If `baseURL` is `nil`, `path` needs to resolve to a valid `NSURL` object using `NSURL +URLWithString:`. Below are a few examples of how `baseURL` and relative paths interact: NSURL *baseURL = [NSURL URLWithString:@"http://example.com/v1/"]; [NSURL URLWithString:@"foo" relativeToURL:baseURL]; // http://example.com/v1/foo [NSURL URLWithString:@"foo?bar=baz" relativeToURL:baseURL]; // http://example.com/v1/foo?bar=baz [NSURL URLWithString:@"/foo" relativeToURL:baseURL]; // http://example.com/foo [NSURL URLWithString:@"foo/" relativeToURL:baseURL]; // http://example.com/v1/foo [NSURL URLWithString:@"/foo/" relativeToURL:baseURL]; // http://example.com/foo/ [NSURL URLWithString:@"http://example2.com/" relativeToURL:baseURL]; // http://example2.com/ Also important to note is that a trailing slash will be added to any `baseURL` without one. This would otherwise cause unexpected behavior when constructing URLs using paths without a leading slash. ## Network Reachability Monitoring Network reachability status and change monitoring is available through the `reachabilityManager` property. Applications may choose to monitor network reachability conditions in order to prevent or suspend any outbound requests. See `AFNetworkReachabilityManager` for more details. ## NSSecureCoding & NSCopying Caveats `AFHTTPRequestOperationManager` conforms to the `NSSecureCoding` and `NSCopying` protocols, allowing operations to be archived to disk, and copied in memory, respectively. There are a few minor caveats to keep in mind, however: - Archives and copies of HTTP clients will be initialized with an empty operation queue. - NSSecureCoding cannot serialize / deserialize block properties, so an archive of an HTTP client will not include any reachability callback block that may be set. */ @interface AFHTTPRequestOperationManager : NSObject /** The URL used to monitor reachability, and construct requests from relative paths in methods like `requestWithMethod:URLString:parameters:`, and the `GET` / `POST` / et al. convenience methods. */ @property (readonly, nonatomic, strong) NSURL *baseURL; /** Requests created with `requestWithMethod:URLString:parameters:` & `multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:` are constructed with a set of default headers using a parameter serialization specified by this property. By default, this is set to an instance of `AFHTTPRequestSerializer`, which serializes query string parameters for `GET`, `HEAD`, and `DELETE` requests, or otherwise URL-form-encodes HTTP message bodies. @warning `requestSerializer` must not be `nil`. */ @property (nonatomic, strong) AFHTTPRequestSerializer * requestSerializer; /** Responses sent from the server in data tasks created with `dataTaskWithRequest:success:failure:` and run using the `GET` / `POST` / et al. convenience methods are automatically validated and serialized by the response serializer. By default, this property is set to a JSON serializer, which serializes data from responses with a `application/json` MIME type, and falls back to the raw data object. The serializer validates the status code to be in the `2XX` range, denoting success. If the response serializer generates an error in `-responseObjectForResponse:data:error:`, the `failure` callback of the session task or request operation will be executed; otherwise, the `success` callback will be executed. @warning `responseSerializer` must not be `nil`. */ @property (nonatomic, strong) AFHTTPResponseSerializer * responseSerializer; /** The operation queue on which request operations are scheduled and run. */ @property (nonatomic, strong) NSOperationQueue *operationQueue; ///------------------------------- /// @name Managing URL Credentials ///------------------------------- /** Whether request operations should consult the credential storage for authenticating the connection. `YES` by default. @see AFURLConnectionOperation -shouldUseCredentialStorage */ @property (nonatomic, assign) BOOL shouldUseCredentialStorage; /** The credential used by request operations for authentication challenges. @see AFURLConnectionOperation -credential */ @property (nonatomic, strong) NSURLCredential *credential; ///------------------------------- /// @name Managing Security Policy ///------------------------------- /** The security policy used by created request operations to evaluate server trust for secure connections. `AFHTTPRequestOperationManager` uses the `defaultPolicy` unless otherwise specified. */ @property (nonatomic, strong) AFSecurityPolicy *securityPolicy; ///------------------------------------ /// @name Managing Network Reachability ///------------------------------------ /** The network reachability manager. `AFHTTPRequestOperationManager` uses the `sharedManager` by default. */ @property (readwrite, nonatomic, strong) AFNetworkReachabilityManager *reachabilityManager; ///------------------------------- /// @name Managing Callback Queues ///------------------------------- /** The dispatch queue for the `completionBlock` of request operations. If `NULL` (default), the main queue is used. */ #if OS_OBJECT_HAVE_OBJC_SUPPORT @property (nonatomic, strong) dispatch_queue_t completionQueue; #else @property (nonatomic, assign) dispatch_queue_t completionQueue; #endif /** The dispatch group for the `completionBlock` of request operations. If `NULL` (default), a private dispatch group is used. */ #if OS_OBJECT_HAVE_OBJC_SUPPORT @property (nonatomic, strong) dispatch_group_t completionGroup; #else @property (nonatomic, assign) dispatch_group_t completionGroup; #endif ///--------------------------------------------- /// @name Creating and Initializing HTTP Clients ///--------------------------------------------- /** Creates and returns an `AFHTTPRequestOperationManager` object. */ + (instancetype)manager; /** Initializes an `AFHTTPRequestOperationManager` object with the specified base URL. This is the designated initializer. @param url The base URL for the HTTP client. @return The newly-initialized HTTP client */ - (instancetype)initWithBaseURL:(NSURL *)url NS_DESIGNATED_INITIALIZER; ///--------------------------------------- /// @name Managing HTTP Request Operations ///--------------------------------------- /** Creates an `AFHTTPRequestOperation`, and sets the response serializers to that of the HTTP client. @param request The request object to be loaded asynchronously during execution of the operation. @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the created request operation and the object created from the response data of request. @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes two arguments:, the created request operation and the `NSError` object describing the network or parsing error that occurred. */ - (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)request success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure; ///--------------------------- /// @name Making HTTP Requests ///--------------------------- /** Creates and runs an `AFHTTPRequestOperation` with a `GET` request. @param URLString The URL string used to create the request URL. @param parameters The parameters to be encoded according to the client request serializer. @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the request operation, and the response object created by the client response serializer. @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the request operation and the error describing the network or parsing error that occurred. @see -HTTPRequestOperationWithRequest:success:failure: */ - (AFHTTPRequestOperation *)GET:(NSString *)URLString parameters:(id)parameters success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure; /** Creates and runs an `AFHTTPRequestOperation` with a `HEAD` request. @param URLString The URL string used to create the request URL. @param parameters The parameters to be encoded according to the client request serializer. @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes a single arguments: the request operation. @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the request operation and the error describing the network or parsing error that occurred. @see -HTTPRequestOperationWithRequest:success:failure: */ - (AFHTTPRequestOperation *)HEAD:(NSString *)URLString parameters:(id)parameters success:(void (^)(AFHTTPRequestOperation *operation))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure; /** Creates and runs an `AFHTTPRequestOperation` with a `POST` request. @param URLString The URL string used to create the request URL. @param parameters The parameters to be encoded according to the client request serializer. @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the request operation, and the response object created by the client response serializer. @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the request operation and the error describing the network or parsing error that occurred. @see -HTTPRequestOperationWithRequest:success:failure: */ - (AFHTTPRequestOperation *)POST:(NSString *)URLString parameters:(id)parameters success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure; /** Creates and runs an `AFHTTPRequestOperation` with a multipart `POST` request. @param URLString The URL string used to create the request URL. @param parameters The parameters to be encoded according to the client request serializer. @param block A block that takes a single argument and appends data to the HTTP body. The block argument is an object adopting the `AFMultipartFormData` protocol. @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the request operation, and the response object created by the client response serializer. @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the request operation and the error describing the network or parsing error that occurred. @see -HTTPRequestOperationWithRequest:success:failure: */ - (AFHTTPRequestOperation *)POST:(NSString *)URLString parameters:(id)parameters constructingBodyWithBlock:(void (^)(id formData))block success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure; /** Creates and runs an `AFHTTPRequestOperation` with a `PUT` request. @param URLString The URL string used to create the request URL. @param parameters The parameters to be encoded according to the client request serializer. @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the request operation, and the response object created by the client response serializer. @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the request operation and the error describing the network or parsing error that occurred. @see -HTTPRequestOperationWithRequest:success:failure: */ - (AFHTTPRequestOperation *)PUT:(NSString *)URLString parameters:(id)parameters success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure; /** Creates and runs an `AFHTTPRequestOperation` with a `PATCH` request. @param URLString The URL string used to create the request URL. @param parameters The parameters to be encoded according to the client request serializer. @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the request operation, and the response object created by the client response serializer. @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the request operation and the error describing the network or parsing error that occurred. @see -HTTPRequestOperationWithRequest:success:failure: */ - (AFHTTPRequestOperation *)PATCH:(NSString *)URLString parameters:(id)parameters success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure; /** Creates and runs an `AFHTTPRequestOperation` with a `DELETE` request. @param URLString The URL string used to create the request URL. @param parameters The parameters to be encoded according to the client request serializer. @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the request operation, and the response object created by the client response serializer. @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the request operation and the error describing the network or parsing error that occurred. @see -HTTPRequestOperationWithRequest:success:failure: */ - (AFHTTPRequestOperation *)DELETE:(NSString *)URLString parameters:(id)parameters success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure; @end ================================================ FILE: Pods/AFNetworking/AFNetworking/AFHTTPRequestOperationManager.m ================================================ // AFHTTPRequestOperationManager.m // // Copyright (c) 2013-2015 AFNetworking (http://afnetworking.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import #import "AFHTTPRequestOperationManager.h" #import "AFHTTPRequestOperation.h" #import #import #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) #import #endif @interface AFHTTPRequestOperationManager () @property (readwrite, nonatomic, strong) NSURL *baseURL; @end @implementation AFHTTPRequestOperationManager + (instancetype)manager { return [[self alloc] initWithBaseURL:nil]; } - (instancetype)init { return [self initWithBaseURL:nil]; } - (instancetype)initWithBaseURL:(NSURL *)url { self = [super init]; if (!self) { return nil; } // Ensure terminal slash for baseURL path, so that NSURL +URLWithString:relativeToURL: works as expected if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) { url = [url URLByAppendingPathComponent:@""]; } self.baseURL = url; self.requestSerializer = [AFHTTPRequestSerializer serializer]; self.responseSerializer = [AFJSONResponseSerializer serializer]; self.securityPolicy = [AFSecurityPolicy defaultPolicy]; self.reachabilityManager = [AFNetworkReachabilityManager sharedManager]; self.operationQueue = [[NSOperationQueue alloc] init]; self.shouldUseCredentialStorage = YES; return self; } #pragma mark - #ifdef _SYSTEMCONFIGURATION_H #endif - (void)setRequestSerializer:(AFHTTPRequestSerializer *)requestSerializer { NSParameterAssert(requestSerializer); _requestSerializer = requestSerializer; } - (void)setResponseSerializer:(AFHTTPResponseSerializer *)responseSerializer { NSParameterAssert(responseSerializer); _responseSerializer = responseSerializer; } #pragma mark - - (AFHTTPRequestOperation *)HTTPRequestOperationWithHTTPMethod:(NSString *)method URLString:(NSString *)URLString parameters:(id)parameters success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure { NSError *serializationError = nil; NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError]; if (serializationError) { if (failure) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{ failure(nil, serializationError); }); #pragma clang diagnostic pop } return nil; } return [self HTTPRequestOperationWithRequest:request success:success failure:failure]; } - (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)request success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure { AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request]; operation.responseSerializer = self.responseSerializer; operation.shouldUseCredentialStorage = self.shouldUseCredentialStorage; operation.credential = self.credential; operation.securityPolicy = self.securityPolicy; [operation setCompletionBlockWithSuccess:success failure:failure]; operation.completionQueue = self.completionQueue; operation.completionGroup = self.completionGroup; return operation; } #pragma mark - - (AFHTTPRequestOperation *)GET:(NSString *)URLString parameters:(id)parameters success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure { AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithHTTPMethod:@"GET" URLString:URLString parameters:parameters success:success failure:failure]; [self.operationQueue addOperation:operation]; return operation; } - (AFHTTPRequestOperation *)HEAD:(NSString *)URLString parameters:(id)parameters success:(void (^)(AFHTTPRequestOperation *operation))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure { AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithHTTPMethod:@"HEAD" URLString:URLString parameters:parameters success:^(AFHTTPRequestOperation *requestOperation, __unused id responseObject) { if (success) { success(requestOperation); } } failure:failure]; [self.operationQueue addOperation:operation]; return operation; } - (AFHTTPRequestOperation *)POST:(NSString *)URLString parameters:(id)parameters success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure { AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithHTTPMethod:@"POST" URLString:URLString parameters:parameters success:success failure:failure]; [self.operationQueue addOperation:operation]; return operation; } - (AFHTTPRequestOperation *)POST:(NSString *)URLString parameters:(id)parameters constructingBodyWithBlock:(void (^)(id formData))block success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure { NSError *serializationError = nil; NSMutableURLRequest *request = [self.requestSerializer multipartFormRequestWithMethod:@"POST" URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters constructingBodyWithBlock:block error:&serializationError]; if (serializationError) { if (failure) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{ failure(nil, serializationError); }); #pragma clang diagnostic pop } return nil; } AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithRequest:request success:success failure:failure]; [self.operationQueue addOperation:operation]; return operation; } - (AFHTTPRequestOperation *)PUT:(NSString *)URLString parameters:(id)parameters success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure { AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithHTTPMethod:@"PUT" URLString:URLString parameters:parameters success:success failure:failure]; [self.operationQueue addOperation:operation]; return operation; } - (AFHTTPRequestOperation *)PATCH:(NSString *)URLString parameters:(id)parameters success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure { AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithHTTPMethod:@"PATCH" URLString:URLString parameters:parameters success:success failure:failure]; [self.operationQueue addOperation:operation]; return operation; } - (AFHTTPRequestOperation *)DELETE:(NSString *)URLString parameters:(id)parameters success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure { AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithHTTPMethod:@"DELETE" URLString:URLString parameters:parameters success:success failure:failure]; [self.operationQueue addOperation:operation]; return operation; } #pragma mark - NSObject - (NSString *)description { return [NSString stringWithFormat:@"<%@: %p, baseURL: %@, operationQueue: %@>", NSStringFromClass([self class]), self, [self.baseURL absoluteString], self.operationQueue]; } #pragma mark - NSSecureCoding + (BOOL)supportsSecureCoding { return YES; } - (id)initWithCoder:(NSCoder *)decoder { NSURL *baseURL = [decoder decodeObjectForKey:NSStringFromSelector(@selector(baseURL))]; self = [self initWithBaseURL:baseURL]; if (!self) { return nil; } self.requestSerializer = [decoder decodeObjectOfClass:[AFHTTPRequestSerializer class] forKey:NSStringFromSelector(@selector(requestSerializer))]; self.responseSerializer = [decoder decodeObjectOfClass:[AFHTTPResponseSerializer class] forKey:NSStringFromSelector(@selector(responseSerializer))]; return self; } - (void)encodeWithCoder:(NSCoder *)coder { [coder encodeObject:self.baseURL forKey:NSStringFromSelector(@selector(baseURL))]; [coder encodeObject:self.requestSerializer forKey:NSStringFromSelector(@selector(requestSerializer))]; [coder encodeObject:self.responseSerializer forKey:NSStringFromSelector(@selector(responseSerializer))]; } #pragma mark - NSCopying - (id)copyWithZone:(NSZone *)zone { AFHTTPRequestOperationManager *HTTPClient = [[[self class] allocWithZone:zone] initWithBaseURL:self.baseURL]; HTTPClient.requestSerializer = [self.requestSerializer copyWithZone:zone]; HTTPClient.responseSerializer = [self.responseSerializer copyWithZone:zone]; return HTTPClient; } @end ================================================ FILE: Pods/AFNetworking/AFNetworking/AFHTTPSessionManager.h ================================================ // AFHTTPSessionManager.h // // Copyright (c) 2013-2015 AFNetworking (http://afnetworking.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import #import #import #if __IPHONE_OS_VERSION_MIN_REQUIRED #import #else #import #endif #import "AFURLSessionManager.h" #ifndef NS_DESIGNATED_INITIALIZER #if __has_attribute(objc_designated_initializer) #define NS_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) #else #define NS_DESIGNATED_INITIALIZER #endif #endif /** `AFHTTPSessionManager` is a subclass of `AFURLSessionManager` with convenience methods for making HTTP requests. When a `baseURL` is provided, requests made with the `GET` / `POST` / et al. convenience methods can be made with relative paths. ## Subclassing Notes Developers targeting iOS 7 or Mac OS X 10.9 or later that deal extensively with a web service are encouraged to subclass `AFHTTPSessionManager`, providing a class method that returns a shared singleton object on which authentication and other configuration can be shared across the application. For developers targeting iOS 6 or Mac OS X 10.8 or earlier, `AFHTTPRequestOperationManager` may be used to similar effect. ## Methods to Override To change the behavior of all data task operation construction, which is also used in the `GET` / `POST` / et al. convenience methods, override `dataTaskWithRequest:completionHandler:`. ## Serialization Requests created by an HTTP client will contain default headers and encode parameters according to the `requestSerializer` property, which is an object conforming to ``. Responses received from the server are automatically validated and serialized by the `responseSerializers` property, which is an object conforming to `` ## URL Construction Using Relative Paths For HTTP convenience methods, the request serializer constructs URLs from the path relative to the `-baseURL`, using `NSURL +URLWithString:relativeToURL:`, when provided. If `baseURL` is `nil`, `path` needs to resolve to a valid `NSURL` object using `NSURL +URLWithString:`. Below are a few examples of how `baseURL` and relative paths interact: NSURL *baseURL = [NSURL URLWithString:@"http://example.com/v1/"]; [NSURL URLWithString:@"foo" relativeToURL:baseURL]; // http://example.com/v1/foo [NSURL URLWithString:@"foo?bar=baz" relativeToURL:baseURL]; // http://example.com/v1/foo?bar=baz [NSURL URLWithString:@"/foo" relativeToURL:baseURL]; // http://example.com/foo [NSURL URLWithString:@"foo/" relativeToURL:baseURL]; // http://example.com/v1/foo [NSURL URLWithString:@"/foo/" relativeToURL:baseURL]; // http://example.com/foo/ [NSURL URLWithString:@"http://example2.com/" relativeToURL:baseURL]; // http://example2.com/ Also important to note is that a trailing slash will be added to any `baseURL` without one. This would otherwise cause unexpected behavior when constructing URLs using paths without a leading slash. @warning Managers for background sessions must be owned for the duration of their use. This can be accomplished by creating an application-wide or shared singleton instance. */ #if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000) || (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1090) @interface AFHTTPSessionManager : AFURLSessionManager /** The URL used to monitor reachability, and construct requests from relative paths in methods like `requestWithMethod:URLString:parameters:`, and the `GET` / `POST` / et al. convenience methods. */ @property (readonly, nonatomic, strong) NSURL *baseURL; /** Requests created with `requestWithMethod:URLString:parameters:` & `multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:` are constructed with a set of default headers using a parameter serialization specified by this property. By default, this is set to an instance of `AFHTTPRequestSerializer`, which serializes query string parameters for `GET`, `HEAD`, and `DELETE` requests, or otherwise URL-form-encodes HTTP message bodies. @warning `requestSerializer` must not be `nil`. */ @property (nonatomic, strong) AFHTTPRequestSerializer * requestSerializer; /** Responses sent from the server in data tasks created with `dataTaskWithRequest:success:failure:` and run using the `GET` / `POST` / et al. convenience methods are automatically validated and serialized by the response serializer. By default, this property is set to an instance of `AFJSONResponseSerializer`. @warning `responseSerializer` must not be `nil`. */ @property (nonatomic, strong) AFHTTPResponseSerializer * responseSerializer; ///--------------------- /// @name Initialization ///--------------------- /** Creates and returns an `AFHTTPSessionManager` object. */ + (instancetype)manager; /** Initializes an `AFHTTPSessionManager` object with the specified base URL. @param url The base URL for the HTTP client. @return The newly-initialized HTTP client */ - (instancetype)initWithBaseURL:(NSURL *)url; /** Initializes an `AFHTTPSessionManager` object with the specified base URL. This is the designated initializer. @param url The base URL for the HTTP client. @param configuration The configuration used to create the managed session. @return The newly-initialized HTTP client */ - (instancetype)initWithBaseURL:(NSURL *)url sessionConfiguration:(NSURLSessionConfiguration *)configuration NS_DESIGNATED_INITIALIZER; ///--------------------------- /// @name Making HTTP Requests ///--------------------------- /** Creates and runs an `NSURLSessionDataTask` with a `GET` request. @param URLString The URL string used to create the request URL. @param parameters The parameters to be encoded according to the client request serializer. @param success A block object to be executed when the task finishes successfully. This block has no return value and takes two arguments: the data task, and the response object created by the client response serializer. @param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred. @see -dataTaskWithRequest:completionHandler: */ - (NSURLSessionDataTask *)GET:(NSString *)URLString parameters:(id)parameters success:(void (^)(NSURLSessionDataTask *task, id responseObject))success failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure; /** Creates and runs an `NSURLSessionDataTask` with a `HEAD` request. @param URLString The URL string used to create the request URL. @param parameters The parameters to be encoded according to the client request serializer. @param success A block object to be executed when the task finishes successfully. This block has no return value and takes a single arguments: the data task. @param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred. @see -dataTaskWithRequest:completionHandler: */ - (NSURLSessionDataTask *)HEAD:(NSString *)URLString parameters:(id)parameters success:(void (^)(NSURLSessionDataTask *task))success failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure; /** Creates and runs an `NSURLSessionDataTask` with a `POST` request. @param URLString The URL string used to create the request URL. @param parameters The parameters to be encoded according to the client request serializer. @param success A block object to be executed when the task finishes successfully. This block has no return value and takes two arguments: the data task, and the response object created by the client response serializer. @param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred. @see -dataTaskWithRequest:completionHandler: */ - (NSURLSessionDataTask *)POST:(NSString *)URLString parameters:(id)parameters success:(void (^)(NSURLSessionDataTask *task, id responseObject))success failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure; /** Creates and runs an `NSURLSessionDataTask` with a multipart `POST` request. @param URLString The URL string used to create the request URL. @param parameters The parameters to be encoded according to the client request serializer. @param block A block that takes a single argument and appends data to the HTTP body. The block argument is an object adopting the `AFMultipartFormData` protocol. @param success A block object to be executed when the task finishes successfully. This block has no return value and takes two arguments: the data task, and the response object created by the client response serializer. @param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred. @see -dataTaskWithRequest:completionHandler: */ - (NSURLSessionDataTask *)POST:(NSString *)URLString parameters:(id)parameters constructingBodyWithBlock:(void (^)(id formData))block success:(void (^)(NSURLSessionDataTask *task, id responseObject))success failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure; /** Creates and runs an `NSURLSessionDataTask` with a `PUT` request. @param URLString The URL string used to create the request URL. @param parameters The parameters to be encoded according to the client request serializer. @param success A block object to be executed when the task finishes successfully. This block has no return value and takes two arguments: the data task, and the response object created by the client response serializer. @param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred. @see -dataTaskWithRequest:completionHandler: */ - (NSURLSessionDataTask *)PUT:(NSString *)URLString parameters:(id)parameters success:(void (^)(NSURLSessionDataTask *task, id responseObject))success failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure; /** Creates and runs an `NSURLSessionDataTask` with a `PATCH` request. @param URLString The URL string used to create the request URL. @param parameters The parameters to be encoded according to the client request serializer. @param success A block object to be executed when the task finishes successfully. This block has no return value and takes two arguments: the data task, and the response object created by the client response serializer. @param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred. @see -dataTaskWithRequest:completionHandler: */ - (NSURLSessionDataTask *)PATCH:(NSString *)URLString parameters:(id)parameters success:(void (^)(NSURLSessionDataTask *task, id responseObject))success failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure; /** Creates and runs an `NSURLSessionDataTask` with a `DELETE` request. @param URLString The URL string used to create the request URL. @param parameters The parameters to be encoded according to the client request serializer. @param success A block object to be executed when the task finishes successfully. This block has no return value and takes two arguments: the data task, and the response object created by the client response serializer. @param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred. @see -dataTaskWithRequest:completionHandler: */ - (NSURLSessionDataTask *)DELETE:(NSString *)URLString parameters:(id)parameters success:(void (^)(NSURLSessionDataTask *task, id responseObject))success failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure; @end #endif ================================================ FILE: Pods/AFNetworking/AFNetworking/AFHTTPSessionManager.m ================================================ // AFHTTPSessionManager.m // // Copyright (c) 2013-2015 AFNetworking (http://afnetworking.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import "AFHTTPSessionManager.h" #if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000) || (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1090) #import "AFURLRequestSerialization.h" #import "AFURLResponseSerialization.h" #import #import #ifdef _SYSTEMCONFIGURATION_H #import #import #import #import #import #endif #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) #import #endif @interface AFHTTPSessionManager () @property (readwrite, nonatomic, strong) NSURL *baseURL; @end @implementation AFHTTPSessionManager @dynamic responseSerializer; + (instancetype)manager { return [[[self class] alloc] initWithBaseURL:nil]; } - (instancetype)init { return [self initWithBaseURL:nil]; } - (instancetype)initWithBaseURL:(NSURL *)url { return [self initWithBaseURL:url sessionConfiguration:nil]; } - (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration { return [self initWithBaseURL:nil sessionConfiguration:configuration]; } - (instancetype)initWithBaseURL:(NSURL *)url sessionConfiguration:(NSURLSessionConfiguration *)configuration { self = [super initWithSessionConfiguration:configuration]; if (!self) { return nil; } // Ensure terminal slash for baseURL path, so that NSURL +URLWithString:relativeToURL: works as expected if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) { url = [url URLByAppendingPathComponent:@""]; } self.baseURL = url; self.requestSerializer = [AFHTTPRequestSerializer serializer]; self.responseSerializer = [AFJSONResponseSerializer serializer]; return self; } #pragma mark - #ifdef _SYSTEMCONFIGURATION_H #endif - (void)setRequestSerializer:(AFHTTPRequestSerializer *)requestSerializer { NSParameterAssert(requestSerializer); _requestSerializer = requestSerializer; } - (void)setResponseSerializer:(AFHTTPResponseSerializer *)responseSerializer { NSParameterAssert(responseSerializer); [super setResponseSerializer:responseSerializer]; } #pragma mark - - (NSURLSessionDataTask *)GET:(NSString *)URLString parameters:(id)parameters success:(void (^)(NSURLSessionDataTask *task, id responseObject))success failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure { NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET" URLString:URLString parameters:parameters success:success failure:failure]; [dataTask resume]; return dataTask; } - (NSURLSessionDataTask *)HEAD:(NSString *)URLString parameters:(id)parameters success:(void (^)(NSURLSessionDataTask *task))success failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure { NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"HEAD" URLString:URLString parameters:parameters success:^(NSURLSessionDataTask *task, __unused id responseObject) { if (success) { success(task); } } failure:failure]; [dataTask resume]; return dataTask; } - (NSURLSessionDataTask *)POST:(NSString *)URLString parameters:(id)parameters success:(void (^)(NSURLSessionDataTask *task, id responseObject))success failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure { NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"POST" URLString:URLString parameters:parameters success:success failure:failure]; [dataTask resume]; return dataTask; } - (NSURLSessionDataTask *)POST:(NSString *)URLString parameters:(id)parameters constructingBodyWithBlock:(void (^)(id formData))block success:(void (^)(NSURLSessionDataTask *task, id responseObject))success failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure { NSError *serializationError = nil; NSMutableURLRequest *request = [self.requestSerializer multipartFormRequestWithMethod:@"POST" URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters constructingBodyWithBlock:block error:&serializationError]; if (serializationError) { if (failure) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{ failure(nil, serializationError); }); #pragma clang diagnostic pop } return nil; } __block NSURLSessionDataTask *task = [self uploadTaskWithStreamedRequest:request progress:nil completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) { if (error) { if (failure) { failure(task, error); } } else { if (success) { success(task, responseObject); } } }]; [task resume]; return task; } - (NSURLSessionDataTask *)PUT:(NSString *)URLString parameters:(id)parameters success:(void (^)(NSURLSessionDataTask *task, id responseObject))success failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure { NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"PUT" URLString:URLString parameters:parameters success:success failure:failure]; [dataTask resume]; return dataTask; } - (NSURLSessionDataTask *)PATCH:(NSString *)URLString parameters:(id)parameters success:(void (^)(NSURLSessionDataTask *task, id responseObject))success failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure { NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"PATCH" URLString:URLString parameters:parameters success:success failure:failure]; [dataTask resume]; return dataTask; } - (NSURLSessionDataTask *)DELETE:(NSString *)URLString parameters:(id)parameters success:(void (^)(NSURLSessionDataTask *task, id responseObject))success failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure { NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"DELETE" URLString:URLString parameters:parameters success:success failure:failure]; [dataTask resume]; return dataTask; } - (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method URLString:(NSString *)URLString parameters:(id)parameters success:(void (^)(NSURLSessionDataTask *, id))success failure:(void (^)(NSURLSessionDataTask *, NSError *))failure { NSError *serializationError = nil; NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError]; if (serializationError) { if (failure) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{ failure(nil, serializationError); }); #pragma clang diagnostic pop } return nil; } __block NSURLSessionDataTask *dataTask = nil; dataTask = [self dataTaskWithRequest:request completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) { if (error) { if (failure) { failure(dataTask, error); } } else { if (success) { success(dataTask, responseObject); } } }]; return dataTask; } #pragma mark - NSObject - (NSString *)description { return [NSString stringWithFormat:@"<%@: %p, baseURL: %@, session: %@, operationQueue: %@>", NSStringFromClass([self class]), self, [self.baseURL absoluteString], self.session, self.operationQueue]; } #pragma mark - NSSecureCoding + (BOOL)supportsSecureCoding { return YES; } - (id)initWithCoder:(NSCoder *)decoder { NSURL *baseURL = [decoder decodeObjectOfClass:[NSURL class] forKey:NSStringFromSelector(@selector(baseURL))]; NSURLSessionConfiguration *configuration = [decoder decodeObjectOfClass:[NSURLSessionConfiguration class] forKey:@"sessionConfiguration"]; if (!configuration) { NSString *configurationIdentifier = [decoder decodeObjectOfClass:[NSString class] forKey:@"identifier"]; if (configurationIdentifier) { #if (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1100) configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:configurationIdentifier]; #else configuration = [NSURLSessionConfiguration backgroundSessionConfiguration:configurationIdentifier]; #endif } } self = [self initWithBaseURL:baseURL sessionConfiguration:configuration]; if (!self) { return nil; } self.requestSerializer = [decoder decodeObjectOfClass:[AFHTTPRequestSerializer class] forKey:NSStringFromSelector(@selector(requestSerializer))]; self.responseSerializer = [decoder decodeObjectOfClass:[AFHTTPResponseSerializer class] forKey:NSStringFromSelector(@selector(responseSerializer))]; return self; } - (void)encodeWithCoder:(NSCoder *)coder { [super encodeWithCoder:coder]; [coder encodeObject:self.baseURL forKey:NSStringFromSelector(@selector(baseURL))]; if ([self.session.configuration conformsToProtocol:@protocol(NSCoding)]) { [coder encodeObject:self.session.configuration forKey:@"sessionConfiguration"]; } else { [coder encodeObject:self.session.configuration.identifier forKey:@"identifier"]; } [coder encodeObject:self.requestSerializer forKey:NSStringFromSelector(@selector(requestSerializer))]; [coder encodeObject:self.responseSerializer forKey:NSStringFromSelector(@selector(responseSerializer))]; } #pragma mark - NSCopying - (id)copyWithZone:(NSZone *)zone { AFHTTPSessionManager *HTTPClient = [[[self class] allocWithZone:zone] initWithBaseURL:self.baseURL sessionConfiguration:self.session.configuration]; HTTPClient.requestSerializer = [self.requestSerializer copyWithZone:zone]; HTTPClient.responseSerializer = [self.responseSerializer copyWithZone:zone]; return HTTPClient; } @end #endif ================================================ FILE: Pods/AFNetworking/AFNetworking/AFNetworkReachabilityManager.h ================================================ // AFNetworkReachabilityManager.h // // Copyright (c) 2013-2015 AFNetworking (http://afnetworking.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import #import #ifndef NS_DESIGNATED_INITIALIZER #if __has_attribute(objc_designated_initializer) #define NS_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) #else #define NS_DESIGNATED_INITIALIZER #endif #endif typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) { AFNetworkReachabilityStatusUnknown = -1, AFNetworkReachabilityStatusNotReachable = 0, AFNetworkReachabilityStatusReachableViaWWAN = 1, AFNetworkReachabilityStatusReachableViaWiFi = 2, }; /** `AFNetworkReachabilityManager` monitors the reachability of domains, and addresses for both WWAN and WiFi network interfaces. Reachability can be used to determine background information about why a network operation failed, or to trigger a network operation retrying when a connection is established. It should not be used to prevent a user from initiating a network request, as it's possible that an initial request may be required to establish reachability. See Apple's Reachability Sample Code (https://developer.apple.com/library/ios/samplecode/reachability/) @warning Instances of `AFNetworkReachabilityManager` must be started with `-startMonitoring` before reachability status can be determined. */ @interface AFNetworkReachabilityManager : NSObject /** The current network reachability status. */ @property (readonly, nonatomic, assign) AFNetworkReachabilityStatus networkReachabilityStatus; /** Whether or not the network is currently reachable. */ @property (readonly, nonatomic, assign, getter = isReachable) BOOL reachable; /** Whether or not the network is currently reachable via WWAN. */ @property (readonly, nonatomic, assign, getter = isReachableViaWWAN) BOOL reachableViaWWAN; /** Whether or not the network is currently reachable via WiFi. */ @property (readonly, nonatomic, assign, getter = isReachableViaWiFi) BOOL reachableViaWiFi; ///--------------------- /// @name Initialization ///--------------------- /** Returns the shared network reachability manager. */ + (instancetype)sharedManager; /** Creates and returns a network reachability manager for the specified domain. @param domain The domain used to evaluate network reachability. @return An initialized network reachability manager, actively monitoring the specified domain. */ + (instancetype)managerForDomain:(NSString *)domain; /** Creates and returns a network reachability manager for the socket address. @param address The socket address (`sockaddr_in`) used to evaluate network reachability. @return An initialized network reachability manager, actively monitoring the specified socket address. */ + (instancetype)managerForAddress:(const void *)address; /** Initializes an instance of a network reachability manager from the specified reachability object. @param reachability The reachability object to monitor. @return An initialized network reachability manager, actively monitoring the specified reachability. */ - (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability NS_DESIGNATED_INITIALIZER; ///-------------------------------------------------- /// @name Starting & Stopping Reachability Monitoring ///-------------------------------------------------- /** Starts monitoring for changes in network reachability status. */ - (void)startMonitoring; /** Stops monitoring for changes in network reachability status. */ - (void)stopMonitoring; ///------------------------------------------------- /// @name Getting Localized Reachability Description ///------------------------------------------------- /** Returns a localized string representation of the current network reachability status. */ - (NSString *)localizedNetworkReachabilityStatusString; ///--------------------------------------------------- /// @name Setting Network Reachability Change Callback ///--------------------------------------------------- /** Sets a callback to be executed when the network availability of the `baseURL` host changes. @param block A block object to be executed when the network availability of the `baseURL` host changes.. This block has no return value and takes a single argument which represents the various reachability states from the device to the `baseURL`. */ - (void)setReachabilityStatusChangeBlock:(void (^)(AFNetworkReachabilityStatus status))block; @end ///---------------- /// @name Constants ///---------------- /** ## Network Reachability The following constants are provided by `AFNetworkReachabilityManager` as possible network reachability statuses. enum { AFNetworkReachabilityStatusUnknown, AFNetworkReachabilityStatusNotReachable, AFNetworkReachabilityStatusReachableViaWWAN, AFNetworkReachabilityStatusReachableViaWiFi, } `AFNetworkReachabilityStatusUnknown` The `baseURL` host reachability is not known. `AFNetworkReachabilityStatusNotReachable` The `baseURL` host cannot be reached. `AFNetworkReachabilityStatusReachableViaWWAN` The `baseURL` host can be reached via a cellular connection, such as EDGE or GPRS. `AFNetworkReachabilityStatusReachableViaWiFi` The `baseURL` host can be reached via a Wi-Fi connection. ### Keys for Notification UserInfo Dictionary Strings that are used as keys in a `userInfo` dictionary in a network reachability status change notification. `AFNetworkingReachabilityNotificationStatusItem` A key in the userInfo dictionary in a `AFNetworkingReachabilityDidChangeNotification` notification. The corresponding value is an `NSNumber` object representing the `AFNetworkReachabilityStatus` value for the current reachability status. */ ///-------------------- /// @name Notifications ///-------------------- /** Posted when network reachability changes. This notification assigns no notification object. The `userInfo` dictionary contains an `NSNumber` object under the `AFNetworkingReachabilityNotificationStatusItem` key, representing the `AFNetworkReachabilityStatus` value for the current network reachability. @warning In order for network reachability to be monitored, include the `SystemConfiguration` framework in the active target's "Link Binary With Library" build phase, and add `#import ` to the header prefix of the project (`Prefix.pch`). */ extern NSString * const AFNetworkingReachabilityDidChangeNotification; extern NSString * const AFNetworkingReachabilityNotificationStatusItem; ///-------------------- /// @name Functions ///-------------------- /** Returns a localized string representation of an `AFNetworkReachabilityStatus` value. */ extern NSString * AFStringFromNetworkReachabilityStatus(AFNetworkReachabilityStatus status); ================================================ FILE: Pods/AFNetworking/AFNetworking/AFNetworkReachabilityManager.m ================================================ // AFNetworkReachabilityManager.m // // Copyright (c) 2013-2015 AFNetworking (http://afnetworking.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import "AFNetworkReachabilityManager.h" #import #import #import #import #import NSString * const AFNetworkingReachabilityDidChangeNotification = @"com.alamofire.networking.reachability.change"; NSString * const AFNetworkingReachabilityNotificationStatusItem = @"AFNetworkingReachabilityNotificationStatusItem"; typedef void (^AFNetworkReachabilityStatusBlock)(AFNetworkReachabilityStatus status); typedef NS_ENUM(NSUInteger, AFNetworkReachabilityAssociation) { AFNetworkReachabilityForAddress = 1, AFNetworkReachabilityForAddressPair = 2, AFNetworkReachabilityForName = 3, }; NSString * AFStringFromNetworkReachabilityStatus(AFNetworkReachabilityStatus status) { switch (status) { case AFNetworkReachabilityStatusNotReachable: return NSLocalizedStringFromTable(@"Not Reachable", @"AFNetworking", nil); case AFNetworkReachabilityStatusReachableViaWWAN: return NSLocalizedStringFromTable(@"Reachable via WWAN", @"AFNetworking", nil); case AFNetworkReachabilityStatusReachableViaWiFi: return NSLocalizedStringFromTable(@"Reachable via WiFi", @"AFNetworking", nil); case AFNetworkReachabilityStatusUnknown: default: return NSLocalizedStringFromTable(@"Unknown", @"AFNetworking", nil); } } static AFNetworkReachabilityStatus AFNetworkReachabilityStatusForFlags(SCNetworkReachabilityFlags flags) { BOOL isReachable = ((flags & kSCNetworkReachabilityFlagsReachable) != 0); BOOL needsConnection = ((flags & kSCNetworkReachabilityFlagsConnectionRequired) != 0); BOOL canConnectionAutomatically = (((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) || ((flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0)); BOOL canConnectWithoutUserInteraction = (canConnectionAutomatically && (flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0); BOOL isNetworkReachable = (isReachable && (!needsConnection || canConnectWithoutUserInteraction)); AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusUnknown; if (isNetworkReachable == NO) { status = AFNetworkReachabilityStatusNotReachable; } #if TARGET_OS_IPHONE else if ((flags & kSCNetworkReachabilityFlagsIsWWAN) != 0) { status = AFNetworkReachabilityStatusReachableViaWWAN; } #endif else { status = AFNetworkReachabilityStatusReachableViaWiFi; } return status; } static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) { AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusForFlags(flags); AFNetworkReachabilityStatusBlock block = (__bridge AFNetworkReachabilityStatusBlock)info; if (block) { block(status); } dispatch_async(dispatch_get_main_queue(), ^{ NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; NSDictionary *userInfo = @{ AFNetworkingReachabilityNotificationStatusItem: @(status) }; [notificationCenter postNotificationName:AFNetworkingReachabilityDidChangeNotification object:nil userInfo:userInfo]; }); } static const void * AFNetworkReachabilityRetainCallback(const void *info) { return Block_copy(info); } static void AFNetworkReachabilityReleaseCallback(const void *info) { if (info) { Block_release(info); } } @interface AFNetworkReachabilityManager () @property (readwrite, nonatomic, assign) SCNetworkReachabilityRef networkReachability; @property (readwrite, nonatomic, assign) AFNetworkReachabilityAssociation networkReachabilityAssociation; @property (readwrite, nonatomic, assign) AFNetworkReachabilityStatus networkReachabilityStatus; @property (readwrite, nonatomic, copy) AFNetworkReachabilityStatusBlock networkReachabilityStatusBlock; @end @implementation AFNetworkReachabilityManager + (instancetype)sharedManager { static AFNetworkReachabilityManager *_sharedManager = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ struct sockaddr_in address; bzero(&address, sizeof(address)); address.sin_len = sizeof(address); address.sin_family = AF_INET; _sharedManager = [self managerForAddress:&address]; }); return _sharedManager; } + (instancetype)managerForDomain:(NSString *)domain { SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, [domain UTF8String]); AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability]; manager.networkReachabilityAssociation = AFNetworkReachabilityForName; return manager; } + (instancetype)managerForAddress:(const void *)address { SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)address); AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability]; manager.networkReachabilityAssociation = AFNetworkReachabilityForAddress; return manager; } - (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability { self = [super init]; if (!self) { return nil; } self.networkReachability = reachability; self.networkReachabilityStatus = AFNetworkReachabilityStatusUnknown; return self; } - (void)dealloc { [self stopMonitoring]; if (_networkReachability) { CFRelease(_networkReachability); _networkReachability = NULL; } } #pragma mark - - (BOOL)isReachable { return [self isReachableViaWWAN] || [self isReachableViaWiFi]; } - (BOOL)isReachableViaWWAN { return self.networkReachabilityStatus == AFNetworkReachabilityStatusReachableViaWWAN; } - (BOOL)isReachableViaWiFi { return self.networkReachabilityStatus == AFNetworkReachabilityStatusReachableViaWiFi; } #pragma mark - - (void)startMonitoring { [self stopMonitoring]; if (!self.networkReachability) { return; } __weak __typeof(self)weakSelf = self; AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) { __strong __typeof(weakSelf)strongSelf = weakSelf; strongSelf.networkReachabilityStatus = status; if (strongSelf.networkReachabilityStatusBlock) { strongSelf.networkReachabilityStatusBlock(status); } }; SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL}; SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context); SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes); switch (self.networkReachabilityAssociation) { case AFNetworkReachabilityForName: break; case AFNetworkReachabilityForAddress: case AFNetworkReachabilityForAddressPair: default: { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{ SCNetworkReachabilityFlags flags; SCNetworkReachabilityGetFlags(self.networkReachability, &flags); AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusForFlags(flags); dispatch_async(dispatch_get_main_queue(), ^{ callback(status); NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; [notificationCenter postNotificationName:AFNetworkingReachabilityDidChangeNotification object:nil userInfo:@{ AFNetworkingReachabilityNotificationStatusItem: @(status) }]; }); }); } break; } } - (void)stopMonitoring { if (!self.networkReachability) { return; } SCNetworkReachabilityUnscheduleFromRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes); } #pragma mark - - (NSString *)localizedNetworkReachabilityStatusString { return AFStringFromNetworkReachabilityStatus(self.networkReachabilityStatus); } #pragma mark - - (void)setReachabilityStatusChangeBlock:(void (^)(AFNetworkReachabilityStatus status))block { self.networkReachabilityStatusBlock = block; } #pragma mark - NSKeyValueObserving + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key { if ([key isEqualToString:@"reachable"] || [key isEqualToString:@"reachableViaWWAN"] || [key isEqualToString:@"reachableViaWiFi"]) { return [NSSet setWithObject:@"networkReachabilityStatus"]; } return [super keyPathsForValuesAffectingValueForKey:key]; } @end ================================================ FILE: Pods/AFNetworking/AFNetworking/AFNetworking.h ================================================ // AFNetworking.h // // Copyright (c) 2013 AFNetworking (http://afnetworking.com/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import #import #ifndef _AFNETWORKING_ #define _AFNETWORKING_ #import "AFURLRequestSerialization.h" #import "AFURLResponseSerialization.h" #import "AFSecurityPolicy.h" #import "AFNetworkReachabilityManager.h" #import "AFURLConnectionOperation.h" #import "AFHTTPRequestOperation.h" #import "AFHTTPRequestOperationManager.h" #if ( ( defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1090) || \ ( defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 ) ) #import "AFURLSessionManager.h" #import "AFHTTPSessionManager.h" #endif #endif /* _AFNETWORKING_ */ ================================================ FILE: Pods/AFNetworking/AFNetworking/AFSecurityPolicy.h ================================================ // AFSecurityPolicy.h // // Copyright (c) 2013-2015 AFNetworking (http://afnetworking.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import #import typedef NS_ENUM(NSUInteger, AFSSLPinningMode) { AFSSLPinningModeNone, AFSSLPinningModePublicKey, AFSSLPinningModeCertificate, }; /** `AFSecurityPolicy` evaluates server trust against pinned X.509 certificates and public keys over secure connections. Adding pinned SSL certificates to your app helps prevent man-in-the-middle attacks and other vulnerabilities. Applications dealing with sensitive customer data or financial information are strongly encouraged to route all communication over an HTTPS connection with SSL pinning configured and enabled. */ @interface AFSecurityPolicy : NSObject /** The criteria by which server trust should be evaluated against the pinned SSL certificates. Defaults to `AFSSLPinningModeNone`. */ @property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode; /** Whether to evaluate an entire SSL certificate chain, or just the leaf certificate. Defaults to `YES`. */ @property (nonatomic, assign) BOOL validatesCertificateChain; /** The certificates used to evaluate server trust according to the SSL pinning mode. By default, this property is set to any (`.cer`) certificates included in the app bundle. */ @property (nonatomic, strong) NSArray *pinnedCertificates; /** Whether or not to trust servers with an invalid or expired SSL certificates. Defaults to `NO`. */ @property (nonatomic, assign) BOOL allowInvalidCertificates; /** Whether or not to validate the domain name in the certificate's CN field. Defaults to `YES`. */ @property (nonatomic, assign) BOOL validatesDomainName; ///----------------------------------------- /// @name Getting Specific Security Policies ///----------------------------------------- /** Returns the shared default security policy, which does not allow invalid certificates, does not validate domain name, and does not validate against pinned certificates or public keys. @return The default security policy. */ + (instancetype)defaultPolicy; ///--------------------- /// @name Initialization ///--------------------- /** Creates and returns a security policy with the specified pinning mode. @param pinningMode The SSL pinning mode. @return A new security policy. */ + (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode; ///------------------------------ /// @name Evaluating Server Trust ///------------------------------ /** Whether or not the specified server trust should be accepted, based on the security policy. This method should be used when responding to an authentication challenge from a server. @param serverTrust The X.509 certificate trust of the server. @return Whether or not to trust the server. @warning This method has been deprecated in favor of `-evaluateServerTrust:forDomain:`. */ - (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust DEPRECATED_ATTRIBUTE; /** Whether or not the specified server trust should be accepted, based on the security policy. This method should be used when responding to an authentication challenge from a server. @param serverTrust The X.509 certificate trust of the server. @param domain The domain of serverTrust. If `nil`, the domain will not be validated. @return Whether or not to trust the server. */ - (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain; @end ///---------------- /// @name Constants ///---------------- /** ## SSL Pinning Modes The following constants are provided by `AFSSLPinningMode` as possible SSL pinning modes. enum { AFSSLPinningModeNone, AFSSLPinningModePublicKey, AFSSLPinningModeCertificate, } `AFSSLPinningModeNone` Do not used pinned certificates to validate servers. `AFSSLPinningModePublicKey` Validate host certificates against public keys of pinned certificates. `AFSSLPinningModeCertificate` Validate host certificates against pinned certificates. */ ================================================ FILE: Pods/AFNetworking/AFNetworking/AFSecurityPolicy.m ================================================ // AFSecurityPolicy.m // // Copyright (c) 2013-2015 AFNetworking (http://afnetworking.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import "AFSecurityPolicy.h" #import #if !defined(__IPHONE_OS_VERSION_MIN_REQUIRED) static NSData * AFSecKeyGetData(SecKeyRef key) { CFDataRef data = NULL; __Require_noErr_Quiet(SecItemExport(key, kSecFormatUnknown, kSecItemPemArmour, NULL, &data), _out); return (__bridge_transfer NSData *)data; _out: if (data) { CFRelease(data); } return nil; } #endif static BOOL AFSecKeyIsEqualToKey(SecKeyRef key1, SecKeyRef key2) { #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) return [(__bridge id)key1 isEqual:(__bridge id)key2]; #else return [AFSecKeyGetData(key1) isEqual:AFSecKeyGetData(key2)]; #endif } static id AFPublicKeyForCertificate(NSData *certificate) { id allowedPublicKey = nil; SecCertificateRef allowedCertificate; SecCertificateRef allowedCertificates[1]; CFArrayRef tempCertificates = nil; SecPolicyRef policy = nil; SecTrustRef allowedTrust = nil; SecTrustResultType result; allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate); __Require_Quiet(allowedCertificate != NULL, _out); allowedCertificates[0] = allowedCertificate; tempCertificates = CFArrayCreate(NULL, (const void **)allowedCertificates, 1, NULL); policy = SecPolicyCreateBasicX509(); __Require_noErr_Quiet(SecTrustCreateWithCertificates(tempCertificates, policy, &allowedTrust), _out); __Require_noErr_Quiet(SecTrustEvaluate(allowedTrust, &result), _out); allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust); _out: if (allowedTrust) { CFRelease(allowedTrust); } if (policy) { CFRelease(policy); } if (tempCertificates) { CFRelease(tempCertificates); } if (allowedCertificate) { CFRelease(allowedCertificate); } return allowedPublicKey; } static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) { BOOL isValid = NO; SecTrustResultType result; __Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out); isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed); _out: return isValid; } static NSArray * AFCertificateTrustChainForServerTrust(SecTrustRef serverTrust) { CFIndex certificateCount = SecTrustGetCertificateCount(serverTrust); NSMutableArray *trustChain = [NSMutableArray arrayWithCapacity:(NSUInteger)certificateCount]; for (CFIndex i = 0; i < certificateCount; i++) { SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, i); [trustChain addObject:(__bridge_transfer NSData *)SecCertificateCopyData(certificate)]; } return [NSArray arrayWithArray:trustChain]; } static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) { SecPolicyRef policy = SecPolicyCreateBasicX509(); CFIndex certificateCount = SecTrustGetCertificateCount(serverTrust); NSMutableArray *trustChain = [NSMutableArray arrayWithCapacity:(NSUInteger)certificateCount]; for (CFIndex i = 0; i < certificateCount; i++) { SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, i); SecCertificateRef someCertificates[] = {certificate}; CFArrayRef certificates = CFArrayCreate(NULL, (const void **)someCertificates, 1, NULL); SecTrustRef trust; __Require_noErr_Quiet(SecTrustCreateWithCertificates(certificates, policy, &trust), _out); SecTrustResultType result; __Require_noErr_Quiet(SecTrustEvaluate(trust, &result), _out); [trustChain addObject:(__bridge_transfer id)SecTrustCopyPublicKey(trust)]; _out: if (trust) { CFRelease(trust); } if (certificates) { CFRelease(certificates); } continue; } CFRelease(policy); return [NSArray arrayWithArray:trustChain]; } #pragma mark - @interface AFSecurityPolicy() @property (readwrite, nonatomic, assign) AFSSLPinningMode SSLPinningMode; @property (readwrite, nonatomic, strong) NSArray *pinnedPublicKeys; @end @implementation AFSecurityPolicy + (NSArray *)defaultPinnedCertificates { static NSArray *_defaultPinnedCertificates = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSBundle *bundle = [NSBundle bundleForClass:[self class]]; NSArray *paths = [bundle pathsForResourcesOfType:@"cer" inDirectory:@"."]; NSMutableArray *certificates = [NSMutableArray arrayWithCapacity:[paths count]]; for (NSString *path in paths) { NSData *certificateData = [NSData dataWithContentsOfFile:path]; [certificates addObject:certificateData]; } _defaultPinnedCertificates = [[NSArray alloc] initWithArray:certificates]; }); return _defaultPinnedCertificates; } + (instancetype)defaultPolicy { AFSecurityPolicy *securityPolicy = [[self alloc] init]; securityPolicy.SSLPinningMode = AFSSLPinningModeNone; return securityPolicy; } + (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode { AFSecurityPolicy *securityPolicy = [[self alloc] init]; securityPolicy.SSLPinningMode = pinningMode; [securityPolicy setPinnedCertificates:[self defaultPinnedCertificates]]; return securityPolicy; } - (id)init { self = [super init]; if (!self) { return nil; } self.validatesCertificateChain = YES; self.validatesDomainName = YES; return self; } - (void)setPinnedCertificates:(NSArray *)pinnedCertificates { _pinnedCertificates = pinnedCertificates; if (self.pinnedCertificates) { NSMutableArray *mutablePinnedPublicKeys = [NSMutableArray arrayWithCapacity:[self.pinnedCertificates count]]; for (NSData *certificate in self.pinnedCertificates) { id publicKey = AFPublicKeyForCertificate(certificate); if (!publicKey) { continue; } [mutablePinnedPublicKeys addObject:publicKey]; } self.pinnedPublicKeys = [NSArray arrayWithArray:mutablePinnedPublicKeys]; } else { self.pinnedPublicKeys = nil; } } #pragma mark - - (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust { return [self evaluateServerTrust:serverTrust forDomain:nil]; } - (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain { NSMutableArray *policies = [NSMutableArray array]; if (self.validatesDomainName) { [policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)]; } else { [policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()]; } SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies); if (self.SSLPinningMode == AFSSLPinningModeNone) { if (self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust)){ return YES; } else { return NO; } } else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) { return NO; } NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust); switch (self.SSLPinningMode) { case AFSSLPinningModeNone: default: return NO; case AFSSLPinningModeCertificate: { NSMutableArray *pinnedCertificates = [NSMutableArray array]; for (NSData *certificateData in self.pinnedCertificates) { [pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)]; } SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates); if (!AFServerTrustIsValid(serverTrust)) { return NO; } if (!self.validatesCertificateChain) { return YES; } NSUInteger trustedCertificateCount = 0; for (NSData *trustChainCertificate in serverCertificates) { if ([self.pinnedCertificates containsObject:trustChainCertificate]) { trustedCertificateCount++; } } return trustedCertificateCount == [serverCertificates count]; } case AFSSLPinningModePublicKey: { NSUInteger trustedPublicKeyCount = 0; NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust); if (!self.validatesCertificateChain && [publicKeys count] > 0) { publicKeys = @[[publicKeys firstObject]]; } for (id trustChainPublicKey in publicKeys) { for (id pinnedPublicKey in self.pinnedPublicKeys) { if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) { trustedPublicKeyCount += 1; } } } return trustedPublicKeyCount > 0 && ((self.validatesCertificateChain && trustedPublicKeyCount == [serverCertificates count]) || (!self.validatesCertificateChain && trustedPublicKeyCount >= 1)); } } return NO; } #pragma mark - NSKeyValueObserving + (NSSet *)keyPathsForValuesAffectingPinnedPublicKeys { return [NSSet setWithObject:@"pinnedCertificates"]; } @end ================================================ FILE: Pods/AFNetworking/AFNetworking/AFURLConnectionOperation.h ================================================ // AFURLConnectionOperation.h // // Copyright (c) 2013-2015 AFNetworking (http://afnetworking.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import #import #import "AFURLRequestSerialization.h" #import "AFURLResponseSerialization.h" #import "AFSecurityPolicy.h" #ifndef NS_DESIGNATED_INITIALIZER #if __has_attribute(objc_designated_initializer) #define NS_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) #else #define NS_DESIGNATED_INITIALIZER #endif #endif /** `AFURLConnectionOperation` is a subclass of `NSOperation` that implements `NSURLConnection` delegate methods. ## Subclassing Notes This is the base class of all network request operations. You may wish to create your own subclass in order to implement additional `NSURLConnection` delegate methods (see "`NSURLConnection` Delegate Methods" below), or to provide additional properties and/or class constructors. If you are creating a subclass that communicates over the HTTP or HTTPS protocols, you may want to consider subclassing `AFHTTPRequestOperation` instead, as it supports specifying acceptable content types or status codes. ## NSURLConnection Delegate Methods `AFURLConnectionOperation` implements the following `NSURLConnection` delegate methods: - `connection:didReceiveResponse:` - `connection:didReceiveData:` - `connectionDidFinishLoading:` - `connection:didFailWithError:` - `connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:` - `connection:willCacheResponse:` - `connectionShouldUseCredentialStorage:` - `connection:needNewBodyStream:` - `connection:willSendRequestForAuthenticationChallenge:` If any of these methods are overridden in a subclass, they _must_ call the `super` implementation first. ## Callbacks and Completion Blocks The built-in `completionBlock` provided by `NSOperation` allows for custom behavior to be executed after the request finishes. It is a common pattern for class constructors in subclasses to take callback block parameters, and execute them conditionally in the body of its `completionBlock`. Make sure to handle cancelled operations appropriately when setting a `completionBlock` (i.e. returning early before parsing response data). See the implementation of any of the `AFHTTPRequestOperation` subclasses for an example of this. Subclasses are strongly discouraged from overriding `setCompletionBlock:`, as `AFURLConnectionOperation`'s implementation includes a workaround to mitigate retain cycles, and what Apple rather ominously refers to as ["The Deallocation Problem"](http://developer.apple.com/library/ios/#technotes/tn2109/). ## SSL Pinning Relying on the CA trust model to validate SSL certificates exposes your app to security vulnerabilities, such as man-in-the-middle attacks. For applications that connect to known servers, SSL certificate pinning provides an increased level of security, by checking server certificate validity against those specified in the app bundle. SSL with certificate pinning is strongly recommended for any application that transmits sensitive information to an external webservice. Connections will be validated on all matching certificates with a `.cer` extension in the bundle root. ## App Extensions When using AFNetworking in an App Extension, `#define AF_APP_EXTENSIONS` to avoid using unavailable APIs. ## NSCoding & NSCopying Conformance `AFURLConnectionOperation` conforms to the `NSCoding` and `NSCopying` protocols, allowing operations to be archived to disk, and copied in memory, respectively. However, because of the intrinsic limitations of capturing the exact state of an operation at a particular moment, there are some important caveats to keep in mind: ### NSCoding Caveats - Encoded operations do not include any block or stream properties. Be sure to set `completionBlock`, `outputStream`, and any callback blocks as necessary when using `-initWithCoder:` or `NSKeyedUnarchiver`. - Operations are paused on `encodeWithCoder:`. If the operation was encoded while paused or still executing, its archived state will return `YES` for `isReady`. Otherwise, the state of an operation when encoding will remain unchanged. ### NSCopying Caveats - `-copy` and `-copyWithZone:` return a new operation with the `NSURLRequest` of the original. So rather than an exact copy of the operation at that particular instant, the copying mechanism returns a completely new instance, which can be useful for retrying operations. - A copy of an operation will not include the `outputStream` of the original. - Operation copies do not include `completionBlock`, as it often strongly captures a reference to `self`, which would otherwise have the unintuitive side-effect of pointing to the _original_ operation when copied. */ @interface AFURLConnectionOperation : NSOperation ///------------------------------- /// @name Accessing Run Loop Modes ///------------------------------- /** The run loop modes in which the operation will run on the network thread. By default, this is a single-member set containing `NSRunLoopCommonModes`. */ @property (nonatomic, strong) NSSet *runLoopModes; ///----------------------------------------- /// @name Getting URL Connection Information ///----------------------------------------- /** The request used by the operation's connection. */ @property (readonly, nonatomic, strong) NSURLRequest *request; /** The last response received by the operation's connection. */ @property (readonly, nonatomic, strong) NSURLResponse *response; /** The error, if any, that occurred in the lifecycle of the request. */ @property (readonly, nonatomic, strong) NSError *error; ///---------------------------- /// @name Getting Response Data ///---------------------------- /** The data received during the request. */ @property (readonly, nonatomic, strong) NSData *responseData; /** The string representation of the response data. */ @property (readonly, nonatomic, copy) NSString *responseString; /** The string encoding of the response. If the response does not specify a valid string encoding, `responseStringEncoding` will return `NSUTF8StringEncoding`. */ @property (readonly, nonatomic, assign) NSStringEncoding responseStringEncoding; ///------------------------------- /// @name Managing URL Credentials ///------------------------------- /** Whether the URL connection should consult the credential storage for authenticating the connection. `YES` by default. This is the value that is returned in the `NSURLConnectionDelegate` method `-connectionShouldUseCredentialStorage:`. */ @property (nonatomic, assign) BOOL shouldUseCredentialStorage; /** The credential used for authentication challenges in `-connection:didReceiveAuthenticationChallenge:`. This will be overridden by any shared credentials that exist for the username or password of the request URL, if present. */ @property (nonatomic, strong) NSURLCredential *credential; ///------------------------------- /// @name Managing Security Policy ///------------------------------- /** The security policy used to evaluate server trust for secure connections. */ @property (nonatomic, strong) AFSecurityPolicy *securityPolicy; ///------------------------ /// @name Accessing Streams ///------------------------ /** The input stream used to read data to be sent during the request. This property acts as a proxy to the `HTTPBodyStream` property of `request`. */ @property (nonatomic, strong) NSInputStream *inputStream; /** The output stream that is used to write data received until the request is finished. By default, data is accumulated into a buffer that is stored into `responseData` upon completion of the request, with the intermediary `outputStream` property set to `nil`. When `outputStream` is set, the data will not be accumulated into an internal buffer, and as a result, the `responseData` property of the completed request will be `nil`. The output stream will be scheduled in the network thread runloop upon being set. */ @property (nonatomic, strong) NSOutputStream *outputStream; ///--------------------------------- /// @name Managing Callback Queues ///--------------------------------- /** The dispatch queue for `completionBlock`. If `NULL` (default), the main queue is used. */ #if OS_OBJECT_HAVE_OBJC_SUPPORT @property (nonatomic, strong) dispatch_queue_t completionQueue; #else @property (nonatomic, assign) dispatch_queue_t completionQueue; #endif /** The dispatch group for `completionBlock`. If `NULL` (default), a private dispatch group is used. */ #if OS_OBJECT_HAVE_OBJC_SUPPORT @property (nonatomic, strong) dispatch_group_t completionGroup; #else @property (nonatomic, assign) dispatch_group_t completionGroup; #endif ///--------------------------------------------- /// @name Managing Request Operation Information ///--------------------------------------------- /** The user info dictionary for the receiver. */ @property (nonatomic, strong) NSDictionary *userInfo; ///------------------------------------------------------ /// @name Initializing an AFURLConnectionOperation Object ///------------------------------------------------------ /** Initializes and returns a newly allocated operation object with a url connection configured with the specified url request. This is the designated initializer. @param urlRequest The request object to be used by the operation connection. */ - (instancetype)initWithRequest:(NSURLRequest *)urlRequest NS_DESIGNATED_INITIALIZER; ///---------------------------------- /// @name Pausing / Resuming Requests ///---------------------------------- /** Pauses the execution of the request operation. A paused operation returns `NO` for `-isReady`, `-isExecuting`, and `-isFinished`. As such, it will remain in an `NSOperationQueue` until it is either cancelled or resumed. Pausing a finished, cancelled, or paused operation has no effect. */ - (void)pause; /** Whether the request operation is currently paused. @return `YES` if the operation is currently paused, otherwise `NO`. */ - (BOOL)isPaused; /** Resumes the execution of the paused request operation. Pause/Resume behavior varies depending on the underlying implementation for the operation class. In its base implementation, resuming a paused requests restarts the original request. However, since HTTP defines a specification for how to request a specific content range, `AFHTTPRequestOperation` will resume downloading the request from where it left off, instead of restarting the original request. */ - (void)resume; ///---------------------------------------------- /// @name Configuring Backgrounding Task Behavior ///---------------------------------------------- /** Specifies that the operation should continue execution after the app has entered the background, and the expiration handler for that background task. @param handler A handler to be called shortly before the application’s remaining background time reaches 0. The handler is wrapped in a block that cancels the operation, and cleans up and marks the end of execution, unlike the `handler` parameter in `UIApplication -beginBackgroundTaskWithExpirationHandler:`, which expects this to be done in the handler itself. The handler is called synchronously on the main thread, thus blocking the application’s suspension momentarily while the application is notified. */ #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && !defined(AF_APP_EXTENSIONS) - (void)setShouldExecuteAsBackgroundTaskWithExpirationHandler:(void (^)(void))handler; #endif ///--------------------------------- /// @name Setting Progress Callbacks ///--------------------------------- /** Sets a callback to be called when an undetermined number of bytes have been uploaded to the server. @param block A block object to be called when an undetermined number of bytes have been uploaded to the server. This block has no return value and takes three arguments: the number of bytes written since the last time the upload progress block was called, the total bytes written, and the total bytes expected to be written during the request, as initially determined by the length of the HTTP body. This block may be called multiple times, and will execute on the main thread. */ - (void)setUploadProgressBlock:(void (^)(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))block; /** Sets a callback to be called when an undetermined number of bytes have been downloaded from the server. @param block A block object to be called when an undetermined number of bytes have been downloaded from the server. This block has no return value and takes three arguments: the number of bytes read since the last time the download progress block was called, the total bytes read, and the total bytes expected to be read during the request, as initially determined by the expected content size of the `NSHTTPURLResponse` object. This block may be called multiple times, and will execute on the main thread. */ - (void)setDownloadProgressBlock:(void (^)(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead))block; ///------------------------------------------------- /// @name Setting NSURLConnection Delegate Callbacks ///------------------------------------------------- /** Sets a block to be executed when the connection will authenticate a challenge in order to download its request, as handled by the `NSURLConnectionDelegate` method `connection:willSendRequestForAuthenticationChallenge:`. @param block A block object to be executed when the connection will authenticate a challenge in order to download its request. The block has no return type and takes two arguments: the URL connection object, and the challenge that must be authenticated. This block must invoke one of the challenge-responder methods (NSURLAuthenticationChallengeSender protocol). If `allowsInvalidSSLCertificate` is set to YES, `connection:willSendRequestForAuthenticationChallenge:` will attempt to have the challenge sender use credentials with invalid SSL certificates. */ - (void)setWillSendRequestForAuthenticationChallengeBlock:(void (^)(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge))block; /** Sets a block to be executed when the server redirects the request from one URL to another URL, or when the request URL changed by the `NSURLProtocol` subclass handling the request in order to standardize its format, as handled by the `NSURLConnectionDataDelegate` method `connection:willSendRequest:redirectResponse:`. @param block A block object to be executed when the request URL was changed. The block returns an `NSURLRequest` object, the URL request to redirect, and takes three arguments: the URL connection object, the the proposed redirected request, and the URL response that caused the redirect. */ - (void)setRedirectResponseBlock:(NSURLRequest * (^)(NSURLConnection *connection, NSURLRequest *request, NSURLResponse *redirectResponse))block; /** Sets a block to be executed to modify the response a connection will cache, if any, as handled by the `NSURLConnectionDelegate` method `connection:willCacheResponse:`. @param block A block object to be executed to determine what response a connection will cache, if any. The block returns an `NSCachedURLResponse` object, the cached response to store in memory or `nil` to prevent the response from being cached, and takes two arguments: the URL connection object, and the cached response provided for the request. */ - (void)setCacheResponseBlock:(NSCachedURLResponse * (^)(NSURLConnection *connection, NSCachedURLResponse *cachedResponse))block; /// /** */ + (NSArray *)batchOfRequestOperations:(NSArray *)operations progressBlock:(void (^)(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations))progressBlock completionBlock:(void (^)(NSArray *operations))completionBlock; @end ///-------------------- /// @name Notifications ///-------------------- /** Posted when an operation begins executing. */ extern NSString * const AFNetworkingOperationDidStartNotification; /** Posted when an operation finishes. */ extern NSString * const AFNetworkingOperationDidFinishNotification; ================================================ FILE: Pods/AFNetworking/AFNetworking/AFURLConnectionOperation.m ================================================ // AFURLConnectionOperation.m // // Copyright (c) 2013-2015 AFNetworking (http://afnetworking.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import "AFURLConnectionOperation.h" #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) #import #endif #if !__has_feature(objc_arc) #error AFNetworking must be built with ARC. // You can turn on ARC for only AFNetworking files by adding -fobjc-arc to the build phase for each of its files. #endif typedef NS_ENUM(NSInteger, AFOperationState) { AFOperationPausedState = -1, AFOperationReadyState = 1, AFOperationExecutingState = 2, AFOperationFinishedState = 3, }; #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && !defined(AF_APP_EXTENSIONS) typedef UIBackgroundTaskIdentifier AFBackgroundTaskIdentifier; #else typedef id AFBackgroundTaskIdentifier; #endif static dispatch_group_t url_request_operation_completion_group() { static dispatch_group_t af_url_request_operation_completion_group; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ af_url_request_operation_completion_group = dispatch_group_create(); }); return af_url_request_operation_completion_group; } static dispatch_queue_t url_request_operation_completion_queue() { static dispatch_queue_t af_url_request_operation_completion_queue; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ af_url_request_operation_completion_queue = dispatch_queue_create("com.alamofire.networking.operation.queue", DISPATCH_QUEUE_CONCURRENT ); }); return af_url_request_operation_completion_queue; } static NSString * const kAFNetworkingLockName = @"com.alamofire.networking.operation.lock"; NSString * const AFNetworkingOperationDidStartNotification = @"com.alamofire.networking.operation.start"; NSString * const AFNetworkingOperationDidFinishNotification = @"com.alamofire.networking.operation.finish"; typedef void (^AFURLConnectionOperationProgressBlock)(NSUInteger bytes, long long totalBytes, long long totalBytesExpected); typedef void (^AFURLConnectionOperationAuthenticationChallengeBlock)(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge); typedef NSCachedURLResponse * (^AFURLConnectionOperationCacheResponseBlock)(NSURLConnection *connection, NSCachedURLResponse *cachedResponse); typedef NSURLRequest * (^AFURLConnectionOperationRedirectResponseBlock)(NSURLConnection *connection, NSURLRequest *request, NSURLResponse *redirectResponse); static inline NSString * AFKeyPathFromOperationState(AFOperationState state) { switch (state) { case AFOperationReadyState: return @"isReady"; case AFOperationExecutingState: return @"isExecuting"; case AFOperationFinishedState: return @"isFinished"; case AFOperationPausedState: return @"isPaused"; default: { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunreachable-code" return @"state"; #pragma clang diagnostic pop } } } static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperationState toState, BOOL isCancelled) { switch (fromState) { case AFOperationReadyState: switch (toState) { case AFOperationPausedState: case AFOperationExecutingState: return YES; case AFOperationFinishedState: return isCancelled; default: return NO; } case AFOperationExecutingState: switch (toState) { case AFOperationPausedState: case AFOperationFinishedState: return YES; default: return NO; } case AFOperationFinishedState: return NO; case AFOperationPausedState: return toState == AFOperationReadyState; default: { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunreachable-code" switch (toState) { case AFOperationPausedState: case AFOperationReadyState: case AFOperationExecutingState: case AFOperationFinishedState: return YES; default: return NO; } } #pragma clang diagnostic pop } } @interface AFURLConnectionOperation () @property (readwrite, nonatomic, assign) AFOperationState state; @property (readwrite, nonatomic, strong) NSRecursiveLock *lock; @property (readwrite, nonatomic, strong) NSURLConnection *connection; @property (readwrite, nonatomic, strong) NSURLRequest *request; @property (readwrite, nonatomic, strong) NSURLResponse *response; @property (readwrite, nonatomic, strong) NSError *error; @property (readwrite, nonatomic, strong) NSData *responseData; @property (readwrite, nonatomic, copy) NSString *responseString; @property (readwrite, nonatomic, assign) NSStringEncoding responseStringEncoding; @property (readwrite, nonatomic, assign) long long totalBytesRead; @property (readwrite, nonatomic, assign) AFBackgroundTaskIdentifier backgroundTaskIdentifier; @property (readwrite, nonatomic, copy) AFURLConnectionOperationProgressBlock uploadProgress; @property (readwrite, nonatomic, copy) AFURLConnectionOperationProgressBlock downloadProgress; @property (readwrite, nonatomic, copy) AFURLConnectionOperationAuthenticationChallengeBlock authenticationChallenge; @property (readwrite, nonatomic, copy) AFURLConnectionOperationCacheResponseBlock cacheResponse; @property (readwrite, nonatomic, copy) AFURLConnectionOperationRedirectResponseBlock redirectResponse; - (void)operationDidStart; - (void)finish; - (void)cancelConnection; @end @implementation AFURLConnectionOperation @synthesize outputStream = _outputStream; + (void)networkRequestThreadEntryPoint:(id)__unused object { @autoreleasepool { [[NSThread currentThread] setName:@"AFNetworking"]; NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; [runLoop run]; } } + (NSThread *)networkRequestThread { static NSThread *_networkRequestThread = nil; static dispatch_once_t oncePredicate; dispatch_once(&oncePredicate, ^{ _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil]; [_networkRequestThread start]; }); return _networkRequestThread; } - (instancetype)initWithRequest:(NSURLRequest *)urlRequest { NSParameterAssert(urlRequest); self = [super init]; if (!self) { return nil; } _state = AFOperationReadyState; self.lock = [[NSRecursiveLock alloc] init]; self.lock.name = kAFNetworkingLockName; self.runLoopModes = [NSSet setWithObject:NSRunLoopCommonModes]; self.request = urlRequest; self.shouldUseCredentialStorage = YES; self.securityPolicy = [AFSecurityPolicy defaultPolicy]; return self; } - (void)dealloc { if (_outputStream) { [_outputStream close]; _outputStream = nil; } #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && !defined(AF_APP_EXTENSIONS) if (_backgroundTaskIdentifier) { [[UIApplication sharedApplication] endBackgroundTask:_backgroundTaskIdentifier]; _backgroundTaskIdentifier = UIBackgroundTaskInvalid; } #endif } #pragma mark - - (void)setResponseData:(NSData *)responseData { [self.lock lock]; if (!responseData) { _responseData = nil; } else { _responseData = [NSData dataWithBytes:responseData.bytes length:responseData.length]; } [self.lock unlock]; } - (NSString *)responseString { [self.lock lock]; if (!_responseString && self.response && self.responseData) { self.responseString = [[NSString alloc] initWithData:self.responseData encoding:self.responseStringEncoding]; } [self.lock unlock]; return _responseString; } - (NSStringEncoding)responseStringEncoding { [self.lock lock]; if (!_responseStringEncoding && self.response) { NSStringEncoding stringEncoding = NSUTF8StringEncoding; if (self.response.textEncodingName) { CFStringEncoding IANAEncoding = CFStringConvertIANACharSetNameToEncoding((__bridge CFStringRef)self.response.textEncodingName); if (IANAEncoding != kCFStringEncodingInvalidId) { stringEncoding = CFStringConvertEncodingToNSStringEncoding(IANAEncoding); } } self.responseStringEncoding = stringEncoding; } [self.lock unlock]; return _responseStringEncoding; } - (NSInputStream *)inputStream { return self.request.HTTPBodyStream; } - (void)setInputStream:(NSInputStream *)inputStream { NSMutableURLRequest *mutableRequest = [self.request mutableCopy]; mutableRequest.HTTPBodyStream = inputStream; self.request = mutableRequest; } - (NSOutputStream *)outputStream { if (!_outputStream) { self.outputStream = [NSOutputStream outputStreamToMemory]; } return _outputStream; } - (void)setOutputStream:(NSOutputStream *)outputStream { [self.lock lock]; if (outputStream != _outputStream) { if (_outputStream) { [_outputStream close]; } _outputStream = outputStream; } [self.lock unlock]; } #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && !defined(AF_APP_EXTENSIONS) - (void)setShouldExecuteAsBackgroundTaskWithExpirationHandler:(void (^)(void))handler { [self.lock lock]; if (!self.backgroundTaskIdentifier) { UIApplication *application = [UIApplication sharedApplication]; __weak __typeof(self)weakSelf = self; self.backgroundTaskIdentifier = [application beginBackgroundTaskWithExpirationHandler:^{ __strong __typeof(weakSelf)strongSelf = weakSelf; if (handler) { handler(); } if (strongSelf) { [strongSelf cancel]; [application endBackgroundTask:strongSelf.backgroundTaskIdentifier]; strongSelf.backgroundTaskIdentifier = UIBackgroundTaskInvalid; } }]; } [self.lock unlock]; } #endif #pragma mark - - (void)setState:(AFOperationState)state { if (!AFStateTransitionIsValid(self.state, state, [self isCancelled])) { return; } [self.lock lock]; NSString *oldStateKey = AFKeyPathFromOperationState(self.state); NSString *newStateKey = AFKeyPathFromOperationState(state); [self willChangeValueForKey:newStateKey]; [self willChangeValueForKey:oldStateKey]; _state = state; [self didChangeValueForKey:oldStateKey]; [self didChangeValueForKey:newStateKey]; [self.lock unlock]; } - (void)pause { if ([self isPaused] || [self isFinished] || [self isCancelled]) { return; } [self.lock lock]; if ([self isExecuting]) { [self performSelector:@selector(operationDidPause) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; dispatch_async(dispatch_get_main_queue(), ^{ NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; [notificationCenter postNotificationName:AFNetworkingOperationDidFinishNotification object:self]; }); } self.state = AFOperationPausedState; [self.lock unlock]; } - (void)operationDidPause { [self.lock lock]; [self.connection cancel]; [self.lock unlock]; } - (BOOL)isPaused { return self.state == AFOperationPausedState; } - (void)resume { if (![self isPaused]) { return; } [self.lock lock]; self.state = AFOperationReadyState; [self start]; [self.lock unlock]; } #pragma mark - - (void)setUploadProgressBlock:(void (^)(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))block { self.uploadProgress = block; } - (void)setDownloadProgressBlock:(void (^)(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead))block { self.downloadProgress = block; } - (void)setWillSendRequestForAuthenticationChallengeBlock:(void (^)(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge))block { self.authenticationChallenge = block; } - (void)setCacheResponseBlock:(NSCachedURLResponse * (^)(NSURLConnection *connection, NSCachedURLResponse *cachedResponse))block { self.cacheResponse = block; } - (void)setRedirectResponseBlock:(NSURLRequest * (^)(NSURLConnection *connection, NSURLRequest *request, NSURLResponse *redirectResponse))block { self.redirectResponse = block; } #pragma mark - NSOperation - (void)setCompletionBlock:(void (^)(void))block { [self.lock lock]; if (!block) { [super setCompletionBlock:nil]; } else { __weak __typeof(self)weakSelf = self; [super setCompletionBlock:^ { __strong __typeof(weakSelf)strongSelf = weakSelf; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" dispatch_group_t group = strongSelf.completionGroup ?: url_request_operation_completion_group(); dispatch_queue_t queue = strongSelf.completionQueue ?: dispatch_get_main_queue(); #pragma clang diagnostic pop dispatch_group_async(group, queue, ^{ block(); }); dispatch_group_notify(group, url_request_operation_completion_queue(), ^{ [strongSelf setCompletionBlock:nil]; }); }]; } [self.lock unlock]; } - (BOOL)isReady { return self.state == AFOperationReadyState && [super isReady]; } - (BOOL)isExecuting { return self.state == AFOperationExecutingState; } - (BOOL)isFinished { return self.state == AFOperationFinishedState; } - (BOOL)isConcurrent { return YES; } - (void)start { [self.lock lock]; if ([self isCancelled]) { [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; } else if ([self isReady]) { self.state = AFOperationExecutingState; [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; } [self.lock unlock]; } - (void)operationDidStart { [self.lock lock]; if (![self isCancelled]) { self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO]; NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; for (NSString *runLoopMode in self.runLoopModes) { [self.connection scheduleInRunLoop:runLoop forMode:runLoopMode]; [self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode]; } [self.outputStream open]; [self.connection start]; } [self.lock unlock]; dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidStartNotification object:self]; }); } - (void)finish { [self.lock lock]; self.state = AFOperationFinishedState; [self.lock unlock]; dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidFinishNotification object:self]; }); } - (void)cancel { [self.lock lock]; if (![self isFinished] && ![self isCancelled]) { [super cancel]; if ([self isExecuting]) { [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; } } [self.lock unlock]; } - (void)cancelConnection { NSDictionary *userInfo = nil; if ([self.request URL]) { userInfo = [NSDictionary dictionaryWithObject:[self.request URL] forKey:NSURLErrorFailingURLErrorKey]; } NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCancelled userInfo:userInfo]; if (![self isFinished]) { if (self.connection) { [self.connection cancel]; [self performSelector:@selector(connection:didFailWithError:) withObject:self.connection withObject:error]; } else { // Accomodate race condition where `self.connection` has not yet been set before cancellation self.error = error; [self finish]; } } } #pragma mark - + (NSArray *)batchOfRequestOperations:(NSArray *)operations progressBlock:(void (^)(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations))progressBlock completionBlock:(void (^)(NSArray *operations))completionBlock { if (!operations || [operations count] == 0) { return @[[NSBlockOperation blockOperationWithBlock:^{ dispatch_async(dispatch_get_main_queue(), ^{ if (completionBlock) { completionBlock(@[]); } }); }]]; } __block dispatch_group_t group = dispatch_group_create(); NSBlockOperation *batchedOperation = [NSBlockOperation blockOperationWithBlock:^{ dispatch_group_notify(group, dispatch_get_main_queue(), ^{ if (completionBlock) { completionBlock(operations); } }); }]; for (AFURLConnectionOperation *operation in operations) { operation.completionGroup = group; void (^originalCompletionBlock)(void) = [operation.completionBlock copy]; __weak __typeof(operation)weakOperation = operation; operation.completionBlock = ^{ __strong __typeof(weakOperation)strongOperation = weakOperation; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" dispatch_queue_t queue = strongOperation.completionQueue ?: dispatch_get_main_queue(); #pragma clang diagnostic pop dispatch_group_async(group, queue, ^{ if (originalCompletionBlock) { originalCompletionBlock(); } NSUInteger numberOfFinishedOperations = [[operations indexesOfObjectsPassingTest:^BOOL(id op, NSUInteger __unused idx, BOOL __unused *stop) { return [op isFinished]; }] count]; if (progressBlock) { progressBlock(numberOfFinishedOperations, [operations count]); } dispatch_group_leave(group); }); }; dispatch_group_enter(group); [batchedOperation addDependency:operation]; } return [operations arrayByAddingObject:batchedOperation]; } #pragma mark - NSObject - (NSString *)description { [self.lock lock]; NSString *description = [NSString stringWithFormat:@"<%@: %p, state: %@, cancelled: %@ request: %@, response: %@>", NSStringFromClass([self class]), self, AFKeyPathFromOperationState(self.state), ([self isCancelled] ? @"YES" : @"NO"), self.request, self.response]; [self.lock unlock]; return description; } #pragma mark - NSURLConnectionDelegate - (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { if (self.authenticationChallenge) { self.authenticationChallenge(connection, challenge); return; } if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) { NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge]; } else { [[challenge sender] cancelAuthenticationChallenge:challenge]; } } else { if ([challenge previousFailureCount] == 0) { if (self.credential) { [[challenge sender] useCredential:self.credential forAuthenticationChallenge:challenge]; } else { [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge]; } } else { [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge]; } } } - (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection __unused *)connection { return self.shouldUseCredentialStorage; } - (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse { if (self.redirectResponse) { return self.redirectResponse(connection, request, redirectResponse); } else { return request; } } - (void)connection:(NSURLConnection __unused *)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite { dispatch_async(dispatch_get_main_queue(), ^{ if (self.uploadProgress) { self.uploadProgress((NSUInteger)bytesWritten, totalBytesWritten, totalBytesExpectedToWrite); } }); } - (void)connection:(NSURLConnection __unused *)connection didReceiveResponse:(NSURLResponse *)response { self.response = response; } - (void)connection:(NSURLConnection __unused *)connection didReceiveData:(NSData *)data { NSUInteger length = [data length]; while (YES) { NSInteger totalNumberOfBytesWritten = 0; if ([self.outputStream hasSpaceAvailable]) { const uint8_t *dataBuffer = (uint8_t *)[data bytes]; NSInteger numberOfBytesWritten = 0; while (totalNumberOfBytesWritten < (NSInteger)length) { numberOfBytesWritten = [self.outputStream write:&dataBuffer[(NSUInteger)totalNumberOfBytesWritten] maxLength:(length - (NSUInteger)totalNumberOfBytesWritten)]; if (numberOfBytesWritten == -1) { break; } totalNumberOfBytesWritten += numberOfBytesWritten; } break; } if (self.outputStream.streamError) { [self.connection cancel]; [self performSelector:@selector(connection:didFailWithError:) withObject:self.connection withObject:self.outputStream.streamError]; return; } } dispatch_async(dispatch_get_main_queue(), ^{ self.totalBytesRead += (long long)length; if (self.downloadProgress) { self.downloadProgress(length, self.totalBytesRead, self.response.expectedContentLength); } }); } - (void)connectionDidFinishLoading:(NSURLConnection __unused *)connection { self.responseData = [self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey]; [self.outputStream close]; if (self.responseData) { self.outputStream = nil; } self.connection = nil; [self finish]; } - (void)connection:(NSURLConnection __unused *)connection didFailWithError:(NSError *)error { self.error = error; [self.outputStream close]; if (self.responseData) { self.outputStream = nil; } self.connection = nil; [self finish]; } - (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse { if (self.cacheResponse) { return self.cacheResponse(connection, cachedResponse); } else { if ([self isCancelled]) { return nil; } return cachedResponse; } } #pragma mark - NSSecureCoding + (BOOL)supportsSecureCoding { return YES; } - (id)initWithCoder:(NSCoder *)decoder { NSURLRequest *request = [decoder decodeObjectOfClass:[NSURLRequest class] forKey:NSStringFromSelector(@selector(request))]; self = [self initWithRequest:request]; if (!self) { return nil; } self.state = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(state))] integerValue]; self.response = [decoder decodeObjectOfClass:[NSHTTPURLResponse class] forKey:NSStringFromSelector(@selector(response))]; self.error = [decoder decodeObjectOfClass:[NSError class] forKey:NSStringFromSelector(@selector(error))]; self.responseData = [decoder decodeObjectOfClass:[NSData class] forKey:NSStringFromSelector(@selector(responseData))]; self.totalBytesRead = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(totalBytesRead))] longLongValue]; return self; } - (void)encodeWithCoder:(NSCoder *)coder { [self pause]; [coder encodeObject:self.request forKey:NSStringFromSelector(@selector(request))]; switch (self.state) { case AFOperationExecutingState: case AFOperationPausedState: [coder encodeInteger:AFOperationReadyState forKey:NSStringFromSelector(@selector(state))]; break; default: [coder encodeInteger:self.state forKey:NSStringFromSelector(@selector(state))]; break; } [coder encodeObject:self.response forKey:NSStringFromSelector(@selector(response))]; [coder encodeObject:self.error forKey:NSStringFromSelector(@selector(error))]; [coder encodeObject:self.responseData forKey:NSStringFromSelector(@selector(responseData))]; [coder encodeInt64:self.totalBytesRead forKey:NSStringFromSelector(@selector(totalBytesRead))]; } #pragma mark - NSCopying - (id)copyWithZone:(NSZone *)zone { AFURLConnectionOperation *operation = [(AFURLConnectionOperation *)[[self class] allocWithZone:zone] initWithRequest:self.request]; operation.uploadProgress = self.uploadProgress; operation.downloadProgress = self.downloadProgress; operation.authenticationChallenge = self.authenticationChallenge; operation.cacheResponse = self.cacheResponse; operation.redirectResponse = self.redirectResponse; operation.completionQueue = self.completionQueue; operation.completionGroup = self.completionGroup; return operation; } @end ================================================ FILE: Pods/AFNetworking/AFNetworking/AFURLRequestSerialization.h ================================================ // AFURLRequestSerialization.h // // Copyright (c) 2013-2015 AFNetworking (http://afnetworking.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) #import #endif /** The `AFURLRequestSerialization` protocol is adopted by an object that encodes parameters for a specified HTTP requests. Request serializers may encode parameters as query strings, HTTP bodies, setting the appropriate HTTP header fields as necessary. For example, a JSON request serializer may set the HTTP body of the request to a JSON representation, and set the `Content-Type` HTTP header field value to `application/json`. */ @protocol AFURLRequestSerialization /** Returns a request with the specified parameters encoded into a copy of the original request. @param request The original request. @param parameters The parameters to be encoded. @param error The error that occurred while attempting to encode the request parameters. @return A serialized request. */ - (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request withParameters:(id)parameters error:(NSError * __autoreleasing *)error; @end #pragma mark - /** */ typedef NS_ENUM(NSUInteger, AFHTTPRequestQueryStringSerializationStyle) { AFHTTPRequestQueryStringDefaultStyle = 0, }; @protocol AFMultipartFormData; /** `AFHTTPRequestSerializer` conforms to the `AFURLRequestSerialization` & `AFURLResponseSerialization` protocols, offering a concrete base implementation of query string / URL form-encoded parameter serialization and default request headers, as well as response status code and content type validation. Any request or response serializer dealing with HTTP is encouraged to subclass `AFHTTPRequestSerializer` in order to ensure consistent default behavior. */ @interface AFHTTPRequestSerializer : NSObject /** The string encoding used to serialize parameters. `NSUTF8StringEncoding` by default. */ @property (nonatomic, assign) NSStringEncoding stringEncoding; /** Whether created requests can use the device’s cellular radio (if present). `YES` by default. @see NSMutableURLRequest -setAllowsCellularAccess: */ @property (nonatomic, assign) BOOL allowsCellularAccess; /** The cache policy of created requests. `NSURLRequestUseProtocolCachePolicy` by default. @see NSMutableURLRequest -setCachePolicy: */ @property (nonatomic, assign) NSURLRequestCachePolicy cachePolicy; /** Whether created requests should use the default cookie handling. `YES` by default. @see NSMutableURLRequest -setHTTPShouldHandleCookies: */ @property (nonatomic, assign) BOOL HTTPShouldHandleCookies; /** Whether created requests can continue transmitting data before receiving a response from an earlier transmission. `NO` by default @see NSMutableURLRequest -setHTTPShouldUsePipelining: */ @property (nonatomic, assign) BOOL HTTPShouldUsePipelining; /** The network service type for created requests. `NSURLNetworkServiceTypeDefault` by default. @see NSMutableURLRequest -setNetworkServiceType: */ @property (nonatomic, assign) NSURLRequestNetworkServiceType networkServiceType; /** The timeout interval, in seconds, for created requests. The default timeout interval is 60 seconds. @see NSMutableURLRequest -setTimeoutInterval: */ @property (nonatomic, assign) NSTimeInterval timeoutInterval; ///--------------------------------------- /// @name Configuring HTTP Request Headers ///--------------------------------------- /** Default HTTP header field values to be applied to serialized requests. By default, these include the following: - `Accept-Language` with the contents of `NSLocale +preferredLanguages` - `User-Agent` with the contents of various bundle identifiers and OS designations @discussion To add or remove default request headers, use `setValue:forHTTPHeaderField:`. */ @property (readonly, nonatomic, strong) NSDictionary *HTTPRequestHeaders; /** Creates and returns a serializer with default configuration. */ + (instancetype)serializer; /** Sets the value for the HTTP headers set in request objects made by the HTTP client. If `nil`, removes the existing value for that header. @param field The HTTP header to set a default value for @param value The value set as default for the specified header, or `nil` */ - (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field; /** Returns the value for the HTTP headers set in the request serializer. @param field The HTTP header to retrieve the default value for @return The value set as default for the specified header, or `nil` */ - (NSString *)valueForHTTPHeaderField:(NSString *)field; /** Sets the "Authorization" HTTP header set in request objects made by the HTTP client to a basic authentication value with Base64-encoded username and password. This overwrites any existing value for this header. @param username The HTTP basic auth username @param password The HTTP basic auth password */ - (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username password:(NSString *)password; /** @deprecated This method has been deprecated. Use -setValue:forHTTPHeaderField: instead. */ - (void)setAuthorizationHeaderFieldWithToken:(NSString *)token DEPRECATED_ATTRIBUTE; /** Clears any existing value for the "Authorization" HTTP header. */ - (void)clearAuthorizationHeader; ///------------------------------------------------------- /// @name Configuring Query String Parameter Serialization ///------------------------------------------------------- /** HTTP methods for which serialized requests will encode parameters as a query string. `GET`, `HEAD`, and `DELETE` by default. */ @property (nonatomic, strong) NSSet *HTTPMethodsEncodingParametersInURI; /** Set the method of query string serialization according to one of the pre-defined styles. @param style The serialization style. @see AFHTTPRequestQueryStringSerializationStyle */ - (void)setQueryStringSerializationWithStyle:(AFHTTPRequestQueryStringSerializationStyle)style; /** Set the a custom method of query string serialization according to the specified block. @param block A block that defines a process of encoding parameters into a query string. This block returns the query string and takes three arguments: the request, the parameters to encode, and the error that occurred when attempting to encode parameters for the given request. */ - (void)setQueryStringSerializationWithBlock:(NSString * (^)(NSURLRequest *request, id parameters, NSError * __autoreleasing *error))block; ///------------------------------- /// @name Creating Request Objects ///------------------------------- /** @deprecated This method has been deprecated. Use -requestWithMethod:URLString:parameters:error: instead. */ - (NSMutableURLRequest *)requestWithMethod:(NSString *)method URLString:(NSString *)URLString parameters:(id)parameters DEPRECATED_ATTRIBUTE; /** Creates an `NSMutableURLRequest` object with the specified HTTP method and URL string. If the HTTP method is `GET`, `HEAD`, or `DELETE`, the parameters will be used to construct a url-encoded query string that is appended to the request's URL. Otherwise, the parameters will be encoded according to the value of the `parameterEncoding` property, and set as the request body. @param method The HTTP method for the request, such as `GET`, `POST`, `PUT`, or `DELETE`. This parameter must not be `nil`. @param URLString The URL string used to create the request URL. @param parameters The parameters to be either set as a query string for `GET` requests, or the request HTTP body. @param error The error that occured while constructing the request. @return An `NSMutableURLRequest` object. */ - (NSMutableURLRequest *)requestWithMethod:(NSString *)method URLString:(NSString *)URLString parameters:(id)parameters error:(NSError * __autoreleasing *)error; /** @deprecated This method has been deprecated. Use -multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:error: instead. */ - (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method URLString:(NSString *)URLString parameters:(NSDictionary *)parameters constructingBodyWithBlock:(void (^)(id formData))block DEPRECATED_ATTRIBUTE; /** Creates an `NSMutableURLRequest` object with the specified HTTP method and URLString, and constructs a `multipart/form-data` HTTP body, using the specified parameters and multipart form data block. See http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.2 Multipart form requests are automatically streamed, reading files directly from disk along with in-memory data in a single HTTP body. The resulting `NSMutableURLRequest` object has an `HTTPBodyStream` property, so refrain from setting `HTTPBodyStream` or `HTTPBody` on this request object, as it will clear out the multipart form body stream. @param method The HTTP method for the request. This parameter must not be `GET` or `HEAD`, or `nil`. @param URLString The URL string used to create the request URL. @param parameters The parameters to be encoded and set in the request HTTP body. @param block A block that takes a single argument and appends data to the HTTP body. The block argument is an object adopting the `AFMultipartFormData` protocol. @param error The error that occured while constructing the request. @return An `NSMutableURLRequest` object */ - (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method URLString:(NSString *)URLString parameters:(NSDictionary *)parameters constructingBodyWithBlock:(void (^)(id formData))block error:(NSError * __autoreleasing *)error; /** Creates an `NSMutableURLRequest` by removing the `HTTPBodyStream` from a request, and asynchronously writing its contents into the specified file, invoking the completion handler when finished. @param request The multipart form request. The `HTTPBodyStream` property of `request` must not be `nil`. @param fileURL The file URL to write multipart form contents to. @param handler A handler block to execute. @discussion There is a bug in `NSURLSessionTask` that causes requests to not send a `Content-Length` header when streaming contents from an HTTP body, which is notably problematic when interacting with the Amazon S3 webservice. As a workaround, this method takes a request constructed with `multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:error:`, or any other request with an `HTTPBodyStream`, writes the contents to the specified file and returns a copy of the original request with the `HTTPBodyStream` property set to `nil`. From here, the file can either be passed to `AFURLSessionManager -uploadTaskWithRequest:fromFile:progress:completionHandler:`, or have its contents read into an `NSData` that's assigned to the `HTTPBody` property of the request. @see https://github.com/AFNetworking/AFNetworking/issues/1398 */ - (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request writingStreamContentsToFile:(NSURL *)fileURL completionHandler:(void (^)(NSError *error))handler; @end #pragma mark - /** The `AFMultipartFormData` protocol defines the methods supported by the parameter in the block argument of `AFHTTPRequestSerializer -multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:`. */ @protocol AFMultipartFormData /** Appends the HTTP header `Content-Disposition: file; filename=#{generated filename}; name=#{name}"` and `Content-Type: #{generated mimeType}`, followed by the encoded file data and the multipart form boundary. The filename and MIME type for this data in the form will be automatically generated, using the last path component of the `fileURL` and system associated MIME type for the `fileURL` extension, respectively. @param fileURL The URL corresponding to the file whose content will be appended to the form. This parameter must not be `nil`. @param name The name to be associated with the specified data. This parameter must not be `nil`. @param error If an error occurs, upon return contains an `NSError` object that describes the problem. @return `YES` if the file data was successfully appended, otherwise `NO`. */ - (BOOL)appendPartWithFileURL:(NSURL *)fileURL name:(NSString *)name error:(NSError * __autoreleasing *)error; /** Appends the HTTP header `Content-Disposition: file; filename=#{filename}; name=#{name}"` and `Content-Type: #{mimeType}`, followed by the encoded file data and the multipart form boundary. @param fileURL The URL corresponding to the file whose content will be appended to the form. This parameter must not be `nil`. @param name The name to be associated with the specified data. This parameter must not be `nil`. @param fileName The file name to be used in the `Content-Disposition` header. This parameter must not be `nil`. @param mimeType The declared MIME type of the file data. This parameter must not be `nil`. @param error If an error occurs, upon return contains an `NSError` object that describes the problem. @return `YES` if the file data was successfully appended otherwise `NO`. */ - (BOOL)appendPartWithFileURL:(NSURL *)fileURL name:(NSString *)name fileName:(NSString *)fileName mimeType:(NSString *)mimeType error:(NSError * __autoreleasing *)error; /** Appends the HTTP header `Content-Disposition: file; filename=#{filename}; name=#{name}"` and `Content-Type: #{mimeType}`, followed by the data from the input stream and the multipart form boundary. @param inputStream The input stream to be appended to the form data @param name The name to be associated with the specified input stream. This parameter must not be `nil`. @param fileName The filename to be associated with the specified input stream. This parameter must not be `nil`. @param length The length of the specified input stream in bytes. @param mimeType The MIME type of the specified data. (For example, the MIME type for a JPEG image is image/jpeg.) For a list of valid MIME types, see http://www.iana.org/assignments/media-types/. This parameter must not be `nil`. */ - (void)appendPartWithInputStream:(NSInputStream *)inputStream name:(NSString *)name fileName:(NSString *)fileName length:(int64_t)length mimeType:(NSString *)mimeType; /** Appends the HTTP header `Content-Disposition: file; filename=#{filename}; name=#{name}"` and `Content-Type: #{mimeType}`, followed by the encoded file data and the multipart form boundary. @param data The data to be encoded and appended to the form data. @param name The name to be associated with the specified data. This parameter must not be `nil`. @param fileName The filename to be associated with the specified data. This parameter must not be `nil`. @param mimeType The MIME type of the specified data. (For example, the MIME type for a JPEG image is image/jpeg.) For a list of valid MIME types, see http://www.iana.org/assignments/media-types/. This parameter must not be `nil`. */ - (void)appendPartWithFileData:(NSData *)data name:(NSString *)name fileName:(NSString *)fileName mimeType:(NSString *)mimeType; /** Appends the HTTP headers `Content-Disposition: form-data; name=#{name}"`, followed by the encoded data and the multipart form boundary. @param data The data to be encoded and appended to the form data. @param name The name to be associated with the specified data. This parameter must not be `nil`. */ - (void)appendPartWithFormData:(NSData *)data name:(NSString *)name; /** Appends HTTP headers, followed by the encoded data and the multipart form boundary. @param headers The HTTP headers to be appended to the form data. @param body The data to be encoded and appended to the form data. This parameter must not be `nil`. */ - (void)appendPartWithHeaders:(NSDictionary *)headers body:(NSData *)body; /** Throttles request bandwidth by limiting the packet size and adding a delay for each chunk read from the upload stream. When uploading over a 3G or EDGE connection, requests may fail with "request body stream exhausted". Setting a maximum packet size and delay according to the recommended values (`kAFUploadStream3GSuggestedPacketSize` and `kAFUploadStream3GSuggestedDelay`) lowers the risk of the input stream exceeding its allocated bandwidth. Unfortunately, there is no definite way to distinguish between a 3G, EDGE, or LTE connection over `NSURLConnection`. As such, it is not recommended that you throttle bandwidth based solely on network reachability. Instead, you should consider checking for the "request body stream exhausted" in a failure block, and then retrying the request with throttled bandwidth. @param numberOfBytes Maximum packet size, in number of bytes. The default packet size for an input stream is 16kb. @param delay Duration of delay each time a packet is read. By default, no delay is set. */ - (void)throttleBandwidthWithPacketSize:(NSUInteger)numberOfBytes delay:(NSTimeInterval)delay; @end #pragma mark - /** `AFJSONRequestSerializer` is a subclass of `AFHTTPRequestSerializer` that encodes parameters as JSON using `NSJSONSerialization`, setting the `Content-Type` of the encoded request to `application/json`. */ @interface AFJSONRequestSerializer : AFHTTPRequestSerializer /** Options for writing the request JSON data from Foundation objects. For possible values, see the `NSJSONSerialization` documentation section "NSJSONWritingOptions". `0` by default. */ @property (nonatomic, assign) NSJSONWritingOptions writingOptions; /** Creates and returns a JSON serializer with specified reading and writing options. @param writingOptions The specified JSON writing options. */ + (instancetype)serializerWithWritingOptions:(NSJSONWritingOptions)writingOptions; @end #pragma mark - /** `AFPropertyListRequestSerializer` is a subclass of `AFHTTPRequestSerializer` that encodes parameters as JSON using `NSPropertyListSerializer`, setting the `Content-Type` of the encoded request to `application/x-plist`. */ @interface AFPropertyListRequestSerializer : AFHTTPRequestSerializer /** The property list format. Possible values are described in "NSPropertyListFormat". */ @property (nonatomic, assign) NSPropertyListFormat format; /** @warning The `writeOptions` property is currently unused. */ @property (nonatomic, assign) NSPropertyListWriteOptions writeOptions; /** Creates and returns a property list serializer with a specified format, read options, and write options. @param format The property list format. @param writeOptions The property list write options. @warning The `writeOptions` property is currently unused. */ + (instancetype)serializerWithFormat:(NSPropertyListFormat)format writeOptions:(NSPropertyListWriteOptions)writeOptions; @end #pragma mark - ///---------------- /// @name Constants ///---------------- /** ## Error Domains The following error domain is predefined. - `NSString * const AFURLRequestSerializationErrorDomain` ### Constants `AFURLRequestSerializationErrorDomain` AFURLRequestSerializer errors. Error codes for `AFURLRequestSerializationErrorDomain` correspond to codes in `NSURLErrorDomain`. */ extern NSString * const AFURLRequestSerializationErrorDomain; /** ## User info dictionary keys These keys may exist in the user info dictionary, in addition to those defined for NSError. - `NSString * const AFNetworkingOperationFailingURLRequestErrorKey` ### Constants `AFNetworkingOperationFailingURLRequestErrorKey` The corresponding value is an `NSURLRequest` containing the request of the operation associated with an error. This key is only present in the `AFURLRequestSerializationErrorDomain`. */ extern NSString * const AFNetworkingOperationFailingURLRequestErrorKey; /** ## Throttling Bandwidth for HTTP Request Input Streams @see -throttleBandwidthWithPacketSize:delay: ### Constants `kAFUploadStream3GSuggestedPacketSize` Maximum packet size, in number of bytes. Equal to 16kb. `kAFUploadStream3GSuggestedDelay` Duration of delay each time a packet is read. Equal to 0.2 seconds. */ extern NSUInteger const kAFUploadStream3GSuggestedPacketSize; extern NSTimeInterval const kAFUploadStream3GSuggestedDelay; ================================================ FILE: Pods/AFNetworking/AFNetworking/AFURLRequestSerialization.m ================================================ // AFURLRequestSerialization.m // // Copyright (c) 2013-2015 AFNetworking (http://afnetworking.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import "AFURLRequestSerialization.h" #if __IPHONE_OS_VERSION_MIN_REQUIRED #import #else #import #endif NSString * const AFURLRequestSerializationErrorDomain = @"com.alamofire.error.serialization.request"; NSString * const AFNetworkingOperationFailingURLRequestErrorKey = @"com.alamofire.serialization.request.error.response"; typedef NSString * (^AFQueryStringSerializationBlock)(NSURLRequest *request, id parameters, NSError *__autoreleasing *error); static NSString * AFBase64EncodedStringFromString(NSString *string) { NSData *data = [NSData dataWithBytes:[string UTF8String] length:[string lengthOfBytesUsingEncoding:NSUTF8StringEncoding]]; NSUInteger length = [data length]; NSMutableData *mutableData = [NSMutableData dataWithLength:((length + 2) / 3) * 4]; uint8_t *input = (uint8_t *)[data bytes]; uint8_t *output = (uint8_t *)[mutableData mutableBytes]; for (NSUInteger i = 0; i < length; i += 3) { NSUInteger value = 0; for (NSUInteger j = i; j < (i + 3); j++) { value <<= 8; if (j < length) { value |= (0xFF & input[j]); } } static uint8_t const kAFBase64EncodingTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; NSUInteger idx = (i / 3) * 4; output[idx + 0] = kAFBase64EncodingTable[(value >> 18) & 0x3F]; output[idx + 1] = kAFBase64EncodingTable[(value >> 12) & 0x3F]; output[idx + 2] = (i + 1) < length ? kAFBase64EncodingTable[(value >> 6) & 0x3F] : '='; output[idx + 3] = (i + 2) < length ? kAFBase64EncodingTable[(value >> 0) & 0x3F] : '='; } return [[NSString alloc] initWithData:mutableData encoding:NSASCIIStringEncoding]; } static NSString * const kAFCharactersToBeEscapedInQueryString = @":/?&=;+!@#$()',*"; static NSString * AFPercentEscapedQueryStringKeyFromStringWithEncoding(NSString *string, NSStringEncoding encoding) { static NSString * const kAFCharactersToLeaveUnescapedInQueryStringPairKey = @"[]."; return (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (__bridge CFStringRef)string, (__bridge CFStringRef)kAFCharactersToLeaveUnescapedInQueryStringPairKey, (__bridge CFStringRef)kAFCharactersToBeEscapedInQueryString, CFStringConvertNSStringEncodingToEncoding(encoding)); } static NSString * AFPercentEscapedQueryStringValueFromStringWithEncoding(NSString *string, NSStringEncoding encoding) { return (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (__bridge CFStringRef)string, NULL, (__bridge CFStringRef)kAFCharactersToBeEscapedInQueryString, CFStringConvertNSStringEncodingToEncoding(encoding)); } #pragma mark - @interface AFQueryStringPair : NSObject @property (readwrite, nonatomic, strong) id field; @property (readwrite, nonatomic, strong) id value; - (id)initWithField:(id)field value:(id)value; - (NSString *)URLEncodedStringValueWithEncoding:(NSStringEncoding)stringEncoding; @end @implementation AFQueryStringPair - (id)initWithField:(id)field value:(id)value { self = [super init]; if (!self) { return nil; } self.field = field; self.value = value; return self; } - (NSString *)URLEncodedStringValueWithEncoding:(NSStringEncoding)stringEncoding { if (!self.value || [self.value isEqual:[NSNull null]]) { return AFPercentEscapedQueryStringKeyFromStringWithEncoding([self.field description], stringEncoding); } else { return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedQueryStringKeyFromStringWithEncoding([self.field description], stringEncoding), AFPercentEscapedQueryStringValueFromStringWithEncoding([self.value description], stringEncoding)]; } } @end #pragma mark - extern NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary); extern NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value); static NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *parameters, NSStringEncoding stringEncoding) { NSMutableArray *mutablePairs = [NSMutableArray array]; for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) { [mutablePairs addObject:[pair URLEncodedStringValueWithEncoding:stringEncoding]]; } return [mutablePairs componentsJoinedByString:@"&"]; } NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) { return AFQueryStringPairsFromKeyAndValue(nil, dictionary); } NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) { NSMutableArray *mutableQueryStringComponents = [NSMutableArray array]; NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)]; if ([value isKindOfClass:[NSDictionary class]]) { NSDictionary *dictionary = value; // Sort dictionary keys to ensure consistent ordering in query string, which is important when deserializing potentially ambiguous sequences, such as an array of dictionaries for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) { id nestedValue = [dictionary objectForKey:nestedKey]; if (nestedValue) { [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)]; } } } else if ([value isKindOfClass:[NSArray class]]) { NSArray *array = value; for (id nestedValue in array) { [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)]; } } else if ([value isKindOfClass:[NSSet class]]) { NSSet *set = value; for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) { [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)]; } } else { [mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]]; } return mutableQueryStringComponents; } #pragma mark - @interface AFStreamingMultipartFormData : NSObject - (instancetype)initWithURLRequest:(NSMutableURLRequest *)urlRequest stringEncoding:(NSStringEncoding)encoding; - (NSMutableURLRequest *)requestByFinalizingMultipartFormData; @end #pragma mark - static NSArray * AFHTTPRequestSerializerObservedKeyPaths() { static NSArray *_AFHTTPRequestSerializerObservedKeyPaths = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _AFHTTPRequestSerializerObservedKeyPaths = @[NSStringFromSelector(@selector(allowsCellularAccess)), NSStringFromSelector(@selector(cachePolicy)), NSStringFromSelector(@selector(HTTPShouldHandleCookies)), NSStringFromSelector(@selector(HTTPShouldUsePipelining)), NSStringFromSelector(@selector(networkServiceType)), NSStringFromSelector(@selector(timeoutInterval))]; }); return _AFHTTPRequestSerializerObservedKeyPaths; } static void *AFHTTPRequestSerializerObserverContext = &AFHTTPRequestSerializerObserverContext; @interface AFHTTPRequestSerializer () @property (readwrite, nonatomic, strong) NSMutableSet *mutableObservedChangedKeyPaths; @property (readwrite, nonatomic, strong) NSMutableDictionary *mutableHTTPRequestHeaders; @property (readwrite, nonatomic, assign) AFHTTPRequestQueryStringSerializationStyle queryStringSerializationStyle; @property (readwrite, nonatomic, copy) AFQueryStringSerializationBlock queryStringSerialization; @end @implementation AFHTTPRequestSerializer + (instancetype)serializer { return [[self alloc] init]; } - (instancetype)init { self = [super init]; if (!self) { return nil; } self.stringEncoding = NSUTF8StringEncoding; self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary]; // Accept-Language HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4 NSMutableArray *acceptLanguagesComponents = [NSMutableArray array]; [[NSLocale preferredLanguages] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { float q = 1.0f - (idx * 0.1f); [acceptLanguagesComponents addObject:[NSString stringWithFormat:@"%@;q=%0.1g", obj, q]]; *stop = q <= 0.5f; }]; [self setValue:[acceptLanguagesComponents componentsJoinedByString:@", "] forHTTPHeaderField:@"Accept-Language"]; NSString *userAgent = nil; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43 userAgent = [NSString stringWithFormat:@"%@/%@ (%@; iOS %@; Scale/%0.2f)", [[[NSBundle mainBundle] infoDictionary] objectForKey:(__bridge NSString *)kCFBundleExecutableKey] ?: [[[NSBundle mainBundle] infoDictionary] objectForKey:(__bridge NSString *)kCFBundleIdentifierKey], [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"] ?: [[[NSBundle mainBundle] infoDictionary] objectForKey:(__bridge NSString *)kCFBundleVersionKey], [[UIDevice currentDevice] model], [[UIDevice currentDevice] systemVersion], [[UIScreen mainScreen] scale]]; #elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED) userAgent = [NSString stringWithFormat:@"%@/%@ (Mac OS X %@)", [[[NSBundle mainBundle] infoDictionary] objectForKey:(__bridge NSString *)kCFBundleExecutableKey] ?: [[[NSBundle mainBundle] infoDictionary] objectForKey:(__bridge NSString *)kCFBundleIdentifierKey], [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"] ?: [[[NSBundle mainBundle] infoDictionary] objectForKey:(__bridge NSString *)kCFBundleVersionKey], [[NSProcessInfo processInfo] operatingSystemVersionString]]; #endif #pragma clang diagnostic pop if (userAgent) { if (![userAgent canBeConvertedToEncoding:NSASCIIStringEncoding]) { NSMutableString *mutableUserAgent = [userAgent mutableCopy]; if (CFStringTransform((__bridge CFMutableStringRef)(mutableUserAgent), NULL, (__bridge CFStringRef)@"Any-Latin; Latin-ASCII; [:^ASCII:] Remove", false)) { userAgent = mutableUserAgent; } } [self setValue:userAgent forHTTPHeaderField:@"User-Agent"]; } // HTTP Method Definitions; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html self.HTTPMethodsEncodingParametersInURI = [NSSet setWithObjects:@"GET", @"HEAD", @"DELETE", nil]; self.mutableObservedChangedKeyPaths = [NSMutableSet set]; for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) { if ([self respondsToSelector:NSSelectorFromString(keyPath)]) { [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext]; } } return self; } - (void)dealloc { for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) { if ([self respondsToSelector:NSSelectorFromString(keyPath)]) { [self removeObserver:self forKeyPath:keyPath context:AFHTTPRequestSerializerObserverContext]; } } } #pragma mark - // Workarounds for crashing behavior using Key-Value Observing with XCTest // See https://github.com/AFNetworking/AFNetworking/issues/2523 - (void)setAllowsCellularAccess:(BOOL)allowsCellularAccess { [self willChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))]; _allowsCellularAccess = allowsCellularAccess; [self didChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))]; } - (void)setCachePolicy:(NSURLRequestCachePolicy)cachePolicy { [self willChangeValueForKey:NSStringFromSelector(@selector(cachePolicy))]; _cachePolicy = cachePolicy; [self didChangeValueForKey:NSStringFromSelector(@selector(cachePolicy))]; } - (void)setHTTPShouldHandleCookies:(BOOL)HTTPShouldHandleCookies { [self willChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldHandleCookies))]; _HTTPShouldHandleCookies = HTTPShouldHandleCookies; [self didChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldHandleCookies))]; } - (void)setHTTPShouldUsePipelining:(BOOL)HTTPShouldUsePipelining { [self willChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldUsePipelining))]; _HTTPShouldUsePipelining = HTTPShouldUsePipelining; [self didChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldUsePipelining))]; } - (void)setNetworkServiceType:(NSURLRequestNetworkServiceType)networkServiceType { [self willChangeValueForKey:NSStringFromSelector(@selector(networkServiceType))]; _networkServiceType = networkServiceType; [self didChangeValueForKey:NSStringFromSelector(@selector(networkServiceType))]; } - (void)setTimeoutInterval:(NSTimeInterval)timeoutInterval { [self willChangeValueForKey:NSStringFromSelector(@selector(timeoutInterval))]; _timeoutInterval = timeoutInterval; [self didChangeValueForKey:NSStringFromSelector(@selector(timeoutInterval))]; } #pragma mark - - (NSDictionary *)HTTPRequestHeaders { return [NSDictionary dictionaryWithDictionary:self.mutableHTTPRequestHeaders]; } - (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field { [self.mutableHTTPRequestHeaders setValue:value forKey:field]; } - (NSString *)valueForHTTPHeaderField:(NSString *)field { return [self.mutableHTTPRequestHeaders valueForKey:field]; } - (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username password:(NSString *)password { NSString *basicAuthCredentials = [NSString stringWithFormat:@"%@:%@", username, password]; [self setValue:[NSString stringWithFormat:@"Basic %@", AFBase64EncodedStringFromString(basicAuthCredentials)] forHTTPHeaderField:@"Authorization"]; } - (void)setAuthorizationHeaderFieldWithToken:(NSString *)token { [self setValue:[NSString stringWithFormat:@"Token token=\"%@\"", token] forHTTPHeaderField:@"Authorization"]; } - (void)clearAuthorizationHeader { [self.mutableHTTPRequestHeaders removeObjectForKey:@"Authorization"]; } #pragma mark - - (void)setQueryStringSerializationWithStyle:(AFHTTPRequestQueryStringSerializationStyle)style { self.queryStringSerializationStyle = style; self.queryStringSerialization = nil; } - (void)setQueryStringSerializationWithBlock:(NSString *(^)(NSURLRequest *, id, NSError *__autoreleasing *))block { self.queryStringSerialization = block; } #pragma mark - - (NSMutableURLRequest *)requestWithMethod:(NSString *)method URLString:(NSString *)URLString parameters:(id)parameters { return [self requestWithMethod:method URLString:URLString parameters:parameters error:nil]; } - (NSMutableURLRequest *)requestWithMethod:(NSString *)method URLString:(NSString *)URLString parameters:(id)parameters error:(NSError *__autoreleasing *)error { NSParameterAssert(method); NSParameterAssert(URLString); NSURL *url = [NSURL URLWithString:URLString]; NSParameterAssert(url); NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url]; mutableRequest.HTTPMethod = method; for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) { if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) { [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath]; } } mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy]; return mutableRequest; } - (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method URLString:(NSString *)URLString parameters:(NSDictionary *)parameters constructingBodyWithBlock:(void (^)(id formData))block { return [self multipartFormRequestWithMethod:method URLString:URLString parameters:parameters constructingBodyWithBlock:block error:nil]; } - (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method URLString:(NSString *)URLString parameters:(NSDictionary *)parameters constructingBodyWithBlock:(void (^)(id formData))block error:(NSError *__autoreleasing *)error { NSParameterAssert(method); NSParameterAssert(![method isEqualToString:@"GET"] && ![method isEqualToString:@"HEAD"]); NSMutableURLRequest *mutableRequest = [self requestWithMethod:method URLString:URLString parameters:nil error:error]; __block AFStreamingMultipartFormData *formData = [[AFStreamingMultipartFormData alloc] initWithURLRequest:mutableRequest stringEncoding:NSUTF8StringEncoding]; if (parameters) { for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) { NSData *data = nil; if ([pair.value isKindOfClass:[NSData class]]) { data = pair.value; } else if ([pair.value isEqual:[NSNull null]]) { data = [NSData data]; } else { data = [[pair.value description] dataUsingEncoding:self.stringEncoding]; } if (data) { [formData appendPartWithFormData:data name:[pair.field description]]; } } } if (block) { block(formData); } return [formData requestByFinalizingMultipartFormData]; } - (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request writingStreamContentsToFile:(NSURL *)fileURL completionHandler:(void (^)(NSError *error))handler { NSParameterAssert(request.HTTPBodyStream); NSParameterAssert([fileURL isFileURL]); NSInputStream *inputStream = request.HTTPBodyStream; NSOutputStream *outputStream = [[NSOutputStream alloc] initWithURL:fileURL append:NO]; __block NSError *error = nil; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [inputStream open]; [outputStream open]; while ([inputStream hasBytesAvailable] && [outputStream hasSpaceAvailable]) { uint8_t buffer[1024]; NSInteger bytesRead = [inputStream read:buffer maxLength:1024]; if (inputStream.streamError || bytesRead < 0) { error = inputStream.streamError; break; } NSInteger bytesWritten = [outputStream write:buffer maxLength:(NSUInteger)bytesRead]; if (outputStream.streamError || bytesWritten < 0) { error = outputStream.streamError; break; } if (bytesRead == 0 && bytesWritten == 0) { break; } } [outputStream close]; [inputStream close]; if (handler) { dispatch_async(dispatch_get_main_queue(), ^{ handler(error); }); } }); NSMutableURLRequest *mutableRequest = [request mutableCopy]; mutableRequest.HTTPBodyStream = nil; return mutableRequest; } #pragma mark - AFURLRequestSerialization - (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request withParameters:(id)parameters error:(NSError *__autoreleasing *)error { NSParameterAssert(request); NSMutableURLRequest *mutableRequest = [request mutableCopy]; [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) { if (![request valueForHTTPHeaderField:field]) { [mutableRequest setValue:value forHTTPHeaderField:field]; } }]; if (parameters) { NSString *query = nil; if (self.queryStringSerialization) { NSError *serializationError; query = self.queryStringSerialization(request, parameters, &serializationError); if (serializationError) { if (error) { *error = serializationError; } return nil; } } else { switch (self.queryStringSerializationStyle) { case AFHTTPRequestQueryStringDefaultStyle: query = AFQueryStringFromParametersWithEncoding(parameters, self.stringEncoding); break; } } if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) { mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]]; } else { if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) { [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"]; } [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]]; } } return mutableRequest; } #pragma mark - NSKeyValueObserving + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key { if ([AFHTTPRequestSerializerObservedKeyPaths() containsObject:key]) { return NO; } return [super automaticallyNotifiesObserversForKey:key]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(__unused id)object change:(NSDictionary *)change context:(void *)context { if (context == AFHTTPRequestSerializerObserverContext) { if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) { [self.mutableObservedChangedKeyPaths removeObject:keyPath]; } else { [self.mutableObservedChangedKeyPaths addObject:keyPath]; } } } #pragma mark - NSSecureCoding + (BOOL)supportsSecureCoding { return YES; } - (id)initWithCoder:(NSCoder *)decoder { self = [self init]; if (!self) { return nil; } self.mutableHTTPRequestHeaders = [[decoder decodeObjectOfClass:[NSDictionary class] forKey:NSStringFromSelector(@selector(mutableHTTPRequestHeaders))] mutableCopy]; self.queryStringSerializationStyle = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(queryStringSerializationStyle))] unsignedIntegerValue]; return self; } - (void)encodeWithCoder:(NSCoder *)coder { [coder encodeObject:self.mutableHTTPRequestHeaders forKey:NSStringFromSelector(@selector(mutableHTTPRequestHeaders))]; [coder encodeInteger:self.queryStringSerializationStyle forKey:NSStringFromSelector(@selector(queryStringSerializationStyle))]; } #pragma mark - NSCopying - (id)copyWithZone:(NSZone *)zone { AFHTTPRequestSerializer *serializer = [[[self class] allocWithZone:zone] init]; serializer.mutableHTTPRequestHeaders = [self.mutableHTTPRequestHeaders mutableCopyWithZone:zone]; serializer.queryStringSerializationStyle = self.queryStringSerializationStyle; serializer.queryStringSerialization = self.queryStringSerialization; return serializer; } @end #pragma mark - static NSString * AFCreateMultipartFormBoundary() { return [NSString stringWithFormat:@"Boundary+%08X%08X", arc4random(), arc4random()]; } static NSString * const kAFMultipartFormCRLF = @"\r\n"; static inline NSString * AFMultipartFormInitialBoundary(NSString *boundary) { return [NSString stringWithFormat:@"--%@%@", boundary, kAFMultipartFormCRLF]; } static inline NSString * AFMultipartFormEncapsulationBoundary(NSString *boundary) { return [NSString stringWithFormat:@"%@--%@%@", kAFMultipartFormCRLF, boundary, kAFMultipartFormCRLF]; } static inline NSString * AFMultipartFormFinalBoundary(NSString *boundary) { return [NSString stringWithFormat:@"%@--%@--%@", kAFMultipartFormCRLF, boundary, kAFMultipartFormCRLF]; } static inline NSString * AFContentTypeForPathExtension(NSString *extension) { #ifdef __UTTYPE__ NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL); NSString *contentType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType); if (!contentType) { return @"application/octet-stream"; } else { return contentType; } #else #pragma unused (extension) return @"application/octet-stream"; #endif } NSUInteger const kAFUploadStream3GSuggestedPacketSize = 1024 * 16; NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2; @interface AFHTTPBodyPart : NSObject @property (nonatomic, assign) NSStringEncoding stringEncoding; @property (nonatomic, strong) NSDictionary *headers; @property (nonatomic, copy) NSString *boundary; @property (nonatomic, strong) id body; @property (nonatomic, assign) unsigned long long bodyContentLength; @property (nonatomic, strong) NSInputStream *inputStream; @property (nonatomic, assign) BOOL hasInitialBoundary; @property (nonatomic, assign) BOOL hasFinalBoundary; @property (readonly, nonatomic, assign, getter = hasBytesAvailable) BOOL bytesAvailable; @property (readonly, nonatomic, assign) unsigned long long contentLength; - (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)length; @end @interface AFMultipartBodyStream : NSInputStream @property (nonatomic, assign) NSUInteger numberOfBytesInPacket; @property (nonatomic, assign) NSTimeInterval delay; @property (nonatomic, strong) NSInputStream *inputStream; @property (readonly, nonatomic, assign) unsigned long long contentLength; @property (readonly, nonatomic, assign, getter = isEmpty) BOOL empty; - (id)initWithStringEncoding:(NSStringEncoding)encoding; - (void)setInitialAndFinalBoundaries; - (void)appendHTTPBodyPart:(AFHTTPBodyPart *)bodyPart; @end #pragma mark - @interface AFStreamingMultipartFormData () @property (readwrite, nonatomic, copy) NSMutableURLRequest *request; @property (readwrite, nonatomic, assign) NSStringEncoding stringEncoding; @property (readwrite, nonatomic, copy) NSString *boundary; @property (readwrite, nonatomic, strong) AFMultipartBodyStream *bodyStream; @end @implementation AFStreamingMultipartFormData - (id)initWithURLRequest:(NSMutableURLRequest *)urlRequest stringEncoding:(NSStringEncoding)encoding { self = [super init]; if (!self) { return nil; } self.request = urlRequest; self.stringEncoding = encoding; self.boundary = AFCreateMultipartFormBoundary(); self.bodyStream = [[AFMultipartBodyStream alloc] initWithStringEncoding:encoding]; return self; } - (BOOL)appendPartWithFileURL:(NSURL *)fileURL name:(NSString *)name error:(NSError * __autoreleasing *)error { NSParameterAssert(fileURL); NSParameterAssert(name); NSString *fileName = [fileURL lastPathComponent]; NSString *mimeType = AFContentTypeForPathExtension([fileURL pathExtension]); return [self appendPartWithFileURL:fileURL name:name fileName:fileName mimeType:mimeType error:error]; } - (BOOL)appendPartWithFileURL:(NSURL *)fileURL name:(NSString *)name fileName:(NSString *)fileName mimeType:(NSString *)mimeType error:(NSError * __autoreleasing *)error { NSParameterAssert(fileURL); NSParameterAssert(name); NSParameterAssert(fileName); NSParameterAssert(mimeType); if (![fileURL isFileURL]) { NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey: NSLocalizedStringFromTable(@"Expected URL to be a file URL", @"AFNetworking", nil)}; if (error) { *error = [[NSError alloc] initWithDomain:AFURLRequestSerializationErrorDomain code:NSURLErrorBadURL userInfo:userInfo]; } return NO; } else if ([fileURL checkResourceIsReachableAndReturnError:error] == NO) { NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey: NSLocalizedStringFromTable(@"File URL not reachable.", @"AFNetworking", nil)}; if (error) { *error = [[NSError alloc] initWithDomain:AFURLRequestSerializationErrorDomain code:NSURLErrorBadURL userInfo:userInfo]; } return NO; } NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[fileURL path] error:error]; if (!fileAttributes) { return NO; } NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary]; [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"]; [mutableHeaders setValue:mimeType forKey:@"Content-Type"]; AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init]; bodyPart.stringEncoding = self.stringEncoding; bodyPart.headers = mutableHeaders; bodyPart.boundary = self.boundary; bodyPart.body = fileURL; bodyPart.bodyContentLength = [[fileAttributes objectForKey:NSFileSize] unsignedLongLongValue]; [self.bodyStream appendHTTPBodyPart:bodyPart]; return YES; } - (void)appendPartWithInputStream:(NSInputStream *)inputStream name:(NSString *)name fileName:(NSString *)fileName length:(int64_t)length mimeType:(NSString *)mimeType { NSParameterAssert(name); NSParameterAssert(fileName); NSParameterAssert(mimeType); NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary]; [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"]; [mutableHeaders setValue:mimeType forKey:@"Content-Type"]; AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init]; bodyPart.stringEncoding = self.stringEncoding; bodyPart.headers = mutableHeaders; bodyPart.boundary = self.boundary; bodyPart.body = inputStream; bodyPart.bodyContentLength = (unsigned long long)length; [self.bodyStream appendHTTPBodyPart:bodyPart]; } - (void)appendPartWithFileData:(NSData *)data name:(NSString *)name fileName:(NSString *)fileName mimeType:(NSString *)mimeType { NSParameterAssert(name); NSParameterAssert(fileName); NSParameterAssert(mimeType); NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary]; [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"]; [mutableHeaders setValue:mimeType forKey:@"Content-Type"]; [self appendPartWithHeaders:mutableHeaders body:data]; } - (void)appendPartWithFormData:(NSData *)data name:(NSString *)name { NSParameterAssert(name); NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary]; [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"", name] forKey:@"Content-Disposition"]; [self appendPartWithHeaders:mutableHeaders body:data]; } - (void)appendPartWithHeaders:(NSDictionary *)headers body:(NSData *)body { NSParameterAssert(body); AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init]; bodyPart.stringEncoding = self.stringEncoding; bodyPart.headers = headers; bodyPart.boundary = self.boundary; bodyPart.bodyContentLength = [body length]; bodyPart.body = body; [self.bodyStream appendHTTPBodyPart:bodyPart]; } - (void)throttleBandwidthWithPacketSize:(NSUInteger)numberOfBytes delay:(NSTimeInterval)delay { self.bodyStream.numberOfBytesInPacket = numberOfBytes; self.bodyStream.delay = delay; } - (NSMutableURLRequest *)requestByFinalizingMultipartFormData { if ([self.bodyStream isEmpty]) { return self.request; } // Reset the initial and final boundaries to ensure correct Content-Length [self.bodyStream setInitialAndFinalBoundaries]; [self.request setHTTPBodyStream:self.bodyStream]; [self.request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", self.boundary] forHTTPHeaderField:@"Content-Type"]; [self.request setValue:[NSString stringWithFormat:@"%llu", [self.bodyStream contentLength]] forHTTPHeaderField:@"Content-Length"]; return self.request; } @end #pragma mark - @interface NSStream () @property (readwrite) NSStreamStatus streamStatus; @property (readwrite, copy) NSError *streamError; @end @interface AFMultipartBodyStream () @property (readwrite, nonatomic, assign) NSStringEncoding stringEncoding; @property (readwrite, nonatomic, strong) NSMutableArray *HTTPBodyParts; @property (readwrite, nonatomic, strong) NSEnumerator *HTTPBodyPartEnumerator; @property (readwrite, nonatomic, strong) AFHTTPBodyPart *currentHTTPBodyPart; @property (readwrite, nonatomic, strong) NSOutputStream *outputStream; @property (readwrite, nonatomic, strong) NSMutableData *buffer; @end @implementation AFMultipartBodyStream #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wimplicit-atomic-properties" #if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000) || (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1100) @synthesize delegate; #endif @synthesize streamStatus; @synthesize streamError; #pragma clang diagnostic pop - (id)initWithStringEncoding:(NSStringEncoding)encoding { self = [super init]; if (!self) { return nil; } self.stringEncoding = encoding; self.HTTPBodyParts = [NSMutableArray array]; self.numberOfBytesInPacket = NSIntegerMax; return self; } - (void)setInitialAndFinalBoundaries { if ([self.HTTPBodyParts count] > 0) { for (AFHTTPBodyPart *bodyPart in self.HTTPBodyParts) { bodyPart.hasInitialBoundary = NO; bodyPart.hasFinalBoundary = NO; } [[self.HTTPBodyParts objectAtIndex:0] setHasInitialBoundary:YES]; [[self.HTTPBodyParts lastObject] setHasFinalBoundary:YES]; } } - (void)appendHTTPBodyPart:(AFHTTPBodyPart *)bodyPart { [self.HTTPBodyParts addObject:bodyPart]; } - (BOOL)isEmpty { return [self.HTTPBodyParts count] == 0; } #pragma mark - NSInputStream - (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)length { if ([self streamStatus] == NSStreamStatusClosed) { return 0; } NSInteger totalNumberOfBytesRead = 0; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" while ((NSUInteger)totalNumberOfBytesRead < MIN(length, self.numberOfBytesInPacket)) { if (!self.currentHTTPBodyPart || ![self.currentHTTPBodyPart hasBytesAvailable]) { if (!(self.currentHTTPBodyPart = [self.HTTPBodyPartEnumerator nextObject])) { break; } } else { NSUInteger maxLength = length - (NSUInteger)totalNumberOfBytesRead; NSInteger numberOfBytesRead = [self.currentHTTPBodyPart read:&buffer[totalNumberOfBytesRead] maxLength:maxLength]; if (numberOfBytesRead == -1) { self.streamError = self.currentHTTPBodyPart.inputStream.streamError; break; } else { totalNumberOfBytesRead += numberOfBytesRead; if (self.delay > 0.0f) { [NSThread sleepForTimeInterval:self.delay]; } } } } #pragma clang diagnostic pop return totalNumberOfBytesRead; } - (BOOL)getBuffer:(__unused uint8_t **)buffer length:(__unused NSUInteger *)len { return NO; } - (BOOL)hasBytesAvailable { return [self streamStatus] == NSStreamStatusOpen; } #pragma mark - NSStream - (void)open { if (self.streamStatus == NSStreamStatusOpen) { return; } self.streamStatus = NSStreamStatusOpen; [self setInitialAndFinalBoundaries]; self.HTTPBodyPartEnumerator = [self.HTTPBodyParts objectEnumerator]; } - (void)close { self.streamStatus = NSStreamStatusClosed; } - (id)propertyForKey:(__unused NSString *)key { return nil; } - (BOOL)setProperty:(__unused id)property forKey:(__unused NSString *)key { return NO; } - (void)scheduleInRunLoop:(__unused NSRunLoop *)aRunLoop forMode:(__unused NSString *)mode {} - (void)removeFromRunLoop:(__unused NSRunLoop *)aRunLoop forMode:(__unused NSString *)mode {} - (unsigned long long)contentLength { unsigned long long length = 0; for (AFHTTPBodyPart *bodyPart in self.HTTPBodyParts) { length += [bodyPart contentLength]; } return length; } #pragma mark - Undocumented CFReadStream Bridged Methods - (void)_scheduleInCFRunLoop:(__unused CFRunLoopRef)aRunLoop forMode:(__unused CFStringRef)aMode {} - (void)_unscheduleFromCFRunLoop:(__unused CFRunLoopRef)aRunLoop forMode:(__unused CFStringRef)aMode {} - (BOOL)_setCFClientFlags:(__unused CFOptionFlags)inFlags callback:(__unused CFReadStreamClientCallBack)inCallback context:(__unused CFStreamClientContext *)inContext { return NO; } #pragma mark - NSCopying -(id)copyWithZone:(NSZone *)zone { AFMultipartBodyStream *bodyStreamCopy = [[[self class] allocWithZone:zone] initWithStringEncoding:self.stringEncoding]; for (AFHTTPBodyPart *bodyPart in self.HTTPBodyParts) { [bodyStreamCopy appendHTTPBodyPart:[bodyPart copy]]; } [bodyStreamCopy setInitialAndFinalBoundaries]; return bodyStreamCopy; } @end #pragma mark - typedef enum { AFEncapsulationBoundaryPhase = 1, AFHeaderPhase = 2, AFBodyPhase = 3, AFFinalBoundaryPhase = 4, } AFHTTPBodyPartReadPhase; @interface AFHTTPBodyPart () { AFHTTPBodyPartReadPhase _phase; NSInputStream *_inputStream; unsigned long long _phaseReadOffset; } - (BOOL)transitionToNextPhase; - (NSInteger)readData:(NSData *)data intoBuffer:(uint8_t *)buffer maxLength:(NSUInteger)length; @end @implementation AFHTTPBodyPart - (id)init { self = [super init]; if (!self) { return nil; } [self transitionToNextPhase]; return self; } - (void)dealloc { if (_inputStream) { [_inputStream close]; _inputStream = nil; } } - (NSInputStream *)inputStream { if (!_inputStream) { if ([self.body isKindOfClass:[NSData class]]) { _inputStream = [NSInputStream inputStreamWithData:self.body]; } else if ([self.body isKindOfClass:[NSURL class]]) { _inputStream = [NSInputStream inputStreamWithURL:self.body]; } else if ([self.body isKindOfClass:[NSInputStream class]]) { _inputStream = self.body; } else { _inputStream = [NSInputStream inputStreamWithData:[NSData data]]; } } return _inputStream; } - (NSString *)stringForHeaders { NSMutableString *headerString = [NSMutableString string]; for (NSString *field in [self.headers allKeys]) { [headerString appendString:[NSString stringWithFormat:@"%@: %@%@", field, [self.headers valueForKey:field], kAFMultipartFormCRLF]]; } [headerString appendString:kAFMultipartFormCRLF]; return [NSString stringWithString:headerString]; } - (unsigned long long)contentLength { unsigned long long length = 0; NSData *encapsulationBoundaryData = [([self hasInitialBoundary] ? AFMultipartFormInitialBoundary(self.boundary) : AFMultipartFormEncapsulationBoundary(self.boundary)) dataUsingEncoding:self.stringEncoding]; length += [encapsulationBoundaryData length]; NSData *headersData = [[self stringForHeaders] dataUsingEncoding:self.stringEncoding]; length += [headersData length]; length += _bodyContentLength; NSData *closingBoundaryData = ([self hasFinalBoundary] ? [AFMultipartFormFinalBoundary(self.boundary) dataUsingEncoding:self.stringEncoding] : [NSData data]); length += [closingBoundaryData length]; return length; } - (BOOL)hasBytesAvailable { // Allows `read:maxLength:` to be called again if `AFMultipartFormFinalBoundary` doesn't fit into the available buffer if (_phase == AFFinalBoundaryPhase) { return YES; } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wcovered-switch-default" switch (self.inputStream.streamStatus) { case NSStreamStatusNotOpen: case NSStreamStatusOpening: case NSStreamStatusOpen: case NSStreamStatusReading: case NSStreamStatusWriting: return YES; case NSStreamStatusAtEnd: case NSStreamStatusClosed: case NSStreamStatusError: default: return NO; } #pragma clang diagnostic pop } - (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)length { NSInteger totalNumberOfBytesRead = 0; if (_phase == AFEncapsulationBoundaryPhase) { NSData *encapsulationBoundaryData = [([self hasInitialBoundary] ? AFMultipartFormInitialBoundary(self.boundary) : AFMultipartFormEncapsulationBoundary(self.boundary)) dataUsingEncoding:self.stringEncoding]; totalNumberOfBytesRead += [self readData:encapsulationBoundaryData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)]; } if (_phase == AFHeaderPhase) { NSData *headersData = [[self stringForHeaders] dataUsingEncoding:self.stringEncoding]; totalNumberOfBytesRead += [self readData:headersData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)]; } if (_phase == AFBodyPhase) { NSInteger numberOfBytesRead = 0; numberOfBytesRead = [self.inputStream read:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)]; if (numberOfBytesRead == -1) { return -1; } else { totalNumberOfBytesRead += numberOfBytesRead; if ([self.inputStream streamStatus] >= NSStreamStatusAtEnd) { [self transitionToNextPhase]; } } } if (_phase == AFFinalBoundaryPhase) { NSData *closingBoundaryData = ([self hasFinalBoundary] ? [AFMultipartFormFinalBoundary(self.boundary) dataUsingEncoding:self.stringEncoding] : [NSData data]); totalNumberOfBytesRead += [self readData:closingBoundaryData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)]; } return totalNumberOfBytesRead; } - (NSInteger)readData:(NSData *)data intoBuffer:(uint8_t *)buffer maxLength:(NSUInteger)length { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" NSRange range = NSMakeRange((NSUInteger)_phaseReadOffset, MIN([data length] - ((NSUInteger)_phaseReadOffset), length)); [data getBytes:buffer range:range]; #pragma clang diagnostic pop _phaseReadOffset += range.length; if (((NSUInteger)_phaseReadOffset) >= [data length]) { [self transitionToNextPhase]; } return (NSInteger)range.length; } - (BOOL)transitionToNextPhase { if (![[NSThread currentThread] isMainThread]) { dispatch_sync(dispatch_get_main_queue(), ^{ [self transitionToNextPhase]; }); return YES; } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wcovered-switch-default" switch (_phase) { case AFEncapsulationBoundaryPhase: _phase = AFHeaderPhase; break; case AFHeaderPhase: [self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; [self.inputStream open]; _phase = AFBodyPhase; break; case AFBodyPhase: [self.inputStream close]; _phase = AFFinalBoundaryPhase; break; case AFFinalBoundaryPhase: default: _phase = AFEncapsulationBoundaryPhase; break; } _phaseReadOffset = 0; #pragma clang diagnostic pop return YES; } #pragma mark - NSCopying - (id)copyWithZone:(NSZone *)zone { AFHTTPBodyPart *bodyPart = [[[self class] allocWithZone:zone] init]; bodyPart.stringEncoding = self.stringEncoding; bodyPart.headers = self.headers; bodyPart.bodyContentLength = self.bodyContentLength; bodyPart.body = self.body; bodyPart.boundary = self.boundary; return bodyPart; } @end #pragma mark - @implementation AFJSONRequestSerializer + (instancetype)serializer { return [self serializerWithWritingOptions:(NSJSONWritingOptions)0]; } + (instancetype)serializerWithWritingOptions:(NSJSONWritingOptions)writingOptions { AFJSONRequestSerializer *serializer = [[self alloc] init]; serializer.writingOptions = writingOptions; return serializer; } #pragma mark - AFURLRequestSerialization - (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request withParameters:(id)parameters error:(NSError *__autoreleasing *)error { NSParameterAssert(request); if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) { return [super requestBySerializingRequest:request withParameters:parameters error:error]; } NSMutableURLRequest *mutableRequest = [request mutableCopy]; [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) { if (![request valueForHTTPHeaderField:field]) { [mutableRequest setValue:value forHTTPHeaderField:field]; } }]; if (parameters) { if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) { [mutableRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; } [mutableRequest setHTTPBody:[NSJSONSerialization dataWithJSONObject:parameters options:self.writingOptions error:error]]; } return mutableRequest; } #pragma mark - NSSecureCoding - (id)initWithCoder:(NSCoder *)decoder { self = [super initWithCoder:decoder]; if (!self) { return nil; } self.writingOptions = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(writingOptions))] unsignedIntegerValue]; return self; } - (void)encodeWithCoder:(NSCoder *)coder { [super encodeWithCoder:coder]; [coder encodeInteger:self.writingOptions forKey:NSStringFromSelector(@selector(writingOptions))]; } #pragma mark - NSCopying - (id)copyWithZone:(NSZone *)zone { AFJSONRequestSerializer *serializer = [super copyWithZone:zone]; serializer.writingOptions = self.writingOptions; return serializer; } @end #pragma mark - @implementation AFPropertyListRequestSerializer + (instancetype)serializer { return [self serializerWithFormat:NSPropertyListXMLFormat_v1_0 writeOptions:0]; } + (instancetype)serializerWithFormat:(NSPropertyListFormat)format writeOptions:(NSPropertyListWriteOptions)writeOptions { AFPropertyListRequestSerializer *serializer = [[self alloc] init]; serializer.format = format; serializer.writeOptions = writeOptions; return serializer; } #pragma mark - AFURLRequestSerializer - (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request withParameters:(id)parameters error:(NSError *__autoreleasing *)error { NSParameterAssert(request); if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) { return [super requestBySerializingRequest:request withParameters:parameters error:error]; } NSMutableURLRequest *mutableRequest = [request mutableCopy]; [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) { if (![request valueForHTTPHeaderField:field]) { [mutableRequest setValue:value forHTTPHeaderField:field]; } }]; if (parameters) { if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) { [mutableRequest setValue:@"application/x-plist" forHTTPHeaderField:@"Content-Type"]; } [mutableRequest setHTTPBody:[NSPropertyListSerialization dataWithPropertyList:parameters format:self.format options:self.writeOptions error:error]]; } return mutableRequest; } #pragma mark - NSSecureCoding - (id)initWithCoder:(NSCoder *)decoder { self = [super initWithCoder:decoder]; if (!self) { return nil; } self.format = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(format))] unsignedIntegerValue]; self.writeOptions = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(writeOptions))] unsignedIntegerValue]; return self; } - (void)encodeWithCoder:(NSCoder *)coder { [super encodeWithCoder:coder]; [coder encodeInteger:self.format forKey:NSStringFromSelector(@selector(format))]; [coder encodeObject:@(self.writeOptions) forKey:NSStringFromSelector(@selector(writeOptions))]; } #pragma mark - NSCopying - (id)copyWithZone:(NSZone *)zone { AFPropertyListRequestSerializer *serializer = [super copyWithZone:zone]; serializer.format = self.format; serializer.writeOptions = self.writeOptions; return serializer; } @end ================================================ FILE: Pods/AFNetworking/AFNetworking/AFURLResponseSerialization.h ================================================ // AFURLResponseSerialization.h // // Copyright (c) 2013-2015 AFNetworking (http://afnetworking.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import #import /** The `AFURLResponseSerialization` protocol is adopted by an object that decodes data into a more useful object representation, according to details in the server response. Response serializers may additionally perform validation on the incoming response and data. For example, a JSON response serializer may check for an acceptable status code (`2XX` range) and content type (`application/json`), decoding a valid JSON response into an object. */ @protocol AFURLResponseSerialization /** The response object decoded from the data associated with a specified response. @param response The response to be processed. @param data The response data to be decoded. @param error The error that occurred while attempting to decode the response data. @return The object decoded from the specified response data. */ - (id)responseObjectForResponse:(NSURLResponse *)response data:(NSData *)data error:(NSError *__autoreleasing *)error; @end #pragma mark - /** `AFHTTPResponseSerializer` conforms to the `AFURLRequestSerialization` & `AFURLResponseSerialization` protocols, offering a concrete base implementation of query string / URL form-encoded parameter serialization and default request headers, as well as response status code and content type validation. Any request or response serializer dealing with HTTP is encouraged to subclass `AFHTTPResponseSerializer` in order to ensure consistent default behavior. */ @interface AFHTTPResponseSerializer : NSObject - (instancetype) init; /** The string encoding used to serialize data received from the server, when no string encoding is specified by the response. `NSUTF8StringEncoding` by default. */ @property (nonatomic, assign) NSStringEncoding stringEncoding; /** Creates and returns a serializer with default configuration. */ + (instancetype)serializer; ///----------------------------------------- /// @name Configuring Response Serialization ///----------------------------------------- /** The acceptable HTTP status codes for responses. When non-`nil`, responses with status codes not contained by the set will result in an error during validation. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html */ @property (nonatomic, copy) NSIndexSet *acceptableStatusCodes; /** The acceptable MIME types for responses. When non-`nil`, responses with a `Content-Type` with MIME types that do not intersect with the set will result in an error during validation. */ @property (nonatomic, copy) NSSet *acceptableContentTypes; /** Validates the specified response and data. In its base implementation, this method checks for an acceptable status code and content type. Subclasses may wish to add other domain-specific checks. @param response The response to be validated. @param data The data associated with the response. @param error The error that occurred while attempting to validate the response. @return `YES` if the response is valid, otherwise `NO`. */ - (BOOL)validateResponse:(NSHTTPURLResponse *)response data:(NSData *)data error:(NSError *__autoreleasing *)error; @end #pragma mark - /** `AFJSONResponseSerializer` is a subclass of `AFHTTPResponseSerializer` that validates and decodes JSON responses. By default, `AFJSONResponseSerializer` accepts the following MIME types, which includes the official standard, `application/json`, as well as other commonly-used types: - `application/json` - `text/json` - `text/javascript` */ @interface AFJSONResponseSerializer : AFHTTPResponseSerializer - (instancetype) init; /** Options for reading the response JSON data and creating the Foundation objects. For possible values, see the `NSJSONSerialization` documentation section "NSJSONReadingOptions". `0` by default. */ @property (nonatomic, assign) NSJSONReadingOptions readingOptions; /** Whether to remove keys with `NSNull` values from response JSON. Defaults to `NO`. */ @property (nonatomic, assign) BOOL removesKeysWithNullValues; /** Creates and returns a JSON serializer with specified reading and writing options. @param readingOptions The specified JSON reading options. */ + (instancetype)serializerWithReadingOptions:(NSJSONReadingOptions)readingOptions; @end #pragma mark - /** `AFXMLParserResponseSerializer` is a subclass of `AFHTTPResponseSerializer` that validates and decodes XML responses as an `NSXMLParser` objects. By default, `AFXMLParserResponseSerializer` accepts the following MIME types, which includes the official standard, `application/xml`, as well as other commonly-used types: - `application/xml` - `text/xml` */ @interface AFXMLParserResponseSerializer : AFHTTPResponseSerializer @end #pragma mark - #ifdef __MAC_OS_X_VERSION_MIN_REQUIRED /** `AFXMLDocumentResponseSerializer` is a subclass of `AFHTTPResponseSerializer` that validates and decodes XML responses as an `NSXMLDocument` objects. By default, `AFXMLDocumentResponseSerializer` accepts the following MIME types, which includes the official standard, `application/xml`, as well as other commonly-used types: - `application/xml` - `text/xml` */ @interface AFXMLDocumentResponseSerializer : AFHTTPResponseSerializer - (instancetype) init; /** Input and output options specifically intended for `NSXMLDocument` objects. For possible values, see the `NSJSONSerialization` documentation section "NSJSONReadingOptions". `0` by default. */ @property (nonatomic, assign) NSUInteger options; /** Creates and returns an XML document serializer with the specified options. @param mask The XML document options. */ + (instancetype)serializerWithXMLDocumentOptions:(NSUInteger)mask; @end #endif #pragma mark - /** `AFPropertyListResponseSerializer` is a subclass of `AFHTTPResponseSerializer` that validates and decodes XML responses as an `NSXMLDocument` objects. By default, `AFPropertyListResponseSerializer` accepts the following MIME types: - `application/x-plist` */ @interface AFPropertyListResponseSerializer : AFHTTPResponseSerializer - (instancetype) init; /** The property list format. Possible values are described in "NSPropertyListFormat". */ @property (nonatomic, assign) NSPropertyListFormat format; /** The property list reading options. Possible values are described in "NSPropertyListMutabilityOptions." */ @property (nonatomic, assign) NSPropertyListReadOptions readOptions; /** Creates and returns a property list serializer with a specified format, read options, and write options. @param format The property list format. @param readOptions The property list reading options. */ + (instancetype)serializerWithFormat:(NSPropertyListFormat)format readOptions:(NSPropertyListReadOptions)readOptions; @end #pragma mark - /** `AFImageResponseSerializer` is a subclass of `AFHTTPResponseSerializer` that validates and decodes image responses. By default, `AFImageResponseSerializer` accepts the following MIME types, which correspond to the image formats supported by UIImage or NSImage: - `image/tiff` - `image/jpeg` - `image/gif` - `image/png` - `image/ico` - `image/x-icon` - `image/bmp` - `image/x-bmp` - `image/x-xbitmap` - `image/x-win-bitmap` */ @interface AFImageResponseSerializer : AFHTTPResponseSerializer #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) /** The scale factor used when interpreting the image data to construct `responseImage`. Specifying a scale factor of 1.0 results in an image whose size matches the pixel-based dimensions of the image. Applying a different scale factor changes the size of the image as reported by the size property. This is set to the value of scale of the main screen by default, which automatically scales images for retina displays, for instance. */ @property (nonatomic, assign) CGFloat imageScale; /** Whether to automatically inflate response image data for compressed formats (such as PNG or JPEG). Enabling this can significantly improve drawing performance on iOS when used with `setCompletionBlockWithSuccess:failure:`, as it allows a bitmap representation to be constructed in the background rather than on the main thread. `YES` by default. */ @property (nonatomic, assign) BOOL automaticallyInflatesResponseImage; #endif @end #pragma mark - /** `AFCompoundSerializer` is a subclass of `AFHTTPResponseSerializer` that delegates the response serialization to the first `AFHTTPResponseSerializer` object that returns an object for `responseObjectForResponse:data:error:`, falling back on the default behavior of `AFHTTPResponseSerializer`. This is useful for supporting multiple potential types and structures of server responses with a single serializer. */ @interface AFCompoundResponseSerializer : AFHTTPResponseSerializer /** The component response serializers. */ @property (readonly, nonatomic, copy) NSArray *responseSerializers; /** Creates and returns a compound serializer comprised of the specified response serializers. @warning Each response serializer specified must be a subclass of `AFHTTPResponseSerializer`, and response to `-validateResponse:data:error:`. */ + (instancetype)compoundSerializerWithResponseSerializers:(NSArray *)responseSerializers; @end ///---------------- /// @name Constants ///---------------- /** ## Error Domains The following error domain is predefined. - `NSString * const AFURLResponseSerializationErrorDomain` ### Constants `AFURLResponseSerializationErrorDomain` AFURLResponseSerializer errors. Error codes for `AFURLResponseSerializationErrorDomain` correspond to codes in `NSURLErrorDomain`. */ extern NSString * const AFURLResponseSerializationErrorDomain; /** ## User info dictionary keys These keys may exist in the user info dictionary, in addition to those defined for NSError. - `NSString * const AFNetworkingOperationFailingURLResponseErrorKey` - `NSString * const AFNetworkingOperationFailingURLResponseDataErrorKey` ### Constants `AFNetworkingOperationFailingURLResponseErrorKey` The corresponding value is an `NSURLResponse` containing the response of the operation associated with an error. This key is only present in the `AFURLResponseSerializationErrorDomain`. `AFNetworkingOperationFailingURLResponseDataErrorKey` The corresponding value is an `NSData` containing the original data of the operation associated with an error. This key is only present in the `AFURLResponseSerializationErrorDomain`. */ extern NSString * const AFNetworkingOperationFailingURLResponseErrorKey; extern NSString * const AFNetworkingOperationFailingURLResponseDataErrorKey; ================================================ FILE: Pods/AFNetworking/AFNetworking/AFURLResponseSerialization.m ================================================ // AFURLResponseSerialization.m // // Copyright (c) 2013-2015 AFNetworking (http://afnetworking.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import "AFURLResponseSerialization.h" #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) #import #elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED) #import #endif NSString * const AFURLResponseSerializationErrorDomain = @"com.alamofire.error.serialization.response"; NSString * const AFNetworkingOperationFailingURLResponseErrorKey = @"com.alamofire.serialization.response.error.response"; NSString * const AFNetworkingOperationFailingURLResponseDataErrorKey = @"com.alamofire.serialization.response.error.data"; static NSError * AFErrorWithUnderlyingError(NSError *error, NSError *underlyingError) { if (!error) { return underlyingError; } if (!underlyingError || error.userInfo[NSUnderlyingErrorKey]) { return error; } NSMutableDictionary *mutableUserInfo = [error.userInfo mutableCopy]; mutableUserInfo[NSUnderlyingErrorKey] = underlyingError; return [[NSError alloc] initWithDomain:error.domain code:error.code userInfo:mutableUserInfo]; } static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger code, NSString *domain) { if ([error.domain isEqualToString:domain] && error.code == code) { return YES; } else if (error.userInfo[NSUnderlyingErrorKey]) { return AFErrorOrUnderlyingErrorHasCodeInDomain(error.userInfo[NSUnderlyingErrorKey], code, domain); } return NO; } static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingOptions readingOptions) { if ([JSONObject isKindOfClass:[NSArray class]]) { NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:[(NSArray *)JSONObject count]]; for (id value in (NSArray *)JSONObject) { [mutableArray addObject:AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions)]; } return (readingOptions & NSJSONReadingMutableContainers) ? mutableArray : [NSArray arrayWithArray:mutableArray]; } else if ([JSONObject isKindOfClass:[NSDictionary class]]) { NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:JSONObject]; for (id key in [(NSDictionary *)JSONObject allKeys]) { id value = [(NSDictionary *)JSONObject objectForKey:key]; if (!value || [value isEqual:[NSNull null]]) { [mutableDictionary removeObjectForKey:key]; } else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]]) { [mutableDictionary setObject:AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions) forKey:key]; } } return (readingOptions & NSJSONReadingMutableContainers) ? mutableDictionary : [NSDictionary dictionaryWithDictionary:mutableDictionary]; } return JSONObject; } @implementation AFHTTPResponseSerializer + (instancetype)serializer { return [[self alloc] init]; } - (instancetype)init { self = [super init]; if (!self) { return nil; } self.stringEncoding = NSUTF8StringEncoding; self.acceptableStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)]; self.acceptableContentTypes = nil; return self; } #pragma mark - - (BOOL)validateResponse:(NSHTTPURLResponse *)response data:(NSData *)data error:(NSError * __autoreleasing *)error { BOOL responseIsValid = YES; NSError *validationError = nil; if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) { if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]]) { if ([data length] > 0 && [response URL]) { NSMutableDictionary *mutableUserInfo = [@{ NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: unacceptable content-type: %@", @"AFNetworking", nil), [response MIMEType]], NSURLErrorFailingURLErrorKey:[response URL], AFNetworkingOperationFailingURLResponseErrorKey: response, } mutableCopy]; if (data) { mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data; } validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:mutableUserInfo], validationError); } responseIsValid = NO; } if (self.acceptableStatusCodes && ![self.acceptableStatusCodes containsIndex:(NSUInteger)response.statusCode] && [response URL]) { NSMutableDictionary *mutableUserInfo = [@{ NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: %@ (%ld)", @"AFNetworking", nil), [NSHTTPURLResponse localizedStringForStatusCode:response.statusCode], (long)response.statusCode], NSURLErrorFailingURLErrorKey:[response URL], AFNetworkingOperationFailingURLResponseErrorKey: response, } mutableCopy]; if (data) { mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data; } validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError); responseIsValid = NO; } } if (error && !responseIsValid) { *error = validationError; } return responseIsValid; } #pragma mark - AFURLResponseSerialization - (id)responseObjectForResponse:(NSURLResponse *)response data:(NSData *)data error:(NSError *__autoreleasing *)error { [self validateResponse:(NSHTTPURLResponse *)response data:data error:error]; return data; } #pragma mark - NSSecureCoding + (BOOL)supportsSecureCoding { return YES; } - (id)initWithCoder:(NSCoder *)decoder { self = [self init]; if (!self) { return nil; } self.acceptableStatusCodes = [decoder decodeObjectOfClass:[NSIndexSet class] forKey:NSStringFromSelector(@selector(acceptableStatusCodes))]; self.acceptableContentTypes = [decoder decodeObjectOfClass:[NSIndexSet class] forKey:NSStringFromSelector(@selector(acceptableContentTypes))]; return self; } - (void)encodeWithCoder:(NSCoder *)coder { [coder encodeObject:self.acceptableStatusCodes forKey:NSStringFromSelector(@selector(acceptableStatusCodes))]; [coder encodeObject:self.acceptableContentTypes forKey:NSStringFromSelector(@selector(acceptableContentTypes))]; } #pragma mark - NSCopying - (id)copyWithZone:(NSZone *)zone { AFHTTPResponseSerializer *serializer = [[[self class] allocWithZone:zone] init]; serializer.acceptableStatusCodes = [self.acceptableStatusCodes copyWithZone:zone]; serializer.acceptableContentTypes = [self.acceptableContentTypes copyWithZone:zone]; return serializer; } @end #pragma mark - @implementation AFJSONResponseSerializer + (instancetype)serializer { return [self serializerWithReadingOptions:(NSJSONReadingOptions)0]; } + (instancetype)serializerWithReadingOptions:(NSJSONReadingOptions)readingOptions { AFJSONResponseSerializer *serializer = [[self alloc] init]; serializer.readingOptions = readingOptions; return serializer; } - (instancetype)init { self = [super init]; if (!self) { return nil; } self.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil]; return self; } #pragma mark - AFURLResponseSerialization - (id)responseObjectForResponse:(NSURLResponse *)response data:(NSData *)data error:(NSError *__autoreleasing *)error { if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) { if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) { return nil; } } // Workaround for behavior of Rails to return a single space for `head :ok` (a workaround for a bug in Safari), which is not interpreted as valid input by NSJSONSerialization. // See https://github.com/rails/rails/issues/1742 NSStringEncoding stringEncoding = self.stringEncoding; if (response.textEncodingName) { CFStringEncoding encoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName); if (encoding != kCFStringEncodingInvalidId) { stringEncoding = CFStringConvertEncodingToNSStringEncoding(encoding); } } id responseObject = nil; NSError *serializationError = nil; @autoreleasepool { NSString *responseString = [[NSString alloc] initWithData:data encoding:stringEncoding]; if (responseString && ![responseString isEqualToString:@" "]) { // Workaround for a bug in NSJSONSerialization when Unicode character escape codes are used instead of the actual character // See http://stackoverflow.com/a/12843465/157142 data = [responseString dataUsingEncoding:NSUTF8StringEncoding]; if (data) { if ([data length] > 0) { responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError]; } else { return nil; } } else { NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: NSLocalizedStringFromTable(@"Data failed decoding as a UTF-8 string", @"AFNetworking", nil), NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Could not decode string: %@", @"AFNetworking", nil), responseString] }; serializationError = [NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:userInfo]; } } } if (self.removesKeysWithNullValues && responseObject) { responseObject = AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions); } if (error) { *error = AFErrorWithUnderlyingError(serializationError, *error); } return responseObject; } #pragma mark - NSSecureCoding - (id)initWithCoder:(NSCoder *)decoder { self = [super initWithCoder:decoder]; if (!self) { return nil; } self.readingOptions = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(readingOptions))] unsignedIntegerValue]; self.removesKeysWithNullValues = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(removesKeysWithNullValues))] boolValue]; return self; } - (void)encodeWithCoder:(NSCoder *)coder { [super encodeWithCoder:coder]; [coder encodeObject:@(self.readingOptions) forKey:NSStringFromSelector(@selector(readingOptions))]; [coder encodeObject:@(self.removesKeysWithNullValues) forKey:NSStringFromSelector(@selector(removesKeysWithNullValues))]; } #pragma mark - NSCopying - (id)copyWithZone:(NSZone *)zone { AFJSONResponseSerializer *serializer = [[[self class] allocWithZone:zone] init]; serializer.readingOptions = self.readingOptions; serializer.removesKeysWithNullValues = self.removesKeysWithNullValues; return serializer; } @end #pragma mark - @implementation AFXMLParserResponseSerializer + (instancetype)serializer { AFXMLParserResponseSerializer *serializer = [[self alloc] init]; return serializer; } - (instancetype)init { self = [super init]; if (!self) { return nil; } self.acceptableContentTypes = [[NSSet alloc] initWithObjects:@"application/xml", @"text/xml", nil]; return self; } #pragma mark - AFURLResponseSerialization - (id)responseObjectForResponse:(NSHTTPURLResponse *)response data:(NSData *)data error:(NSError *__autoreleasing *)error { if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) { if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) { return nil; } } return [[NSXMLParser alloc] initWithData:data]; } @end #pragma mark - #ifdef __MAC_OS_X_VERSION_MIN_REQUIRED @implementation AFXMLDocumentResponseSerializer + (instancetype)serializer { return [self serializerWithXMLDocumentOptions:0]; } + (instancetype)serializerWithXMLDocumentOptions:(NSUInteger)mask { AFXMLDocumentResponseSerializer *serializer = [[self alloc] init]; serializer.options = mask; return serializer; } - (instancetype)init { self = [super init]; if (!self) { return nil; } self.acceptableContentTypes = [[NSSet alloc] initWithObjects:@"application/xml", @"text/xml", nil]; return self; } #pragma mark - AFURLResponseSerialization - (id)responseObjectForResponse:(NSURLResponse *)response data:(NSData *)data error:(NSError *__autoreleasing *)error { if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) { if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) { return nil; } } NSError *serializationError = nil; NSXMLDocument *document = [[NSXMLDocument alloc] initWithData:data options:self.options error:&serializationError]; if (error) { *error = AFErrorWithUnderlyingError(serializationError, *error); } return document; } #pragma mark - NSSecureCoding - (id)initWithCoder:(NSCoder *)decoder { self = [super initWithCoder:decoder]; if (!self) { return nil; } self.options = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(options))] unsignedIntegerValue]; return self; } - (void)encodeWithCoder:(NSCoder *)coder { [super encodeWithCoder:coder]; [coder encodeObject:@(self.options) forKey:NSStringFromSelector(@selector(options))]; } #pragma mark - NSCopying - (id)copyWithZone:(NSZone *)zone { AFXMLDocumentResponseSerializer *serializer = [[[self class] allocWithZone:zone] init]; serializer.options = self.options; return serializer; } @end #endif #pragma mark - @implementation AFPropertyListResponseSerializer + (instancetype)serializer { return [self serializerWithFormat:NSPropertyListXMLFormat_v1_0 readOptions:0]; } + (instancetype)serializerWithFormat:(NSPropertyListFormat)format readOptions:(NSPropertyListReadOptions)readOptions { AFPropertyListResponseSerializer *serializer = [[self alloc] init]; serializer.format = format; serializer.readOptions = readOptions; return serializer; } - (instancetype)init { self = [super init]; if (!self) { return nil; } self.acceptableContentTypes = [[NSSet alloc] initWithObjects:@"application/x-plist", nil]; return self; } #pragma mark - AFURLResponseSerialization - (id)responseObjectForResponse:(NSURLResponse *)response data:(NSData *)data error:(NSError *__autoreleasing *)error { if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) { if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) { return nil; } } id responseObject; NSError *serializationError = nil; if (data) { responseObject = [NSPropertyListSerialization propertyListWithData:data options:self.readOptions format:NULL error:&serializationError]; } if (error) { *error = AFErrorWithUnderlyingError(serializationError, *error); } return responseObject; } #pragma mark - NSSecureCoding - (id)initWithCoder:(NSCoder *)decoder { self = [super initWithCoder:decoder]; if (!self) { return nil; } self.format = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(format))] unsignedIntegerValue]; self.readOptions = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(readOptions))] unsignedIntegerValue]; return self; } - (void)encodeWithCoder:(NSCoder *)coder { [super encodeWithCoder:coder]; [coder encodeObject:@(self.format) forKey:NSStringFromSelector(@selector(format))]; [coder encodeObject:@(self.readOptions) forKey:NSStringFromSelector(@selector(readOptions))]; } #pragma mark - NSCopying - (id)copyWithZone:(NSZone *)zone { AFPropertyListResponseSerializer *serializer = [[[self class] allocWithZone:zone] init]; serializer.format = self.format; serializer.readOptions = self.readOptions; return serializer; } @end #pragma mark - #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) #import static UIImage * AFImageWithDataAtScale(NSData *data, CGFloat scale) { UIImage *image = [[UIImage alloc] initWithData:data]; if (image.images) { return image; } return [[UIImage alloc] initWithCGImage:[image CGImage] scale:scale orientation:image.imageOrientation]; } static UIImage * AFInflatedImageFromResponseWithDataAtScale(NSHTTPURLResponse *response, NSData *data, CGFloat scale) { if (!data || [data length] == 0) { return nil; } CGImageRef imageRef = NULL; CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data); if ([response.MIMEType isEqualToString:@"image/png"]) { imageRef = CGImageCreateWithPNGDataProvider(dataProvider, NULL, true, kCGRenderingIntentDefault); } else if ([response.MIMEType isEqualToString:@"image/jpeg"]) { imageRef = CGImageCreateWithJPEGDataProvider(dataProvider, NULL, true, kCGRenderingIntentDefault); if (imageRef) { CGColorSpaceRef imageColorSpace = CGImageGetColorSpace(imageRef); CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(imageColorSpace); // CGImageCreateWithJPEGDataProvider does not properly handle CMKY, so fall back to AFImageWithDataAtScale if (imageColorSpaceModel == kCGColorSpaceModelCMYK) { CGImageRelease(imageRef); imageRef = NULL; } } } CGDataProviderRelease(dataProvider); UIImage *image = AFImageWithDataAtScale(data, scale); if (!imageRef) { if (image.images || !image) { return image; } imageRef = CGImageCreateCopy([image CGImage]); if (!imageRef) { return nil; } } size_t width = CGImageGetWidth(imageRef); size_t height = CGImageGetHeight(imageRef); size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef); if (width * height > 1024 * 1024 || bitsPerComponent > 8) { CGImageRelease(imageRef); return image; } // CGImageGetBytesPerRow() calculates incorrectly in iOS 5.0, so defer to CGBitmapContextCreate size_t bytesPerRow = 0; CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGColorSpaceModel colorSpaceModel = CGColorSpaceGetModel(colorSpace); CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef); if (colorSpaceModel == kCGColorSpaceModelRGB) { uint32_t alpha = (bitmapInfo & kCGBitmapAlphaInfoMask); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wassign-enum" if (alpha == kCGImageAlphaNone) { bitmapInfo &= ~kCGBitmapAlphaInfoMask; bitmapInfo |= kCGImageAlphaNoneSkipFirst; } else if (!(alpha == kCGImageAlphaNoneSkipFirst || alpha == kCGImageAlphaNoneSkipLast)) { bitmapInfo &= ~kCGBitmapAlphaInfoMask; bitmapInfo |= kCGImageAlphaPremultipliedFirst; } #pragma clang diagnostic pop } CGContextRef context = CGBitmapContextCreate(NULL, width, height, bitsPerComponent, bytesPerRow, colorSpace, bitmapInfo); CGColorSpaceRelease(colorSpace); if (!context) { CGImageRelease(imageRef); return image; } CGContextDrawImage(context, CGRectMake(0.0f, 0.0f, width, height), imageRef); CGImageRef inflatedImageRef = CGBitmapContextCreateImage(context); CGContextRelease(context); UIImage *inflatedImage = [[UIImage alloc] initWithCGImage:inflatedImageRef scale:scale orientation:image.imageOrientation]; CGImageRelease(inflatedImageRef); CGImageRelease(imageRef); return inflatedImage; } #endif @implementation AFImageResponseSerializer - (instancetype)init { self = [super init]; if (!self) { return nil; } self.acceptableContentTypes = [[NSSet alloc] initWithObjects:@"image/tiff", @"image/jpeg", @"image/gif", @"image/png", @"image/ico", @"image/x-icon", @"image/bmp", @"image/x-bmp", @"image/x-xbitmap", @"image/x-win-bitmap", nil]; #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) self.imageScale = [[UIScreen mainScreen] scale]; self.automaticallyInflatesResponseImage = YES; #endif return self; } #pragma mark - AFURLResponseSerializer - (id)responseObjectForResponse:(NSURLResponse *)response data:(NSData *)data error:(NSError *__autoreleasing *)error { if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) { if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) { return nil; } } #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) if (self.automaticallyInflatesResponseImage) { return AFInflatedImageFromResponseWithDataAtScale((NSHTTPURLResponse *)response, data, self.imageScale); } else { return AFImageWithDataAtScale(data, self.imageScale); } #elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED) // Ensure that the image is set to it's correct pixel width and height NSBitmapImageRep *bitimage = [[NSBitmapImageRep alloc] initWithData:data]; NSImage *image = [[NSImage alloc] initWithSize:NSMakeSize([bitimage pixelsWide], [bitimage pixelsHigh])]; [image addRepresentation:bitimage]; return image; #endif return nil; } #pragma mark - NSSecureCoding - (id)initWithCoder:(NSCoder *)decoder { self = [super initWithCoder:decoder]; if (!self) { return nil; } #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) NSNumber *imageScale = [decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(imageScale))]; #if CGFLOAT_IS_DOUBLE self.imageScale = [imageScale doubleValue]; #else self.imageScale = [imageScale floatValue]; #endif self.automaticallyInflatesResponseImage = [decoder decodeBoolForKey:NSStringFromSelector(@selector(automaticallyInflatesResponseImage))]; #endif return self; } - (void)encodeWithCoder:(NSCoder *)coder { [super encodeWithCoder:coder]; #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) [coder encodeObject:@(self.imageScale) forKey:NSStringFromSelector(@selector(imageScale))]; [coder encodeBool:self.automaticallyInflatesResponseImage forKey:NSStringFromSelector(@selector(automaticallyInflatesResponseImage))]; #endif } #pragma mark - NSCopying - (id)copyWithZone:(NSZone *)zone { AFImageResponseSerializer *serializer = [[[self class] allocWithZone:zone] init]; #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) serializer.imageScale = self.imageScale; serializer.automaticallyInflatesResponseImage = self.automaticallyInflatesResponseImage; #endif return serializer; } @end #pragma mark - @interface AFCompoundResponseSerializer () @property (readwrite, nonatomic, copy) NSArray *responseSerializers; @end @implementation AFCompoundResponseSerializer + (instancetype)compoundSerializerWithResponseSerializers:(NSArray *)responseSerializers { AFCompoundResponseSerializer *serializer = [[self alloc] init]; serializer.responseSerializers = responseSerializers; return serializer; } #pragma mark - AFURLResponseSerialization - (id)responseObjectForResponse:(NSURLResponse *)response data:(NSData *)data error:(NSError *__autoreleasing *)error { for (id serializer in self.responseSerializers) { if (![serializer isKindOfClass:[AFHTTPResponseSerializer class]]) { continue; } NSError *serializerError = nil; id responseObject = [serializer responseObjectForResponse:response data:data error:&serializerError]; if (responseObject) { if (error) { *error = AFErrorWithUnderlyingError(serializerError, *error); } return responseObject; } } return [super responseObjectForResponse:response data:data error:error]; } #pragma mark - NSSecureCoding - (id)initWithCoder:(NSCoder *)decoder { self = [super initWithCoder:decoder]; if (!self) { return nil; } self.responseSerializers = [decoder decodeObjectOfClass:[NSArray class] forKey:NSStringFromSelector(@selector(responseSerializers))]; return self; } - (void)encodeWithCoder:(NSCoder *)coder { [super encodeWithCoder:coder]; [coder encodeObject:self.responseSerializers forKey:NSStringFromSelector(@selector(responseSerializers))]; } #pragma mark - NSCopying - (id)copyWithZone:(NSZone *)zone { AFCompoundResponseSerializer *serializer = [[[self class] allocWithZone:zone] init]; serializer.responseSerializers = self.responseSerializers; return serializer; } @end ================================================ FILE: Pods/AFNetworking/AFNetworking/AFURLSessionManager.h ================================================ // AFURLSessionManager.h // // Copyright (c) 2013-2015 AFNetworking (http://afnetworking.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import #import "AFURLResponseSerialization.h" #import "AFURLRequestSerialization.h" #import "AFSecurityPolicy.h" #import "AFNetworkReachabilityManager.h" #ifndef NS_DESIGNATED_INITIALIZER #if __has_attribute(objc_designated_initializer) #define NS_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) #else #define NS_DESIGNATED_INITIALIZER #endif #endif /** `AFURLSessionManager` creates and manages an `NSURLSession` object based on a specified `NSURLSessionConfiguration` object, which conforms to ``, ``, ``, and ``. ## Subclassing Notes This is the base class for `AFHTTPSessionManager`, which adds functionality specific to making HTTP requests. If you are looking to extend `AFURLSessionManager` specifically for HTTP, consider subclassing `AFHTTPSessionManager` instead. ## NSURLSession & NSURLSessionTask Delegate Methods `AFURLSessionManager` implements the following delegate methods: ### `NSURLSessionDelegate` - `URLSession:didBecomeInvalidWithError:` - `URLSession:didReceiveChallenge:completionHandler:` - `URLSessionDidFinishEventsForBackgroundURLSession:` ### `NSURLSessionTaskDelegate` - `URLSession:willPerformHTTPRedirection:newRequest:completionHandler:` - `URLSession:task:didReceiveChallenge:completionHandler:` - `URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:` - `URLSession:task:didCompleteWithError:` ### `NSURLSessionDataDelegate` - `URLSession:dataTask:didReceiveResponse:completionHandler:` - `URLSession:dataTask:didBecomeDownloadTask:` - `URLSession:dataTask:didReceiveData:` - `URLSession:dataTask:willCacheResponse:completionHandler:` ### `NSURLSessionDownloadDelegate` - `URLSession:downloadTask:didFinishDownloadingToURL:` - `URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesWritten:totalBytesExpectedToWrite:` - `URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:` If any of these methods are overridden in a subclass, they _must_ call the `super` implementation first. ## Network Reachability Monitoring Network reachability status and change monitoring is available through the `reachabilityManager` property. Applications may choose to monitor network reachability conditions in order to prevent or suspend any outbound requests. See `AFNetworkReachabilityManager` for more details. ## NSCoding Caveats - Encoded managers do not include any block properties. Be sure to set delegate callback blocks when using `-initWithCoder:` or `NSKeyedUnarchiver`. ## NSCopying Caveats - `-copy` and `-copyWithZone:` return a new manager with a new `NSURLSession` created from the configuration of the original. - Operation copies do not include any delegate callback blocks, as they often strongly captures a reference to `self`, which would otherwise have the unintuitive side-effect of pointing to the _original_ session manager when copied. @warning Managers for background sessions must be owned for the duration of their use. This can be accomplished by creating an application-wide or shared singleton instance. */ #if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000) || (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1090) @interface AFURLSessionManager : NSObject /** The managed session. */ @property (readonly, nonatomic, strong) NSURLSession *session; /** The operation queue on which delegate callbacks are run. */ @property (readonly, nonatomic, strong) NSOperationQueue *operationQueue; /** Responses sent from the server in data tasks created with `dataTaskWithRequest:success:failure:` and run using the `GET` / `POST` / et al. convenience methods are automatically validated and serialized by the response serializer. By default, this property is set to an instance of `AFJSONResponseSerializer`. @warning `responseSerializer` must not be `nil`. */ @property (nonatomic, strong) id responseSerializer; ///------------------------------- /// @name Managing Security Policy ///------------------------------- /** The security policy used by created request operations to evaluate server trust for secure connections. `AFURLSessionManager` uses the `defaultPolicy` unless otherwise specified. */ @property (nonatomic, strong) AFSecurityPolicy *securityPolicy; ///-------------------------------------- /// @name Monitoring Network Reachability ///-------------------------------------- /** The network reachability manager. `AFURLSessionManager` uses the `sharedManager` by default. */ @property (readwrite, nonatomic, strong) AFNetworkReachabilityManager *reachabilityManager; ///---------------------------- /// @name Getting Session Tasks ///---------------------------- /** The data, upload, and download tasks currently run by the managed session. */ @property (readonly, nonatomic, strong) NSArray *tasks; /** The data tasks currently run by the managed session. */ @property (readonly, nonatomic, strong) NSArray *dataTasks; /** The upload tasks currently run by the managed session. */ @property (readonly, nonatomic, strong) NSArray *uploadTasks; /** The download tasks currently run by the managed session. */ @property (readonly, nonatomic, strong) NSArray *downloadTasks; ///------------------------------- /// @name Managing Callback Queues ///------------------------------- /** The dispatch queue for `completionBlock`. If `NULL` (default), the main queue is used. */ #if OS_OBJECT_HAVE_OBJC_SUPPORT @property (nonatomic, strong) dispatch_queue_t completionQueue; #else @property (nonatomic, assign) dispatch_queue_t completionQueue; #endif /** The dispatch group for `completionBlock`. If `NULL` (default), a private dispatch group is used. */ #if OS_OBJECT_HAVE_OBJC_SUPPORT @property (nonatomic, strong) dispatch_group_t completionGroup; #else @property (nonatomic, assign) dispatch_group_t completionGroup; #endif ///--------------------------------- /// @name Working Around System Bugs ///--------------------------------- /** Whether to attempt to retry creation of upload tasks for background sessions when initial call returns `nil`. `NO` by default. @bug As of iOS 7.0, there is a bug where upload tasks created for background tasks are sometimes `nil`. As a workaround, if this property is `YES`, AFNetworking will follow Apple's recommendation to try creating the task again. @see https://github.com/AFNetworking/AFNetworking/issues/1675 */ @property (nonatomic, assign) BOOL attemptsToRecreateUploadTasksForBackgroundSessions; ///--------------------- /// @name Initialization ///--------------------- /** Creates and returns a manager for a session created with the specified configuration. This is the designated initializer. @param configuration The configuration used to create the managed session. @return A manager for a newly-created session. */ - (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration NS_DESIGNATED_INITIALIZER; /** Invalidates the managed session, optionally canceling pending tasks. @param cancelPendingTasks Whether or not to cancel pending tasks. */ - (void)invalidateSessionCancelingTasks:(BOOL)cancelPendingTasks; ///------------------------- /// @name Running Data Tasks ///------------------------- /** Creates an `NSURLSessionDataTask` with the specified request. @param request The HTTP request for the request. @param completionHandler A block object to be executed when the task finishes. This block has no return value and takes three arguments: the server response, the response object created by that serializer, and the error that occurred, if any. */ - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler; ///--------------------------- /// @name Running Upload Tasks ///--------------------------- /** Creates an `NSURLSessionUploadTask` with the specified request for a local file. @param request The HTTP request for the request. @param fileURL A URL to the local file to be uploaded. @param progress A progress object monitoring the current upload progress. @param completionHandler A block object to be executed when the task finishes. This block has no return value and takes three arguments: the server response, the response object created by that serializer, and the error that occurred, if any. @see `attemptsToRecreateUploadTasksForBackgroundSessions` */ - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL progress:(NSProgress * __autoreleasing *)progress completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler; /** Creates an `NSURLSessionUploadTask` with the specified request for an HTTP body. @param request The HTTP request for the request. @param bodyData A data object containing the HTTP body to be uploaded. @param progress A progress object monitoring the current upload progress. @param completionHandler A block object to be executed when the task finishes. This block has no return value and takes three arguments: the server response, the response object created by that serializer, and the error that occurred, if any. */ - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData progress:(NSProgress * __autoreleasing *)progress completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler; /** Creates an `NSURLSessionUploadTask` with the specified streaming request. @param request The HTTP request for the request. @param progress A progress object monitoring the current upload progress. @param completionHandler A block object to be executed when the task finishes. This block has no return value and takes three arguments: the server response, the response object created by that serializer, and the error that occurred, if any. */ - (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request progress:(NSProgress * __autoreleasing *)progress completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler; ///----------------------------- /// @name Running Download Tasks ///----------------------------- /** Creates an `NSURLSessionDownloadTask` with the specified request. @param request The HTTP request for the request. @param progress A progress object monitoring the current download progress. @param destination A block object to be executed in order to determine the destination of the downloaded file. This block takes two arguments, the target path & the server response, and returns the desired file URL of the resulting download. The temporary file used during the download will be automatically deleted after being moved to the returned URL. @param completionHandler A block to be executed when a task finishes. This block has no return value and takes three arguments: the server response, the path of the downloaded file, and the error describing the network or parsing error that occurred, if any. @warning If using a background `NSURLSessionConfiguration` on iOS, these blocks will be lost when the app is terminated. Background sessions may prefer to use `-setDownloadTaskDidFinishDownloadingBlock:` to specify the URL for saving the downloaded file, rather than the destination block of this method. */ - (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request progress:(NSProgress * __autoreleasing *)progress destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler; /** Creates an `NSURLSessionDownloadTask` with the specified resume data. @param resumeData The data used to resume downloading. @param progress A progress object monitoring the current download progress. @param destination A block object to be executed in order to determine the destination of the downloaded file. This block takes two arguments, the target path & the server response, and returns the desired file URL of the resulting download. The temporary file used during the download will be automatically deleted after being moved to the returned URL. @param completionHandler A block to be executed when a task finishes. This block has no return value and takes three arguments: the server response, the path of the downloaded file, and the error describing the network or parsing error that occurred, if any. */ - (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData progress:(NSProgress * __autoreleasing *)progress destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler; ///--------------------------------- /// @name Getting Progress for Tasks ///--------------------------------- /** Returns the upload progress of the specified task. @param uploadTask The session upload task. Must not be `nil`. @return An `NSProgress` object reporting the upload progress of a task, or `nil` if the progress is unavailable. */ - (NSProgress *)uploadProgressForTask:(NSURLSessionUploadTask *)uploadTask; /** Returns the download progress of the specified task. @param downloadTask The session download task. Must not be `nil`. @return An `NSProgress` object reporting the download progress of a task, or `nil` if the progress is unavailable. */ - (NSProgress *)downloadProgressForTask:(NSURLSessionDownloadTask *)downloadTask; ///----------------------------------------- /// @name Setting Session Delegate Callbacks ///----------------------------------------- /** Sets a block to be executed when the managed session becomes invalid, as handled by the `NSURLSessionDelegate` method `URLSession:didBecomeInvalidWithError:`. @param block A block object to be executed when the managed session becomes invalid. The block has no return value, and takes two arguments: the session, and the error related to the cause of invalidation. */ - (void)setSessionDidBecomeInvalidBlock:(void (^)(NSURLSession *session, NSError *error))block; /** Sets a block to be executed when a connection level authentication challenge has occurred, as handled by the `NSURLSessionDelegate` method `URLSession:didReceiveChallenge:completionHandler:`. @param block A block object to be executed when a connection level authentication challenge has occurred. The block returns the disposition of the authentication challenge, and takes three arguments: the session, the authentication challenge, and a pointer to the credential that should be used to resolve the challenge. */ - (void)setSessionDidReceiveAuthenticationChallengeBlock:(NSURLSessionAuthChallengeDisposition (^)(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential * __autoreleasing *credential))block; ///-------------------------------------- /// @name Setting Task Delegate Callbacks ///-------------------------------------- /** Sets a block to be executed when a task requires a new request body stream to send to the remote server, as handled by the `NSURLSessionTaskDelegate` method `URLSession:task:needNewBodyStream:`. @param block A block object to be executed when a task requires a new request body stream. */ - (void)setTaskNeedNewBodyStreamBlock:(NSInputStream * (^)(NSURLSession *session, NSURLSessionTask *task))block; /** Sets a block to be executed when an HTTP request is attempting to perform a redirection to a different URL, as handled by the `NSURLSessionTaskDelegate` method `URLSession:willPerformHTTPRedirection:newRequest:completionHandler:`. @param block A block object to be executed when an HTTP request is attempting to perform a redirection to a different URL. The block returns the request to be made for the redirection, and takes four arguments: the session, the task, the redirection response, and the request corresponding to the redirection response. */ - (void)setTaskWillPerformHTTPRedirectionBlock:(NSURLRequest * (^)(NSURLSession *session, NSURLSessionTask *task, NSURLResponse *response, NSURLRequest *request))block; /** Sets a block to be executed when a session task has received a request specific authentication challenge, as handled by the `NSURLSessionTaskDelegate` method `URLSession:task:didReceiveChallenge:completionHandler:`. @param block A block object to be executed when a session task has received a request specific authentication challenge. The block returns the disposition of the authentication challenge, and takes four arguments: the session, the task, the authentication challenge, and a pointer to the credential that should be used to resolve the challenge. */ - (void)setTaskDidReceiveAuthenticationChallengeBlock:(NSURLSessionAuthChallengeDisposition (^)(NSURLSession *session, NSURLSessionTask *task, NSURLAuthenticationChallenge *challenge, NSURLCredential * __autoreleasing *credential))block; /** Sets a block to be executed periodically to track upload progress, as handled by the `NSURLSessionTaskDelegate` method `URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:`. @param block A block object to be called when an undetermined number of bytes have been uploaded to the server. This block has no return value and takes five arguments: the session, the task, the number of bytes written since the last time the upload progress block was called, the total bytes written, and the total bytes expected to be written during the request, as initially determined by the length of the HTTP body. This block may be called multiple times, and will execute on the main thread. */ - (void)setTaskDidSendBodyDataBlock:(void (^)(NSURLSession *session, NSURLSessionTask *task, int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend))block; /** Sets a block to be executed as the last message related to a specific task, as handled by the `NSURLSessionTaskDelegate` method `URLSession:task:didCompleteWithError:`. @param block A block object to be executed when a session task is completed. The block has no return value, and takes three arguments: the session, the task, and any error that occurred in the process of executing the task. */ - (void)setTaskDidCompleteBlock:(void (^)(NSURLSession *session, NSURLSessionTask *task, NSError *error))block; ///------------------------------------------- /// @name Setting Data Task Delegate Callbacks ///------------------------------------------- /** Sets a block to be executed when a data task has received a response, as handled by the `NSURLSessionDataDelegate` method `URLSession:dataTask:didReceiveResponse:completionHandler:`. @param block A block object to be executed when a data task has received a response. The block returns the disposition of the session response, and takes three arguments: the session, the data task, and the received response. */ - (void)setDataTaskDidReceiveResponseBlock:(NSURLSessionResponseDisposition (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLResponse *response))block; /** Sets a block to be executed when a data task has become a download task, as handled by the `NSURLSessionDataDelegate` method `URLSession:dataTask:didBecomeDownloadTask:`. @param block A block object to be executed when a data task has become a download task. The block has no return value, and takes three arguments: the session, the data task, and the download task it has become. */ - (void)setDataTaskDidBecomeDownloadTaskBlock:(void (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLSessionDownloadTask *downloadTask))block; /** Sets a block to be executed when a data task receives data, as handled by the `NSURLSessionDataDelegate` method `URLSession:dataTask:didReceiveData:`. @param block A block object to be called when an undetermined number of bytes have been downloaded from the server. This block has no return value and takes three arguments: the session, the data task, and the data received. This block may be called multiple times, and will execute on the session manager operation queue. */ - (void)setDataTaskDidReceiveDataBlock:(void (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSData *data))block; /** Sets a block to be executed to determine the caching behavior of a data task, as handled by the `NSURLSessionDataDelegate` method `URLSession:dataTask:willCacheResponse:completionHandler:`. @param block A block object to be executed to determine the caching behavior of a data task. The block returns the response to cache, and takes three arguments: the session, the data task, and the proposed cached URL response. */ - (void)setDataTaskWillCacheResponseBlock:(NSCachedURLResponse * (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSCachedURLResponse *proposedResponse))block; /** Sets a block to be executed once all messages enqueued for a session have been delivered, as handled by the `NSURLSessionDataDelegate` method `URLSessionDidFinishEventsForBackgroundURLSession:`. @param block A block object to be executed once all messages enqueued for a session have been delivered. The block has no return value and takes a single argument: the session. */ - (void)setDidFinishEventsForBackgroundURLSessionBlock:(void (^)(NSURLSession *session))block; ///----------------------------------------------- /// @name Setting Download Task Delegate Callbacks ///----------------------------------------------- /** Sets a block to be executed when a download task has completed a download, as handled by the `NSURLSessionDownloadDelegate` method `URLSession:downloadTask:didFinishDownloadingToURL:`. @param block A block object to be executed when a download task has completed. The block returns the URL the download should be moved to, and takes three arguments: the session, the download task, and the temporary location of the downloaded file. If the file manager encounters an error while attempting to move the temporary file to the destination, an `AFURLSessionDownloadTaskDidFailToMoveFileNotification` will be posted, with the download task as its object, and the user info of the error. */ - (void)setDownloadTaskDidFinishDownloadingBlock:(NSURL * (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location))block; /** Sets a block to be executed periodically to track download progress, as handled by the `NSURLSessionDownloadDelegate` method `URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesWritten:totalBytesExpectedToWrite:`. @param block A block object to be called when an undetermined number of bytes have been downloaded from the server. This block has no return value and takes five arguments: the session, the download task, the number of bytes read since the last time the download progress block was called, the total bytes read, and the total bytes expected to be read during the request, as initially determined by the expected content size of the `NSHTTPURLResponse` object. This block may be called multiple times, and will execute on the session manager operation queue. */ - (void)setDownloadTaskDidWriteDataBlock:(void (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite))block; /** Sets a block to be executed when a download task has been resumed, as handled by the `NSURLSessionDownloadDelegate` method `URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:`. @param block A block object to be executed when a download task has been resumed. The block has no return value and takes four arguments: the session, the download task, the file offset of the resumed download, and the total number of bytes expected to be downloaded. */ - (void)setDownloadTaskDidResumeBlock:(void (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t fileOffset, int64_t expectedTotalBytes))block; @end #endif ///-------------------- /// @name Notifications ///-------------------- /** Posted when a task begins executing. @deprecated Use `AFNetworkingTaskDidResumeNotification` instead. */ extern NSString * const AFNetworkingTaskDidStartNotification DEPRECATED_ATTRIBUTE; /** Posted when a task resumes. */ extern NSString * const AFNetworkingTaskDidResumeNotification; /** Posted when a task finishes executing. Includes a userInfo dictionary with additional information about the task. @deprecated Use `AFNetworkingTaskDidCompleteNotification` instead. */ extern NSString * const AFNetworkingTaskDidFinishNotification DEPRECATED_ATTRIBUTE; /** Posted when a task finishes executing. Includes a userInfo dictionary with additional information about the task. */ extern NSString * const AFNetworkingTaskDidCompleteNotification; /** Posted when a task suspends its execution. */ extern NSString * const AFNetworkingTaskDidSuspendNotification; /** Posted when a session is invalidated. */ extern NSString * const AFURLSessionDidInvalidateNotification; /** Posted when a session download task encountered an error when moving the temporary download file to a specified destination. */ extern NSString * const AFURLSessionDownloadTaskDidFailToMoveFileNotification; /** The raw response data of the task. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if response data exists for the task. @deprecated Use `AFNetworkingTaskDidCompleteResponseDataKey` instead. */ extern NSString * const AFNetworkingTaskDidFinishResponseDataKey DEPRECATED_ATTRIBUTE; /** The raw response data of the task. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if response data exists for the task. */ extern NSString * const AFNetworkingTaskDidCompleteResponseDataKey; /** The serialized response object of the task. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if the response was serialized. @deprecated Use `AFNetworkingTaskDidCompleteSerializedResponseKey` instead. */ extern NSString * const AFNetworkingTaskDidFinishSerializedResponseKey DEPRECATED_ATTRIBUTE; /** The serialized response object of the task. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if the response was serialized. */ extern NSString * const AFNetworkingTaskDidCompleteSerializedResponseKey; /** The response serializer used to serialize the response. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if the task has an associated response serializer. @deprecated Use `AFNetworkingTaskDidCompleteResponseSerializerKey` instead. */ extern NSString * const AFNetworkingTaskDidFinishResponseSerializerKey DEPRECATED_ATTRIBUTE; /** The response serializer used to serialize the response. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if the task has an associated response serializer. */ extern NSString * const AFNetworkingTaskDidCompleteResponseSerializerKey; /** The file path associated with the download task. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if an the response data has been stored directly to disk. @deprecated Use `AFNetworkingTaskDidCompleteAssetPathKey` instead. */ extern NSString * const AFNetworkingTaskDidFinishAssetPathKey DEPRECATED_ATTRIBUTE; /** The file path associated with the download task. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if an the response data has been stored directly to disk. */ extern NSString * const AFNetworkingTaskDidCompleteAssetPathKey; /** Any error associated with the task, or the serialization of the response. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if an error exists. @deprecated Use `AFNetworkingTaskDidCompleteErrorKey` instead. */ extern NSString * const AFNetworkingTaskDidFinishErrorKey DEPRECATED_ATTRIBUTE; /** Any error associated with the task, or the serialization of the response. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if an error exists. */ extern NSString * const AFNetworkingTaskDidCompleteErrorKey; ================================================ FILE: Pods/AFNetworking/AFNetworking/AFURLSessionManager.m ================================================ // AFURLSessionManager.m // // Copyright (c) 2013-2015 AFNetworking (http://afnetworking.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import "AFURLSessionManager.h" #import #if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000) || (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1090) static dispatch_queue_t url_session_manager_creation_queue() { static dispatch_queue_t af_url_session_manager_creation_queue; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ af_url_session_manager_creation_queue = dispatch_queue_create("com.alamofire.networking.session.manager.creation", DISPATCH_QUEUE_SERIAL); }); return af_url_session_manager_creation_queue; } static dispatch_queue_t url_session_manager_processing_queue() { static dispatch_queue_t af_url_session_manager_processing_queue; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ af_url_session_manager_processing_queue = dispatch_queue_create("com.alamofire.networking.session.manager.processing", DISPATCH_QUEUE_CONCURRENT); }); return af_url_session_manager_processing_queue; } static dispatch_group_t url_session_manager_completion_group() { static dispatch_group_t af_url_session_manager_completion_group; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ af_url_session_manager_completion_group = dispatch_group_create(); }); return af_url_session_manager_completion_group; } NSString * const AFNetworkingTaskDidResumeNotification = @"com.alamofire.networking.task.resume"; NSString * const AFNetworkingTaskDidCompleteNotification = @"com.alamofire.networking.task.complete"; NSString * const AFNetworkingTaskDidSuspendNotification = @"com.alamofire.networking.task.suspend"; NSString * const AFURLSessionDidInvalidateNotification = @"com.alamofire.networking.session.invalidate"; NSString * const AFURLSessionDownloadTaskDidFailToMoveFileNotification = @"com.alamofire.networking.session.download.file-manager-error"; NSString * const AFNetworkingTaskDidStartNotification = @"com.alamofire.networking.task.resume"; // Deprecated NSString * const AFNetworkingTaskDidFinishNotification = @"com.alamofire.networking.task.complete"; // Deprecated NSString * const AFNetworkingTaskDidCompleteSerializedResponseKey = @"com.alamofire.networking.task.complete.serializedresponse"; NSString * const AFNetworkingTaskDidCompleteResponseSerializerKey = @"com.alamofire.networking.task.complete.responseserializer"; NSString * const AFNetworkingTaskDidCompleteResponseDataKey = @"com.alamofire.networking.complete.finish.responsedata"; NSString * const AFNetworkingTaskDidCompleteErrorKey = @"com.alamofire.networking.task.complete.error"; NSString * const AFNetworkingTaskDidCompleteAssetPathKey = @"com.alamofire.networking.task.complete.assetpath"; NSString * const AFNetworkingTaskDidFinishSerializedResponseKey = @"com.alamofire.networking.task.complete.serializedresponse"; // Deprecated NSString * const AFNetworkingTaskDidFinishResponseSerializerKey = @"com.alamofire.networking.task.complete.responseserializer"; // Deprecated NSString * const AFNetworkingTaskDidFinishResponseDataKey = @"com.alamofire.networking.complete.finish.responsedata"; // Deprecated NSString * const AFNetworkingTaskDidFinishErrorKey = @"com.alamofire.networking.task.complete.error"; // Deprecated NSString * const AFNetworkingTaskDidFinishAssetPathKey = @"com.alamofire.networking.task.complete.assetpath"; // Deprecated static NSString * const AFURLSessionManagerLockName = @"com.alamofire.networking.session.manager.lock"; static NSUInteger const AFMaximumNumberOfAttemptsToRecreateBackgroundSessionUploadTask = 3; static void * AFTaskStateChangedContext = &AFTaskStateChangedContext; typedef void (^AFURLSessionDidBecomeInvalidBlock)(NSURLSession *session, NSError *error); typedef NSURLSessionAuthChallengeDisposition (^AFURLSessionDidReceiveAuthenticationChallengeBlock)(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential * __autoreleasing *credential); typedef NSURLRequest * (^AFURLSessionTaskWillPerformHTTPRedirectionBlock)(NSURLSession *session, NSURLSessionTask *task, NSURLResponse *response, NSURLRequest *request); typedef NSURLSessionAuthChallengeDisposition (^AFURLSessionTaskDidReceiveAuthenticationChallengeBlock)(NSURLSession *session, NSURLSessionTask *task, NSURLAuthenticationChallenge *challenge, NSURLCredential * __autoreleasing *credential); typedef void (^AFURLSessionDidFinishEventsForBackgroundURLSessionBlock)(NSURLSession *session); typedef NSInputStream * (^AFURLSessionTaskNeedNewBodyStreamBlock)(NSURLSession *session, NSURLSessionTask *task); typedef void (^AFURLSessionTaskDidSendBodyDataBlock)(NSURLSession *session, NSURLSessionTask *task, int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend); typedef void (^AFURLSessionTaskDidCompleteBlock)(NSURLSession *session, NSURLSessionTask *task, NSError *error); typedef NSURLSessionResponseDisposition (^AFURLSessionDataTaskDidReceiveResponseBlock)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLResponse *response); typedef void (^AFURLSessionDataTaskDidBecomeDownloadTaskBlock)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLSessionDownloadTask *downloadTask); typedef void (^AFURLSessionDataTaskDidReceiveDataBlock)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSData *data); typedef NSCachedURLResponse * (^AFURLSessionDataTaskWillCacheResponseBlock)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSCachedURLResponse *proposedResponse); typedef NSURL * (^AFURLSessionDownloadTaskDidFinishDownloadingBlock)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location); typedef void (^AFURLSessionDownloadTaskDidWriteDataBlock)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite); typedef void (^AFURLSessionDownloadTaskDidResumeBlock)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t fileOffset, int64_t expectedTotalBytes); typedef void (^AFURLSessionTaskCompletionHandler)(NSURLResponse *response, id responseObject, NSError *error); #pragma mark - @interface AFURLSessionManagerTaskDelegate : NSObject @property (nonatomic, weak) AFURLSessionManager *manager; @property (nonatomic, strong) NSMutableData *mutableData; @property (nonatomic, strong) NSProgress *progress; @property (nonatomic, copy) NSURL *downloadFileURL; @property (nonatomic, copy) AFURLSessionDownloadTaskDidFinishDownloadingBlock downloadTaskDidFinishDownloading; @property (nonatomic, copy) AFURLSessionTaskCompletionHandler completionHandler; @end @implementation AFURLSessionManagerTaskDelegate - (instancetype)init { self = [super init]; if (!self) { return nil; } self.mutableData = [NSMutableData data]; self.progress = [NSProgress progressWithTotalUnitCount:0]; return self; } #pragma mark - NSURLSessionTaskDelegate - (void)URLSession:(__unused NSURLSession *)session task:(__unused NSURLSessionTask *)task didSendBodyData:(__unused int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend { self.progress.totalUnitCount = totalBytesExpectedToSend; self.progress.completedUnitCount = totalBytesSent; } - (void)URLSession:(__unused NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" __strong AFURLSessionManager *manager = self.manager; __block id responseObject = nil; __block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer; if (self.downloadFileURL) { userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL; } else if (self.mutableData) { userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = [NSData dataWithData:self.mutableData]; } if (error) { userInfo[AFNetworkingTaskDidCompleteErrorKey] = error; dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{ if (self.completionHandler) { self.completionHandler(task.response, responseObject, error); } dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo]; }); }); } else { dispatch_async(url_session_manager_processing_queue(), ^{ NSError *serializationError = nil; responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:[NSData dataWithData:self.mutableData] error:&serializationError]; if (self.downloadFileURL) { responseObject = self.downloadFileURL; } if (responseObject) { userInfo[AFNetworkingTaskDidCompleteSerializedResponseKey] = responseObject; } if (serializationError) { userInfo[AFNetworkingTaskDidCompleteErrorKey] = serializationError; } dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{ if (self.completionHandler) { self.completionHandler(task.response, responseObject, serializationError); } dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo]; }); }); }); } #pragma clang diagnostic pop } #pragma mark - NSURLSessionDataTaskDelegate - (void)URLSession:(__unused NSURLSession *)session dataTask:(__unused NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { [self.mutableData appendData:data]; } #pragma mark - NSURLSessionDownloadTaskDelegate - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location { NSError *fileManagerError = nil; self.downloadFileURL = nil; if (self.downloadTaskDidFinishDownloading) { self.downloadFileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location); if (self.downloadFileURL) { [[NSFileManager defaultManager] moveItemAtURL:location toURL:self.downloadFileURL error:&fileManagerError]; if (fileManagerError) { [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo]; } } } } - (void)URLSession:(__unused NSURLSession *)session downloadTask:(__unused NSURLSessionDownloadTask *)downloadTask didWriteData:(__unused int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite { self.progress.totalUnitCount = totalBytesExpectedToWrite; self.progress.completedUnitCount = totalBytesWritten; } - (void)URLSession:(__unused NSURLSession *)session downloadTask:(__unused NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes { self.progress.totalUnitCount = expectedTotalBytes; self.progress.completedUnitCount = fileOffset; } @end #pragma mark - /* A workaround for issues related to key-value observing the `state` of an `NSURLSessionTask`. See https://github.com/AFNetworking/AFNetworking/issues/1477 */ static inline void af_swizzleSelector(Class class, SEL originalSelector, SEL swizzledSelector) { Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); if (class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) { class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } } static inline void af_addMethod(Class class, SEL selector, Method method) { class_addMethod(class, selector, method_getImplementation(method), method_getTypeEncoding(method)); } static NSString * const AFNSURLSessionTaskDidResumeNotification = @"com.alamofire.networking.nsurlsessiontask.resume"; static NSString * const AFNSURLSessionTaskDidSuspendNotification = @"com.alamofire.networking.nsurlsessiontask.suspend"; @interface NSURLSessionTask (_AFStateObserving) @end @implementation NSURLSessionTask (_AFStateObserving) + (void)initialize { if ([NSURLSessionTask class]) { NSURLSessionDataTask *dataTask = [[NSURLSession sessionWithConfiguration:nil] dataTaskWithURL:nil]; Class taskClass = [dataTask superclass]; af_addMethod(taskClass, @selector(af_resume), class_getInstanceMethod(self, @selector(af_resume))); af_addMethod(taskClass, @selector(af_suspend), class_getInstanceMethod(self, @selector(af_suspend))); af_swizzleSelector(taskClass, @selector(resume), @selector(af_resume)); af_swizzleSelector(taskClass, @selector(suspend), @selector(af_suspend)); [dataTask cancel]; } } #pragma mark - - (void)af_resume { NSURLSessionTaskState state = self.state; [self af_resume]; if (state != NSURLSessionTaskStateRunning) { [[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidResumeNotification object:self]; } } - (void)af_suspend { NSURLSessionTaskState state = self.state; [self af_suspend]; if (state != NSURLSessionTaskStateSuspended) { [[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidSuspendNotification object:self]; } } @end #pragma mark - @interface AFURLSessionManager () @property (readwrite, nonatomic, strong) NSURLSessionConfiguration *sessionConfiguration; @property (readwrite, nonatomic, strong) NSOperationQueue *operationQueue; @property (readwrite, nonatomic, strong) NSURLSession *session; @property (readwrite, nonatomic, strong) NSMutableDictionary *mutableTaskDelegatesKeyedByTaskIdentifier; @property (readonly, nonatomic, copy) NSString *taskDescriptionForSessionTasks; @property (readwrite, nonatomic, strong) NSLock *lock; @property (readwrite, nonatomic, copy) AFURLSessionDidBecomeInvalidBlock sessionDidBecomeInvalid; @property (readwrite, nonatomic, copy) AFURLSessionDidReceiveAuthenticationChallengeBlock sessionDidReceiveAuthenticationChallenge; @property (readwrite, nonatomic, copy) AFURLSessionDidFinishEventsForBackgroundURLSessionBlock didFinishEventsForBackgroundURLSession; @property (readwrite, nonatomic, copy) AFURLSessionTaskWillPerformHTTPRedirectionBlock taskWillPerformHTTPRedirection; @property (readwrite, nonatomic, copy) AFURLSessionTaskDidReceiveAuthenticationChallengeBlock taskDidReceiveAuthenticationChallenge; @property (readwrite, nonatomic, copy) AFURLSessionTaskNeedNewBodyStreamBlock taskNeedNewBodyStream; @property (readwrite, nonatomic, copy) AFURLSessionTaskDidSendBodyDataBlock taskDidSendBodyData; @property (readwrite, nonatomic, copy) AFURLSessionTaskDidCompleteBlock taskDidComplete; @property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidReceiveResponseBlock dataTaskDidReceiveResponse; @property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidBecomeDownloadTaskBlock dataTaskDidBecomeDownloadTask; @property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidReceiveDataBlock dataTaskDidReceiveData; @property (readwrite, nonatomic, copy) AFURLSessionDataTaskWillCacheResponseBlock dataTaskWillCacheResponse; @property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidFinishDownloadingBlock downloadTaskDidFinishDownloading; @property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidWriteDataBlock downloadTaskDidWriteData; @property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidResumeBlock downloadTaskDidResume; @end @implementation AFURLSessionManager - (instancetype)init { return [self initWithSessionConfiguration:nil]; } - (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration { self = [super init]; if (!self) { return nil; } if (!configuration) { configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; } self.sessionConfiguration = configuration; self.operationQueue = [[NSOperationQueue alloc] init]; self.operationQueue.maxConcurrentOperationCount = 1; self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue]; self.responseSerializer = [AFJSONResponseSerializer serializer]; self.securityPolicy = [AFSecurityPolicy defaultPolicy]; self.reachabilityManager = [AFNetworkReachabilityManager sharedManager]; self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init]; self.lock = [[NSLock alloc] init]; self.lock.name = AFURLSessionManagerLockName; [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) { for (NSURLSessionDataTask *task in dataTasks) { [self addDelegateForDataTask:task completionHandler:nil]; } for (NSURLSessionUploadTask *uploadTask in uploadTasks) { [self addDelegateForUploadTask:uploadTask progress:nil completionHandler:nil]; } for (NSURLSessionDownloadTask *downloadTask in downloadTasks) { [self addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil]; } }]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidResume:) name:AFNSURLSessionTaskDidResumeNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidSuspend:) name:AFNSURLSessionTaskDidSuspendNotification object:nil]; return self; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } #pragma mark - - (NSString *)taskDescriptionForSessionTasks { return [NSString stringWithFormat:@"%p", self]; } - (void)taskDidResume:(NSNotification *)notification { NSURLSessionTask *task = notification.object; if ([task respondsToSelector:@selector(taskDescription)]) { if ([task.taskDescription isEqualToString:self.taskDescriptionForSessionTasks]) { dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidResumeNotification object:task]; }); } } } - (void)taskDidSuspend:(NSNotification *)notification { NSURLSessionTask *task = notification.object; if ([task respondsToSelector:@selector(taskDescription)]) { if ([task.taskDescription isEqualToString:self.taskDescriptionForSessionTasks]) { dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidSuspendNotification object:task]; }); } } } #pragma mark - - (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task { NSParameterAssert(task); AFURLSessionManagerTaskDelegate *delegate = nil; [self.lock lock]; delegate = self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)]; [self.lock unlock]; return delegate; } - (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate forTask:(NSURLSessionTask *)task { NSParameterAssert(task); NSParameterAssert(delegate); [self.lock lock]; self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate; [self.lock unlock]; } - (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler { AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init]; delegate.manager = self; delegate.completionHandler = completionHandler; dataTask.taskDescription = self.taskDescriptionForSessionTasks; [self setDelegate:delegate forTask:dataTask]; } - (void)addDelegateForUploadTask:(NSURLSessionUploadTask *)uploadTask progress:(NSProgress * __autoreleasing *)progress completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler { AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init]; delegate.manager = self; delegate.completionHandler = completionHandler; int64_t totalUnitCount = uploadTask.countOfBytesExpectedToSend; if(totalUnitCount == NSURLSessionTransferSizeUnknown) { NSString *contentLength = [uploadTask.originalRequest valueForHTTPHeaderField:@"Content-Length"]; if(contentLength) { totalUnitCount = (int64_t)[contentLength longLongValue]; } } if (delegate.progress) { delegate.progress.totalUnitCount = totalUnitCount; } else { delegate.progress = [NSProgress progressWithTotalUnitCount:totalUnitCount]; } delegate.progress.pausingHandler = ^{ [uploadTask suspend]; }; delegate.progress.cancellationHandler = ^{ [uploadTask cancel]; }; if (progress) { *progress = delegate.progress; } uploadTask.taskDescription = self.taskDescriptionForSessionTasks; [self setDelegate:delegate forTask:uploadTask]; } - (void)addDelegateForDownloadTask:(NSURLSessionDownloadTask *)downloadTask progress:(NSProgress * __autoreleasing *)progress destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler { AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init]; delegate.manager = self; delegate.completionHandler = completionHandler; if (destination) { delegate.downloadTaskDidFinishDownloading = ^NSURL * (NSURLSession * __unused session, NSURLSessionDownloadTask *task, NSURL *location) { return destination(location, task.response); }; } if (progress) { *progress = delegate.progress; } downloadTask.taskDescription = self.taskDescriptionForSessionTasks; [self setDelegate:delegate forTask:downloadTask]; } - (void)removeDelegateForTask:(NSURLSessionTask *)task { NSParameterAssert(task); [self.lock lock]; [self.mutableTaskDelegatesKeyedByTaskIdentifier removeObjectForKey:@(task.taskIdentifier)]; [self.lock unlock]; } - (void)removeAllDelegates { [self.lock lock]; [self.mutableTaskDelegatesKeyedByTaskIdentifier removeAllObjects]; [self.lock unlock]; } #pragma mark - - (NSArray *)tasksForKeyPath:(NSString *)keyPath { __block NSArray *tasks = nil; dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) { if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) { tasks = dataTasks; } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) { tasks = uploadTasks; } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) { tasks = downloadTasks; } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) { tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"]; } dispatch_semaphore_signal(semaphore); }]; dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); return tasks; } - (NSArray *)tasks { return [self tasksForKeyPath:NSStringFromSelector(_cmd)]; } - (NSArray *)dataTasks { return [self tasksForKeyPath:NSStringFromSelector(_cmd)]; } - (NSArray *)uploadTasks { return [self tasksForKeyPath:NSStringFromSelector(_cmd)]; } - (NSArray *)downloadTasks { return [self tasksForKeyPath:NSStringFromSelector(_cmd)]; } #pragma mark - - (void)invalidateSessionCancelingTasks:(BOOL)cancelPendingTasks { dispatch_async(dispatch_get_main_queue(), ^{ if (cancelPendingTasks) { [self.session invalidateAndCancel]; } else { [self.session finishTasksAndInvalidate]; } }); } #pragma mark - - (void)setResponseSerializer:(id )responseSerializer { NSParameterAssert(responseSerializer); _responseSerializer = responseSerializer; } #pragma mark - - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler { __block NSURLSessionDataTask *dataTask = nil; dispatch_sync(url_session_manager_creation_queue(), ^{ dataTask = [self.session dataTaskWithRequest:request]; }); [self addDelegateForDataTask:dataTask completionHandler:completionHandler]; return dataTask; } #pragma mark - - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL progress:(NSProgress * __autoreleasing *)progress completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler { __block NSURLSessionUploadTask *uploadTask = nil; dispatch_sync(url_session_manager_creation_queue(), ^{ uploadTask = [self.session uploadTaskWithRequest:request fromFile:fileURL]; }); if (!uploadTask && self.attemptsToRecreateUploadTasksForBackgroundSessions && self.session.configuration.identifier) { for (NSUInteger attempts = 0; !uploadTask && attempts < AFMaximumNumberOfAttemptsToRecreateBackgroundSessionUploadTask; attempts++) { uploadTask = [self.session uploadTaskWithRequest:request fromFile:fileURL]; } } [self addDelegateForUploadTask:uploadTask progress:progress completionHandler:completionHandler]; return uploadTask; } - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData progress:(NSProgress * __autoreleasing *)progress completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler { __block NSURLSessionUploadTask *uploadTask = nil; dispatch_sync(url_session_manager_creation_queue(), ^{ uploadTask = [self.session uploadTaskWithRequest:request fromData:bodyData]; }); [self addDelegateForUploadTask:uploadTask progress:progress completionHandler:completionHandler]; return uploadTask; } - (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request progress:(NSProgress * __autoreleasing *)progress completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler { __block NSURLSessionUploadTask *uploadTask = nil; dispatch_sync(url_session_manager_creation_queue(), ^{ uploadTask = [self.session uploadTaskWithStreamedRequest:request]; }); [self addDelegateForUploadTask:uploadTask progress:progress completionHandler:completionHandler]; return uploadTask; } #pragma mark - - (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request progress:(NSProgress * __autoreleasing *)progress destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler { __block NSURLSessionDownloadTask *downloadTask = nil; dispatch_sync(url_session_manager_creation_queue(), ^{ downloadTask = [self.session downloadTaskWithRequest:request]; }); [self addDelegateForDownloadTask:downloadTask progress:progress destination:destination completionHandler:completionHandler]; return downloadTask; } - (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData progress:(NSProgress * __autoreleasing *)progress destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler { __block NSURLSessionDownloadTask *downloadTask = nil; dispatch_sync(url_session_manager_creation_queue(), ^{ downloadTask = [self.session downloadTaskWithResumeData:resumeData]; }); [self addDelegateForDownloadTask:downloadTask progress:progress destination:destination completionHandler:completionHandler]; return downloadTask; } #pragma mark - - (NSProgress *)uploadProgressForTask:(NSURLSessionUploadTask *)uploadTask { return [[self delegateForTask:uploadTask] progress]; } - (NSProgress *)downloadProgressForTask:(NSURLSessionDownloadTask *)downloadTask { return [[self delegateForTask:downloadTask] progress]; } #pragma mark - - (void)setSessionDidBecomeInvalidBlock:(void (^)(NSURLSession *session, NSError *error))block { self.sessionDidBecomeInvalid = block; } - (void)setSessionDidReceiveAuthenticationChallengeBlock:(NSURLSessionAuthChallengeDisposition (^)(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential * __autoreleasing *credential))block { self.sessionDidReceiveAuthenticationChallenge = block; } - (void)setDidFinishEventsForBackgroundURLSessionBlock:(void (^)(NSURLSession *session))block { self.didFinishEventsForBackgroundURLSession = block; } #pragma mark - - (void)setTaskNeedNewBodyStreamBlock:(NSInputStream * (^)(NSURLSession *session, NSURLSessionTask *task))block { self.taskNeedNewBodyStream = block; } - (void)setTaskWillPerformHTTPRedirectionBlock:(NSURLRequest * (^)(NSURLSession *session, NSURLSessionTask *task, NSURLResponse *response, NSURLRequest *request))block { self.taskWillPerformHTTPRedirection = block; } - (void)setTaskDidReceiveAuthenticationChallengeBlock:(NSURLSessionAuthChallengeDisposition (^)(NSURLSession *session, NSURLSessionTask *task, NSURLAuthenticationChallenge *challenge, NSURLCredential * __autoreleasing *credential))block { self.taskDidReceiveAuthenticationChallenge = block; } - (void)setTaskDidSendBodyDataBlock:(void (^)(NSURLSession *session, NSURLSessionTask *task, int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend))block { self.taskDidSendBodyData = block; } - (void)setTaskDidCompleteBlock:(void (^)(NSURLSession *session, NSURLSessionTask *task, NSError *error))block { self.taskDidComplete = block; } #pragma mark - - (void)setDataTaskDidReceiveResponseBlock:(NSURLSessionResponseDisposition (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLResponse *response))block { self.dataTaskDidReceiveResponse = block; } - (void)setDataTaskDidBecomeDownloadTaskBlock:(void (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLSessionDownloadTask *downloadTask))block { self.dataTaskDidBecomeDownloadTask = block; } - (void)setDataTaskDidReceiveDataBlock:(void (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSData *data))block { self.dataTaskDidReceiveData = block; } - (void)setDataTaskWillCacheResponseBlock:(NSCachedURLResponse * (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSCachedURLResponse *proposedResponse))block { self.dataTaskWillCacheResponse = block; } #pragma mark - - (void)setDownloadTaskDidFinishDownloadingBlock:(NSURL * (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location))block { self.downloadTaskDidFinishDownloading = block; } - (void)setDownloadTaskDidWriteDataBlock:(void (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite))block { self.downloadTaskDidWriteData = block; } - (void)setDownloadTaskDidResumeBlock:(void (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t fileOffset, int64_t expectedTotalBytes))block { self.downloadTaskDidResume = block; } #pragma mark - NSObject - (NSString *)description { return [NSString stringWithFormat:@"<%@: %p, session: %@, operationQueue: %@>", NSStringFromClass([self class]), self, self.session, self.operationQueue]; } - (BOOL)respondsToSelector:(SEL)selector { if (selector == @selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)) { return self.taskWillPerformHTTPRedirection != nil; } else if (selector == @selector(URLSession:dataTask:didReceiveResponse:completionHandler:)) { return self.dataTaskDidReceiveResponse != nil; } else if (selector == @selector(URLSession:dataTask:willCacheResponse:completionHandler:)) { return self.dataTaskWillCacheResponse != nil; } else if (selector == @selector(URLSessionDidFinishEventsForBackgroundURLSession:)) { return self.didFinishEventsForBackgroundURLSession != nil; } return [[self class] instancesRespondToSelector:selector]; } #pragma mark - NSURLSessionDelegate - (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error { if (self.sessionDidBecomeInvalid) { self.sessionDidBecomeInvalid(session, error); } [self removeAllDelegates]; [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDidInvalidateNotification object:session]; } - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling; __block NSURLCredential *credential = nil; if (self.sessionDidReceiveAuthenticationChallenge) { disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential); } else { if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) { credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; if (credential) { disposition = NSURLSessionAuthChallengeUseCredential; } else { disposition = NSURLSessionAuthChallengePerformDefaultHandling; } } else { disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge; } } else { disposition = NSURLSessionAuthChallengePerformDefaultHandling; } } if (completionHandler) { completionHandler(disposition, credential); } } #pragma mark - NSURLSessionTaskDelegate - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest *))completionHandler { NSURLRequest *redirectRequest = request; if (self.taskWillPerformHTTPRedirection) { redirectRequest = self.taskWillPerformHTTPRedirection(session, task, response, request); } if (completionHandler) { completionHandler(redirectRequest); } } - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling; __block NSURLCredential *credential = nil; if (self.taskDidReceiveAuthenticationChallenge) { disposition = self.taskDidReceiveAuthenticationChallenge(session, task, challenge, &credential); } else { if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) { disposition = NSURLSessionAuthChallengeUseCredential; credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; } else { disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge; } } else { disposition = NSURLSessionAuthChallengePerformDefaultHandling; } } if (completionHandler) { completionHandler(disposition, credential); } } - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler { NSInputStream *inputStream = nil; if (self.taskNeedNewBodyStream) { inputStream = self.taskNeedNewBodyStream(session, task); } else if (task.originalRequest.HTTPBodyStream && [task.originalRequest.HTTPBodyStream conformsToProtocol:@protocol(NSCopying)]) { inputStream = [task.originalRequest.HTTPBodyStream copy]; } if (completionHandler) { completionHandler(inputStream); } } - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend { int64_t totalUnitCount = totalBytesExpectedToSend; if(totalUnitCount == NSURLSessionTransferSizeUnknown) { NSString *contentLength = [task.originalRequest valueForHTTPHeaderField:@"Content-Length"]; if(contentLength) { totalUnitCount = (int64_t) [contentLength longLongValue]; } } AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task]; [delegate URLSession:session task:task didSendBodyData:bytesSent totalBytesSent:totalBytesSent totalBytesExpectedToSend:totalUnitCount]; if (self.taskDidSendBodyData) { self.taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalUnitCount); } } - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task]; // delegate may be nil when completing a task in the background if (delegate) { [delegate URLSession:session task:task didCompleteWithError:error]; [self removeDelegateForTask:task]; } if (self.taskDidComplete) { self.taskDidComplete(session, task, error); } } #pragma mark - NSURLSessionDataDelegate - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler { NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow; if (self.dataTaskDidReceiveResponse) { disposition = self.dataTaskDidReceiveResponse(session, dataTask, response); } if (completionHandler) { completionHandler(disposition); } } - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask { AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask]; if (delegate) { [self removeDelegateForTask:dataTask]; [self setDelegate:delegate forTask:downloadTask]; } if (self.dataTaskDidBecomeDownloadTask) { self.dataTaskDidBecomeDownloadTask(session, dataTask, downloadTask); } } - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask]; [delegate URLSession:session dataTask:dataTask didReceiveData:data]; if (self.dataTaskDidReceiveData) { self.dataTaskDidReceiveData(session, dataTask, data); } } - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler { NSCachedURLResponse *cachedResponse = proposedResponse; if (self.dataTaskWillCacheResponse) { cachedResponse = self.dataTaskWillCacheResponse(session, dataTask, proposedResponse); } if (completionHandler) { completionHandler(cachedResponse); } } - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session { if (self.didFinishEventsForBackgroundURLSession) { dispatch_async(dispatch_get_main_queue(), ^{ self.didFinishEventsForBackgroundURLSession(session); }); } } #pragma mark - NSURLSessionDownloadDelegate - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location { AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask]; if (self.downloadTaskDidFinishDownloading) { NSURL *fileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location); if (fileURL) { delegate.downloadFileURL = fileURL; NSError *error = nil; [[NSFileManager defaultManager] moveItemAtURL:location toURL:fileURL error:&error]; if (error) { [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:error.userInfo]; } return; } } if (delegate) { [delegate URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location]; } } - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite { AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask]; [delegate URLSession:session downloadTask:downloadTask didWriteData:bytesWritten totalBytesWritten:totalBytesWritten totalBytesExpectedToWrite:totalBytesExpectedToWrite]; if (self.downloadTaskDidWriteData) { self.downloadTaskDidWriteData(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite); } } - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes { AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask]; [delegate URLSession:session downloadTask:downloadTask didResumeAtOffset:fileOffset expectedTotalBytes:expectedTotalBytes]; if (self.downloadTaskDidResume) { self.downloadTaskDidResume(session, downloadTask, fileOffset, expectedTotalBytes); } } #pragma mark - NSSecureCoding + (BOOL)supportsSecureCoding { return YES; } - (id)initWithCoder:(NSCoder *)decoder { NSURLSessionConfiguration *configuration = [decoder decodeObjectOfClass:[NSURLSessionConfiguration class] forKey:@"sessionConfiguration"]; self = [self initWithSessionConfiguration:configuration]; if (!self) { return nil; } return self; } - (void)encodeWithCoder:(NSCoder *)coder { [coder encodeObject:self.session.configuration forKey:@"sessionConfiguration"]; } #pragma mark - NSCopying - (id)copyWithZone:(NSZone *)zone { return [[[self class] allocWithZone:zone] initWithSessionConfiguration:self.session.configuration]; } @end #endif ================================================ FILE: Pods/AFNetworking/LICENSE ================================================ Copyright (c) 2013-2015 AFNetworking (http://afnetworking.com/) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Pods/AFNetworking/README.md ================================================

AFNetworking

[![Build Status](https://travis-ci.org/AFNetworking/AFNetworking.svg)](https://travis-ci.org/AFNetworking/AFNetworking) AFNetworking is a delightful networking library for iOS and Mac OS X. It's built on top of the [Foundation URL Loading System](http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/URLLoadingSystem/URLLoadingSystem.html), extending the powerful high-level networking abstractions built into Cocoa. It has a modular architecture with well-designed, feature-rich APIs that are a joy to use. Perhaps the most important feature of all, however, is the amazing community of developers who use and contribute to AFNetworking every day. AFNetworking powers some of the most popular and critically-acclaimed apps on the iPhone, iPad, and Mac. Choose AFNetworking for your next project, or migrate over your existing projects—you'll be happy you did! ## How To Get Started - [Download AFNetworking](https://github.com/AFNetworking/AFNetworking/archive/master.zip) and try out the included Mac and iPhone example apps - Read the ["Getting Started" guide](https://github.com/AFNetworking/AFNetworking/wiki/Getting-Started-with-AFNetworking), [FAQ](https://github.com/AFNetworking/AFNetworking/wiki/AFNetworking-FAQ), or [other articles on the Wiki](https://github.com/AFNetworking/AFNetworking/wiki) - Check out the [documentation](http://cocoadocs.org/docsets/AFNetworking/) for a comprehensive look at all of the APIs available in AFNetworking - Read the [AFNetworking 2.0 Migration Guide](https://github.com/AFNetworking/AFNetworking/wiki/AFNetworking-2.0-Migration-Guide) for an overview of the architectural changes from 1.0. ## Communication - If you **need help**, use [Stack Overflow](http://stackoverflow.com/questions/tagged/afnetworking). (Tag 'afnetworking') - If you'd like to **ask a general question**, use [Stack Overflow](http://stackoverflow.com/questions/tagged/afnetworking). - If you **found a bug**, _and can provide steps to reliably reproduce it_, open an issue. - If you **have a feature request**, open an issue. - If you **want to contribute**, submit a pull request. ### Installation with CocoaPods [CocoaPods](http://cocoapods.org) is a dependency manager for Objective-C, which automates and simplifies the process of using 3rd-party libraries like AFNetworking in your projects. See the ["Getting Started" guide for more information](https://github.com/AFNetworking/AFNetworking/wiki/Getting-Started-with-AFNetworking). #### Podfile ```ruby platform :ios, '7.0' pod "AFNetworking", "~> 2.0" ``` ## Requirements | AFNetworking Version | Minimum iOS Target | Minimum OS X Target | Notes | |:--------------------:|:---------------------------:|:----------------------------:|:-------------------------------------------------------------------------:| | 2.x | iOS 6 | OS X 10.8 | Xcode 5 is required. `NSURLSession` subspec requires iOS 7 or OS X 10.9. | | [1.x](https://github.com/AFNetworking/AFNetworking/tree/1.x) | iOS 5 | Mac OS X 10.7 | | | [0.10.x](https://github.com/AFNetworking/AFNetworking/tree/0.10.x) | iOS 4 | Mac OS X 10.6 | | (OS X projects must support [64-bit with modern Cocoa runtime](https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtVersionsPlatforms.html)). > Programming in Swift? Try [Alamofire](https://github.com/Alamofire/Alamofire) for a more conventional set of APIs. ## Architecture ### NSURLConnection - `AFURLConnectionOperation` - `AFHTTPRequestOperation` - `AFHTTPRequestOperationManager` ### NSURLSession _(iOS 7 / Mac OS X 10.9)_ - `AFURLSessionManager` - `AFHTTPSessionManager` ### Serialization * `` - `AFHTTPRequestSerializer` - `AFJSONRequestSerializer` - `AFPropertyListRequestSerializer` * `` - `AFHTTPResponseSerializer` - `AFJSONResponseSerializer` - `AFXMLParserResponseSerializer` - `AFXMLDocumentResponseSerializer` _(Mac OS X)_ - `AFPropertyListResponseSerializer` - `AFImageResponseSerializer` - `AFCompoundResponseSerializer` ### Additional Functionality - `AFSecurityPolicy` - `AFNetworkReachabilityManager` ## Usage ### HTTP Request Operation Manager `AFHTTPRequestOperationManager` encapsulates the common patterns of communicating with a web application over HTTP, including request creation, response serialization, network reachability monitoring, and security, as well as request operation management. #### `GET` Request ```objective-c AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager]; [manager GET:@"http://example.com/resources.json" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) { NSLog(@"JSON: %@", responseObject); } failure:^(AFHTTPRequestOperation *operation, NSError *error) { NSLog(@"Error: %@", error); }]; ``` #### `POST` URL-Form-Encoded Request ```objective-c AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager]; NSDictionary *parameters = @{@"foo": @"bar"}; [manager POST:@"http://example.com/resources.json" parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) { NSLog(@"JSON: %@", responseObject); } failure:^(AFHTTPRequestOperation *operation, NSError *error) { NSLog(@"Error: %@", error); }]; ``` #### `POST` Multi-Part Request ```objective-c AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager]; NSDictionary *parameters = @{@"foo": @"bar"}; NSURL *filePath = [NSURL fileURLWithPath:@"file://path/to/image.png"]; [manager POST:@"http://example.com/resources.json" parameters:parameters constructingBodyWithBlock:^(id formData) { [formData appendPartWithFileURL:filePath name:@"image" error:nil]; } success:^(AFHTTPRequestOperation *operation, id responseObject) { NSLog(@"Success: %@", responseObject); } failure:^(AFHTTPRequestOperation *operation, NSError *error) { NSLog(@"Error: %@", error); }]; ``` --- ### AFURLSessionManager `AFURLSessionManager` creates and manages an `NSURLSession` object based on a specified `NSURLSessionConfiguration` object, which conforms to ``, ``, ``, and ``. #### Creating a Download Task ```objective-c NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration]; NSURL *URL = [NSURL URLWithString:@"http://example.com/download.zip"]; NSURLRequest *request = [NSURLRequest requestWithURL:URL]; NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) { NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil]; return [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]]; } completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) { NSLog(@"File downloaded to: %@", filePath); }]; [downloadTask resume]; ``` #### Creating an Upload Task ```objective-c NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration]; NSURL *URL = [NSURL URLWithString:@"http://example.com/upload"]; NSURLRequest *request = [NSURLRequest requestWithURL:URL]; NSURL *filePath = [NSURL fileURLWithPath:@"file://path/to/image.png"]; NSURLSessionUploadTask *uploadTask = [manager uploadTaskWithRequest:request fromFile:filePath progress:nil completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) { if (error) { NSLog(@"Error: %@", error); } else { NSLog(@"Success: %@ %@", response, responseObject); } }]; [uploadTask resume]; ``` #### Creating an Upload Task for a Multi-Part Request, with Progress ```objective-c NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:@"POST" URLString:@"http://example.com/upload" parameters:nil constructingBodyWithBlock:^(id formData) { [formData appendPartWithFileURL:[NSURL fileURLWithPath:@"file://path/to/image.jpg"] name:@"file" fileName:@"filename.jpg" mimeType:@"image/jpeg" error:nil]; } error:nil]; AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; NSProgress *progress = nil; NSURLSessionUploadTask *uploadTask = [manager uploadTaskWithStreamedRequest:request progress:&progress completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) { if (error) { NSLog(@"Error: %@", error); } else { NSLog(@"%@ %@", response, responseObject); } }]; [uploadTask resume]; ``` #### Creating a Data Task ```objective-c NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration]; NSURL *URL = [NSURL URLWithString:@"http://example.com/upload"]; NSURLRequest *request = [NSURLRequest requestWithURL:URL]; NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) { if (error) { NSLog(@"Error: %@", error); } else { NSLog(@"%@ %@", response, responseObject); } }]; [dataTask resume]; ``` --- ### Request Serialization Request serializers create requests from URL strings, encoding parameters as either a query string or HTTP body. ```objective-c NSString *URLString = @"http://example.com"; NSDictionary *parameters = @{@"foo": @"bar", @"baz": @[@1, @2, @3]}; ``` #### Query String Parameter Encoding ```objective-c [[AFHTTPRequestSerializer serializer] requestWithMethod:@"GET" URLString:URLString parameters:parameters error:nil]; ``` GET http://example.com?foo=bar&baz[]=1&baz[]=2&baz[]=3 #### URL Form Parameter Encoding ```objective-c [[AFHTTPRequestSerializer serializer] requestWithMethod:@"POST" URLString:URLString parameters:parameters]; ``` POST http://example.com/ Content-Type: application/x-www-form-urlencoded foo=bar&baz[]=1&baz[]=2&baz[]=3 #### JSON Parameter Encoding ```objective-c [[AFJSONRequestSerializer serializer] requestWithMethod:@"POST" URLString:URLString parameters:parameters]; ``` POST http://example.com/ Content-Type: application/json {"foo": "bar", "baz": [1,2,3]} --- ### Network Reachability Manager `AFNetworkReachabilityManager` monitors the reachability of domains, and addresses for both WWAN and WiFi network interfaces. **Network reachability is a diagnostic tool that can be used to understand why a request might have failed. It should not be used to determine whether or not to make a request.** #### Shared Network Reachability ```objective-c [[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) { NSLog(@"Reachability: %@", AFStringFromNetworkReachabilityStatus(status)); }]; ``` #### HTTP Manager Reachability ```objective-c NSURL *baseURL = [NSURL URLWithString:@"http://example.com/"]; AFHTTPRequestOperationManager *manager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:baseURL]; NSOperationQueue *operationQueue = manager.operationQueue; [manager.reachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) { switch (status) { case AFNetworkReachabilityStatusReachableViaWWAN: case AFNetworkReachabilityStatusReachableViaWiFi: [operationQueue setSuspended:NO]; break; case AFNetworkReachabilityStatusNotReachable: default: [operationQueue setSuspended:YES]; break; } }]; [manager.reachabilityManager startMonitoring]; ``` --- ### Security Policy `AFSecurityPolicy` evaluates server trust against pinned X.509 certificates and public keys over secure connections. Adding pinned SSL certificates to your app helps prevent man-in-the-middle attacks and other vulnerabilities. Applications dealing with sensitive customer data or financial information are strongly encouraged to route all communication over an HTTPS connection with SSL pinning configured and enabled. #### Allowing Invalid SSL Certificates ```objective-c AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager]; manager.securityPolicy.allowInvalidCertificates = YES; // not recommended for production ``` --- ### AFHTTPRequestOperation `AFHTTPRequestOperation` is a subclass of `AFURLConnectionOperation` for requests using the HTTP or HTTPS protocols. It encapsulates the concept of acceptable status codes and content types, which determine the success or failure of a request. Although `AFHTTPRequestOperationManager` is usually the best way to go about making requests, `AFHTTPRequestOperation` can be used by itself. #### `GET` with `AFHTTPRequestOperation` ```objective-c NSURL *URL = [NSURL URLWithString:@"http://example.com/resources/123.json"]; NSURLRequest *request = [NSURLRequest requestWithURL:URL]; AFHTTPRequestOperation *op = [[AFHTTPRequestOperation alloc] initWithRequest:request]; op.responseSerializer = [AFJSONResponseSerializer serializer]; [op setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { NSLog(@"JSON: %@", responseObject); } failure:^(AFHTTPRequestOperation *operation, NSError *error) { NSLog(@"Error: %@", error); }]; [[NSOperationQueue mainQueue] addOperation:op]; ``` #### Batch of Operations ```objective-c NSMutableArray *mutableOperations = [NSMutableArray array]; for (NSURL *fileURL in filesToUpload) { NSURLRequest *request = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:@"POST" URLString:@"http://example.com/upload" parameters:nil constructingBodyWithBlock:^(id formData) { [formData appendPartWithFileURL:fileURL name:@"images[]" error:nil]; }]; AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request]; [mutableOperations addObject:operation]; } NSArray *operations = [AFURLConnectionOperation batchOfRequestOperations:@[...] progressBlock:^(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations) { NSLog(@"%lu of %lu complete", numberOfFinishedOperations, totalNumberOfOperations); } completionBlock:^(NSArray *operations) { NSLog(@"All operations in batch complete"); }]; [[NSOperationQueue mainQueue] addOperations:operations waitUntilFinished:NO]; ``` ## Unit Tests AFNetworking includes a suite of unit tests within the Tests subdirectory. In order to run the unit tests, you must install the testing dependencies via [CocoaPods](http://cocoapods.org/): $ cd Tests $ pod install Once testing dependencies are installed, you can execute the test suite via the 'iOS Tests' and 'OS X Tests' schemes within Xcode. ### Running Tests from the Command Line Tests can also be run from the command line or within a continuous integration environment. The [`xcpretty`](https://github.com/mneorr/xcpretty) utility needs to be installed before running the tests from the command line: $ gem install xcpretty Once `xcpretty` is installed, you can execute the suite via `rake test`. ## Credits AFNetworking was originally created by [Scott Raymond](https://github.com/sco/) and [Mattt Thompson](https://github.com/mattt/) in the development of [Gowalla for iPhone](http://en.wikipedia.org/wiki/Gowalla). AFNetworking's logo was designed by [Alan Defibaugh](http://www.alandefibaugh.com/). And most of all, thanks to AFNetworking's [growing list of contributors](https://github.com/AFNetworking/AFNetworking/contributors). ## Contact Follow AFNetworking on Twitter ([@AFNetworking](https://twitter.com/AFNetworking)) ### Maintainers - [Mattt Thompson](http://github.com/mattt) ([@mattt](https://twitter.com/mattt)) ## License AFNetworking is available under the MIT license. See the LICENSE file for more info. ================================================ FILE: Pods/AFNetworking/UIKit+AFNetworking/AFNetworkActivityIndicatorManager.h ================================================ // AFNetworkActivityIndicatorManager.h // // Copyright (c) 2013-2015 AFNetworking (http://afnetworking.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import #import #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) #import /** `AFNetworkActivityIndicatorManager` manages the state of the network activity indicator in the status bar. When enabled, it will listen for notifications indicating that a network request operation has started or finished, and start or stop animating the indicator accordingly. The number of active requests is incremented and decremented much like a stack or a semaphore, and the activity indicator will animate so long as that number is greater than zero. You should enable the shared instance of `AFNetworkActivityIndicatorManager` when your application finishes launching. In `AppDelegate application:didFinishLaunchingWithOptions:` you can do so with the following code: [[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES]; By setting `enabled` to `YES` for `sharedManager`, the network activity indicator will show and hide automatically as requests start and finish. You should not ever need to call `incrementActivityCount` or `decrementActivityCount` yourself. See the Apple Human Interface Guidelines section about the Network Activity Indicator for more information: http://developer.apple.com/library/iOS/#documentation/UserExperience/Conceptual/MobileHIG/UIElementGuidelines/UIElementGuidelines.html#//apple_ref/doc/uid/TP40006556-CH13-SW44 */ @interface AFNetworkActivityIndicatorManager : NSObject /** A Boolean value indicating whether the manager is enabled. If YES, the manager will change status bar network activity indicator according to network operation notifications it receives. The default value is NO. */ @property (nonatomic, assign, getter = isEnabled) BOOL enabled; /** A Boolean value indicating whether the network activity indicator is currently displayed in the status bar. */ @property (readonly, nonatomic, assign) BOOL isNetworkActivityIndicatorVisible; /** Returns the shared network activity indicator manager object for the system. @return The systemwide network activity indicator manager. */ + (instancetype)sharedManager; /** Increments the number of active network requests. If this number was zero before incrementing, this will start animating the status bar network activity indicator. */ - (void)incrementActivityCount; /** Decrements the number of active network requests. If this number becomes zero after decrementing, this will stop animating the status bar network activity indicator. */ - (void)decrementActivityCount; @end #endif ================================================ FILE: Pods/AFNetworking/UIKit+AFNetworking/AFNetworkActivityIndicatorManager.m ================================================ // AFNetworkActivityIndicatorManager.m // // Copyright (c) 2013-2015 AFNetworking (http://afnetworking.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import "AFNetworkActivityIndicatorManager.h" #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) #import "AFHTTPRequestOperation.h" #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 #import "AFURLSessionManager.h" #endif static NSTimeInterval const kAFNetworkActivityIndicatorInvisibilityDelay = 0.17; static NSURLRequest * AFNetworkRequestFromNotification(NSNotification *notification) { if ([[notification object] isKindOfClass:[AFURLConnectionOperation class]]) { return [(AFURLConnectionOperation *)[notification object] request]; } #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 if ([[notification object] respondsToSelector:@selector(originalRequest)]) { return [(NSURLSessionTask *)[notification object] originalRequest]; } #endif return nil; } @interface AFNetworkActivityIndicatorManager () @property (readwrite, nonatomic, assign) NSInteger activityCount; @property (readwrite, nonatomic, strong) NSTimer *activityIndicatorVisibilityTimer; @property (readonly, nonatomic, getter = isNetworkActivityIndicatorVisible) BOOL networkActivityIndicatorVisible; - (void)updateNetworkActivityIndicatorVisibility; - (void)updateNetworkActivityIndicatorVisibilityDelayed; @end @implementation AFNetworkActivityIndicatorManager @dynamic networkActivityIndicatorVisible; + (instancetype)sharedManager { static AFNetworkActivityIndicatorManager *_sharedManager = nil; static dispatch_once_t oncePredicate; dispatch_once(&oncePredicate, ^{ _sharedManager = [[self alloc] init]; }); return _sharedManager; } + (NSSet *)keyPathsForValuesAffectingIsNetworkActivityIndicatorVisible { return [NSSet setWithObject:@"activityCount"]; } - (id)init { self = [super init]; if (!self) { return nil; } [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidStart:) name:AFNetworkingOperationDidStartNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidFinish:) name:AFNetworkingOperationDidFinishNotification object:nil]; #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidStart:) name:AFNetworkingTaskDidResumeNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidFinish:) name:AFNetworkingTaskDidSuspendNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidFinish:) name:AFNetworkingTaskDidCompleteNotification object:nil]; #endif return self; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; [_activityIndicatorVisibilityTimer invalidate]; } - (void)updateNetworkActivityIndicatorVisibilityDelayed { if (self.enabled) { // Delay hiding of activity indicator for a short interval, to avoid flickering if (![self isNetworkActivityIndicatorVisible]) { [self.activityIndicatorVisibilityTimer invalidate]; self.activityIndicatorVisibilityTimer = [NSTimer timerWithTimeInterval:kAFNetworkActivityIndicatorInvisibilityDelay target:self selector:@selector(updateNetworkActivityIndicatorVisibility) userInfo:nil repeats:NO]; [[NSRunLoop mainRunLoop] addTimer:self.activityIndicatorVisibilityTimer forMode:NSRunLoopCommonModes]; } else { [self performSelectorOnMainThread:@selector(updateNetworkActivityIndicatorVisibility) withObject:nil waitUntilDone:NO modes:@[NSRunLoopCommonModes]]; } } } - (BOOL)isNetworkActivityIndicatorVisible { return self.activityCount > 0; } - (void)updateNetworkActivityIndicatorVisibility { #if !defined(AF_APP_EXTENSIONS) [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:[self isNetworkActivityIndicatorVisible]]; #endif } - (void)setActivityCount:(NSInteger)activityCount { @synchronized(self) { _activityCount = activityCount; } dispatch_async(dispatch_get_main_queue(), ^{ [self updateNetworkActivityIndicatorVisibilityDelayed]; }); } - (void)incrementActivityCount { [self willChangeValueForKey:@"activityCount"]; @synchronized(self) { _activityCount++; } [self didChangeValueForKey:@"activityCount"]; dispatch_async(dispatch_get_main_queue(), ^{ [self updateNetworkActivityIndicatorVisibilityDelayed]; }); } - (void)decrementActivityCount { [self willChangeValueForKey:@"activityCount"]; @synchronized(self) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" _activityCount = MAX(_activityCount - 1, 0); #pragma clang diagnostic pop } [self didChangeValueForKey:@"activityCount"]; dispatch_async(dispatch_get_main_queue(), ^{ [self updateNetworkActivityIndicatorVisibilityDelayed]; }); } - (void)networkRequestDidStart:(NSNotification *)notification { if ([AFNetworkRequestFromNotification(notification) URL]) { [self incrementActivityCount]; } } - (void)networkRequestDidFinish:(NSNotification *)notification { if ([AFNetworkRequestFromNotification(notification) URL]) { [self decrementActivityCount]; } } @end #endif ================================================ FILE: Pods/AFNetworking/UIKit+AFNetworking/UIActivityIndicatorView+AFNetworking.h ================================================ // UIActivityIndicatorView+AFNetworking.h // // Copyright (c) 2013-2015 AFNetworking (http://afnetworking.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import #import #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) #import @class AFURLConnectionOperation; /** This category adds methods to the UIKit framework's `UIActivityIndicatorView` class. The methods in this category provide support for automatically starting and stopping animation depending on the loading state of a request operation or session task. */ @interface UIActivityIndicatorView (AFNetworking) ///---------------------------------- /// @name Animating for Session Tasks ///---------------------------------- /** Binds the animating state to the state of the specified task. @param task The task. If `nil`, automatic updating from any previously specified operation will be disabled. */ #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 - (void)setAnimatingWithStateOfTask:(NSURLSessionTask *)task; #endif ///--------------------------------------- /// @name Animating for Request Operations ///--------------------------------------- /** Binds the animating state to the execution state of the specified operation. @param operation The operation. If `nil`, automatic updating from any previously specified operation will be disabled. */ - (void)setAnimatingWithStateOfOperation:(AFURLConnectionOperation *)operation; @end #endif ================================================ FILE: Pods/AFNetworking/UIKit+AFNetworking/UIActivityIndicatorView+AFNetworking.m ================================================ // UIActivityIndicatorView+AFNetworking.m // // Copyright (c) 2013-2015 AFNetworking (http://afnetworking.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import "UIActivityIndicatorView+AFNetworking.h" #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) #import "AFHTTPRequestOperation.h" #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 #import "AFURLSessionManager.h" #endif @implementation UIActivityIndicatorView (AFNetworking) #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 - (void)setAnimatingWithStateOfTask:(NSURLSessionTask *)task { NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; [notificationCenter removeObserver:self name:AFNetworkingTaskDidResumeNotification object:nil]; [notificationCenter removeObserver:self name:AFNetworkingTaskDidSuspendNotification object:nil]; [notificationCenter removeObserver:self name:AFNetworkingTaskDidCompleteNotification object:nil]; if (task) { if (task.state != NSURLSessionTaskStateCompleted) { if (task.state == NSURLSessionTaskStateRunning) { [self startAnimating]; } else { [self stopAnimating]; } [notificationCenter addObserver:self selector:@selector(af_startAnimating) name:AFNetworkingTaskDidResumeNotification object:task]; [notificationCenter addObserver:self selector:@selector(af_stopAnimating) name:AFNetworkingTaskDidCompleteNotification object:task]; [notificationCenter addObserver:self selector:@selector(af_stopAnimating) name:AFNetworkingTaskDidSuspendNotification object:task]; } } } #endif #pragma mark - - (void)setAnimatingWithStateOfOperation:(AFURLConnectionOperation *)operation { NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; [notificationCenter removeObserver:self name:AFNetworkingOperationDidStartNotification object:nil]; [notificationCenter removeObserver:self name:AFNetworkingOperationDidFinishNotification object:nil]; if (operation) { if (![operation isFinished]) { if ([operation isExecuting]) { [self startAnimating]; } else { [self stopAnimating]; } [notificationCenter addObserver:self selector:@selector(af_startAnimating) name:AFNetworkingOperationDidStartNotification object:operation]; [notificationCenter addObserver:self selector:@selector(af_stopAnimating) name:AFNetworkingOperationDidFinishNotification object:operation]; } } } #pragma mark - - (void)af_startAnimating { dispatch_async(dispatch_get_main_queue(), ^{ [self startAnimating]; }); } - (void)af_stopAnimating { dispatch_async(dispatch_get_main_queue(), ^{ [self stopAnimating]; }); } @end #endif ================================================ FILE: Pods/AFNetworking/UIKit+AFNetworking/UIAlertView+AFNetworking.h ================================================ // UIAlertView+AFNetworking.h // // Copyright (c) 2013-2015 AFNetworking (http://afnetworking.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import #import #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && !defined(AF_APP_EXTENSIONS) #import @class AFURLConnectionOperation; /** This category adds methods to the UIKit framework's `UIAlertView` class. The methods in this category provide support for automatically showing an alert if a session task or request operation finishes with an error. Alert title and message are filled from the corresponding `localizedDescription` & `localizedRecoverySuggestion` or `localizedFailureReason` of the error. */ @interface UIAlertView (AFNetworking) ///------------------------------------- /// @name Showing Alert for Session Task ///------------------------------------- /** Shows an alert view with the error of the specified session task, if any. @param task The session task. @param delegate The alert view delegate. */ #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 + (void)showAlertViewForTaskWithErrorOnCompletion:(NSURLSessionTask *)task delegate:(id)delegate; #endif /** Shows an alert view with the error of the specified session task, if any, with a custom cancel button title and other button titles. @param task The session task. @param delegate The alert view delegate. @param cancelButtonTitle The title of the cancel button or nil if there is no cancel button. Using this argument is equivalent to setting the cancel button index to the value returned by invoking addButtonWithTitle: specifying this title. @param otherButtonTitles The title of another button. Using this argument is equivalent to invoking addButtonWithTitle: with this title to add more buttons. Too many buttons can cause the alert view to scroll. For guidelines on the best ways to use an alert in an app, see "Temporary Views". Titles of additional buttons to add to the receiver, terminated with `nil`. */ #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 + (void)showAlertViewForTaskWithErrorOnCompletion:(NSURLSessionTask *)task delegate:(id)delegate cancelButtonTitle:(NSString *)cancelButtonTitle otherButtonTitles:(NSString *)otherButtonTitles, ... NS_REQUIRES_NIL_TERMINATION; #endif ///------------------------------------------ /// @name Showing Alert for Request Operation ///------------------------------------------ /** Shows an alert view with the error of the specified request operation, if any. @param operation The request operation. @param delegate The alert view delegate. */ + (void)showAlertViewForRequestOperationWithErrorOnCompletion:(AFURLConnectionOperation *)operation delegate:(id)delegate; /** Shows an alert view with the error of the specified request operation, if any, with a custom cancel button title and other button titles. @param operation The request operation. @param delegate The alert view delegate. @param cancelButtonTitle The title of the cancel button or nil if there is no cancel button. Using this argument is equivalent to setting the cancel button index to the value returned by invoking addButtonWithTitle: specifying this title. @param otherButtonTitles The title of another button. Using this argument is equivalent to invoking addButtonWithTitle: with this title to add more buttons. Too many buttons can cause the alert view to scroll. For guidelines on the best ways to use an alert in an app, see "Temporary Views". Titles of additional buttons to add to the receiver, terminated with `nil`. */ + (void)showAlertViewForRequestOperationWithErrorOnCompletion:(AFURLConnectionOperation *)operation delegate:(id)delegate cancelButtonTitle:(NSString *)cancelButtonTitle otherButtonTitles:(NSString *)otherButtonTitles, ... NS_REQUIRES_NIL_TERMINATION; @end #endif ================================================ FILE: Pods/AFNetworking/UIKit+AFNetworking/UIAlertView+AFNetworking.m ================================================ // UIAlertView+AFNetworking.m // // Copyright (c) 2013-2015 AFNetworking (http://afnetworking.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import "UIAlertView+AFNetworking.h" #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && !defined(AF_APP_EXTENSIONS) #import "AFURLConnectionOperation.h" #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 #import "AFURLSessionManager.h" #endif static void AFGetAlertViewTitleAndMessageFromError(NSError *error, NSString * __autoreleasing *title, NSString * __autoreleasing *message) { if (error.localizedDescription && (error.localizedRecoverySuggestion || error.localizedFailureReason)) { *title = error.localizedDescription; if (error.localizedRecoverySuggestion) { *message = error.localizedRecoverySuggestion; } else { *message = error.localizedFailureReason; } } else if (error.localizedDescription) { *title = NSLocalizedStringFromTable(@"Error", @"AFNetworking", @"Fallback Error Description"); *message = error.localizedDescription; } else { *title = NSLocalizedStringFromTable(@"Error", @"AFNetworking", @"Fallback Error Description"); *message = [NSString stringWithFormat:NSLocalizedStringFromTable(@"%@ Error: %ld", @"AFNetworking", @"Fallback Error Failure Reason Format"), error.domain, (long)error.code]; } } @implementation UIAlertView (AFNetworking) #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 + (void)showAlertViewForTaskWithErrorOnCompletion:(NSURLSessionTask *)task delegate:(id)delegate { [self showAlertViewForTaskWithErrorOnCompletion:task delegate:delegate cancelButtonTitle:NSLocalizedStringFromTable(@"Dismiss", @"AFNetworking", @"UIAlertView Cancel Button Title") otherButtonTitles:nil, nil]; } + (void)showAlertViewForTaskWithErrorOnCompletion:(NSURLSessionTask *)task delegate:(id)delegate cancelButtonTitle:(NSString *)cancelButtonTitle otherButtonTitles:(NSString *)otherButtonTitles, ... NS_REQUIRES_NIL_TERMINATION { NSMutableArray *mutableOtherTitles = [NSMutableArray array]; va_list otherButtonTitleList; va_start(otherButtonTitleList, otherButtonTitles); { for (NSString *otherButtonTitle = otherButtonTitles; otherButtonTitle != nil; otherButtonTitle = va_arg(otherButtonTitleList, NSString *)) { [mutableOtherTitles addObject:otherButtonTitle]; } } va_end(otherButtonTitleList); __block __weak id observer = [[NSNotificationCenter defaultCenter] addObserverForName:AFNetworkingTaskDidCompleteNotification object:task queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notification) { NSError *error = notification.userInfo[AFNetworkingTaskDidCompleteErrorKey]; if (error) { NSString *title, *message; AFGetAlertViewTitleAndMessageFromError(error, &title, &message); UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:nil message:nil delegate:delegate cancelButtonTitle:cancelButtonTitle otherButtonTitles:nil, nil]; for (NSString *otherButtonTitle in mutableOtherTitles) { [alertView addButtonWithTitle:otherButtonTitle]; } [alertView setTitle:title]; [alertView setMessage:message]; [alertView show]; } [[NSNotificationCenter defaultCenter] removeObserver:observer]; }]; } #endif #pragma mark - + (void)showAlertViewForRequestOperationWithErrorOnCompletion:(AFURLConnectionOperation *)operation delegate:(id)delegate { [self showAlertViewForRequestOperationWithErrorOnCompletion:operation delegate:delegate cancelButtonTitle:NSLocalizedStringFromTable(@"Dismiss", @"AFNetworking", @"UIAlertView Cancel Button Title") otherButtonTitles:nil, nil]; } + (void)showAlertViewForRequestOperationWithErrorOnCompletion:(AFURLConnectionOperation *)operation delegate:(id)delegate cancelButtonTitle:(NSString *)cancelButtonTitle otherButtonTitles:(NSString *)otherButtonTitles, ... NS_REQUIRES_NIL_TERMINATION { NSMutableArray *mutableOtherTitles = [NSMutableArray array]; va_list otherButtonTitleList; va_start(otherButtonTitleList, otherButtonTitles); { for (NSString *otherButtonTitle = otherButtonTitles; otherButtonTitle != nil; otherButtonTitle = va_arg(otherButtonTitleList, NSString *)) { [mutableOtherTitles addObject:otherButtonTitle]; } } va_end(otherButtonTitleList); __block __weak id observer = [[NSNotificationCenter defaultCenter] addObserverForName:AFNetworkingOperationDidFinishNotification object:operation queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notification) { if (notification.object && [notification.object isKindOfClass:[AFURLConnectionOperation class]]) { NSError *error = [(AFURLConnectionOperation *)notification.object error]; if (error) { NSString *title, *message; AFGetAlertViewTitleAndMessageFromError(error, &title, &message); UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:nil message:nil delegate:delegate cancelButtonTitle:cancelButtonTitle otherButtonTitles:nil, nil]; for (NSString *otherButtonTitle in mutableOtherTitles) { [alertView addButtonWithTitle:otherButtonTitle]; } [alertView setTitle:title]; [alertView setMessage:message]; [alertView show]; } } [[NSNotificationCenter defaultCenter] removeObserver:observer]; }]; } @end #endif ================================================ FILE: Pods/AFNetworking/UIKit+AFNetworking/UIButton+AFNetworking.h ================================================ // UIButton+AFNetworking.h // // Copyright (c) 2013-2015 AFNetworking (http://afnetworking.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import #import #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) #import @protocol AFURLResponseSerialization, AFImageCache; /** This category adds methods to the UIKit framework's `UIButton` class. The methods in this category provide support for loading remote images and background images asynchronously from a URL. @warning Compound values for control `state` (such as `UIControlStateHighlighted | UIControlStateDisabled`) are unsupported. */ @interface UIButton (AFNetworking) ///---------------------------- /// @name Accessing Image Cache ///---------------------------- /** The image cache used to improve image loadiing performance on scroll views. By default, `UIButton` will use the `sharedImageCache` of `UIImageView`. */ + (id )sharedImageCache; /** Set the cache used for image loading. @param imageCache The image cache. */ + (void)setSharedImageCache:(id )imageCache; ///------------------------------------ /// @name Accessing Response Serializer ///------------------------------------ /** The response serializer used to create an image representation from the server response and response data. By default, this is an instance of `AFImageResponseSerializer`. @discussion Subclasses of `AFImageResponseSerializer` could be used to perform post-processing, such as color correction, face detection, or other effects. See https://github.com/AFNetworking/AFCoreImageSerializer */ @property (nonatomic, strong) id imageResponseSerializer; ///-------------------- /// @name Setting Image ///-------------------- /** Asynchronously downloads an image from the specified URL, and sets it as the image for the specified state once the request is finished. Any previous image request for the receiver will be cancelled. If the image is cached locally, the image is set immediately, otherwise the specified placeholder image will be set immediately, and then the remote image will be set once the request is finished. @param state The control state. @param url The URL used for the image request. */ - (void)setImageForState:(UIControlState)state withURL:(NSURL *)url; /** Asynchronously downloads an image from the specified URL, and sets it as the image for the specified state once the request is finished. Any previous image request for the receiver will be cancelled. If the image is cached locally, the image is set immediately, otherwise the specified placeholder image will be set immediately, and then the remote image will be set once the request is finished. @param state The control state. @param url The URL used for the image request. @param placeholderImage The image to be set initially, until the image request finishes. If `nil`, the button will not change its image until the image request finishes. */ - (void)setImageForState:(UIControlState)state withURL:(NSURL *)url placeholderImage:(UIImage *)placeholderImage; /** Asynchronously downloads an image from the specified URL request, and sets it as the image for the specified state once the request is finished. Any previous image request for the receiver will be cancelled. If the image is cached locally, the image is set immediately, otherwise the specified placeholder image will be set immediately, and then the remote image will be set once the request is finished. If a success block is specified, it is the responsibility of the block to set the image of the button before returning. If no success block is specified, the default behavior of setting the image with `setImage:forState:` is applied. @param state The control state. @param urlRequest The URL request used for the image request. @param placeholderImage The image to be set initially, until the image request finishes. If `nil`, the button will not change its image until the image request finishes. @param success A block to be executed when the image request operation finishes successfully. This block has no return value and takes two arguments: the server response and the image. If the image was returned from cache, the request and response parameters will be `nil`. @param failure A block object to be executed when the image request operation finishes unsuccessfully, or that finishes successfully. This block has no return value and takes a single argument: the error that occurred. */ - (void)setImageForState:(UIControlState)state withURLRequest:(NSURLRequest *)urlRequest placeholderImage:(UIImage *)placeholderImage success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image))success failure:(void (^)(NSError *error))failure; ///------------------------------- /// @name Setting Background Image ///------------------------------- /** Asynchronously downloads an image from the specified URL, and sets it as the background image for the specified state once the request is finished. Any previous background image request for the receiver will be cancelled. If the background image is cached locally, the background image is set immediately, otherwise the specified placeholder background image will be set immediately, and then the remote background image will be set once the request is finished. @param state The control state. @param url The URL used for the background image request. */ - (void)setBackgroundImageForState:(UIControlState)state withURL:(NSURL *)url; /** Asynchronously downloads an image from the specified URL, and sets it as the background image for the specified state once the request is finished. Any previous image request for the receiver will be cancelled. If the image is cached locally, the image is set immediately, otherwise the specified placeholder image will be set immediately, and then the remote image will be set once the request is finished. @param state The control state. @param url The URL used for the background image request. @param placeholderImage The background image to be set initially, until the background image request finishes. If `nil`, the button will not change its background image until the background image request finishes. */ - (void)setBackgroundImageForState:(UIControlState)state withURL:(NSURL *)url placeholderImage:(UIImage *)placeholderImage; /** Asynchronously downloads an image from the specified URL request, and sets it as the image for the specified state once the request is finished. Any previous image request for the receiver will be cancelled. If the image is cached locally, the image is set immediately, otherwise the specified placeholder image will be set immediately, and then the remote image will be set once the request is finished. If a success block is specified, it is the responsibility of the block to set the image of the button before returning. If no success block is specified, the default behavior of setting the image with `setBackgroundImage:forState:` is applied. @param state The control state. @param urlRequest The URL request used for the image request. @param placeholderImage The background image to be set initially, until the background image request finishes. If `nil`, the button will not change its background image until the background image request finishes. */ - (void)setBackgroundImageForState:(UIControlState)state withURLRequest:(NSURLRequest *)urlRequest placeholderImage:(UIImage *)placeholderImage success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image))success failure:(void (^)(NSError *error))failure; ///------------------------------ /// @name Canceling Image Loading ///------------------------------ /** Cancels any executing image operation for the specified control state of the receiver, if one exists. @param state The control state. */ - (void)cancelImageRequestOperationForState:(UIControlState)state; /** Cancels any executing background image operation for the specified control state of the receiver, if one exists. @param state The control state. */ - (void)cancelBackgroundImageRequestOperationForState:(UIControlState)state; @end #endif ================================================ FILE: Pods/AFNetworking/UIKit+AFNetworking/UIButton+AFNetworking.m ================================================ // UIButton+AFNetworking.m // // Copyright (c) 2013-2015 AFNetworking (http://afnetworking.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import "UIButton+AFNetworking.h" #import #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) #import "AFURLResponseSerialization.h" #import "AFHTTPRequestOperation.h" #import "UIImageView+AFNetworking.h" @interface UIButton (_AFNetworking) @end @implementation UIButton (_AFNetworking) + (NSOperationQueue *)af_sharedImageRequestOperationQueue { static NSOperationQueue *_af_sharedImageRequestOperationQueue = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _af_sharedImageRequestOperationQueue = [[NSOperationQueue alloc] init]; _af_sharedImageRequestOperationQueue.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount; }); return _af_sharedImageRequestOperationQueue; } #pragma mark - static char AFImageRequestOperationNormal; static char AFImageRequestOperationHighlighted; static char AFImageRequestOperationSelected; static char AFImageRequestOperationDisabled; static const char * af_imageRequestOperationKeyForState(UIControlState state) { switch (state) { case UIControlStateHighlighted: return &AFImageRequestOperationHighlighted; case UIControlStateSelected: return &AFImageRequestOperationSelected; case UIControlStateDisabled: return &AFImageRequestOperationDisabled; case UIControlStateNormal: default: return &AFImageRequestOperationNormal; } } - (AFHTTPRequestOperation *)af_imageRequestOperationForState:(UIControlState)state { return (AFHTTPRequestOperation *)objc_getAssociatedObject(self, af_imageRequestOperationKeyForState(state)); } - (void)af_setImageRequestOperation:(AFHTTPRequestOperation *)imageRequestOperation forState:(UIControlState)state { objc_setAssociatedObject(self, af_imageRequestOperationKeyForState(state), imageRequestOperation, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } #pragma mark - static char AFBackgroundImageRequestOperationNormal; static char AFBackgroundImageRequestOperationHighlighted; static char AFBackgroundImageRequestOperationSelected; static char AFBackgroundImageRequestOperationDisabled; static const char * af_backgroundImageRequestOperationKeyForState(UIControlState state) { switch (state) { case UIControlStateHighlighted: return &AFBackgroundImageRequestOperationHighlighted; case UIControlStateSelected: return &AFBackgroundImageRequestOperationSelected; case UIControlStateDisabled: return &AFBackgroundImageRequestOperationDisabled; case UIControlStateNormal: default: return &AFBackgroundImageRequestOperationNormal; } } - (AFHTTPRequestOperation *)af_backgroundImageRequestOperationForState:(UIControlState)state { return (AFHTTPRequestOperation *)objc_getAssociatedObject(self, af_backgroundImageRequestOperationKeyForState(state)); } - (void)af_setBackgroundImageRequestOperation:(AFHTTPRequestOperation *)imageRequestOperation forState:(UIControlState)state { objc_setAssociatedObject(self, af_backgroundImageRequestOperationKeyForState(state), imageRequestOperation, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } @end #pragma mark - @implementation UIButton (AFNetworking) + (id )sharedImageCache { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" return objc_getAssociatedObject(self, @selector(sharedImageCache)) ?: [UIImageView sharedImageCache]; #pragma clang diagnostic pop } + (void)setSharedImageCache:(id )imageCache { objc_setAssociatedObject(self, @selector(sharedImageCache), imageCache, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } #pragma mark - - (id )imageResponseSerializer { static id _af_defaultImageResponseSerializer = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _af_defaultImageResponseSerializer = [AFImageResponseSerializer serializer]; }); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" return objc_getAssociatedObject(self, @selector(imageResponseSerializer)) ?: _af_defaultImageResponseSerializer; #pragma clang diagnostic pop } - (void)setImageResponseSerializer:(id )serializer { objc_setAssociatedObject(self, @selector(imageResponseSerializer), serializer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } #pragma mark - - (void)setImageForState:(UIControlState)state withURL:(NSURL *)url { [self setImageForState:state withURL:url placeholderImage:nil]; } - (void)setImageForState:(UIControlState)state withURL:(NSURL *)url placeholderImage:(UIImage *)placeholderImage { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; [request addValue:@"image/*" forHTTPHeaderField:@"Accept"]; [self setImageForState:state withURLRequest:request placeholderImage:placeholderImage success:nil failure:nil]; } - (void)setImageForState:(UIControlState)state withURLRequest:(NSURLRequest *)urlRequest placeholderImage:(UIImage *)placeholderImage success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image))success failure:(void (^)(NSError *error))failure { [self cancelImageRequestOperationForState:state]; UIImage *cachedImage = [[[self class] sharedImageCache] cachedImageForRequest:urlRequest]; if (cachedImage) { if (success) { success(nil, nil, cachedImage); } else { [self setImage:cachedImage forState:state]; } [self af_setImageRequestOperation:nil forState:state]; } else { if (placeholderImage) { [self setImage:placeholderImage forState:state]; } __weak __typeof(self)weakSelf = self; AFHTTPRequestOperation *imageRequestOperation = [[AFHTTPRequestOperation alloc] initWithRequest:urlRequest]; imageRequestOperation.responseSerializer = self.imageResponseSerializer; [imageRequestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { __strong __typeof(weakSelf)strongSelf = weakSelf; if ([[urlRequest URL] isEqual:[operation.request URL]]) { if (success) { success(operation.request, operation.response, responseObject); } else if (responseObject) { [strongSelf setImage:responseObject forState:state]; } } [[[strongSelf class] sharedImageCache] cacheImage:responseObject forRequest:urlRequest]; } failure:^(AFHTTPRequestOperation *operation, NSError *error) { if ([[urlRequest URL] isEqual:[operation.request URL]]) { if (failure) { failure(error); } } }]; [self af_setImageRequestOperation:imageRequestOperation forState:state]; [[[self class] af_sharedImageRequestOperationQueue] addOperation:imageRequestOperation]; } } #pragma mark - - (void)setBackgroundImageForState:(UIControlState)state withURL:(NSURL *)url { [self setBackgroundImageForState:state withURL:url placeholderImage:nil]; } - (void)setBackgroundImageForState:(UIControlState)state withURL:(NSURL *)url placeholderImage:(UIImage *)placeholderImage { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; [request addValue:@"image/*" forHTTPHeaderField:@"Accept"]; [self setBackgroundImageForState:state withURLRequest:request placeholderImage:placeholderImage success:nil failure:nil]; } - (void)setBackgroundImageForState:(UIControlState)state withURLRequest:(NSURLRequest *)urlRequest placeholderImage:(UIImage *)placeholderImage success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image))success failure:(void (^)(NSError *error))failure { [self cancelBackgroundImageRequestOperationForState:state]; UIImage *cachedImage = [[[self class] sharedImageCache] cachedImageForRequest:urlRequest]; if (cachedImage) { if (success) { success(nil, nil, cachedImage); } else { [self setBackgroundImage:cachedImage forState:state]; } [self af_setBackgroundImageRequestOperation:nil forState:state]; } else { if (placeholderImage) { [self setBackgroundImage:placeholderImage forState:state]; } __weak __typeof(self)weakSelf = self; AFHTTPRequestOperation *backgroundImageRequestOperation = [[AFHTTPRequestOperation alloc] initWithRequest:urlRequest]; backgroundImageRequestOperation.responseSerializer = self.imageResponseSerializer; [backgroundImageRequestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { __strong __typeof(weakSelf)strongSelf = weakSelf; if ([[urlRequest URL] isEqual:[operation.request URL]]) { if (success) { success(operation.request, operation.response, responseObject); } else if (responseObject) { [strongSelf setBackgroundImage:responseObject forState:state]; } } [[[strongSelf class] sharedImageCache] cacheImage:responseObject forRequest:urlRequest]; } failure:^(AFHTTPRequestOperation *operation, NSError *error) { if ([[urlRequest URL] isEqual:[operation.request URL]]) { if (failure) { failure(error); } } }]; [self af_setBackgroundImageRequestOperation:backgroundImageRequestOperation forState:state]; [[[self class] af_sharedImageRequestOperationQueue] addOperation:backgroundImageRequestOperation]; } } #pragma mark - - (void)cancelImageRequestOperationForState:(UIControlState)state { [[self af_imageRequestOperationForState:state] cancel]; [self af_setImageRequestOperation:nil forState:state]; } - (void)cancelBackgroundImageRequestOperationForState:(UIControlState)state { [[self af_backgroundImageRequestOperationForState:state] cancel]; [self af_setBackgroundImageRequestOperation:nil forState:state]; } @end #endif ================================================ FILE: Pods/AFNetworking/UIKit+AFNetworking/UIImageView+AFNetworking.h ================================================ // UIImageView+AFNetworking.h // // Copyright (c) 2013-2015 AFNetworking (http://afnetworking.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import #import #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) #import @protocol AFURLResponseSerialization, AFImageCache; /** This category adds methods to the UIKit framework's `UIImageView` class. The methods in this category provide support for loading remote images asynchronously from a URL. */ @interface UIImageView (AFNetworking) ///---------------------------- /// @name Accessing Image Cache ///---------------------------- /** The image cache used to improve image loading performance on scroll views. By default, this is an `NSCache` subclass conforming to the `AFImageCache` protocol, which listens for notification warnings and evicts objects accordingly. */ + (id )sharedImageCache; /** Set the cache used for image loading. @param imageCache The image cache. */ + (void)setSharedImageCache:(id )imageCache; ///------------------------------------ /// @name Accessing Response Serializer ///------------------------------------ /** The response serializer used to create an image representation from the server response and response data. By default, this is an instance of `AFImageResponseSerializer`. @discussion Subclasses of `AFImageResponseSerializer` could be used to perform post-processing, such as color correction, face detection, or other effects. See https://github.com/AFNetworking/AFCoreImageSerializer */ @property (nonatomic, strong) id imageResponseSerializer; ///-------------------- /// @name Setting Image ///-------------------- /** Asynchronously downloads an image from the specified URL, and sets it once the request is finished. Any previous image request for the receiver will be cancelled. If the image is cached locally, the image is set immediately, otherwise the specified placeholder image will be set immediately, and then the remote image will be set once the request is finished. By default, URL requests have a `Accept` header field value of "image / *", a cache policy of `NSURLCacheStorageAllowed` and a timeout interval of 30 seconds, and are set not handle cookies. To configure URL requests differently, use `setImageWithURLRequest:placeholderImage:success:failure:` @param url The URL used for the image request. */ - (void)setImageWithURL:(NSURL *)url; /** Asynchronously downloads an image from the specified URL, and sets it once the request is finished. Any previous image request for the receiver will be cancelled. If the image is cached locally, the image is set immediately, otherwise the specified placeholder image will be set immediately, and then the remote image will be set once the request is finished. By default, URL requests have a `Accept` header field value of "image / *", a cache policy of `NSURLCacheStorageAllowed` and a timeout interval of 30 seconds, and are set not handle cookies. To configure URL requests differently, use `setImageWithURLRequest:placeholderImage:success:failure:` @param url The URL used for the image request. @param placeholderImage The image to be set initially, until the image request finishes. If `nil`, the image view will not change its image until the image request finishes. */ - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholderImage; /** Asynchronously downloads an image from the specified URL request, and sets it once the request is finished. Any previous image request for the receiver will be cancelled. If the image is cached locally, the image is set immediately, otherwise the specified placeholder image will be set immediately, and then the remote image will be set once the request is finished. If a success block is specified, it is the responsibility of the block to set the image of the image view before returning. If no success block is specified, the default behavior of setting the image with `self.image = image` is applied. @param urlRequest The URL request used for the image request. @param placeholderImage The image to be set initially, until the image request finishes. If `nil`, the image view will not change its image until the image request finishes. @param success A block to be executed when the image request operation finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the image created from the response data of request. If the image was returned from cache, the request and response parameters will be `nil`. @param failure A block object to be executed when the image request operation finishes unsuccessfully, or that finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the error object describing the network or parsing error that occurred. */ - (void)setImageWithURLRequest:(NSURLRequest *)urlRequest placeholderImage:(UIImage *)placeholderImage success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image))success failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure; /** Cancels any executing image operation for the receiver, if one exists. */ - (void)cancelImageRequestOperation; @end #pragma mark - /** The `AFImageCache` protocol is adopted by an object used to cache images loaded by the AFNetworking category on `UIImageView`. */ @protocol AFImageCache /** Returns a cached image for the specififed request, if available. @param request The image request. @return The cached image. */ - (UIImage *)cachedImageForRequest:(NSURLRequest *)request; /** Caches a particular image for the specified request. @param image The image to cache. @param request The request to be used as a cache key. */ - (void)cacheImage:(UIImage *)image forRequest:(NSURLRequest *)request; @end #endif ================================================ FILE: Pods/AFNetworking/UIKit+AFNetworking/UIImageView+AFNetworking.m ================================================ // UIImageView+AFNetworking.m // // Copyright (c) 2013-2015 AFNetworking (http://afnetworking.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import "UIImageView+AFNetworking.h" #import #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) #import "AFHTTPRequestOperation.h" @interface AFImageCache : NSCache @end #pragma mark - @interface UIImageView (_AFNetworking) @property (readwrite, nonatomic, strong, setter = af_setImageRequestOperation:) AFHTTPRequestOperation *af_imageRequestOperation; @end @implementation UIImageView (_AFNetworking) + (NSOperationQueue *)af_sharedImageRequestOperationQueue { static NSOperationQueue *_af_sharedImageRequestOperationQueue = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _af_sharedImageRequestOperationQueue = [[NSOperationQueue alloc] init]; _af_sharedImageRequestOperationQueue.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount; }); return _af_sharedImageRequestOperationQueue; } - (AFHTTPRequestOperation *)af_imageRequestOperation { return (AFHTTPRequestOperation *)objc_getAssociatedObject(self, @selector(af_imageRequestOperation)); } - (void)af_setImageRequestOperation:(AFHTTPRequestOperation *)imageRequestOperation { objc_setAssociatedObject(self, @selector(af_imageRequestOperation), imageRequestOperation, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } @end #pragma mark - @implementation UIImageView (AFNetworking) @dynamic imageResponseSerializer; + (id )sharedImageCache { static AFImageCache *_af_defaultImageCache = nil; static dispatch_once_t oncePredicate; dispatch_once(&oncePredicate, ^{ _af_defaultImageCache = [[AFImageCache alloc] init]; [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidReceiveMemoryWarningNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * __unused notification) { [_af_defaultImageCache removeAllObjects]; }]; }); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" return objc_getAssociatedObject(self, @selector(sharedImageCache)) ?: _af_defaultImageCache; #pragma clang diagnostic pop } + (void)setSharedImageCache:(id )imageCache { objc_setAssociatedObject(self, @selector(sharedImageCache), imageCache, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } #pragma mark - - (id )imageResponseSerializer { static id _af_defaultImageResponseSerializer = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _af_defaultImageResponseSerializer = [AFImageResponseSerializer serializer]; }); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" return objc_getAssociatedObject(self, @selector(imageResponseSerializer)) ?: _af_defaultImageResponseSerializer; #pragma clang diagnostic pop } - (void)setImageResponseSerializer:(id )serializer { objc_setAssociatedObject(self, @selector(imageResponseSerializer), serializer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } #pragma mark - - (void)setImageWithURL:(NSURL *)url { [self setImageWithURL:url placeholderImage:nil]; } - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholderImage { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; [request addValue:@"image/*" forHTTPHeaderField:@"Accept"]; [self setImageWithURLRequest:request placeholderImage:placeholderImage success:nil failure:nil]; } - (void)setImageWithURLRequest:(NSURLRequest *)urlRequest placeholderImage:(UIImage *)placeholderImage success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image))success failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure { [self cancelImageRequestOperation]; UIImage *cachedImage = [[[self class] sharedImageCache] cachedImageForRequest:urlRequest]; if (cachedImage) { if (success) { success(nil, nil, cachedImage); } else { self.image = cachedImage; } self.af_imageRequestOperation = nil; } else { if (placeholderImage) { self.image = placeholderImage; } __weak __typeof(self)weakSelf = self; self.af_imageRequestOperation = [[AFHTTPRequestOperation alloc] initWithRequest:urlRequest]; self.af_imageRequestOperation.responseSerializer = self.imageResponseSerializer; [self.af_imageRequestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { __strong __typeof(weakSelf)strongSelf = weakSelf; if ([[urlRequest URL] isEqual:[strongSelf.af_imageRequestOperation.request URL]]) { if (success) { success(urlRequest, operation.response, responseObject); } else if (responseObject) { strongSelf.image = responseObject; } if (operation == strongSelf.af_imageRequestOperation){ strongSelf.af_imageRequestOperation = nil; } } [[[strongSelf class] sharedImageCache] cacheImage:responseObject forRequest:urlRequest]; } failure:^(AFHTTPRequestOperation *operation, NSError *error) { __strong __typeof(weakSelf)strongSelf = weakSelf; if ([[urlRequest URL] isEqual:[strongSelf.af_imageRequestOperation.request URL]]) { if (failure) { failure(urlRequest, operation.response, error); } if (operation == strongSelf.af_imageRequestOperation){ strongSelf.af_imageRequestOperation = nil; } } }]; [[[self class] af_sharedImageRequestOperationQueue] addOperation:self.af_imageRequestOperation]; } } - (void)cancelImageRequestOperation { [self.af_imageRequestOperation cancel]; self.af_imageRequestOperation = nil; } @end #pragma mark - static inline NSString * AFImageCacheKeyFromURLRequest(NSURLRequest *request) { return [[request URL] absoluteString]; } @implementation AFImageCache - (UIImage *)cachedImageForRequest:(NSURLRequest *)request { switch ([request cachePolicy]) { case NSURLRequestReloadIgnoringCacheData: case NSURLRequestReloadIgnoringLocalAndRemoteCacheData: return nil; default: break; } return [self objectForKey:AFImageCacheKeyFromURLRequest(request)]; } - (void)cacheImage:(UIImage *)image forRequest:(NSURLRequest *)request { if (image && request) { [self setObject:image forKey:AFImageCacheKeyFromURLRequest(request)]; } } @end #endif ================================================ FILE: Pods/AFNetworking/UIKit+AFNetworking/UIKit+AFNetworking.h ================================================ // UIKit+AFNetworking.h // // Copyright (c) 2013 AFNetworking (http://afnetworking.com/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import #ifndef _UIKIT_AFNETWORKING_ #define _UIKIT_AFNETWORKING_ #import "AFNetworkActivityIndicatorManager.h" #import "UIActivityIndicatorView+AFNetworking.h" #import "UIAlertView+AFNetworking.h" #import "UIButton+AFNetworking.h" #import "UIImageView+AFNetworking.h" #import "UIKit+AFNetworking.h" #import "UIProgressView+AFNetworking.h" #import "UIRefreshControl+AFNetworking.h" #import "UIWebView+AFNetworking.h" #endif /* _UIKIT_AFNETWORKING_ */ ================================================ FILE: Pods/AFNetworking/UIKit+AFNetworking/UIProgressView+AFNetworking.h ================================================ // UIProgressView+AFNetworking.h // // Copyright (c) 2013-2015 AFNetworking (http://afnetworking.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import #import #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) #import @class AFURLConnectionOperation; /** This category adds methods to the UIKit framework's `UIProgressView` class. The methods in this category provide support for binding the progress to the upload and download progress of a session task or request operation. */ @interface UIProgressView (AFNetworking) ///------------------------------------ /// @name Setting Session Task Progress ///------------------------------------ /** Binds the progress to the upload progress of the specified session task. @param task The session task. @param animated `YES` if the change should be animated, `NO` if the change should happen immediately. */ #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 - (void)setProgressWithUploadProgressOfTask:(NSURLSessionUploadTask *)task animated:(BOOL)animated; #endif /** Binds the progress to the download progress of the specified session task. @param task The session task. @param animated `YES` if the change should be animated, `NO` if the change should happen immediately. */ #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 - (void)setProgressWithDownloadProgressOfTask:(NSURLSessionDownloadTask *)task animated:(BOOL)animated; #endif ///------------------------------------ /// @name Setting Session Task Progress ///------------------------------------ /** Binds the progress to the upload progress of the specified request operation. @param operation The request operation. @param animated `YES` if the change should be animated, `NO` if the change should happen immediately. */ - (void)setProgressWithUploadProgressOfOperation:(AFURLConnectionOperation *)operation animated:(BOOL)animated; /** Binds the progress to the download progress of the specified request operation. @param operation The request operation. @param animated `YES` if the change should be animated, `NO` if the change should happen immediately. */ - (void)setProgressWithDownloadProgressOfOperation:(AFURLConnectionOperation *)operation animated:(BOOL)animated; @end #endif ================================================ FILE: Pods/AFNetworking/UIKit+AFNetworking/UIProgressView+AFNetworking.m ================================================ // UIProgressView+AFNetworking.m // // Copyright (c) 2013-2015 AFNetworking (http://afnetworking.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import "UIProgressView+AFNetworking.h" #import #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) #import "AFURLConnectionOperation.h" #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 #import "AFURLSessionManager.h" #endif static void * AFTaskCountOfBytesSentContext = &AFTaskCountOfBytesSentContext; static void * AFTaskCountOfBytesReceivedContext = &AFTaskCountOfBytesReceivedContext; @interface AFURLConnectionOperation (_UIProgressView) @property (readwrite, nonatomic, copy) void (^uploadProgress)(NSUInteger bytes, long long totalBytes, long long totalBytesExpected); @property (readwrite, nonatomic, assign, setter = af_setUploadProgressAnimated:) BOOL af_uploadProgressAnimated; @property (readwrite, nonatomic, copy) void (^downloadProgress)(NSUInteger bytes, long long totalBytes, long long totalBytesExpected); @property (readwrite, nonatomic, assign, setter = af_setDownloadProgressAnimated:) BOOL af_downloadProgressAnimated; @end @implementation AFURLConnectionOperation (_UIProgressView) @dynamic uploadProgress; // Implemented in AFURLConnectionOperation @dynamic af_uploadProgressAnimated; @dynamic downloadProgress; // Implemented in AFURLConnectionOperation @dynamic af_downloadProgressAnimated; @end #pragma mark - @implementation UIProgressView (AFNetworking) - (BOOL)af_uploadProgressAnimated { return [(NSNumber *)objc_getAssociatedObject(self, @selector(af_uploadProgressAnimated)) boolValue]; } - (void)af_setUploadProgressAnimated:(BOOL)animated { objc_setAssociatedObject(self, @selector(af_uploadProgressAnimated), @(animated), OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (BOOL)af_downloadProgressAnimated { return [(NSNumber *)objc_getAssociatedObject(self, @selector(af_downloadProgressAnimated)) boolValue]; } - (void)af_setDownloadProgressAnimated:(BOOL)animated { objc_setAssociatedObject(self, @selector(af_downloadProgressAnimated), @(animated), OBJC_ASSOCIATION_RETAIN_NONATOMIC); } #pragma mark - #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 - (void)setProgressWithUploadProgressOfTask:(NSURLSessionUploadTask *)task animated:(BOOL)animated { [task addObserver:self forKeyPath:@"state" options:(NSKeyValueObservingOptions)0 context:AFTaskCountOfBytesSentContext]; [task addObserver:self forKeyPath:@"countOfBytesSent" options:(NSKeyValueObservingOptions)0 context:AFTaskCountOfBytesSentContext]; [self af_setUploadProgressAnimated:animated]; } - (void)setProgressWithDownloadProgressOfTask:(NSURLSessionDownloadTask *)task animated:(BOOL)animated { [task addObserver:self forKeyPath:@"state" options:(NSKeyValueObservingOptions)0 context:AFTaskCountOfBytesReceivedContext]; [task addObserver:self forKeyPath:@"countOfBytesReceived" options:(NSKeyValueObservingOptions)0 context:AFTaskCountOfBytesReceivedContext]; [self af_setDownloadProgressAnimated:animated]; } #endif #pragma mark - - (void)setProgressWithUploadProgressOfOperation:(AFURLConnectionOperation *)operation animated:(BOOL)animated { __weak __typeof(self)weakSelf = self; void (^original)(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) = [operation.uploadProgress copy]; [operation setUploadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) { if (original) { original(bytesWritten, totalBytesWritten, totalBytesExpectedToWrite); } dispatch_async(dispatch_get_main_queue(), ^{ if (totalBytesExpectedToWrite > 0) { __strong __typeof(weakSelf)strongSelf = weakSelf; [strongSelf setProgress:(totalBytesWritten / (totalBytesExpectedToWrite * 1.0f)) animated:animated]; } }); }]; } - (void)setProgressWithDownloadProgressOfOperation:(AFURLConnectionOperation *)operation animated:(BOOL)animated { __weak __typeof(self)weakSelf = self; void (^original)(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) = [operation.downloadProgress copy]; [operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) { if (original) { original(bytesRead, totalBytesRead, totalBytesExpectedToRead); } dispatch_async(dispatch_get_main_queue(), ^{ if (totalBytesExpectedToRead > 0) { __strong __typeof(weakSelf)strongSelf = weakSelf; [strongSelf setProgress:(totalBytesRead / (totalBytesExpectedToRead * 1.0f)) animated:animated]; } }); }]; } #pragma mark - NSKeyValueObserving - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(__unused NSDictionary *)change context:(void *)context { #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 if (context == AFTaskCountOfBytesSentContext || context == AFTaskCountOfBytesReceivedContext) { if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesSent))]) { if ([object countOfBytesExpectedToSend] > 0) { dispatch_async(dispatch_get_main_queue(), ^{ [self setProgress:[object countOfBytesSent] / ([object countOfBytesExpectedToSend] * 1.0f) animated:self.af_uploadProgressAnimated]; }); } } if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesReceived))]) { if ([object countOfBytesExpectedToReceive] > 0) { dispatch_async(dispatch_get_main_queue(), ^{ [self setProgress:[object countOfBytesReceived] / ([object countOfBytesExpectedToReceive] * 1.0f) animated:self.af_downloadProgressAnimated]; }); } } if ([keyPath isEqualToString:NSStringFromSelector(@selector(state))]) { if ([(NSURLSessionTask *)object state] == NSURLSessionTaskStateCompleted) { @try { [object removeObserver:self forKeyPath:NSStringFromSelector(@selector(state))]; if (context == AFTaskCountOfBytesSentContext) { [object removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesSent))]; } if (context == AFTaskCountOfBytesReceivedContext) { [object removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesReceived))]; } } @catch (NSException * __unused exception) {} } } } #endif } @end #endif ================================================ FILE: Pods/AFNetworking/UIKit+AFNetworking/UIRefreshControl+AFNetworking.h ================================================ // UIRefreshControl+AFNetworking.m // // Copyright (c) 2014 AFNetworking (http://afnetworking.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import #import #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) #import @class AFURLConnectionOperation; /** This category adds methods to the UIKit framework's `UIRefreshControl` class. The methods in this category provide support for automatically begining and ending refreshing depending on the loading state of a request operation or session task. */ @interface UIRefreshControl (AFNetworking) ///----------------------------------- /// @name Refreshing for Session Tasks ///----------------------------------- /** Binds the refreshing state to the state of the specified task. @param task The task. If `nil`, automatic updating from any previously specified operation will be disabled. */ #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 - (void)setRefreshingWithStateOfTask:(NSURLSessionTask *)task; #endif ///---------------------------------------- /// @name Refreshing for Request Operations ///---------------------------------------- /** Binds the refreshing state to the execution state of the specified operation. @param operation The operation. If `nil`, automatic updating from any previously specified operation will be disabled. */ - (void)setRefreshingWithStateOfOperation:(AFURLConnectionOperation *)operation; @end #endif ================================================ FILE: Pods/AFNetworking/UIKit+AFNetworking/UIRefreshControl+AFNetworking.m ================================================ // UIRefreshControl+AFNetworking.m // // Copyright (c) 2014 AFNetworking (http://afnetworking.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import "UIRefreshControl+AFNetworking.h" #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) #import "AFHTTPRequestOperation.h" #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 #import "AFURLSessionManager.h" #endif @implementation UIRefreshControl (AFNetworking) #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 - (void)setRefreshingWithStateOfTask:(NSURLSessionTask *)task { NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; [notificationCenter removeObserver:self name:AFNetworkingTaskDidResumeNotification object:nil]; [notificationCenter removeObserver:self name:AFNetworkingTaskDidSuspendNotification object:nil]; [notificationCenter removeObserver:self name:AFNetworkingTaskDidCompleteNotification object:nil]; if (task) { if (task.state == NSURLSessionTaskStateRunning) { [self beginRefreshing]; [notificationCenter addObserver:self selector:@selector(af_beginRefreshing) name:AFNetworkingTaskDidResumeNotification object:task]; [notificationCenter addObserver:self selector:@selector(af_endRefreshing) name:AFNetworkingTaskDidCompleteNotification object:task]; [notificationCenter addObserver:self selector:@selector(af_endRefreshing) name:AFNetworkingTaskDidSuspendNotification object:task]; } else { [self endRefreshing]; } } } #endif - (void)setRefreshingWithStateOfOperation:(AFURLConnectionOperation *)operation { NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; [notificationCenter removeObserver:self name:AFNetworkingOperationDidStartNotification object:nil]; [notificationCenter removeObserver:self name:AFNetworkingOperationDidFinishNotification object:nil]; if (operation) { if (![operation isFinished]) { if ([operation isExecuting]) { [self beginRefreshing]; } else { [self endRefreshing]; } [notificationCenter addObserver:self selector:@selector(af_beginRefreshing) name:AFNetworkingOperationDidStartNotification object:operation]; [notificationCenter addObserver:self selector:@selector(af_endRefreshing) name:AFNetworkingOperationDidFinishNotification object:operation]; } } } #pragma mark - - (void)af_beginRefreshing { dispatch_async(dispatch_get_main_queue(), ^{ [self beginRefreshing]; }); } - (void)af_endRefreshing { dispatch_async(dispatch_get_main_queue(), ^{ [self endRefreshing]; }); } @end #endif ================================================ FILE: Pods/AFNetworking/UIKit+AFNetworking/UIWebView+AFNetworking.h ================================================ // UIWebView+AFNetworking.h // // Copyright (c) 2013-2015 AFNetworking (http://afnetworking.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import #import #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) #import @class AFHTTPRequestSerializer, AFHTTPResponseSerializer; @protocol AFURLRequestSerialization, AFURLResponseSerialization; /** This category adds methods to the UIKit framework's `UIWebView` class. The methods in this category provide increased control over the request cycle, including progress monitoring and success / failure handling. @discussion When using these category methods, make sure to assign `delegate` for the web view, which implements `–webView:shouldStartLoadWithRequest:navigationType:` appropriately. This allows for tapped links to be loaded through AFNetworking, and can ensure that `canGoBack` & `canGoForward` update their values correctly. */ @interface UIWebView (AFNetworking) /** The request serializer used to serialize requests made with the `-loadRequest:...` category methods. By default, this is an instance of `AFHTTPRequestSerializer`. */ @property (nonatomic, strong) AFHTTPRequestSerializer * requestSerializer; /** The response serializer used to serialize responses made with the `-loadRequest:...` category methods. By default, this is an instance of `AFHTTPResponseSerializer`. */ @property (nonatomic, strong) AFHTTPResponseSerializer * responseSerializer; /** Asynchronously loads the specified request. @param request A URL request identifying the location of the content to load. This must not be `nil`. @param progress A block object to be called when an undetermined number of bytes have been downloaded from the server. This block has no return value and takes three arguments: the number of bytes read since the last time the download progress block was called, the total bytes read, and the total bytes expected to be read during the request, as initially determined by the expected content size of the `NSHTTPURLResponse` object. This block may be called multiple times, and will execute on the main thread. @param success A block object to be executed when the request finishes loading successfully. This block returns the HTML string to be loaded by the web view, and takes two arguments: the response, and the response string. @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a single argument: the error that occurred. */ - (void)loadRequest:(NSURLRequest *)request progress:(void (^)(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))progress success:(NSString * (^)(NSHTTPURLResponse *response, NSString *HTML))success failure:(void (^)(NSError *error))failure; /** Asynchronously loads the data associated with a particular request with a specified MIME type and text encoding. @param request A URL request identifying the location of the content to load. This must not be `nil`. @param MIMEType The MIME type of the content. Defaults to the content type of the response if not specified. @param textEncodingName The IANA encoding name, as in `utf-8` or `utf-16`. Defaults to the response text encoding if not specified. @param progress A block object to be called when an undetermined number of bytes have been downloaded from the server. This block has no return value and takes three arguments: the number of bytes read since the last time the download progress block was called, the total bytes read, and the total bytes expected to be read during the request, as initially determined by the expected content size of the `NSHTTPURLResponse` object. This block may be called multiple times, and will execute on the main thread. @param success A block object to be executed when the request finishes loading successfully. This block returns the data to be loaded by the web view and takes two arguments: the response, and the downloaded data. @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a single argument: the error that occurred. */ - (void)loadRequest:(NSURLRequest *)request MIMEType:(NSString *)MIMEType textEncodingName:(NSString *)textEncodingName progress:(void (^)(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))progress success:(NSData * (^)(NSHTTPURLResponse *response, NSData *data))success failure:(void (^)(NSError *error))failure; @end #endif ================================================ FILE: Pods/AFNetworking/UIKit+AFNetworking/UIWebView+AFNetworking.m ================================================ // UIWebView+AFNetworking.m // // Copyright (c) 2013-2015 AFNetworking (http://afnetworking.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import "UIWebView+AFNetworking.h" #import #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) #import "AFHTTPRequestOperation.h" #import "AFURLResponseSerialization.h" #import "AFURLRequestSerialization.h" @interface UIWebView (_AFNetworking) @property (readwrite, nonatomic, strong, setter = af_setHTTPRequestOperation:) AFHTTPRequestOperation *af_HTTPRequestOperation; @end @implementation UIWebView (_AFNetworking) - (AFHTTPRequestOperation *)af_HTTPRequestOperation { return (AFHTTPRequestOperation *)objc_getAssociatedObject(self, @selector(af_HTTPRequestOperation)); } - (void)af_setHTTPRequestOperation:(AFHTTPRequestOperation *)operation { objc_setAssociatedObject(self, @selector(af_HTTPRequestOperation), operation, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } @end #pragma mark - @implementation UIWebView (AFNetworking) - (AFHTTPRequestSerializer *)requestSerializer { static AFHTTPRequestSerializer *_af_defaultRequestSerializer = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _af_defaultRequestSerializer = [AFHTTPRequestSerializer serializer]; }); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" return objc_getAssociatedObject(self, @selector(requestSerializer)) ?: _af_defaultRequestSerializer; #pragma clang diagnostic pop } - (void)setRequestSerializer:(AFHTTPRequestSerializer *)requestSerializer { objc_setAssociatedObject(self, @selector(requestSerializer), requestSerializer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (AFHTTPResponseSerializer *)responseSerializer { static AFHTTPResponseSerializer *_af_defaultResponseSerializer = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _af_defaultResponseSerializer = [AFHTTPResponseSerializer serializer]; }); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" return objc_getAssociatedObject(self, @selector(responseSerializer)) ?: _af_defaultResponseSerializer; #pragma clang diagnostic pop } - (void)setResponseSerializer:(AFHTTPResponseSerializer *)responseSerializer { objc_setAssociatedObject(self, @selector(responseSerializer), responseSerializer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } #pragma mark - - (void)loadRequest:(NSURLRequest *)request progress:(void (^)(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))progress success:(NSString * (^)(NSHTTPURLResponse *response, NSString *HTML))success failure:(void (^)(NSError *error))failure { [self loadRequest:request MIMEType:nil textEncodingName:nil progress:progress success:^NSData *(NSHTTPURLResponse *response, NSData *data) { NSStringEncoding stringEncoding = NSUTF8StringEncoding; if (response.textEncodingName) { CFStringEncoding encoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName); if (encoding != kCFStringEncodingInvalidId) { stringEncoding = CFStringConvertEncodingToNSStringEncoding(encoding); } } NSString *string = [[NSString alloc] initWithData:data encoding:stringEncoding]; if (success) { string = success(response, string); } return [string dataUsingEncoding:stringEncoding]; } failure:failure]; } - (void)loadRequest:(NSURLRequest *)request MIMEType:(NSString *)MIMEType textEncodingName:(NSString *)textEncodingName progress:(void (^)(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))progress success:(NSData * (^)(NSHTTPURLResponse *response, NSData *data))success failure:(void (^)(NSError *error))failure { NSParameterAssert(request); if (self.af_HTTPRequestOperation) { [self.af_HTTPRequestOperation cancel]; } request = [self.requestSerializer requestBySerializingRequest:request withParameters:nil error:nil]; self.af_HTTPRequestOperation = [[AFHTTPRequestOperation alloc] initWithRequest:request]; self.af_HTTPRequestOperation.responseSerializer = self.responseSerializer; __weak __typeof(self)weakSelf = self; [self.af_HTTPRequestOperation setDownloadProgressBlock:progress]; [self.af_HTTPRequestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id __unused responseObject) { NSData *data = success ? success(operation.response, operation.responseData) : operation.responseData; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" __strong __typeof(weakSelf) strongSelf = weakSelf; [strongSelf loadData:data MIMEType:(MIMEType ?: [operation.response MIMEType]) textEncodingName:(textEncodingName ?: [operation.response textEncodingName]) baseURL:[operation.response URL]]; if ([strongSelf.delegate respondsToSelector:@selector(webViewDidFinishLoad:)]) { [strongSelf.delegate webViewDidFinishLoad:strongSelf]; } #pragma clang diagnostic pop } failure:^(AFHTTPRequestOperation * __unused operation, NSError *error) { if (failure) { failure(error); } }]; [self.af_HTTPRequestOperation start]; if ([self.delegate respondsToSelector:@selector(webViewDidStartLoad:)]) { [self.delegate webViewDidStartLoad:self]; } } @end #endif ================================================ FILE: Pods/CoreBitcoin/CoreBitcoin/BTC256.h ================================================ // CoreBitcoin by Oleg Andreev , WTFPL. #import // A set of ubiquitous types and functions to deal with fixed-length chunks of data // (160-bit, 256-bit and 512-bit). These are relevant almost always to hashes, // but there's no hash-specific about them. // The purpose of these is to avoid dynamic memory allocations via NSData when // we need to move exactly 32 bytes around. // // We don't call these BTCFixedData256 because these types are way too ubiquituous // in CoreBitcoin to have such an explicit name. // // Somewhat similar to uint256 in bitcoind, but here we don't try // to pretend that these are integers and then allow arithmetic on them // and create a mess with the byte order. // Use BTCBigNumber to do arithmetic on big numbers and convert // to bignum format explicitly. // BTCBigNumber has API for converting BTC256 to a big int. // // We also declare BTC160 and BTC512 for use with RIPEMD-160, SHA-1 and SHA-512 hashes. // 1. Fixed-length types struct private_BTC160 { // 160 bits can't be formed with 64-bit words, so we have to use 32-bit ones instead. uint32_t words32[5]; } __attribute__((packed)); typedef struct private_BTC160 BTC160; struct private_BTC256 { // Since all modern CPUs are 64-bit (ARM is 64-bit starting with iPhone 5s), // we will use 64-bit words. uint64_t words64[4]; } __attribute__((aligned(1))); typedef struct private_BTC256 BTC256; struct private_BTC512 { // Since all modern CPUs are 64-bit (ARM is 64-bit starting with iPhone 5s), // we will use 64-bit words. uint64_t words64[8]; } __attribute__((aligned(1))); typedef struct private_BTC512 BTC512; // 2. Constants // All-zero constants extern const BTC160 BTC160Zero; extern const BTC256 BTC256Zero; extern const BTC512 BTC512Zero; // All-one constants extern const BTC160 BTC160Max; extern const BTC256 BTC256Max; extern const BTC512 BTC512Max; // First 160 bits of SHA512("CoreBitcoin/BTC160Null") extern const BTC160 BTC160Null; // First 256 bits of SHA512("CoreBitcoin/BTC256Null") extern const BTC256 BTC256Null; // Value of SHA512("CoreBitcoin/BTC512Null") extern const BTC512 BTC512Null; // 3. Comparison BOOL BTC160Equal(BTC160 chunk1, BTC160 chunk2); BOOL BTC256Equal(BTC256 chunk1, BTC256 chunk2); BOOL BTC512Equal(BTC512 chunk1, BTC512 chunk2); NSComparisonResult BTC160Compare(BTC160 chunk1, BTC160 chunk2); NSComparisonResult BTC256Compare(BTC256 chunk1, BTC256 chunk2); NSComparisonResult BTC512Compare(BTC512 chunk1, BTC512 chunk2); // 4. Operations // Inverse (b = ~a) BTC160 BTC160Inverse(BTC160 chunk); BTC256 BTC256Inverse(BTC256 chunk); BTC512 BTC512Inverse(BTC512 chunk); // Swap byte order BTC160 BTC160Swap(BTC160 chunk); BTC256 BTC256Swap(BTC256 chunk); BTC512 BTC512Swap(BTC512 chunk); // Bitwise AND operation (a & b) BTC160 BTC160AND(BTC160 chunk1, BTC160 chunk2); BTC256 BTC256AND(BTC256 chunk1, BTC256 chunk2); BTC512 BTC512AND(BTC512 chunk1, BTC512 chunk2); // Bitwise OR operation (a | b) BTC160 BTC160OR(BTC160 chunk1, BTC160 chunk2); BTC256 BTC256OR(BTC256 chunk1, BTC256 chunk2); BTC512 BTC512OR(BTC512 chunk1, BTC512 chunk2); // Bitwise exclusive-OR operation (a ^ b) BTC160 BTC160XOR(BTC160 chunk1, BTC160 chunk2); BTC256 BTC256XOR(BTC256 chunk1, BTC256 chunk2); BTC512 BTC512XOR(BTC512 chunk1, BTC512 chunk2); // Concatenation of two 256-bit chunks BTC512 BTC512Concat(BTC256 chunk1, BTC256 chunk2); // 5. Conversion functions // Conversion to NSData NSData* NSDataFromBTC160(BTC160 chunk); NSData* NSDataFromBTC256(BTC256 chunk); NSData* NSDataFromBTC512(BTC512 chunk); // Conversion from NSData. // If NSData is not big enough, returns BTCHash{160,256,512}Null. BTC160 BTC160FromNSData(NSData* data); BTC256 BTC256FromNSData(NSData* data); BTC512 BTC512FromNSData(NSData* data); // Returns lowercase hex representation of the chunk NSString* NSStringFromBTC160(BTC160 chunk); NSString* NSStringFromBTC256(BTC256 chunk); NSString* NSStringFromBTC512(BTC512 chunk); // Conversion from hex NSString (lower- or uppercase). // If string is invalid or data is too short, returns BTCHash{160,256,512}Null. BTC160 BTC160FromNSString(NSString* string); BTC256 BTC256FromNSString(NSString* string); BTC512 BTC512FromNSString(NSString* string); ================================================ FILE: Pods/CoreBitcoin/CoreBitcoin/BTC256.m ================================================ // CoreBitcoin by Oleg Andreev , WTFPL. #import "BTC256.h" #import "BTCData.h" // 1. Structs are already defined in the .h file. // 2. Constants const BTC160 BTC160Zero = {0,0,0,0,0}; const BTC256 BTC256Zero = {0,0,0,0}; const BTC512 BTC512Zero = {0,0,0,0,0,0,0,0}; const BTC160 BTC160Max = {0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff}; const BTC256 BTC256Max = {0xffffffffffffffffLL,0xffffffffffffffffLL,0xffffffffffffffffLL,0xffffffffffffffffLL}; const BTC512 BTC512Max = {0xffffffffffffffffLL,0xffffffffffffffffLL,0xffffffffffffffffLL,0xffffffffffffffffLL, 0xffffffffffffffffLL,0xffffffffffffffffLL,0xffffffffffffffffLL,0xffffffffffffffffLL}; // Using ints assuming little-endian platform. 160-bit chunk actually begins with 82963d5e. Same thing about the rest. // Digest::SHA512.hexdigest("CoreBitcoin/BTC160Null")[0,2*20].scan(/.{8}/).map{|x| "0x" + x.scan(/../).reverse.join}.join(",") // 82963d5edd842f1e6bd2b6bc2e9a97a40a7d8652 const BTC160 BTC160Null = {0x5e3d9682,0x1e2f84dd,0xbcb6d26b,0xa4979a2e,0x52867d0a}; // Digest::SHA512.hexdigest("CoreBitcoin/BTC256Null")[0,2*32].scan(/.{16}/).map{|x| "0x" + x.scan(/../).reverse.join}.join(",") // d1007a1fe826e95409e21595845f44c3b9411d5285b6b5982285aabfa5999a5e const BTC256 BTC256Null = {0x54e926e81f7a00d1LL,0xc3445f849515e209LL,0x98b5b685521d41b9LL,0x5e9a99a5bfaa8522LL}; // Digest::SHA512.hexdigest("CoreBitcoin/BTC512Null")[0,2*64].scan(/.{16}/).map{|x| "0x" + x.scan(/../).reverse.join}.join(",") // 62ce64dd92836e6e99d83eee3f623652f6049cf8c22272f295b262861738f0363e01b5d7a53c4a2e5a76d283f3e4a04d28ab54849c6e3e874ca31128bcb759e1 const BTC512 BTC512Null = {0x6e6e8392dd64ce62LL,0x5236623fee3ed899LL,0xf27222c2f89c04f6LL,0x36f038178662b295LL,0x2e4a3ca5d7b5013eLL,0x4da0e4f383d2765aLL,0x873e6e9c8454ab28LL,0xe159b7bc2811a34cLL}; // 3. Comparison BOOL BTC160Equal(BTC160 chunk1, BTC160 chunk2) { // Which one is faster: memcmp or word-by-word check? The latter does not need any loop or extra checks to compare bytes. // return memcmp(&chunk1, &chunk2, sizeof(chunk1)) == 0; return chunk1.words32[0] == chunk2.words32[0] && chunk1.words32[1] == chunk2.words32[1] && chunk1.words32[2] == chunk2.words32[2] && chunk1.words32[3] == chunk2.words32[3] && chunk1.words32[4] == chunk2.words32[4]; } BOOL BTC256Equal(BTC256 chunk1, BTC256 chunk2) { return chunk1.words64[0] == chunk2.words64[0] && chunk1.words64[1] == chunk2.words64[1] && chunk1.words64[2] == chunk2.words64[2] && chunk1.words64[3] == chunk2.words64[3]; } BOOL BTC512Equal(BTC512 chunk1, BTC512 chunk2) { return chunk1.words64[0] == chunk2.words64[0] && chunk1.words64[1] == chunk2.words64[1] && chunk1.words64[2] == chunk2.words64[2] && chunk1.words64[3] == chunk2.words64[3] && chunk1.words64[4] == chunk2.words64[4] && chunk1.words64[5] == chunk2.words64[5] && chunk1.words64[6] == chunk2.words64[6] && chunk1.words64[7] == chunk2.words64[7]; } NSComparisonResult BTC160Compare(BTC160 chunk1, BTC160 chunk2) { int r = memcmp(&chunk1, &chunk2, sizeof(chunk1)); if (r > 0) return NSOrderedDescending; else if (r < 0) return NSOrderedAscending; return NSOrderedSame; } NSComparisonResult BTC256Compare(BTC256 chunk1, BTC256 chunk2) { int r = memcmp(&chunk1, &chunk2, sizeof(chunk1)); if (r > 0) return NSOrderedDescending; else if (r < 0) return NSOrderedAscending; return NSOrderedSame; } NSComparisonResult BTC512Compare(BTC512 chunk1, BTC512 chunk2) { int r = memcmp(&chunk1, &chunk2, sizeof(chunk1)); if (r > 0) return NSOrderedDescending; else if (r < 0) return NSOrderedAscending; return NSOrderedSame; } // 4. Operations // Inverse (b = ~a) BTC160 BTC160Inverse(BTC160 chunk) { chunk.words32[0] = ~chunk.words32[0]; chunk.words32[1] = ~chunk.words32[1]; chunk.words32[2] = ~chunk.words32[2]; chunk.words32[3] = ~chunk.words32[3]; chunk.words32[4] = ~chunk.words32[4]; return chunk; } BTC256 BTC256Inverse(BTC256 chunk) { chunk.words64[0] = ~chunk.words64[0]; chunk.words64[1] = ~chunk.words64[1]; chunk.words64[2] = ~chunk.words64[2]; chunk.words64[3] = ~chunk.words64[3]; return chunk; } BTC512 BTC512Inverse(BTC512 chunk) { chunk.words64[0] = ~chunk.words64[0]; chunk.words64[1] = ~chunk.words64[1]; chunk.words64[2] = ~chunk.words64[2]; chunk.words64[3] = ~chunk.words64[3]; chunk.words64[4] = ~chunk.words64[4]; chunk.words64[5] = ~chunk.words64[5]; chunk.words64[6] = ~chunk.words64[6]; chunk.words64[7] = ~chunk.words64[7]; return chunk; } // Swap byte order BTC160 BTC160Swap(BTC160 chunk) { BTC160 chunk2; chunk2.words32[4] = OSSwapConstInt32(chunk.words32[0]); chunk2.words32[3] = OSSwapConstInt32(chunk.words32[1]); chunk2.words32[2] = OSSwapConstInt32(chunk.words32[2]); chunk2.words32[1] = OSSwapConstInt32(chunk.words32[3]); chunk2.words32[0] = OSSwapConstInt32(chunk.words32[4]); return chunk2; } BTC256 BTC256Swap(BTC256 chunk) { BTC256 chunk2; chunk2.words64[3] = OSSwapConstInt64(chunk.words64[0]); chunk2.words64[2] = OSSwapConstInt64(chunk.words64[1]); chunk2.words64[1] = OSSwapConstInt64(chunk.words64[2]); chunk2.words64[0] = OSSwapConstInt64(chunk.words64[3]); return chunk2; } BTC512 BTC512Swap(BTC512 chunk) { BTC512 chunk2; chunk2.words64[7] = OSSwapConstInt64(chunk.words64[0]); chunk2.words64[6] = OSSwapConstInt64(chunk.words64[1]); chunk2.words64[5] = OSSwapConstInt64(chunk.words64[2]); chunk2.words64[4] = OSSwapConstInt64(chunk.words64[3]); chunk2.words64[3] = OSSwapConstInt64(chunk.words64[4]); chunk2.words64[2] = OSSwapConstInt64(chunk.words64[5]); chunk2.words64[1] = OSSwapConstInt64(chunk.words64[6]); chunk2.words64[0] = OSSwapConstInt64(chunk.words64[7]); return chunk2; } // Bitwise AND operation (a & b) BTC160 BTC160AND(BTC160 chunk1, BTC160 chunk2) { chunk1.words32[0] = chunk1.words32[0] & chunk2.words32[0]; chunk1.words32[1] = chunk1.words32[1] & chunk2.words32[1]; chunk1.words32[2] = chunk1.words32[2] & chunk2.words32[2]; chunk1.words32[3] = chunk1.words32[3] & chunk2.words32[3]; chunk1.words32[4] = chunk1.words32[4] & chunk2.words32[4]; return chunk1; } BTC256 BTC256AND(BTC256 chunk1, BTC256 chunk2) { chunk1.words64[0] = chunk1.words64[0] & chunk2.words64[0]; chunk1.words64[1] = chunk1.words64[1] & chunk2.words64[1]; chunk1.words64[2] = chunk1.words64[2] & chunk2.words64[2]; chunk1.words64[3] = chunk1.words64[3] & chunk2.words64[3]; return chunk1; } BTC512 BTC512AND(BTC512 chunk1, BTC512 chunk2) { chunk1.words64[0] = chunk1.words64[0] & chunk2.words64[0]; chunk1.words64[1] = chunk1.words64[1] & chunk2.words64[1]; chunk1.words64[2] = chunk1.words64[2] & chunk2.words64[2]; chunk1.words64[3] = chunk1.words64[3] & chunk2.words64[3]; chunk1.words64[4] = chunk1.words64[4] & chunk2.words64[4]; chunk1.words64[5] = chunk1.words64[5] & chunk2.words64[5]; chunk1.words64[6] = chunk1.words64[6] & chunk2.words64[6]; chunk1.words64[7] = chunk1.words64[7] & chunk2.words64[7]; return chunk1; } // Bitwise OR operation (a | b) BTC160 BTC160OR(BTC160 chunk1, BTC160 chunk2) { chunk1.words32[0] = chunk1.words32[0] | chunk2.words32[0]; chunk1.words32[1] = chunk1.words32[1] | chunk2.words32[1]; chunk1.words32[2] = chunk1.words32[2] | chunk2.words32[2]; chunk1.words32[3] = chunk1.words32[3] | chunk2.words32[3]; chunk1.words32[4] = chunk1.words32[4] | chunk2.words32[4]; return chunk1; } BTC256 BTC256OR(BTC256 chunk1, BTC256 chunk2) { chunk1.words64[0] = chunk1.words64[0] | chunk2.words64[0]; chunk1.words64[1] = chunk1.words64[1] | chunk2.words64[1]; chunk1.words64[2] = chunk1.words64[2] | chunk2.words64[2]; chunk1.words64[3] = chunk1.words64[3] | chunk2.words64[3]; return chunk1; } BTC512 BTC512OR(BTC512 chunk1, BTC512 chunk2) { chunk1.words64[0] = chunk1.words64[0] | chunk2.words64[0]; chunk1.words64[1] = chunk1.words64[1] | chunk2.words64[1]; chunk1.words64[2] = chunk1.words64[2] | chunk2.words64[2]; chunk1.words64[3] = chunk1.words64[3] | chunk2.words64[3]; chunk1.words64[4] = chunk1.words64[4] | chunk2.words64[4]; chunk1.words64[5] = chunk1.words64[5] | chunk2.words64[5]; chunk1.words64[6] = chunk1.words64[6] | chunk2.words64[6]; chunk1.words64[7] = chunk1.words64[7] | chunk2.words64[7]; return chunk1; } // Bitwise exclusive-OR operation (a ^ b) BTC160 BTC160XOR(BTC160 chunk1, BTC160 chunk2) { chunk1.words32[0] = chunk1.words32[0] ^ chunk2.words32[0]; chunk1.words32[1] = chunk1.words32[1] ^ chunk2.words32[1]; chunk1.words32[2] = chunk1.words32[2] ^ chunk2.words32[2]; chunk1.words32[3] = chunk1.words32[3] ^ chunk2.words32[3]; chunk1.words32[4] = chunk1.words32[4] ^ chunk2.words32[4]; return chunk1; } BTC256 BTC256XOR(BTC256 chunk1, BTC256 chunk2) { chunk1.words64[0] = chunk1.words64[0] ^ chunk2.words64[0]; chunk1.words64[1] = chunk1.words64[1] ^ chunk2.words64[1]; chunk1.words64[2] = chunk1.words64[2] ^ chunk2.words64[2]; chunk1.words64[3] = chunk1.words64[3] ^ chunk2.words64[3]; return chunk1; } BTC512 BTC512XOR(BTC512 chunk1, BTC512 chunk2) { chunk1.words64[0] = chunk1.words64[0] ^ chunk2.words64[0]; chunk1.words64[1] = chunk1.words64[1] ^ chunk2.words64[1]; chunk1.words64[2] = chunk1.words64[2] ^ chunk2.words64[2]; chunk1.words64[3] = chunk1.words64[3] ^ chunk2.words64[3]; chunk1.words64[4] = chunk1.words64[4] ^ chunk2.words64[4]; chunk1.words64[5] = chunk1.words64[5] ^ chunk2.words64[5]; chunk1.words64[6] = chunk1.words64[6] ^ chunk2.words64[6]; chunk1.words64[7] = chunk1.words64[7] ^ chunk2.words64[7]; return chunk1; } BTC512 BTC512Concat(BTC256 chunk1, BTC256 chunk2) { BTC512 result; *((BTC256*)(&result)) = chunk1; *((BTC256*)(((unsigned char*)&result) + sizeof(chunk2))) = chunk2; return result; } // 5. Conversion functions // Conversion to NSData NSData* NSDataFromBTC160(BTC160 chunk) { return [[NSData alloc] initWithBytes:&chunk length:sizeof(chunk)]; } NSData* NSDataFromBTC256(BTC256 chunk) { return [[NSData alloc] initWithBytes:&chunk length:sizeof(chunk)]; } NSData* NSDataFromBTC512(BTC512 chunk) { return [[NSData alloc] initWithBytes:&chunk length:sizeof(chunk)]; } // Conversion from NSData. // If NSData is not big enough, returns BTCHash{160,256,512}Null. BTC160 BTC160FromNSData(NSData* data) { if (data.length < 160/8) return BTC160Null; BTC160 chunk = *((BTC160*)data.bytes); return chunk; } BTC256 BTC256FromNSData(NSData* data) { if (data.length < 256/8) return BTC256Null; BTC256 chunk = *((BTC256*)data.bytes); return chunk; } BTC512 BTC512FromNSData(NSData* data) { if (data.length < 512/8) return BTC512Null; BTC512 chunk = *((BTC512*)data.bytes); return chunk; } // Returns lowercase hex representation of the chunk NSString* NSStringFromBTC160(BTC160 chunk) { const int length = 20; char dest[2*length + 1]; const unsigned char *src = (unsigned char *)&chunk; for (int i = 0; i < length; ++i) { sprintf(dest + i*2, "%02x", (unsigned int)(src[i])); } return [[NSString alloc] initWithBytes:dest length:2*length encoding:NSASCIIStringEncoding]; } NSString* NSStringFromBTC256(BTC256 chunk) { const int length = 32; char dest[2*length + 1]; const unsigned char *src = (unsigned char *)&chunk; for (int i = 0; i < length; ++i) { sprintf(dest + i*2, "%02x", (unsigned int)(src[i])); } return [[NSString alloc] initWithBytes:dest length:2*length encoding:NSASCIIStringEncoding]; } NSString* NSStringFromBTC512(BTC512 chunk) { const int length = 64; char dest[2*length + 1]; const unsigned char *src = (unsigned char *)&chunk; for (int i = 0; i < length; ++i) { sprintf(dest + i*2, "%02x", (unsigned int)(src[i])); } return [[NSString alloc] initWithBytes:dest length:2*length encoding:NSASCIIStringEncoding]; } // Conversion from hex NSString (lower- or uppercase). // If string is invalid or data is too short, returns BTCHash{160,256,512}Null. BTC160 BTC160FromNSString(NSString* string) { return BTC160FromNSData(BTCDataFromHex(string)); } BTC256 BTC256FromNSString(NSString* string) { return BTC256FromNSData(BTCDataFromHex(string)); } BTC512 BTC512FromNSString(NSString* string) { return BTC512FromNSData(BTCDataFromHex(string)); } ================================================ FILE: Pods/CoreBitcoin/CoreBitcoin/BTCAddress.h ================================================ // CoreBitcoin by Oleg Andreev , WTFPL. #import // Addresses are Base58-encoded pieces of data representing various objects: // // 1. Public key (actually, a hash of a public key) address. Example: 19FGfswVqxNubJbh1NW8A4t51T9x9RDVWQ. // 2. Private key for uncompressed public key. Example: 5KQntKuhYWSRXNqp2yhdXzjekYAR7US3MT1715Mbv5CyUKV6hVe. // 3. Private key for compressed public key. Example: L3p8oAcQTtuokSCRHQ7i4MhjWc9zornvpJLfmg62sYpLRJF9woSu. // 4. Script address (P2SH). Example: 3NukJ6fYZJ5Kk8bPjycAnruZkE5Q7UW7i8. // // Testnet addresses are also supported with the following subclasses: // BTCPublicKeyAddressTestnet, // BTCPrivateKeyAddressTestnet, // BTCScriptHashAddressTestnet. // @class BTCKey; @class BTCNetwork; @interface BTCAddress : NSObject /*! * Allows subclasses to be instantiated by a call to a superclass: BTCAddress(string: "...") */ + (void) registerAddressClass:(nonnull Class)addressClass version:(uint8_t)version; /*! * Returns an instance of a specific subclass depending on version number. * Returns nil for unsupported addresses. */ + (nullable instancetype) addressWithString:(nullable NSString*)string; /*! * Initializes address with raw data. Should only be used in subclasses, base class will raise exception. * Returns an instance of a specific subclass depending on version number. */ + (nullable instancetype) addressWithData:(nullable NSData*)data; /*! * Returns binary contents of an address (without checksums or version number). * 20 bytes for hashes, 32 bytes for private key. */ @property(nonatomic, readonly, nonnull) NSData* data; /*! * Returns representation in base58 encoding. */ @property(nonatomic, readonly, nonnull) NSString* string; /*! * Returns a public version of this address. By default it is a receiver itself. * PrivateKeyAddress returns appropriate PublicKeyAddress. */ @property(nonatomic, readonly, nonnull) BTCAddress* publicAddress; /*! * Returns mainnet or testnet3 instance. */ @property(nonatomic, readonly, nonnull) BTCNetwork* network; /*! * Returns YES if this address is intended for testnet. */ @property(nonatomic, readonly) BOOL isTestnet; /*! * Clears contents of the data to prevent leaks. */ - (void) clear; // Returns an instance of a specific subclass depending on version number. // Returns nil for unsupported addresses. // DEPRECATED! Use `-addressWithString:` instead. + (nullable instancetype) addressWithBase58String:(nullable NSString*)string DEPRECATED_ATTRIBUTE; // Returns representation in base58 encoding. // DEPRECATED! Use -string instead. @property(nonatomic, readonly, nonnull) NSString* base58String DEPRECATED_ATTRIBUTE; @end // Standard public key address (19FGfswVqxNubJbh1NW8A4t51T9x9RDVWQ) @interface BTCPublicKeyAddress : BTCAddress @end @interface BTCPublicKeyAddressTestnet : BTCPublicKeyAddress @end // Private key in WIF format (5KQntKuhYWSRXNq... or L3p8oAcQTtuokSC..., base58-encoded) @interface BTCPrivateKeyAddress : BTCAddress // Private key itself is not compressed, but it has extra 0x01 byte to indicate // that derived pubkey must be compressed (as this affects the address derived from pubkey). @property(nonatomic, getter=isPublicKeyCompressed) BOOL publicKeyCompressed; // Returns BTCKey containing a key pair. Its public key is compressed as specified by the address. @property(nonatomic, readonly, nonnull) BTCKey* key; // Creates address from raw private key data. If the public key must be compressed, pass YES to publicKeyCompressed:. + (nullable instancetype) addressWithData:(nullable NSData*)data publicKeyCompressed:(BOOL)compressedPubkey; @end @interface BTCPrivateKeyAddressTestnet : BTCPrivateKeyAddress @end // P2SH address (e.g. 3NukJ6fYZJ5Kk8bPjycAnruZkE5Q7UW7i8) @interface BTCScriptHashAddress : BTCAddress @end @interface BTCScriptHashAddressTestnet : BTCScriptHashAddress @end ================================================ FILE: Pods/CoreBitcoin/CoreBitcoin/BTCAddress.m ================================================ // Oleg Andreev #import "BTCAddress.h" #import "BTCAddressSubclass.h" #import "BTCNetwork.h" #import "BTCData.h" #import "BTCBase58.h" #import "BTCKey.h" #import enum { BTCPublicKeyAddressVersion = 0, BTCPrivateKeyAddressVersion = 128, BTCScriptHashAddressVersion = 5, BTCPublicKeyAddressVersionTestnet = 111, BTCPrivateKeyAddressVersionTestnet = 239, BTCScriptHashAddressVersionTestnet = 196, }; @implementation BTCAddress { char* _cstring; NSData* _data; } - (id) init { if (self = [super init]) { _data = nil; } return self; } - (void) setData:(NSData *)data { id d = [data copy]; _data = d; } - (NSData*) data { return _data; } - (void) dealloc { // The data may be retained by someone and should not be cleared like that. // [self clear]; if (_cstring) free(_cstring); } + (instancetype) addressWithString:(NSString*)string { return [self addressWithBase58CString:[string cStringUsingEncoding:NSASCIIStringEncoding]]; } + (instancetype) addressWithBase58String:(NSString*)string { // DEPRECATED return [self addressWithString:string]; } // Initializes address with raw data. Should only be used in subclasses, base class will raise exception. + (instancetype) addressWithData:(NSData*)data { @throw [NSException exceptionWithName:@"BTCAddress Exception" reason:@"Cannot init base class with raw data. Please use specialized subclass." userInfo:nil]; return nil; } // prototype to make clang happy. + (instancetype) addressWithComposedData:(NSData*)data cstring:(const char*)cstring version:(uint8_t)version { return nil; } // Returns an instance of a specific subclass depending on version number. // Returns nil for unsupported addresses. + (id) addressWithBase58CString:(const char*)cstring { NSMutableData* composedData = BTCDataFromBase58CheckCString(cstring); if (!composedData) return nil; if (composedData.length < 2) return nil; uint8_t version = ((unsigned char*)composedData.bytes)[0]; NSDictionary* classes = [self registeredAddressClasses]; Class cls = classes[@(version)]; BTCAddress* address = [cls addressWithComposedData:composedData cstring:cstring version:version]; if (!address) { NSLog(@"BTCAddress: unknown address version: %d", version); } // Verify that address is compatible with the class being invoked. // So if someone asked to parse P2PKH address with P2SH string, they will get nil instead of P2SH instance. if (![address isKindOfClass:self]) { return nil; } // Securely erase decoded address data BTCDataClear(composedData); return address; } - (void) setBase58CString:(const char*)cstring { if (_cstring) { BTCSecureClearCString(_cstring); free(_cstring); _cstring = NULL; } if (cstring) { size_t len = strlen(cstring) + 1; // with \0 _cstring = malloc(len); memcpy(_cstring, cstring, len); } } // for subclasses - (NSMutableData*) dataForBase58Encoding { return nil; } - (const char*) base58CString { if (!_cstring) { NSMutableData* data = [self dataForBase58Encoding]; _cstring = BTCBase58CheckCStringWithData(data); BTCDataClear(data); } return _cstring; } // Returns representation in base58 encoding. - (NSString*) string { const char* cstr = [self base58CString]; if (!cstr) return nil; return [NSString stringWithCString:cstr encoding:NSASCIIStringEncoding]; } - (NSString*) base58String { // deprecated return [self string]; } - (BTCAddress*) publicAddress { return self; } - (BTCNetwork*) network { // TODO: use this as a primary source and replace isTestnet return [self isTestnet] ? [BTCNetwork testnet] : [BTCNetwork mainnet]; } - (BOOL) isTestnet { // TODO: replace this method with network property exclusively. return NO; } - (uint8_t) versionByte { return [[self class] BTCVersionPrefix]; } + (uint8_t) BTCVersionPrefix { [NSException raise:@"BTCAddress BTCVersionPrefix must be accessed via subclasses" format:@""]; return 0xff; } - (void) clear { BTCSecureClearCString(_cstring); BTCDataClear(_data); } - (NSString*) description { return [NSString stringWithFormat:@"<%@: %@>", [self class], self.string]; } - (BOOL) isEqual:(BTCAddress*)other { if (![other isKindOfClass:[BTCAddress class]]) return NO; return [self.string isEqualToString:other.string]; } // Known Addresses + (NSMutableDictionary*) registeredAddressClasses { static dispatch_once_t onceToken; static NSMutableDictionary* registeredAddressClasses; dispatch_once(&onceToken, ^{ registeredAddressClasses = [NSMutableDictionary dictionary]; }); return registeredAddressClasses; } // Registers a price source with a given name. + (void) registerAddressClass:(Class)addressClass version:(uint8_t)version { if (!addressClass) return; [self registeredAddressClasses][@(version)] = addressClass; } @end @implementation BTCPublicKeyAddress + (void) load { [BTCAddress registerAddressClass:self version:[self BTCVersionPrefix]]; } + (uint8_t) BTCVersionPrefix { return BTCPublicKeyAddressVersion; } #define BTCPublicKeyAddressLength 20 + (instancetype) addressWithData:(NSData*)data { if (!data) return nil; if (data.length != BTCPublicKeyAddressLength) { NSLog(@"+[BTCPublicKeyAddress addressWithData] cannot init with hash %d bytes long", (int)data.length); return nil; } BTCPublicKeyAddress* addr = [[self alloc] init]; addr.data = [NSMutableData dataWithData:data]; return addr; } + (instancetype) addressWithComposedData:(NSData*)composedData cstring:(const char*)cstring version:(uint8_t)version { if (composedData.length != (1 + BTCPublicKeyAddressLength)) { NSLog(@"BTCPublicKeyAddress: cannot init with %d bytes (need 20+1 bytes)", (int)composedData.length); return nil; } BTCPublicKeyAddress* addr = [[self alloc] init]; addr.data = [[NSMutableData alloc] initWithBytes:((const char*)composedData.bytes) + 1 length:composedData.length - 1]; addr.base58CString = cstring; return addr; } - (NSMutableData*) dataForBase58Encoding { NSMutableData* data = [NSMutableData dataWithLength:1 + BTCPublicKeyAddressLength]; char* buf = data.mutableBytes; buf[0] = [self versionByte]; memcpy(buf + 1, self.data.bytes, BTCPublicKeyAddressLength); return data; } @end @implementation BTCPublicKeyAddressTestnet + (void) load { [BTCAddress registerAddressClass:self version:[self BTCVersionPrefix]]; } + (uint8_t) BTCVersionPrefix { return BTCPublicKeyAddressVersionTestnet; } - (BOOL) isTestnet { return YES; } @end // Private key in Base58 format (5KQntKuhYWSRXNq... or L3p8oAcQTtuokSC...) @implementation BTCPrivateKeyAddress { BOOL _publicKeyCompressed; } + (void) load { [BTCAddress registerAddressClass:self version:[self BTCVersionPrefix]]; } + (uint8_t) BTCVersionPrefix { return BTCPrivateKeyAddressVersion; } #define BTCPrivateKeyAddressLength 32 + (instancetype) addressWithData:(NSData*)data { return [self addressWithData:data publicKeyCompressed:NO]; } + (instancetype) addressWithData:(NSData*)data publicKeyCompressed:(BOOL)compressedPubkey { if (!data) return nil; if (data.length != BTCPrivateKeyAddressLength) { NSLog(@"+[BTCPrivateKeyAddress addressWithData] cannot init with secret of %d bytes long", (int)data.length); return nil; } BTCPrivateKeyAddress* addr = [[self alloc] init]; addr.data = [NSMutableData dataWithData:data]; addr.publicKeyCompressed = compressedPubkey; return addr; } + (id) addressWithComposedData:(NSData*)data cstring:(const char*)cstring version:(uint8_t)version { if (data.length != (1 + BTCPrivateKeyAddressLength + 1) && data.length != (1 + BTCPrivateKeyAddressLength)) { NSLog(@"BTCPrivateKeyAddress: cannot init with %d bytes (need 1+32(+1) bytes)", (int)data.length); return nil; } // The life is not always easy. Somehow some people added one extra byte to a private key in Base58 to // let us know that the resulting public key must be compressed. // Private key itself is always 32 bytes. BOOL compressed = (data.length == (1+BTCPrivateKeyAddressLength+1)); BTCPrivateKeyAddress* addr = [[self alloc] init]; addr.data = [NSMutableData dataWithBytes:((const char*)data.bytes) + 1 length:32]; addr.base58CString = cstring; addr->_publicKeyCompressed = compressed; return addr; } - (BTCKey*) key { BTCKey* key = [[BTCKey alloc] initWithPrivateKey:self.data]; key.publicKeyCompressed = self.isPublicKeyCompressed; return key; } - (BTCAddress*) publicAddress { return [BTCPublicKeyAddress addressWithData:BTCHash160(self.key.publicKey)]; } // Private key itself is not compressed, but it has extra 0x01 byte to indicate // that derived pubkey must be compressed (as this affects the pubkey address). - (BOOL) isPublicKeyCompressed { return _publicKeyCompressed; } - (void) setPublicKeyCompressed:(BOOL)compressed { if (_publicKeyCompressed != compressed) { _publicKeyCompressed = compressed; self.base58CString = NULL; } } - (NSMutableData*) dataForBase58Encoding { NSMutableData* data = [NSMutableData dataWithLength:1 + BTCPrivateKeyAddressLength + (_publicKeyCompressed ? 1 : 0)]; char* buf = data.mutableBytes; buf[0] = [self versionByte]; memcpy(buf + 1, self.data.bytes, BTCPrivateKeyAddressLength); if (_publicKeyCompressed) { // Add extra byte 0x01 in the end. buf[1 + BTCPrivateKeyAddressLength] = (unsigned char)1; } return data; } @end @implementation BTCPrivateKeyAddressTestnet + (void) load { [BTCAddress registerAddressClass:self version:[self BTCVersionPrefix]]; } + (uint8_t) BTCVersionPrefix { return BTCPrivateKeyAddressVersionTestnet; } - (BTCAddress*) publicAddress { return [BTCPublicKeyAddressTestnet addressWithData:BTCHash160(self.key.publicKey)]; } - (BOOL) isTestnet { return YES; } @end // P2SH address (e.g. 3NukJ6fYZJ5Kk8bPjycAnruZkE5Q7UW7i8) @implementation BTCScriptHashAddress + (void) load { [BTCAddress registerAddressClass:self version:[self BTCVersionPrefix]]; } + (uint8_t) BTCVersionPrefix { return BTCScriptHashAddressVersion; } #define BTCScriptHashAddressLength 20 + (instancetype) addressWithData:(NSData*)data { if (!data) return nil; if (data.length != BTCScriptHashAddressLength) { NSLog(@"+[BTCScriptHashAddress addressWithData] cannot init with hash %d bytes long", (int)data.length); return nil; } BTCScriptHashAddress* addr = [[self alloc] init]; addr.data = [NSMutableData dataWithData:data]; return addr; } + (id) addressWithComposedData:(NSData*)data cstring:(const char*)cstring version:(uint8_t)version { if (data.length != (1 + BTCScriptHashAddressLength)) { NSLog(@"BTCPublicKeyAddress: cannot init with %d bytes (need 20+1 bytes)", (int)data.length); return nil; } BTCScriptHashAddress* addr = [[self alloc] init]; addr.data = [[NSMutableData dataWithBytes:((const char*)data.bytes) + 1 length:data.length - 1] copy]; NSLog(@"Created P2SH data: %p (%@)", addr.data, addr.data); addr.base58CString = cstring; return addr; } - (NSMutableData*) dataForBase58Encoding { NSMutableData* data = [NSMutableData dataWithLength:1 + BTCScriptHashAddressLength]; char* buf = data.mutableBytes; buf[0] = [self versionByte]; memcpy(buf + 1, self.data.bytes, BTCScriptHashAddressLength); return data; } @end @implementation BTCScriptHashAddressTestnet + (void) load { [BTCAddress registerAddressClass:self version:[self BTCVersionPrefix]]; } + (uint8_t) BTCVersionPrefix { return BTCScriptHashAddressVersionTestnet; } - (BOOL) isTestnet { return YES; } @end ================================================ FILE: Pods/CoreBitcoin/CoreBitcoin/BTCAddressSubclass.h ================================================ // CoreBitcoin by Oleg Andreev , WTFPL. #ifndef CoreBitcoin_BTCAddressSubclass_h #define CoreBitcoin_BTCAddressSubclass_h @interface BTCAddress () @property(nonatomic, readwrite) NSData* data; @end #endif ================================================ FILE: Pods/CoreBitcoin/CoreBitcoin/BTCAssetAddress.h ================================================ // CoreBitcoin by Oleg Andreev , WTFPL. #import "BTCAddress.h" @interface BTCAssetAddress : BTCAddress @property(nonatomic, readonly, nonnull) BTCAddress* bitcoinAddress; + (nonnull instancetype) addressWithBitcoinAddress:(nonnull BTCAddress*)btcAddress; @end ================================================ FILE: Pods/CoreBitcoin/CoreBitcoin/BTCAssetAddress.m ================================================ // CoreBitcoin by Oleg Andreev , WTFPL. #import "BTCAssetAddress.h" #import "BTCData.h" #import "BTCBase58.h" @interface BTCAssetAddress () @property(nonatomic, readwrite) BTCAddress* bitcoinAddress; @end // OpenAssets Address, e.g. akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy (corresponds to 16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM) @implementation BTCAssetAddress #define BTCAssetAddressNamespace 0x13 + (void) load { [BTCAddress registerAddressClass:self version:BTCAssetAddressNamespace]; } + (instancetype) addressWithBitcoinAddress:(BTCAddress*)btcAddress { if (!btcAddress) return nil; BTCAssetAddress* addr = [[self alloc] init]; addr.bitcoinAddress = btcAddress; return addr; } + (instancetype) addressWithString:(NSString*)string { NSMutableData* composedData = BTCDataFromBase58Check(string); uint8_t version = ((unsigned char*)composedData.bytes)[0]; return [self addressWithComposedData:composedData cstring:[string cStringUsingEncoding:NSUTF8StringEncoding] version:version]; } + (instancetype) addressWithComposedData:(NSData*)composedData cstring:(const char*)cstring version:(uint8_t)version { if (!composedData) return nil; if (composedData.length < 2) return nil; if (version == BTCAssetAddressNamespace) { // same for testnet and mainnet BTCAddress* btcAddr = [BTCAddress addressWithString:BTCBase58CheckStringWithData([composedData subdataWithRange:NSMakeRange(1, composedData.length - 1)])]; return [self addressWithBitcoinAddress:btcAddr]; } else { return nil; } } - (NSMutableData*) dataForBase58Encoding { NSMutableData* data = [NSMutableData dataWithLength:1]; char* buf = data.mutableBytes; buf[0] = BTCAssetAddressNamespace; [data appendData:[(BTCAssetAddress* /* cast only to expose the method that is defined in BTCAddress anyway */)self.bitcoinAddress dataForBase58Encoding]]; return data; } - (unsigned char) versionByte { return BTCAssetAddressNamespace; } - (BOOL) isTestnet { return self.bitcoinAddress.isTestnet; } @end ================================================ FILE: Pods/CoreBitcoin/CoreBitcoin/BTCAssetID.h ================================================ // CoreBitcoin by Oleg Andreev , WTFPL. #import "BTCAddress.h" @interface BTCAssetID : BTCAddress + (nullable instancetype) assetIDWithHash:(nullable NSData*)data; + (nullable instancetype) assetIDWithString:(nullable NSString*)string; @end ================================================ FILE: Pods/CoreBitcoin/CoreBitcoin/BTCAssetID.m ================================================ // CoreBitcoin by Oleg Andreev , WTFPL. #import "BTCAssetID.h" #import "BTCAddressSubclass.h" static const uint8_t BTCAssetIDVersionMainnet = 23; // "A" prefix static const uint8_t BTCAssetIDVersionTestnet = 115; @implementation BTCAssetID + (void) load { [BTCAddress registerAddressClass:self version:BTCAssetIDVersionMainnet]; [BTCAddress registerAddressClass:self version:BTCAssetIDVersionTestnet]; } #define BTCAssetIDLength 20 + (instancetype) assetIDWithString:(NSString*)string { return [self addressWithString:string]; } + (instancetype) assetIDWithHash:(NSData*)data { if (!data) return nil; if (data.length != BTCAssetIDLength) { NSLog(@"+[BTCAssetID addressWithData] cannot init with hash %d bytes long", (int)data.length); return nil; } BTCAssetID* addr = [[self alloc] init]; addr.data = [NSMutableData dataWithData:data]; return addr; } + (instancetype) addressWithComposedData:(NSData*)composedData cstring:(const char*)cstring version:(uint8_t)version { if (composedData.length != (1 + BTCAssetIDLength)) { NSLog(@"BTCAssetID: cannot init with %d bytes (need 20+1 bytes)", (int)composedData.length); return nil; } BTCAssetID* addr = [[self alloc] init]; addr.data = [[NSMutableData alloc] initWithBytes:((const char*)composedData.bytes) + 1 length:composedData.length - 1]; return addr; } - (NSMutableData*) dataForBase58Encoding { NSMutableData* data = [NSMutableData dataWithLength:1 + BTCAssetIDLength]; char* buf = data.mutableBytes; buf[0] = [self versionByte]; memcpy(buf + 1, self.data.bytes, BTCAssetIDLength); return data; } - (uint8_t) versionByte { // TODO: support testnet return BTCAssetIDVersionMainnet; } @end ================================================ FILE: Pods/CoreBitcoin/CoreBitcoin/BTCAssetType.h ================================================ // CoreBitcoin by Oleg Andreev , WTFPL. #import extern NSString* __nonnull const BTCAssetTypeBitcoin; extern NSString* __nonnull const BTCAssetTypeOpenAssets; ================================================ FILE: Pods/CoreBitcoin/CoreBitcoin/BTCAssetType.m ================================================ // CoreBitcoin by Oleg Andreev , WTFPL. #import "BTCAssetType.h" NSString* __nonnull const BTCAssetTypeBitcoin = @"bitcoin"; NSString* __nonnull const BTCAssetTypeOpenAssets = @"openassets"; ================================================ FILE: Pods/CoreBitcoin/CoreBitcoin/BTCBase58.h ================================================ // CoreBitcoin by Oleg Andreev , WTFPL. #import // Base58 is used for compact human-friendly representation of Bitcoin addresses and private keys. // Typically Base58-encoded text also contains a checksum. // Addresses look like 19FGfswVqxNubJbh1NW8A4t51T9x9RDVWQ // Private keys look like 5KQntKuhYWSRXNqp2yhdXzjekYAR7US3MT1715Mbv5CyUKV6hVe // // Here is what Satoshi said about Base58: // Why base-58 instead of standard base-64 encoding? // - Don't want 0OIl characters that look the same in some fonts and // could be used to create visually identical looking account numbers. // - A string with non-alphanumeric characters is not as easily accepted as an account number. // - E-mail usually won't line-break if there's no punctuation to break at. // - Double-clicking selects the whole number as one word if it's all alphanumeric. // See NS+BTCBase58.h for easy to use categories. // Returns data for a Base58 string without checksum // Data is mutable so you can clear sensitive information as soon as possible. NSMutableData* BTCDataFromBase58(NSString* string); NSMutableData* BTCDataFromBase58CString(const char* cstring); // Returns data for a Base58 string with checksum NSMutableData* BTCDataFromBase58Check(NSString* string); NSMutableData* BTCDataFromBase58CheckCString(const char* cstring); // String in Base58 without checksum, you need to free it yourself. // It's mutable so you can clear it securely yourself. char* BTCBase58CStringWithData(NSData* data); // Same as above, but returns an immutable autoreleased string. Suitable for non-sensitive data. NSString* BTCBase58StringWithData(NSData* data); // String in Base58 with checksum, you need to free it yourself. // It's mutable so you can clear it securely yourself. char* BTCBase58CheckCStringWithData(NSData* data); // Same as above, but returns an immutable autoreleased string. Suitable for non-sensitive data. NSString* BTCBase58CheckStringWithData(NSData* data); ================================================ FILE: Pods/CoreBitcoin/CoreBitcoin/BTCBase58.m ================================================ // Oleg Andreev #import "BTCBase58.h" #import "BTCData.h" #import static const char* BTCBase58Alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; NSMutableData* BTCDataFromBase58(NSString* string) { return BTCDataFromBase58CString([string cStringUsingEncoding:NSASCIIStringEncoding]); } NSMutableData* BTCDataFromBase58Check(NSString* string) { return BTCDataFromBase58CheckCString([string cStringUsingEncoding:NSASCIIStringEncoding]); } NSMutableData* BTCDataFromBase58CString(const char* cstring) { if (cstring == NULL) return nil; // empty string -> empty data. if (cstring[0] == '\0') return [NSMutableData data]; NSMutableData* result = nil; BN_CTX* pctx = BN_CTX_new(); __block BIGNUM bn58; BN_init(&bn58); BN_set_word(&bn58, 58); __block BIGNUM bn; BN_init(&bn); BN_zero(&bn); __block BIGNUM bnChar; BN_init(&bnChar); void(^finish)() = ^{ if (pctx) BN_CTX_free(pctx); BN_clear_free(&bn58); BN_clear_free(&bn); BN_clear_free(&bnChar); }; while (isspace(*cstring)) cstring++; // Convert big endian string to bignum for (const char* p = cstring; *p; p++) { const char* p1 = strchr(BTCBase58Alphabet, *p); if (p1 == NULL) { while (isspace(*p)) p++; if (*p != '\0') { finish(); return nil; } break; } BN_set_word(&bnChar, (BN_ULONG)(p1 - BTCBase58Alphabet)); if (!BN_mul(&bn, &bn, &bn58, pctx)) { finish(); return nil; } if (!BN_add(&bn, &bn, &bnChar)) { finish(); return nil; } } // Get bignum as little endian data NSMutableData* bndata = nil; { size_t bnsize = BN_bn2mpi(&bn, NULL); if (bnsize <= 4) { bndata = [NSMutableData data]; } else { bndata = [NSMutableData dataWithLength:bnsize]; BN_bn2mpi(&bn, bndata.mutableBytes); [bndata replaceBytesInRange:NSMakeRange(0, 4) withBytes:NULL length:0]; BTCDataReverse(bndata); } } size_t bnsize = bndata.length; // Trim off sign byte if present if (bnsize >= 2 && ((unsigned char*)bndata.bytes)[bnsize - 1] == 0 && ((unsigned char*)bndata.bytes)[bnsize - 2] >= 0x80) { bnsize -= 1; [bndata setLength:bnsize]; } // Restore leading zeros int nLeadingZeros = 0; for (const char* p = cstring; *p == BTCBase58Alphabet[0]; p++) nLeadingZeros++; result = [NSMutableData dataWithLength:nLeadingZeros + bnsize]; // Copy the bignum to the beginning of array. We'll reverse it then and zeros will become leading zeros. [result replaceBytesInRange:NSMakeRange(0, bnsize) withBytes:bndata.bytes length:bnsize]; // Convert little endian data to big endian BTCDataReverse(result); finish(); return result; } NSMutableData* BTCDataFromBase58CheckCString(const char* cstring) { if (cstring == NULL) return nil; NSMutableData* result = BTCDataFromBase58CString(cstring); size_t length = result.length; if (length < 4) { return nil; } NSData* hash = BTCHash256([result subdataWithRange:NSMakeRange(0, length - 4)]); // Last 4 bytes should be equal first 4 bytes of the hash. if (memcmp(hash.bytes, result.bytes + length - 4, 4) != 0) { return nil; } [result setLength:length - 4]; return result; } char* BTCBase58CStringWithData(NSData* data) { if (!data) return NULL; BN_CTX* pctx = BN_CTX_new(); __block BIGNUM bn58; BN_init(&bn58); BN_set_word(&bn58, 58); __block BIGNUM bn0; BN_init(&bn0); BN_zero(&bn0); __block BIGNUM bn; BN_init(&bn); BN_zero(&bn); __block BIGNUM dv; BN_init(&dv); BN_zero(&dv); __block BIGNUM rem; BN_init(&rem); BN_zero(&rem); void(^finish)() = ^{ if (pctx) BN_CTX_free(pctx); BN_clear_free(&bn58); BN_clear_free(&bn0); BN_clear_free(&bn); BN_clear_free(&dv); BN_clear_free(&rem); }; // Convert big endian data to little endian. // Extra zero at the end make sure bignum will interpret as a positive number. NSMutableData* tmp = BTCReversedMutableData(data); tmp.length += 1; // Convert little endian data to bignum { NSUInteger size = tmp.length; NSMutableData* mdata = [tmp mutableCopy]; // Reverse to convert to OpenSSL bignum endianess BTCDataReverse(mdata); // BIGNUM's byte stream format expects 4 bytes of // big endian size data info at the front [mdata replaceBytesInRange:NSMakeRange(0, 0) withBytes:"\0\0\0\0" length:4]; unsigned char* bytes = mdata.mutableBytes; bytes[0] = (size >> 24) & 0xff; bytes[1] = (size >> 16) & 0xff; bytes[2] = (size >> 8) & 0xff; bytes[3] = (size >> 0) & 0xff; BN_mpi2bn(bytes, (int)mdata.length, &bn); } // Expected size increase from base58 conversion is approximately 137% // use 138% to be safe NSMutableData* stringData = [NSMutableData dataWithCapacity:data.length*138/100 + 1]; while (BN_cmp(&bn, &bn0) > 0) { if (!BN_div(&dv, &rem, &bn, &bn58, pctx)) { finish(); return nil; } BN_copy(&bn, &dv); unsigned long c = BN_get_word(&rem); [stringData appendBytes:BTCBase58Alphabet + c length:1]; } finish(); // Leading zeroes encoded as base58 ones ("1") const unsigned char* pbegin = data.bytes; const unsigned char* pend = data.bytes + data.length; for (const unsigned char* p = pbegin; p < pend && *p == 0; p++) { [stringData appendBytes:BTCBase58Alphabet + 0 length:1]; } // Convert little endian std::string to big endian BTCDataReverse(stringData); [stringData appendBytes:"" length:1]; char* r = malloc(stringData.length); memcpy(r, stringData.bytes, stringData.length); BTCDataClear(stringData); return r; } // String in Base58 with checksum char* BTCBase58CheckCStringWithData(NSData* immutabledata) { if (!immutabledata) return NULL; // add 4-byte hash check to the end NSMutableData* data = [immutabledata mutableCopy]; NSData* checksum = BTCHash256(data); [data appendBytes:checksum.bytes length:4]; char* result = BTCBase58CStringWithData(data); BTCDataClear(data); return result; } NSString* BTCBase58StringWithData(NSData* data) { if (!data) return nil; char* s = BTCBase58CStringWithData(data); id r = [NSString stringWithCString:s encoding:NSASCIIStringEncoding]; BTCSecureClearCString(s); free(s); return r; } NSString* BTCBase58CheckStringWithData(NSData* data) { if (!data) return nil; char* s = BTCBase58CheckCStringWithData(data); id r = [NSString stringWithCString:s encoding:NSASCIIStringEncoding]; BTCSecureClearCString(s); free(s); return r; } ================================================ FILE: Pods/CoreBitcoin/CoreBitcoin/BTCBigNumber.h ================================================ // CoreBitcoin by Oleg Andreev , WTFPL. #import #import // Bitcoin-flavoured big number wrapping OpenSSL BIGNUM. // It is doing byte ordering like bitcoind does to stay compatible. // BTCBigNumber is immutable. BTCMutableBigNumber is its mutable counterpart. // -copy always returns immutable instance, like in other Cocoa containers. @class BTCBigNumber; @class BTCMutableBigNumber; @interface BTCBigNumber : NSObject @property(nonatomic, readonly) uint32_t compact; // compact representation used for the difficulty target @property(nonatomic, readonly) uint32_t uint32value; @property(nonatomic, readonly) int32_t int32value; @property(nonatomic, readonly) uint64_t uint64value; @property(nonatomic, readonly) int64_t int64value; @property(nonatomic, readonly) NSString* hexString; @property(nonatomic, readonly) NSString* decimalString; @property(nonatomic, readonly) NSData* signedLittleEndian; @property(nonatomic, readonly) NSData* unsignedBigEndian; // Deprecated. Use `-signedLittleEndian` instead. @property(nonatomic, readonly) NSData* littleEndianData DEPRECATED_ATTRIBUTE; // Deprecated. Use `-unsignedBigEndian` instead. @property(nonatomic, readonly) NSData* unsignedData DEPRECATED_ATTRIBUTE; // Pointer to an internal BIGNUM value. You should not modify it. // To modify, use [[bn mutableCopy] mutableBIGNUM] methods. @property(nonatomic, readonly) const BIGNUM* BIGNUM; @property(nonatomic, readonly) BOOL isZero; @property(nonatomic, readonly) BOOL isOne; // BTCBigNumber returns always the same object for these constants. // BTCMutableBigNumber returns a new object every time. + (instancetype) zero; // 0 + (instancetype) one; // 1 + (instancetype) negativeOne; // -1 - (id) init; - (id) initWithCompact:(uint32_t)compact; - (id) initWithUInt32:(uint32_t)value; - (id) initWithInt32:(int32_t)value; - (id) initWithUInt64:(uint64_t)value; - (id) initWithInt64:(int64_t)value; - (id) initWithSignedLittleEndian:(NSData*)data; - (id) initWithUnsignedBigEndian:(NSData*)data; - (id) initWithLittleEndianData:(NSData*)data DEPRECATED_ATTRIBUTE; - (id) initWithUnsignedData:(NSData*)data DEPRECATED_ATTRIBUTE; // Initialized with OpenSSL representation of bignum. - (id) initWithBIGNUM:(const BIGNUM*)bignum; // Inits with setString:base: - (id) initWithString:(NSString*)string base:(NSUInteger)base; // Same as initWithString:base:16 - (id) initWithHexString:(NSString*)hexString DEPRECATED_ATTRIBUTE; // Same as initWithString:base:10 - (id) initWithDecimalString:(NSString*)decimalString; - (NSString*) stringInBase:(NSUInteger)base; // Re-declared copy and mutableCopy to provide exact return type. - (BTCBigNumber*) copy; - (BTCMutableBigNumber*) mutableCopy; // TODO: maybe add support for hash, figure out what the heck is that. //void set_hash(hash_digest load_hash); //hash_digest hash() const; // Returns MIN(self, other) - (BTCBigNumber*) min:(BTCBigNumber*)other; // Returns MAX(self, other) - (BTCBigNumber*) max:(BTCBigNumber*)other; - (BOOL) less:(BTCBigNumber*)other; - (BOOL) lessOrEqual:(BTCBigNumber*)other; - (BOOL) greater:(BTCBigNumber*)other; - (BOOL) greaterOrEqual:(BTCBigNumber*)other; // Divides receiver by another bignum. // Returns an array of two new BTCBigNumber instances: @[ quotient, remainder ] - (NSArray*) divmod:(BTCBigNumber*)other; // Destroys sensitive data and sets the value to 0. // It is also called on dealloc. // This method is available for both mutable and immutable numbers by design. - (void) clear; @end @interface BTCMutableBigNumber : BTCBigNumber @property(nonatomic, readwrite) uint32_t compact; // compact representation used for the difficulty target @property(nonatomic, readwrite) uint32_t uint32value; @property(nonatomic, readwrite) int32_t int32value; @property(nonatomic, readwrite) uint64_t uint64value; @property(nonatomic, readwrite) int64_t int64value; @property(nonatomic, readwrite) NSString* hexString; @property(nonatomic, readwrite) NSString* decimalString; @property(nonatomic, readwrite) NSData* signedLittleEndian; @property(nonatomic, readwrite) NSData* unsignedBigEndian; @property(nonatomic, readwrite) NSData* littleEndianData DEPRECATED_ATTRIBUTE; @property(nonatomic, readwrite) NSData* unsignedData DEPRECATED_ATTRIBUTE; @property(nonatomic, readonly) BIGNUM* mutableBIGNUM; // BTCBigNumber returns always the same object for these constants. // BTCMutableBigNumber returns a new object every time. + (instancetype) zero; // 0 + (instancetype) one; // 1 + (instancetype) negativeOne; // -1 // Supports bases from 2 to 36. For base 2 allows optional 0b prefix, base 16 allows optional 0x prefix. Spaces are ignored. - (void) setString:(NSString*)string base:(NSUInteger)base; // Operators modify the receiver and return self. // To create a new instance z = x + y use copy method: z = [[x copy] add:y] - (instancetype) add:(BTCBigNumber*)other; // += - (instancetype) add:(BTCBigNumber*)other mod:(BTCBigNumber*)mod; - (instancetype) subtract:(BTCBigNumber*)other; // -= - (instancetype) subtract:(BTCBigNumber*)other mod:(BTCBigNumber*)mod; - (instancetype) multiply:(BTCBigNumber*)other; // *= - (instancetype) multiply:(BTCBigNumber*)other mod:(BTCBigNumber*)mod; - (instancetype) divide:(BTCBigNumber*)other; // /= - (instancetype) mod:(BTCBigNumber*)other; // %= - (instancetype) lshift:(unsigned int)shift; // <<= - (instancetype) rshift:(unsigned int)shift; // >>= - (instancetype) inverseMod:(BTCBigNumber*)mod; // (a^-1) mod n - (instancetype) exp:(BTCBigNumber*)power; - (instancetype) exp:(BTCBigNumber*)power mod:(BTCBigNumber *)mod; @end ================================================ FILE: Pods/CoreBitcoin/CoreBitcoin/BTCBigNumber.m ================================================ // Oleg Andreev #import "BTCBigNumber.h" #import "BTCData.h" #define BTCBigNumberCompare(a, b) (BN_cmp(&(a->_bignum), &(b->_bignum))) @implementation BTCBigNumber { @package BIGNUM _bignum; // Used as a guard in case a private setter is called on immutable instance after initialization. BOOL _immutable; } @dynamic compact; @dynamic uint32value; @dynamic int32value; @dynamic uint64value; @dynamic int64value; @dynamic hexString; @dynamic decimalString; @dynamic signedLittleEndian; @dynamic unsignedBigEndian; @dynamic littleEndianData; // deprecated @dynamic unsignedData; // deprecated + (instancetype) zero { static BTCBigNumber* bn = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ bn = [[self alloc] init]; BN_zero(&(bn->_bignum)); }); return bn; } + (instancetype) one { static BTCBigNumber* bn = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ bn = [[self alloc] init]; BN_one(&(bn->_bignum)); }); return bn; } + (instancetype) negativeOne { static BTCBigNumber* bn = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ bn = [[self alloc] initWithInt32:-1]; }); return bn; } - (id) init { if (self = [super init]) { BN_init(&_bignum); } return self; } - (void) dealloc { BN_clear_free(&_bignum); } - (void) clear { BN_clear(&_bignum); } - (void) throwIfImmutable { if (_immutable) { @throw [NSException exceptionWithName:@"Immutable BTCBigNumber is modified" reason:@"" userInfo:nil]; } } // Since we use private setters in the init* methods, - (id) initWithCompact:(uint32_t)value { if (self = [self init]) self.compact = value; _immutable = YES; return self; } - (id) initWithUInt32:(uint32_t)value { if (self = [self init]) self.uint32value = value; _immutable = YES; return self; } - (id) initWithInt32:(int32_t)value { if (self = [self init]) self.int32value = value; _immutable = YES; return self; } - (id) initWithUInt64:(uint64_t)value { if (self = [self init]) self.uint64value = value; _immutable = YES; return self; } - (id) initWithInt64:(int64_t)value { if (self = [self init]) self.int64value = value; _immutable = YES; return self; } - (id) initWithSignedLittleEndian:(NSData *)data { if (!data) return nil; if (self = [self init]) self.signedLittleEndian = data; _immutable = YES; return self; } - (id) initWithUnsignedBigEndian:(NSData *)data { if (!data) return nil; if (self = [self init]) self.unsignedBigEndian = data; _immutable = YES; return self; } - (id) initWithLittleEndianData:(NSData*)data { // deprecated if (!data) return nil; if (self = [self init]) self.signedLittleEndian = data; _immutable = YES; return self; } - (id) initWithUnsignedData:(NSData *)data { // deprecated if (!data) return nil; if (self = [self init]) self.unsignedBigEndian = data; _immutable = YES; return self; } - (id) initWithString:(NSString*)string base:(NSUInteger)base { if (!string) return nil; if (self = [self init]) [self setString:string base:base]; _immutable = YES; return self; } - (id) initWithHexString:(NSString*)hexString { return [self initWithString:hexString base:16]; } - (id) initWithDecimalString:(NSString*)decimalString { return [self initWithString:decimalString base:10]; } - (id) initWithBIGNUM:(const BIGNUM*)otherBIGNUM { if (self = [self init]) { BN_copy(&_bignum, otherBIGNUM); } return self; } - (const BIGNUM*) BIGNUM { return &_bignum; } - (BOOL) isZero { return BN_is_zero(&_bignum); } - (BOOL) isOne { return BN_is_one(&_bignum); } #pragma mark - NSObject - (BTCBigNumber*) copy { return [self copyWithZone:nil]; } - (BTCMutableBigNumber*) mutableCopy { return [self mutableCopyWithZone:nil]; } - (BTCBigNumber*) copyWithZone:(NSZone *)zone { BTCBigNumber* to = [[BTCBigNumber alloc] init]; if (BN_copy(&(to->_bignum), &_bignum)) { return to; } return nil; } - (BTCMutableBigNumber*) mutableCopyWithZone:(NSZone *)zone { BTCMutableBigNumber* to = [[BTCMutableBigNumber alloc] init]; if (BN_copy(&(to->_bignum), &_bignum)) { return to; } return nil; } - (BOOL) isEqual:(BTCBigNumber*)other { if (![other isKindOfClass:[BTCBigNumber class]]) return NO; return BTCBigNumberCompare(self, other) == NSOrderedSame; } - (NSComparisonResult)compare:(BTCBigNumber *)other { return BTCBigNumberCompare(self, other); } - (NSUInteger)hash { return (NSUInteger)BN_get_word(&_bignum); } - (NSString*) description { return [NSString stringWithFormat:@"<%@:0x%p 0x%@ (%@)>", [self class], self, [self stringInBase:16], [self stringInBase:10]]; } #pragma mark - Comparison - (BTCBigNumber*) min:(BTCBigNumber*)other { return (BTCBigNumberCompare(self, other) <= 0) ? self : other; } - (BTCBigNumber*) max:(BTCBigNumber*)other { return (BTCBigNumberCompare(self, other) >= 0) ? self : other; } - (BOOL) less:(BTCBigNumber *)other { return BTCBigNumberCompare(self, other) < 0; } - (BOOL) lessOrEqual:(BTCBigNumber *)other { return BTCBigNumberCompare(self, other) <= 0; } - (BOOL) greater:(BTCBigNumber *)other { return BTCBigNumberCompare(self, other) > 0; } - (BOOL) greaterOrEqual:(BTCBigNumber *)other { return BTCBigNumberCompare(self, other) >= 0; } #pragma mark - Conversion - (NSString*) hexString { return [self stringInBase:16]; } - (void) setHexString:(NSString *)hexString { [self throwIfImmutable]; [self setString:hexString base:16]; } - (NSString*) decimalString { return [self stringInBase:10]; } - (void) setDecimalString:(NSString *)decimalString { [self throwIfImmutable]; [self setString:decimalString base:10]; } - (void) setString:(NSString*)string base:(NSUInteger)base { [self throwIfImmutable]; if (base > 36 || base < 2) return; BN_set_word(&_bignum, 0); if (string.length == 0) return; const unsigned char *psz = (const unsigned char*)[string cStringUsingEncoding:NSASCIIStringEncoding]; while (isspace(*psz)) psz++; bool isNegative = false; if (*psz == '-') { isNegative = true; psz++; } // Strip 0x from a 16-base string and 0b from a binary string. // String is null-terminated, so it's safe to check for [1] if [0] is not null if (base == 16 && psz[0] == '0' && tolower(psz[1]) == 'x') psz += 2; if (base == 2 && psz[0] == '0' && tolower(psz[1]) == 'b') psz += 2; while (isspace(*psz)) psz++; static const signed char digits[256] = { // 0 1 2 3 4 5 6 7 8 9 A B C D E F 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, // @ A B C D E F G H I J K L M N O 0, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // P Q R S T U V W X Y Z [ \ ] ^ _ 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 0, 0, 0, 0, 0, // ` a b c d e f g h i j k l m n o 0, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // p q r s t u v w x y z { | } ~ del 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; BN_CTX* pctx = NULL; BIGNUM bnBase; BN_init(&bnBase); BN_set_word(&bnBase, (BN_ULONG)base); while (1) { unsigned char c = (unsigned char)*psz++; if (c == 0) break; // break when null-terminator is hit if (c != 0x20) { // skip space int n = digits[c]; if (n == 0 && c != 0x30) break; // discard characters outside the 36-char range if (n >= base) break; // discard character outside the base range. if (base == 16) { BN_lshift(&_bignum, &_bignum, 4); } else if (base == 8) { BN_lshift(&_bignum, &_bignum, 3); } else if (base == 4) { BN_lshift(&_bignum, &_bignum, 2); } else if (base == 2) { BN_lshift(&_bignum, &_bignum, 1); } else if (base == 32) { BN_lshift(&_bignum, &_bignum, 5); } else { if (!pctx) pctx = BN_CTX_new(); BN_mul(&_bignum, &_bignum, &bnBase, pctx); } BN_add_word(&_bignum, n); } } if (isNegative) { BN_set_negative(&_bignum, 1); } BN_free(&bnBase); if (pctx) BN_CTX_free(pctx); } - (NSString*) stringInBase:(NSUInteger)base { if (base > 36 || base < 2) return nil; NSMutableData* resultData = nil; BN_CTX* pctx = BN_CTX_new(); BIGNUM bnBase; BN_init(&bnBase); BN_set_word(&bnBase, (BN_ULONG)base); BIGNUM bn0; BN_init(&bn0); BN_zero(&bn0); BIGNUM bn; BN_init(&bn); BN_copy(&bn, &_bignum); BN_set_negative(&bn, false); BIGNUM dv; BN_init(&dv); BIGNUM rem; BN_init(&rem); if (BN_cmp(&bn, &bn0) == 0) { resultData = [NSMutableData dataWithBytes:"0" length:1]; } else { while (BN_cmp(&bn, &bn0) > 0) { if (!BN_div(&dv, &rem, &bn, &bnBase, pctx)) { NSLog(@"BTCBigNumber: stringInBase failed to BN_div"); break; } BN_copy(&bn, &dv); BN_ULONG c = BN_get_word(&rem); if (!resultData) resultData = [NSMutableData data]; // 36 characters: 0123456789 123456789 123456789 12345 unsigned char ch = "0123456789abcdefghijklmnopqrstuvwxyz"[c]; [resultData replaceBytesInRange:NSMakeRange(0, 0) withBytes:&ch length:1]; } if (resultData && BN_is_negative(&_bignum)) { unsigned char ch = '-'; [resultData replaceBytesInRange:NSMakeRange(0, 0) withBytes:&ch length:1]; } } BN_clear_free(&dv); BN_clear_free(&rem); BN_clear_free(&bn); BN_free(&bn0); BN_free(&bnBase); BN_CTX_free(pctx); return resultData ? [[NSString alloc] initWithData:resultData encoding:NSASCIIStringEncoding] : nil; } // The "compact" format is a representation of a whole // number N using an unsigned 32bit number similar to a // floating point format. // The most significant 8 bits are the unsigned exponent of base 256. // This exponent can be thought of as "number of bytes of N". // The lower 23 bits are the mantissa. // Bit number 24 (0x800000) represents the sign of N. // N = (-1^sign) * mantissa * 256^(exponent-3) // // Satoshi's original implementation used BN_bn2mpi() and BN_mpi2bn(). // MPI uses the most significant bit of the first byte as sign. // Thus 0x1234560000 is compact (0x05123456) // and 0xc0de000000 is compact (0x0600c0de) // (0x05c0de00) would be -0x40de000000 // // Bitcoin only uses this "compact" format for encoding difficulty // targets, which are unsigned 256bit quantities. Thus, all the // complexities of the sign bit and using base 256 are probably an // implementation accident. // // This implementation directly uses shifts instead of going // through an intermediate MPI representation. - (uint32_t) compact { uint32_t size = BN_num_bytes(&_bignum); uint32_t result = 0; if (size <= 3) { result = (uint32_t)(BN_get_word(&_bignum) << 8*(3-size)); } else { BIGNUM bn; BN_init(&bn); BN_rshift(&bn, &_bignum, 8*(size-3)); result = (uint32_t)BN_get_word(&bn); } // The 0x00800000 bit denotes the sign. // Thus, if it is already set, divide the mantissa by 256 and increase the exponent. if (result & 0x00800000) { result >>= 8; size++; } result |= size << 24; result |= (BN_is_negative(&_bignum) ? 0x00800000 : 0); return result; } - (void) setCompact:(uint32_t)value { [self throwIfImmutable]; unsigned int size = value >> 24; bool isNegative = (value & 0x00800000) != 0; unsigned int word = value & 0x007fffff; if (size <= 3) { word >>= 8*(3-size); BN_set_word(&_bignum, word); } else { BN_set_word(&_bignum, word); BN_lshift(&_bignum, &_bignum, 8*(size-3)); } BN_set_negative(&_bignum, isNegative); } - (uint32_t) uint32value { return (uint32_t)BN_get_word(&_bignum); } - (void) setUint32value:(uint32_t)value { [self throwIfImmutable]; BN_set_word(&_bignum, value); } - (int32_t) int32value { uint32_t value = [self uint32value]; if (!BN_is_negative(&_bignum)) { if (value > INT32_MAX) return INT32_MAX; else return value; } else { if (value > INT32_MAX) return INT32_MIN; else return -value; } } - (void) setInt32value:(int32_t)value { [self throwIfImmutable]; if (value >= 0) { self.uint32value = value; } else { self.int64value = value; } } - (uint64_t) uint64value { return (uint64_t)BN_get_word(&_bignum); } - (int64_t) int64value { return (int64_t)BN_get_word(&_bignum); } - (void) setUint64value:(uint64_t)value { [self throwIfImmutable]; [self setUint64valuePrivate:value negative:NO]; } - (void) setInt64value:(int64_t)value { [self throwIfImmutable]; bool isNegative = NO; uint64_t uintValue; if (value < 0) { // Since the minimum signed integer cannot be represented as // positive so long as its type is signed, and it's not well-defined // what happens if you make it unsigned before negating it, we // instead increment the negative integer by 1, convert it, then // increment the (now positive) unsigned integer by 1 to compensate. uintValue = -(value + 1); ++uintValue; isNegative = YES; } else { uintValue = value; } [self setUint64valuePrivate:uintValue negative:isNegative]; } - (void) setUint64valuePrivate:(uint64_t)value negative:(BOOL)isNegative { // Numbers are represented in OpenSSL using the MPI format. 4 byte length. unsigned char rawMPI[sizeof(value) + 6]; unsigned char* currentByte = &rawMPI[4]; BOOL leadingZeros = YES; for (int i = 0; i < 8; ++i) { uint8_t c = (value >> 56) & 0xff; value <<= 8; if (leadingZeros) { if (c == 0) continue; // Skip beginning zeros if (c & 0x80) { *currentByte = (isNegative ? 0x80 : 0); ++currentByte; } else if (isNegative) { c |= 0x80; } leadingZeros = false; } *currentByte = c; ++currentByte; } unsigned long size = currentByte - (rawMPI + 4); rawMPI[0] = (size >> 24) & 0xff; rawMPI[1] = (size >> 16) & 0xff; rawMPI[2] = (size >> 8) & 0xff; rawMPI[3] = (size) & 0xff; BN_mpi2bn(rawMPI, (int)(currentByte - rawMPI), &_bignum); } - (NSData*) signedLittleEndian { size_t size = BN_bn2mpi(&_bignum, NULL); if (size <= 4) { return [NSData data]; } NSMutableData* data = [NSMutableData dataWithLength:size]; BN_bn2mpi(&_bignum, data.mutableBytes); [data replaceBytesInRange:NSMakeRange(0, 4) withBytes:NULL length:0]; BTCDataReverse(data); return data; } - (void) setSignedLittleEndian:(NSData *)data { [self throwIfImmutable]; NSUInteger size = data.length; NSMutableData* mdata = [data mutableCopy]; // Reverse to convert to OpenSSL bignum endianess BTCDataReverse(mdata); // BIGNUM's byte stream format expects 4 bytes of // big endian size data info at the front [mdata replaceBytesInRange:NSMakeRange(0, 0) withBytes:"\0\0\0\0" length:4]; unsigned char* bytes = mdata.mutableBytes; bytes[0] = (size >> 24) & 0xff; bytes[1] = (size >> 16) & 0xff; bytes[2] = (size >> 8) & 0xff; bytes[3] = (size >> 0) & 0xff; BN_mpi2bn(bytes, (int)mdata.length, &_bignum); } // deprecated - (NSData*) littleEndianData { return self.signedLittleEndian; } // deprecated - (void) setLittleEndianData:(NSData *)data { [self setSignedLittleEndian:data]; } - (NSData*) unsignedBigEndian { int num_bytes = BN_num_bytes(&_bignum); NSMutableData* data = [[NSMutableData alloc] initWithLength:32]; // zeroed data int copied_bytes = BN_bn2bin(&_bignum, &data.mutableBytes[32 - num_bytes]); // fill the tail of the data so it's zero-padded to the left if (copied_bytes != num_bytes) return nil; return data; } - (void) setUnsignedBigEndian:(NSData *)data { [self throwIfImmutable]; if (!data) return; if (!BN_bin2bn(data.bytes, (int)data.length, &_bignum)) { return; } } // deprecated - (NSData*) unsignedData { return self.unsignedBigEndian; } // deprecated - (void) setUnsignedData:(NSData *)data { self.unsignedBigEndian = data; } // Divides receiver by another bignum. // Returns an array of two new BTCBigNumber instances: @[ quotient, remainder ] - (NSArray*) divmod:(BTCBigNumber*)other { BN_CTX* pctx = BN_CTX_new(); BTCBigNumber* r = [BTCBigNumber new]; BTCBigNumber* m = [BTCBigNumber new]; BN_div(&(r->_bignum), &(m->_bignum), &(self->_bignum), &(other->_bignum), pctx); BN_CTX_free(pctx); return @[r, m]; } #pragma mark - Util - (void) withContext:(void(^)(BN_CTX* pctx))block { BN_CTX* pctx = BN_CTX_new(); block(pctx); BN_CTX_free(pctx); } @end @implementation BTCMutableBigNumber @dynamic compact; @dynamic uint32value; @dynamic int32value; @dynamic uint64value; @dynamic int64value; @dynamic hexString; @dynamic decimalString; @dynamic signedLittleEndian; @dynamic unsignedBigEndian; @dynamic littleEndianData; @dynamic unsignedData; - (BIGNUM*) mutableBIGNUM { return &(self->_bignum); } + (instancetype) zero { return [[BTCBigNumber zero] mutableCopy]; } + (instancetype) one { return [[BTCBigNumber one] mutableCopy]; } + (instancetype) negativeOne { return [[BTCBigNumber negativeOne] mutableCopy]; } // We are mutable, disable checks. - (void) throwIfImmutable { } - (void) setString:(NSString*)string base:(NSUInteger)base { [super setString:string base:base]; } #pragma mark - Operations - (instancetype) add:(BTCBigNumber*)other { // += BN_add(&(self->_bignum), &(self->_bignum), &(other->_bignum)); return self; } - (instancetype) add:(BTCBigNumber*)other mod:(BTCBigNumber*)mod { BN_CTX* pctx = BN_CTX_new(); BN_mod_add(&(self->_bignum), &(self->_bignum), &(other->_bignum), &(mod->_bignum), pctx); BN_CTX_free(pctx); return self; } - (instancetype) subtract:(BTCBigNumber *)other { // -= BN_sub(&(self->_bignum), &(self->_bignum), &(other->_bignum)); return self; } - (instancetype) subtract:(BTCBigNumber*)other mod:(BTCBigNumber*)mod { BN_CTX* pctx = BN_CTX_new(); BN_mod_sub(&(self->_bignum), &(self->_bignum), &(other->_bignum), &(mod->_bignum), pctx); BN_CTX_free(pctx); return self; } - (instancetype) multiply:(BTCBigNumber*)other { // *= BN_CTX* pctx = BN_CTX_new(); BN_mul(&(self->_bignum), &(self->_bignum), &(other->_bignum), pctx); BN_CTX_free(pctx); return self; } - (instancetype) multiply:(BTCBigNumber*)other mod:(BTCBigNumber *)mod { BN_CTX* pctx = BN_CTX_new(); BN_mod_mul(&(self->_bignum), &(self->_bignum), &(other->_bignum), &(mod->_bignum), pctx); BN_CTX_free(pctx); return self; } - (instancetype) divide:(BTCBigNumber*)other { // /= BN_CTX* pctx = BN_CTX_new(); BN_div(&(self->_bignum), NULL, &(self->_bignum), &(other->_bignum), pctx); BN_CTX_free(pctx); return self; } - (instancetype) mod:(BTCBigNumber*)other { // %= BN_CTX* pctx = BN_CTX_new(); BN_div(NULL, &(self->_bignum), &(self->_bignum), &(other->_bignum), pctx); BN_CTX_free(pctx); return self; } - (instancetype) lshift:(unsigned int)shift { // <<= BN_lshift(&(self->_bignum), &(self->_bignum), shift); return self; } - (instancetype) rshift:(unsigned int)shift { // >>= // Note: BN_rshift segfaults on 64-bit if 2^shift is greater than the number // if built on ubuntu 9.04 or 9.10, probably depends on version of OpenSSL BTCMutableBigNumber* a = [BTCMutableBigNumber one]; [a lshift:shift]; if (BN_cmp(&(a->_bignum), &(self->_bignum)) > 0) { BN_zero(&(self->_bignum)); return self; } BN_rshift(&(self->_bignum), &(self->_bignum), shift); return self; } - (instancetype) inverseMod:(BTCBigNumber*)mod { // (a^-1) mod n BN_CTX* pctx = BN_CTX_new(); BN_mod_inverse(&(self->_bignum), &(self->_bignum), &(mod->_bignum), pctx); BN_CTX_free(pctx); return self; } - (instancetype) exp:(BTCBigNumber*)power { // pow(self, p) BN_CTX* pctx = BN_CTX_new(); BN_exp(&(self->_bignum), &(self->_bignum), &(power->_bignum), pctx); BN_CTX_free(pctx); return self; } - (instancetype) exp:(BTCBigNumber*)power mod:(BTCBigNumber *)mod { // pow(self,p) % m BN_CTX* pctx = BN_CTX_new(); BN_mod_exp(&(self->_bignum), &(self->_bignum), &(power->_bignum), &(mod->_bignum), pctx); BN_CTX_free(pctx); return self; } @end ================================================ FILE: Pods/CoreBitcoin/CoreBitcoin/BTCBitcoinURL.h ================================================ // CoreBitcoin by Oleg Andreev , WTFPL. #import #import "BTCUnitsAndLimits.h" // TODO: support handling URL from UIApplicationDelegate. /*! * Class to compose and handle various Bitcoin URLs according to BIP21. * See: https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki */ @class BTCAddress; @class BTCAssetID; @interface BTCBitcoinURL : NSObject /*! * Encoded address. */ @property(nonatomic, nullable) BTCAddress* address; /*! * Amount in satoshis. Default is 0. */ @property(nonatomic) BTCAmount amount; /*! * Asset ID for Open Assets URL. */ @property(nonatomic, nullable) BTCAssetID* assetID; /*! * Label. Default is nil. */ @property(nonatomic, nullable) NSString* label; /*! * Message. Default is nil. */ @property(nonatomic, nullable) NSString* message; /*! * Query parameters. Default is nil. */ @property(nonatomic, nonnull) NSDictionary* queryParameters; /*! * Payment request URL (r=...). Default is nil. */ @property(nonatomic, nullable) NSURL* paymentRequestURL; /*! * Complete URL built from the individual properties. */ @property(nonatomic, readonly, nonnull) NSURL* URL; /*! * Returns YES if it has a valid address or paymentRequestURL. */ @property(nonatomic, readonly) BOOL isValid; /*! * Returns YES if it is a pure bitcoin URL. That does not specify asset_id and not uses openassets: scheme. */ @property(nonatomic, readonly) BOOL isValidBitcoinURL; /*! * Returns YES if it is an Open Assets URL. */ @property(nonatomic, readonly) BOOL isValidOpenAssetsURL; /*! * Makes a URL in form "bitcoin:
?amount=1.2345&label=