├── .gitmodules ├── DemoApp ├── HIBitcoinJKitDemo │ ├── en.lproj │ │ ├── InfoPlist.strings │ │ └── Credits.rtf │ ├── HIBitcoinJKitDemo-Prefix.pch │ ├── main.m │ ├── HISendWindowController.h │ ├── HIAppDelegate.h │ ├── HIBitcoinJKitDemo-Info.plist │ ├── HISendWindowController.m │ ├── HIAppDelegate.m │ └── HISendWindowController.xib └── HIBitcoinKitDemo.xcodeproj │ └── project.pbxproj ├── BitcoinJKit ├── HIBitcoinInternalErrorCodes.m ├── BitcoinJKit-Prefix.pch ├── BitcoinJKit.h ├── HIBitcoinInternalErrorCodes.h ├── java │ └── bitcoinkit │ │ ├── src │ │ └── main │ │ │ └── java │ │ │ ├── com │ │ │ └── hivewallet │ │ │ │ └── bitcoinkit │ │ │ │ ├── WrongNetworkException.java │ │ │ │ ├── NoWalletException.java │ │ │ │ ├── WrongPasswordException.java │ │ │ │ ├── ExistingWalletException.java │ │ │ │ ├── ExtensionPathsReader.java │ │ │ │ ├── LastWalletChangeExtension.java │ │ │ │ └── BitcoinManager.java │ │ │ └── org │ │ │ └── slf4j │ │ │ └── impl │ │ │ ├── CocoaLoggerFactory.java │ │ │ ├── StaticLoggerBinder.java │ │ │ └── CocoaLogger.java │ │ └── pom.xml ├── jni_md.h ├── HIBitcoinErrorCodes.m ├── HILogger.m ├── HILogger.h ├── BitcoinJKit-Info.plist ├── HIBitcoinErrorCodes.h ├── HIBitcoinManager.h └── HIBitcoinManager.m ├── .gitignore ├── LICENSE ├── README.md └── BitcoinKit.xcodeproj └── project.pbxproj /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "avian"] 2 | path = avian 3 | url = https://github.com/ReadyTalk/avian.git 4 | -------------------------------------------------------------------------------- /DemoApp/HIBitcoinJKitDemo/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /BitcoinJKit/HIBitcoinInternalErrorCodes.m: -------------------------------------------------------------------------------- 1 | #import "HIBitcoinInternalErrorCodes.h" 2 | 3 | NSInteger const kHIBitcoinManagerUnexpectedError = 0; 4 | NSInteger const kHIIllegalArgumentException = -1000; 5 | -------------------------------------------------------------------------------- /DemoApp/HIBitcoinJKitDemo/HIBitcoinJKitDemo-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'HIBitcoinJKitDemo' target in the 'HIBitcoinJKitDemo' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /BitcoinJKit/BitcoinJKit-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'BitcoinJKit' target in the 'BitcoinJKit' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #import 8 | #endif 9 | -------------------------------------------------------------------------------- /BitcoinJKit/BitcoinJKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // BitcoinJKit.h 3 | // BitcoinJKit 4 | // 5 | // Created by Bazyli Zygan on 26.07.2013. 6 | // Copyright (c) 2013 Hive Developers. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | -------------------------------------------------------------------------------- /DemoApp/HIBitcoinJKitDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // HIBitcoinJKitDemo 4 | // 5 | // Created by Bazyli Zygan on 26.07.2013. 6 | // Copyright (c) 2013 Hive Developers. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, char *argv[]) 12 | { 13 | return NSApplicationMain(argc, (const char **)argv); 14 | } 15 | -------------------------------------------------------------------------------- /BitcoinJKit/HIBitcoinInternalErrorCodes.h: -------------------------------------------------------------------------------- 1 | // 2 | // HIBitcoinInternalErrorCodes.h 3 | // Hive 4 | // 5 | // Created by Nikolaj Schumacher on 30.11.2013. 6 | // Copyright (c) 2013 Hive Developers. All rights reserved. 7 | // 8 | 9 | /* Unknown error: This should have been mapped to an error code. */ 10 | extern NSInteger const kHIBitcoinManagerUnexpectedError; 11 | 12 | extern NSInteger const kHIIllegalArgumentException; 13 | -------------------------------------------------------------------------------- /BitcoinJKit/java/bitcoinkit/src/main/java/com/hivewallet/bitcoinkit/WrongNetworkException.java: -------------------------------------------------------------------------------- 1 | package com.hivewallet.bitcoinkit; 2 | 3 | import com.google.bitcoin.protocols.payments.PaymentRequestException; 4 | 5 | public class WrongNetworkException extends PaymentRequestException { 6 | public WrongNetworkException(String message) { 7 | super(message); 8 | } 9 | 10 | public WrongNetworkException(Exception e) { 11 | super(e); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /BitcoinJKit/java/bitcoinkit/src/main/java/com/hivewallet/bitcoinkit/NoWalletException.java: -------------------------------------------------------------------------------- 1 | package com.hivewallet.bitcoinkit; 2 | 3 | public class NoWalletException extends Exception { 4 | public NoWalletException(String message) { 5 | super(message); 6 | } 7 | 8 | public NoWalletException(String message, Throwable cause) { 9 | super(message, cause); 10 | } 11 | 12 | public NoWalletException(Throwable cause) { 13 | super(cause); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /BitcoinJKit/java/bitcoinkit/src/main/java/com/hivewallet/bitcoinkit/WrongPasswordException.java: -------------------------------------------------------------------------------- 1 | package com.hivewallet.bitcoinkit; 2 | 3 | public class WrongPasswordException extends Exception { 4 | public WrongPasswordException(String message) { 5 | super(message); 6 | } 7 | 8 | public WrongPasswordException(String message, Throwable cause) { 9 | super(message, cause); 10 | } 11 | 12 | public WrongPasswordException(Throwable cause) { 13 | super(cause); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /BitcoinJKit/java/bitcoinkit/src/main/java/com/hivewallet/bitcoinkit/ExistingWalletException.java: -------------------------------------------------------------------------------- 1 | package com.hivewallet.bitcoinkit; 2 | 3 | public class ExistingWalletException extends Exception { 4 | public ExistingWalletException(String message) { 5 | super(message); 6 | } 7 | 8 | public ExistingWalletException(String message, Throwable cause) { 9 | super(message, cause); 10 | } 11 | 12 | public ExistingWalletException(Throwable cause) { 13 | super(cause); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /DemoApp/HIBitcoinJKitDemo/HISendWindowController.h: -------------------------------------------------------------------------------- 1 | // 2 | // HISendWindowController.h 3 | // HIBitcoinKitDemo 4 | // 5 | // Created by Bazyli Zygan on 14.07.2013. 6 | // Copyright (c) 2013 Hive Developers. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface HISendWindowController : NSWindowController 12 | 13 | @property (weak) IBOutlet NSTextField *addressField; 14 | @property (weak) IBOutlet NSTextField *amountField; 15 | 16 | - (IBAction)cancelClicked:(NSButton *)sender; 17 | - (IBAction)sendClicked:(NSButton *)sender; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /DemoApp/HIBitcoinJKitDemo/en.lproj/Credits.rtf: -------------------------------------------------------------------------------- 1 | {\rtf0\ansi{\fonttbl\f0\fswiss Helvetica;} 2 | {\colortbl;\red255\green255\blue255;} 3 | \paperw9840\paperh8400 4 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural 5 | 6 | \f0\b\fs24 \cf0 Engineering: 7 | \b0 \ 8 | Some people\ 9 | \ 10 | 11 | \b Human Interface Design: 12 | \b0 \ 13 | Some other people\ 14 | \ 15 | 16 | \b Testing: 17 | \b0 \ 18 | Hopefully not nobody\ 19 | \ 20 | 21 | \b Documentation: 22 | \b0 \ 23 | Whoever\ 24 | \ 25 | 26 | \b With special thanks to: 27 | \b0 \ 28 | Mom\ 29 | } 30 | -------------------------------------------------------------------------------- /BitcoinJKit/jni_md.h: -------------------------------------------------------------------------------- 1 | /* 2 | * @(#)jni_md.h 1.19 05/11/17 3 | * 4 | * Copyright 2006 Sun Microsystems, Inc. All rights reserved. 5 | * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. 6 | */ 7 | 8 | #ifndef _JAVASOFT_JNI_MD_H_ 9 | #define _JAVASOFT_JNI_MD_H_ 10 | 11 | #define JNIEXPORT __attribute__((visibility("default"))) 12 | #define JNIIMPORT 13 | #define JNICALL 14 | 15 | #if defined(__LP64__) && __LP64__ /* for -Wundef */ 16 | typedef int jint; 17 | #else 18 | typedef long jint; 19 | #endif 20 | typedef long long jlong; 21 | typedef signed char jbyte; 22 | 23 | #endif /* !_JAVASOFT_JNI_MD_H_ */ 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | build/* 3 | *.pbxuser 4 | !default.pbxuser 5 | *.mode1v3 6 | !default.mode1v3 7 | *.mode2v3 8 | !default.mode2v3 9 | *.perspectivev3 10 | !default.perspectivev3 11 | *.xcworkspace 12 | !default.xcworkspace 13 | xcuserdata 14 | profile 15 | *.moved-aside 16 | 17 | .DS_Store 18 | 19 | # Thumbnails 20 | ._* 21 | 22 | # Files that might appear on external disk 23 | .Spotlight-V100 24 | .Trashes 25 | 26 | # SourceTree (Mac) Configuration Files 27 | sourcetreeconfig 28 | 29 | bitcoinj 30 | 31 | # bitcoinj/aviary compilation temps 32 | boot-jar.o 33 | boot.jar 34 | tmp 35 | *core* 36 | *tmp* 37 | *target* 38 | classpath.jar 39 | 40 | # Podfiles 41 | Podfile.lock 42 | Pods/* 43 | -------------------------------------------------------------------------------- /BitcoinJKit/java/bitcoinkit/src/main/java/com/hivewallet/bitcoinkit/ExtensionPathsReader.java: -------------------------------------------------------------------------------- 1 | package com.hivewallet.bitcoinkit; 2 | 3 | class ExtensionPathsReader { 4 | public static void main(String[] args) { 5 | String[] paths = System.getProperty("java.ext.dirs").split(":"); 6 | StringBuilder buffer = new StringBuilder(); 7 | 8 | for (String path : paths) { 9 | if (path.startsWith("/System")) { 10 | if (buffer.length() > 0) { 11 | buffer.append(":"); 12 | } 13 | 14 | buffer.append(path); 15 | } 16 | } 17 | 18 | System.out.print(buffer.toString()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /BitcoinJKit/HIBitcoinErrorCodes.m: -------------------------------------------------------------------------------- 1 | #import "HIBitcoinErrorCodes.h" 2 | 3 | NSInteger const kHIBitcoinManagerUnreadableWallet = 1000; 4 | NSInteger const kHIBitcoinManagerBlockStoreLockError = 1001; 5 | NSInteger const kHIBitcoinManagerNoWallet = 1002; 6 | NSInteger const kHIBitcoinManagerWalletExists = 1003; 7 | NSInteger const kHIBitcoinManagerWrongPassword = 1004; 8 | NSInteger const kHIBitcoinManagerBlockStoreReadError = 1005; 9 | NSInteger const kHIBitcoinManagerSendingDustError = 1006; 10 | NSInteger const kHIBitcoinManagerInsufficientMoneyError = 1007; 11 | NSInteger const kHIBitcoinManagerPaymentRequestExpiredError = 1008; 12 | NSInteger const kHIBitcoinManagerPaymentRequestWrongNetworkError = 1009; 13 | NSInteger const kHIBitcoinManagerInvalidProtocolBufferError = 1010; 14 | 15 | NSInteger const kHIFileNotFoundException = 2000; 16 | -------------------------------------------------------------------------------- /BitcoinJKit/java/bitcoinkit/src/main/java/org/slf4j/impl/CocoaLoggerFactory.java: -------------------------------------------------------------------------------- 1 | package org.slf4j.impl; 2 | 3 | import org.slf4j.ILoggerFactory; 4 | import org.slf4j.Logger; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | // based on http://javaeenotes.blogspot.com/2011/12/custom-slf4j-logger-adapter.html 10 | 11 | public class CocoaLoggerFactory implements ILoggerFactory { 12 | private Map loggerMap; 13 | 14 | public CocoaLoggerFactory() { 15 | loggerMap = new HashMap(); 16 | } 17 | 18 | @Override 19 | public Logger getLogger(String name) { 20 | synchronized (loggerMap) { 21 | if (!loggerMap.containsKey(name)) { 22 | loggerMap.put(name, new CocoaLogger(name)); 23 | } 24 | 25 | return loggerMap.get(name); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /DemoApp/HIBitcoinJKitDemo/HIAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // HIAppDelegate.h 3 | // HIBitcoinKitDemo 4 | // 5 | // Created by Bazyli Zygan on 12.07.2013. 6 | // Copyright (c) 2013 Hive Developers. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface HIAppDelegate : NSObject 12 | 13 | @property (assign) IBOutlet NSWindow *window; 14 | @property (weak) IBOutlet NSTextField *addressLabel; 15 | @property (weak) IBOutlet NSTextField *balanceLabel; 16 | @property (weak) IBOutlet NSTextField *connectionsLabel; 17 | @property (weak) IBOutlet NSProgressIndicator *progressIndicator; 18 | @property (weak) IBOutlet NSTextField *stateLabel; 19 | @property (weak) IBOutlet NSTableView *transactionList; 20 | @property (weak) IBOutlet NSButton *sendMoneyBtn; 21 | @property (weak) IBOutlet NSButton *importBtn; 22 | @property (weak) IBOutlet NSButton *exportBtn; 23 | 24 | - (IBAction)sendMoneyClicked:(NSButton *)sender; 25 | - (IBAction)exportWalletClicked:(NSButton *)sender; 26 | - (IBAction)importWalletClicked:(NSButton *)sender; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Hive Developers (http://www.grabhive.com/) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /BitcoinJKit/HILogger.m: -------------------------------------------------------------------------------- 1 | // 2 | // HILogger.m 3 | // BitcoinKit 4 | // 5 | // Created by Jakub Suder on 17.12.2013. 6 | // Copyright (c) 2013 Hive Developers. All rights reserved. 7 | // 8 | 9 | #import "HILogger.h" 10 | 11 | void HILoggerLog(const char *fileName, const char *functionName, int lineNumber, 12 | HILoggerLevel level, NSString *message, ...) { 13 | va_list args; 14 | va_start(args, message); 15 | NSString *logText = [[NSString alloc] initWithFormat:message arguments:args]; 16 | va_end(args); 17 | 18 | HILogger *logger = [HILogger sharedLogger]; 19 | logger.logHandler(fileName, functionName, lineNumber, level, logText); 20 | } 21 | 22 | @implementation HILogger 23 | 24 | + (HILogger *)sharedLogger { 25 | static HILogger *sharedLogger = nil; 26 | static dispatch_once_t oncePredicate; 27 | 28 | dispatch_once(&oncePredicate, ^{ 29 | sharedLogger = [[self alloc] init]; 30 | sharedLogger.logHandler = ^(const char *fileName, const char *functionName, int lineNumber, 31 | HILoggerLevel level, NSString *message) { 32 | NSLog(@"%@", message); 33 | }; 34 | }); 35 | 36 | return sharedLogger; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /BitcoinJKit/HILogger.h: -------------------------------------------------------------------------------- 1 | // 2 | // HILogger.h 3 | // BitcoinKit 4 | // 5 | // Created by Jakub Suder on 17.12.2013. 6 | // Copyright (c) 2013 Hive Developers. All rights reserved. 7 | // 8 | 9 | typedef NS_ENUM(int, HILoggerLevel) { 10 | HILoggerLevelDebug = 1, 11 | HILoggerLevelInfo = 2, 12 | HILoggerLevelWarn = 3, 13 | HILoggerLevelError = 4, 14 | }; 15 | 16 | extern void HILoggerLog(const char *fileName, const char *functionName, int lineNumber, 17 | HILoggerLevel level, NSString *message, ...) NS_FORMAT_FUNCTION(5, 6); 18 | 19 | #define HILogError(...) HILoggerLog(__FILE__, __FUNCTION__, __LINE__, HILoggerLevelError, __VA_ARGS__) 20 | #define HILogWarn(...) HILoggerLog(__FILE__, __FUNCTION__, __LINE__, HILoggerLevelWarn, __VA_ARGS__) 21 | #define HILogInfo(...) HILoggerLog(__FILE__, __FUNCTION__, __LINE__, HILoggerLevelInfo, __VA_ARGS__) 22 | #define HILogDebug(...) HILoggerLog(__FILE__, __FUNCTION__, __LINE__, HILoggerLevelDebug, __VA_ARGS__) 23 | 24 | 25 | @interface HILogger : NSObject 26 | 27 | @property (strong) void (^logHandler)(const char *fileName, const char *functionName, int lineNumber, 28 | HILoggerLevel level, NSString *message); 29 | 30 | + (instancetype)sharedLogger; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /BitcoinJKit/BitcoinJKit-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | com.hivewallet.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | FMWK 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | NSHumanReadableCopyright 26 | Copyright © 2013 Hive Developers. All rights reserved. 27 | NSJavaNeeded 28 | 29 | NSJavaPath 30 | 31 | booj.jar 32 | 33 | NSJavaRoot 34 | Content/Resources/ 35 | NSPrincipalClass 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /DemoApp/HIBitcoinJKitDemo/HIBitcoinJKitDemo-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | com.hivewallet.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSMinimumSystemVersion 26 | ${MACOSX_DEPLOYMENT_TARGET} 27 | NSHumanReadableCopyright 28 | Copyright © 2013 Hive Developers. All rights reserved. 29 | NSJavaNeeded 30 | 31 | NSMainNibFile 32 | MainMenu 33 | NSPrincipalClass 34 | NSApplication 35 | 36 | 37 | -------------------------------------------------------------------------------- /BitcoinJKit/java/bitcoinkit/src/main/java/org/slf4j/impl/StaticLoggerBinder.java: -------------------------------------------------------------------------------- 1 | package org.slf4j.impl; 2 | 3 | import org.slf4j.ILoggerFactory; 4 | import org.slf4j.spi.LoggerFactoryBinder; 5 | 6 | // based on http://javaeenotes.blogspot.com/2011/12/custom-slf4j-logger-adapter.html 7 | 8 | public class StaticLoggerBinder implements LoggerFactoryBinder { 9 | 10 | private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder(); 11 | 12 | public static final StaticLoggerBinder getSingleton() { 13 | return SINGLETON; 14 | } 15 | 16 | /** 17 | * Declare the version of the SLF4J API this implementation is 18 | * compiled against. The value of this field is usually modified 19 | * with each release. 20 | */ 21 | // To avoid constant folding by the compiler, 22 | // this field must *not* be final 23 | public static String REQUESTED_API_VERSION = "1.6.4"; // !final 24 | 25 | private static final String loggerFactoryClassStr = CocoaLoggerFactory.class.getName(); 26 | 27 | /** 28 | * The ILoggerFactory instance returned by the 29 | * {@link #getLoggerFactory} method should always be the same 30 | * object. 31 | */ 32 | private final ILoggerFactory loggerFactory; 33 | 34 | private StaticLoggerBinder() { 35 | loggerFactory = new CocoaLoggerFactory(); 36 | } 37 | 38 | public ILoggerFactory getLoggerFactory() { 39 | return loggerFactory; 40 | } 41 | 42 | public String getLoggerFactoryClassStr() { 43 | return loggerFactoryClassStr; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /BitcoinJKit/HIBitcoinErrorCodes.h: -------------------------------------------------------------------------------- 1 | // 2 | // HIBitcoinErrorCodes.h 3 | // Hive 4 | // 5 | // Created by Nikolaj Schumacher on 30.11.2013. 6 | // Copyright (c) 2013 Hive Developers. All rights reserved. 7 | // 8 | 9 | /* The wallet file exists but could not be read. */ 10 | extern NSInteger const kHIBitcoinManagerUnreadableWallet; 11 | 12 | /* Acessing a block store file failed because it's locked by another process. */ 13 | extern NSInteger const kHIBitcoinManagerBlockStoreLockError; 14 | 15 | /* There is no wallet and it needs to be created. */ 16 | extern NSInteger const kHIBitcoinManagerNoWallet; 17 | 18 | /* A wallet could not be created because it already exists. */ 19 | extern NSInteger const kHIBitcoinManagerWalletExists; 20 | 21 | /* An operation could not be completed because a wrong password was specified. */ 22 | extern NSInteger const kHIBitcoinManagerWrongPassword; 23 | 24 | /* Accessing block store file failed (e.g. file is corrupted). */ 25 | extern NSInteger const kHIBitcoinManagerBlockStoreReadError; 26 | 27 | /* The user tried to send dust. */ 28 | extern NSInteger const kHIBitcoinManagerSendingDustError; 29 | 30 | /* The user tried to send more than the wallet balance. */ 31 | extern NSInteger const kHIBitcoinManagerInsufficientMoneyError; 32 | 33 | /* The payment request has already expired. */ 34 | extern NSInteger const kHIBitcoinManagerPaymentRequestExpiredError; 35 | 36 | /* The payment request is meant for a different Bitcoin network. */ 37 | extern NSInteger const kHIBitcoinManagerPaymentRequestWrongNetworkError; 38 | 39 | /* The file could not be parsed as a protocol buffer data file. */ 40 | extern NSInteger const kHIBitcoinManagerInvalidProtocolBufferError; 41 | 42 | 43 | /* java.io.FileNotFoundException - invalid file name or file is missing. */ 44 | extern NSInteger const kHIFileNotFoundException; 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | BitcoinKit.framework 2 | =================== 3 | 4 | BitcoinKit.framework allows you to access and use Bitcoin wallets in your applications. It uses Mike Hearn's bitcoinj Java library. This is an SPV implementation, so it doesn't need to download the whole blockchain to work. 5 | 6 | Since a large part of the code is in Java, your application will need to ask the user to install a JRE in the system. This might change in future if we find a way to integrate a lightweight JVM into the project. 7 | 8 | 9 | Build Instructions for BitcoinJKit.framework 10 | ------------------------------------------- 11 | 12 | For that you need to have java and maven installed: 13 | 14 | brew install maven 15 | 16 | And you also have to remember to fetch all submodules! 17 | 18 | git submodule update --init --recursive 19 | 20 | Time to compile! 21 | 22 | 23 | How to use 24 | ---------- 25 | 26 | The main access point is the singleton object of class HIBitcoinManager. With this object you are able to access the Bitcoin network and manage your wallet. 27 | 28 | First you need to prepare the library for launching. 29 | 30 | Set up where wallet and Bitcoin network data should be kept: 31 | 32 | ```objective-c 33 | [HIBitcoinManager defaultManager].dataURL = [[self applicationSupportDir] URLByAppendingPathComponent:@"com.mycompany.MyBitcoinWalletData"]; 34 | ``` 35 | 36 | Decide if you want to use a testing network (or not): 37 | 38 | ```objective-c 39 | [HIBitcoinManager defaultManager].testingNetwork = YES; 40 | ``` 41 | 42 | ...and start the network! 43 | 44 | ```objective-c 45 | [[HIBitcoinManager defaultManager] start:&error]; 46 | ``` 47 | 48 | Now you can easily get the balance or wallet address: 49 | 50 | ```objective-c 51 | NSString *walletAddress [HIBitcoinManager defaultManager].walletAddress; 52 | uint64_t balance = [HIBitcoinManager defaultManager].balance 53 | ``` 54 | 55 | You can send coins: 56 | 57 | ```objective-c 58 | [[HIBitcoinManager defaultManager] sendCoins:1000 toRecipient:hashAddress comment:@"Here's some money for you!" password:nil error:&error completion:nil]; 59 | ``` 60 | 61 | And more! 62 | 63 | 64 | Demo App 65 | -------- 66 | 67 | There's a demo application included with the sources. Start it up and check out how to use BitcoinKit.framework! 68 | 69 | License 70 | ------- 71 | 72 | BitcoinKit.framework is available under the MIT license. 73 | -------------------------------------------------------------------------------- /BitcoinJKit/java/bitcoinkit/src/main/java/com/hivewallet/bitcoinkit/LastWalletChangeExtension.java: -------------------------------------------------------------------------------- 1 | package com.hivewallet.bitcoinkit; 2 | 3 | import com.google.bitcoin.core.Wallet; 4 | import com.google.bitcoin.core.WalletExtension; 5 | import java.nio.ByteBuffer; 6 | import java.util.Date; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | public class LastWalletChangeExtension implements WalletExtension { 11 | static final String EXTENSION_ID = LastWalletChangeExtension.class.getName(); 12 | private static final Logger log = LoggerFactory.getLogger(LastWalletChangeExtension.class); 13 | 14 | private Date lastWalletChangeDate; 15 | 16 | public LastWalletChangeExtension() { 17 | } 18 | 19 | public Date getLastWalletChangeDate() { 20 | return lastWalletChangeDate; 21 | } 22 | 23 | public void setLastWalletChangeDate(Date date) { 24 | lastWalletChangeDate = date; 25 | } 26 | 27 | 28 | /** Returns a Java package/class style name used to disambiguate this extension from others. */ 29 | @Override 30 | public String getWalletExtensionID() { 31 | return EXTENSION_ID; 32 | } 33 | 34 | /** 35 | * If this returns true, the mandatory flag is set when the wallet is serialized and attempts to load it without 36 | * the extension being in the wallet will throw an exception. This method should not change its result during 37 | * the objects lifetime. 38 | */ 39 | @Override 40 | public boolean isWalletExtensionMandatory() { 41 | return false; 42 | } 43 | 44 | /** Returns bytes that will be saved in the wallet. */ 45 | @Override 46 | public byte[] serializeWalletExtension() { 47 | long timestamp = (lastWalletChangeDate != null) ? lastWalletChangeDate.getTime() : 0; 48 | return ByteBuffer.allocate(8).putLong(timestamp).array(); 49 | } 50 | 51 | /** Loads the contents of this object from the wallet. */ 52 | @Override 53 | public void deserializeWalletExtension(Wallet containingWallet, byte[] data) throws Exception { 54 | ByteBuffer buffer = ByteBuffer.allocate(8); 55 | buffer.put(data).flip(); 56 | long timestamp = buffer.getLong(); 57 | lastWalletChangeDate = (timestamp > 0) ? new Date(timestamp) : null; 58 | } 59 | 60 | @Override 61 | public String toString() { 62 | return "LastWalletChangeExtension: date = " + lastWalletChangeDate; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /BitcoinJKit/java/bitcoinkit/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 4.0.0 7 | 8 | com.hivewallet 9 | BitcoinKit 10 | 0.0.0.1-SNAPSHOT 11 | 12 | BitcoinKit Java bridge 13 | BitcoinKit Java bridge. 14 | 15 | 16 | 17 | 18 | org.apache.maven.plugins 19 | maven-shade-plugin 20 | 1.6 21 | 22 | 23 | false 24 | 25 | 26 | 27 | *:* 28 | 29 | META-INF/*.SF 30 | META-INF/*.DSA 31 | META-INF/*.RSA 32 | 33 | 34 | 35 | 36 | 38 | com.hivewallet.bitcoinkit.BitcoinManager 39 | 40 | 41 | 42 | 43 | 44 | package 45 | 46 | shade 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | com.google 57 | bitcoinj 58 | 0.11.3 59 | 60 | 61 | org.codehaus.jettison 62 | jettison 63 | 1.3.1 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /DemoApp/HIBitcoinJKitDemo/HISendWindowController.m: -------------------------------------------------------------------------------- 1 | // 2 | // HISendWindowController.m 3 | // HIBitcoinKitDemo 4 | // 5 | // Created by Bazyli Zygan on 14.07.2013. 6 | // Copyright (c) 2013 Hive Developers. All rights reserved. 7 | // 8 | #import 9 | #import "HISendWindowController.h" 10 | 11 | 12 | @implementation HISendWindowController 13 | 14 | - (instancetype)initWithWindow:(NSWindow *)window 15 | { 16 | self = [super initWithWindow:window]; 17 | 18 | if (self) { 19 | // Initialization code here. 20 | } 21 | 22 | return self; 23 | } 24 | 25 | - (void)windowDidLoad 26 | { 27 | [super windowDidLoad]; 28 | 29 | // Implement this method to handle any initialization after your window controller's window has been loaded 30 | // from its nib file. 31 | } 32 | 33 | - (IBAction)cancelClicked:(NSButton *)sender 34 | { 35 | [NSApp endSheet:self.window]; 36 | [self.window close]; 37 | } 38 | 39 | - (IBAction)sendClicked:(NSButton *)sender 40 | { 41 | HIBitcoinManager *manager = [HIBitcoinManager defaultManager]; 42 | 43 | // Sanity check first 44 | NSString *address = _addressField.stringValue; 45 | CGFloat amount = [_amountField.stringValue floatValue]; 46 | NSLog(@"Balance %llu and amount %llu", [manager balance], (long long) (amount * 100000000)); 47 | 48 | if (amount <= 0 || 49 | [manager balance] < (long long) (amount * 100000000) || 50 | ![manager isAddressValid:address]) 51 | { 52 | NSAlert *alert = [[NSAlert alloc] init]; 53 | [alert setMessageText:@"Cannot send money"]; 54 | 55 | if (amount <= 0) 56 | { 57 | [alert setInformativeText:@"Your amount is invalid"]; 58 | } 59 | else if ([manager balance] < (long long)(amount * 100000000)) 60 | { 61 | [alert setInformativeText:@"You can't send more than you own"]; 62 | } 63 | else if (![manager isAddressValid:address]) 64 | { 65 | [alert setInformativeText:@"Given receipent address is invalid"]; 66 | } 67 | 68 | [alert addButtonWithTitle:@"Ok"]; 69 | [alert runModal]; 70 | } 71 | else 72 | { 73 | [manager sendCoins:(amount * 100000000) 74 | toRecipient:address 75 | comment:nil 76 | password:nil 77 | error:NULL 78 | completion:^(NSString *hash) 79 | { 80 | if (hash) 81 | { 82 | [self cancelClicked:sender]; 83 | } 84 | else 85 | { 86 | NSAlert *alert = [[NSAlert alloc] init]; 87 | [alert setMessageText:@"Cannot send money"]; 88 | [alert setInformativeText:@"Failed to send money"]; 89 | [alert addButtonWithTitle:@"Ok"]; 90 | [alert runModal]; 91 | } 92 | }]; 93 | } 94 | } 95 | 96 | @end 97 | -------------------------------------------------------------------------------- /DemoApp/HIBitcoinJKitDemo/HIAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // HIAppDelegate.m 3 | // HIBitcoinKitDemo 4 | // 5 | // Created by Bazyli Zygan on 12.07.2013. 6 | // Copyright (c) 2013 Hive Developers. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "HIAppDelegate.h" 11 | #import "HISendWindowController.h" 12 | 13 | @interface HIAppDelegate () 14 | { 15 | NSArray *_transactions; 16 | NSDateFormatter *_dateFormatter; 17 | HIBitcoinManager *_manager; 18 | HISendWindowController *_sendController; 19 | } 20 | 21 | @end 22 | 23 | @implementation HIAppDelegate 24 | 25 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification 26 | { 27 | // Configure columns in tableview 28 | _dateFormatter = [[NSDateFormatter alloc] init]; 29 | [_dateFormatter setDateStyle:NSDateFormatterFullStyle]; 30 | [_dateFormatter setTimeStyle:NSDateFormatterFullStyle]; 31 | 32 | [_transactionList.tableColumns[0] setIdentifier:@"category"]; 33 | [_transactionList.tableColumns[1] setIdentifier:@"amount"]; 34 | [_transactionList.tableColumns[2] setIdentifier:@"address"]; 35 | [_transactionList.tableColumns[3] setIdentifier:@"time"]; 36 | 37 | _manager = [HIBitcoinManager defaultManager]; 38 | 39 | [_manager addObserver:self 40 | forKeyPath:@"connections" 41 | options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew 42 | context:NULL]; 43 | [_manager addObserver:self 44 | forKeyPath:@"balance" 45 | options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew 46 | context:NULL]; 47 | [_manager addObserver:self 48 | forKeyPath:@"syncProgress" 49 | options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew 50 | context:NULL]; 51 | [_manager addObserver:self 52 | forKeyPath:@"isRunning" 53 | options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew 54 | context:NULL]; 55 | 56 | [_progressIndicator startAnimation:self]; 57 | 58 | NSArray *applicationSupport = [[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory 59 | inDomains:NSUserDomainMask]; 60 | 61 | _manager.testingNetwork = YES; 62 | _manager.dataURL = [[applicationSupport lastObject] URLByAppendingPathComponent:@"BitcoinKitDemo"]; 63 | // _manager.enableMining = YES; 64 | 65 | [_manager start:NULL]; 66 | 67 | [[NSNotificationCenter defaultCenter] addObserver:self 68 | selector:@selector(transactionUpdated:) 69 | name:kHIBitcoinManagerTransactionChangedNotification 70 | object:nil]; 71 | } 72 | 73 | - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender 74 | { 75 | if (_manager.isRunning) 76 | { 77 | return NSTerminateNow; 78 | } 79 | 80 | return NSTerminateCancel; 81 | } 82 | 83 | - (void)applicationWillTerminate:(NSNotification *)notification 84 | { 85 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 86 | [_progressIndicator stopAnimation:self]; 87 | 88 | [_manager stop]; 89 | [_manager removeObserver:self forKeyPath:@"connections"]; 90 | [_manager removeObserver:self forKeyPath:@"balance"]; 91 | [_manager removeObserver:self forKeyPath:@"syncProgress"]; 92 | [_manager removeObserver:self forKeyPath:@"isRunning"]; 93 | } 94 | 95 | - (void)observeValueForKeyPath:(NSString *)keyPath 96 | ofObject:(id)object 97 | change:(NSDictionary *)change 98 | context:(void *)context 99 | { 100 | if (object == _manager) 101 | { 102 | if ([keyPath isEqual:@"connections"]) 103 | { 104 | _connectionsLabel.stringValue = [NSString stringWithFormat:@"%lu", _manager.connections]; 105 | } 106 | else if ([keyPath isEqual:@"balance"]) 107 | { 108 | _balanceLabel.stringValue = [NSString stringWithFormat:@"%.4f ฿", (CGFloat) _manager.balance / 100000000.0]; 109 | } 110 | else if ([keyPath isEqual:@"isRunning"]) 111 | { 112 | if (_manager.isRunning) 113 | { 114 | _stateLabel.stringValue = @"Synchronizing..."; 115 | _addressLabel.stringValue = _manager.walletAddress; 116 | [_sendMoneyBtn setEnabled:YES]; 117 | [_importBtn setEnabled:YES]; 118 | [_exportBtn setEnabled:YES]; 119 | 120 | // We have to refresh transaction list here 121 | _transactions = [_manager allTransactions]; 122 | [_transactionList reloadData]; 123 | } 124 | } 125 | else if ([keyPath isEqual:@"syncProgress"]) 126 | { 127 | if (_manager.syncProgress > 0) 128 | { 129 | [_progressIndicator setIndeterminate:NO]; 130 | [_progressIndicator setDoubleValue:_manager.syncProgress]; 131 | } 132 | else 133 | { 134 | [_progressIndicator setIndeterminate:YES]; 135 | } 136 | 137 | if (_manager.syncProgress == 10000) 138 | { 139 | [_progressIndicator stopAnimation:self]; 140 | _stateLabel.stringValue = @"Synchronized"; 141 | } 142 | else 143 | { 144 | [_progressIndicator startAnimation:self]; 145 | if (_manager.isRunning) 146 | { 147 | _stateLabel.stringValue = @"Synchronizing..."; 148 | } 149 | } 150 | } 151 | } 152 | } 153 | 154 | - (void)transactionUpdated:(NSNotification *)not 155 | { 156 | // In here we simply reload all transactions. 157 | // Real apps can do it in more inteligent fashion 158 | _transactions = [_manager allTransactions]; 159 | [_transactionList reloadData]; 160 | } 161 | 162 | #pragma mark - TableView methods 163 | 164 | - (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView 165 | { 166 | return _transactions.count; 167 | } 168 | 169 | - (id)tableView:(NSTableView *)aTableView 170 | objectValueForTableColumn:(NSTableColumn *)aTableColumn 171 | row:(NSInteger)rowIndex 172 | { 173 | NSDictionary *transaction = _transactions[rowIndex]; 174 | NSString *identifier = [aTableColumn identifier]; 175 | 176 | if ([identifier isEqual:@"category"]) 177 | { 178 | return transaction[@"details"][0][@"category"]; 179 | } 180 | else if ([identifier isEqual:@"amount"]) 181 | { 182 | return [NSString stringWithFormat:@"%.4f ฿", [transaction[@"amount"] longLongValue] / 100000000.0]; 183 | } 184 | else if ([identifier isEqual:@"address"]) 185 | { 186 | return transaction[@"details"][0][@"address"]; 187 | } 188 | else if ([identifier isEqual:@"time"]) 189 | { 190 | return [_dateFormatter stringFromDate:transaction[@"time"]]; 191 | } 192 | 193 | return nil; 194 | } 195 | 196 | - (IBAction)sendMoneyClicked:(NSButton *)sender 197 | { 198 | _sendController = [[HISendWindowController alloc] initWithWindowNibName:@"HISendWindowController"]; 199 | 200 | [NSApp beginSheet:_sendController.window 201 | modalForWindow:self.window 202 | modalDelegate:self 203 | didEndSelector:@selector(sendClosed:) 204 | contextInfo:NULL]; 205 | } 206 | 207 | - (IBAction)exportWalletClicked:(NSButton *)sender 208 | { 209 | NSSavePanel *sp = [NSSavePanel savePanel]; 210 | sp.title = @"Select where to save your wallet dump"; 211 | sp.prompt = @"Dump"; 212 | sp.allowedFileTypes = @[@"dat"]; 213 | 214 | if ([sp runModal] == NSFileHandlingPanelOKButton) 215 | { 216 | NSAlert *alert = [[NSAlert alloc] init]; 217 | [alert setMessageText:@"Wallet export"]; 218 | 219 | if ([_manager exportWalletTo:sp.URL]) 220 | { 221 | [alert setInformativeText:@"Export has been successful"]; 222 | } 223 | else 224 | { 225 | [alert setInformativeText:@"Export has failed"]; 226 | } 227 | 228 | [alert addButtonWithTitle:@"Ok"]; 229 | [alert runModal]; 230 | } 231 | } 232 | 233 | - (IBAction)importWalletClicked:(NSButton *)sender 234 | { 235 | NSOpenPanel *op = [NSOpenPanel openPanel]; 236 | op.title = @"Select dump file to import"; 237 | op.prompt = @"Import"; 238 | op.allowedFileTypes = @[@"dat"]; 239 | 240 | if ([op runModal] == NSFileHandlingPanelOKButton) 241 | { 242 | NSAlert *alert = [[NSAlert alloc] init]; 243 | [alert setMessageText:@"Wallet import"]; 244 | 245 | if ([_manager importWalletFrom:op.URL]) 246 | { 247 | [alert setInformativeText:@"Import has been successful"]; 248 | } 249 | else 250 | { 251 | [alert setInformativeText:@"Import has failed"]; 252 | } 253 | 254 | [alert addButtonWithTitle:@"Ok"]; 255 | [alert runModal]; 256 | } 257 | } 258 | 259 | - (void)sendClosed:(id)sender 260 | { 261 | _sendController = nil; 262 | } 263 | 264 | @end 265 | -------------------------------------------------------------------------------- /BitcoinJKit/java/bitcoinkit/src/main/java/org/slf4j/impl/CocoaLogger.java: -------------------------------------------------------------------------------- 1 | package org.slf4j.impl; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.helpers.FormattingTuple; 5 | import org.slf4j.helpers.MarkerIgnoringBase; 6 | import org.slf4j.helpers.MessageFormatter; 7 | 8 | // based on http://javaeenotes.blogspot.com/2011/12/custom-slf4j-logger-adapter.html and JDK14LoggerAdapter 9 | 10 | public class CocoaLogger extends MarkerIgnoringBase implements Logger { 11 | public static final int HILoggerLevelNotSet = -1; 12 | public static final int HILoggerLevelDebug = 1; 13 | public static final int HILoggerLevelInfo = 2; 14 | public static final int HILoggerLevelWarn = 3; 15 | public static final int HILoggerLevelError = 4; 16 | 17 | private static String SELF = CocoaLogger.class.getName(); 18 | private static String SUPER = MarkerIgnoringBase.class.getName(); 19 | 20 | private static int globalLevel = HILoggerLevelDebug; 21 | private int level = HILoggerLevelNotSet; 22 | 23 | public static int getGlobalLevel() { 24 | return globalLevel; 25 | } 26 | 27 | public static void setGlobalLevel(int newLevel) { 28 | globalLevel = newLevel; 29 | } 30 | 31 | CocoaLogger(String name) { 32 | this.name = name; 33 | } 34 | 35 | public int getLevel() { 36 | return (level == HILoggerLevelNotSet) ? globalLevel : level; 37 | } 38 | 39 | public void setLevel(int newLevel) { 40 | level = newLevel; 41 | } 42 | 43 | public boolean isTraceEnabled() { 44 | return getLevel() <= HILoggerLevelDebug; 45 | } 46 | 47 | public void trace(String msg) { 48 | if (isTraceEnabled()) { 49 | log(SELF, HILoggerLevelDebug, msg, null); 50 | } 51 | } 52 | 53 | public void trace(String format, Object arg) { 54 | if (isTraceEnabled()) { 55 | FormattingTuple ft = MessageFormatter.format(format, arg); 56 | log(SELF, HILoggerLevelDebug, ft.getMessage(), ft.getThrowable()); 57 | } 58 | } 59 | 60 | public void trace(String format, Object arg1, Object arg2) { 61 | if (isTraceEnabled()) { 62 | FormattingTuple ft = MessageFormatter.format(format, arg1, arg2); 63 | log(SELF, HILoggerLevelDebug, ft.getMessage(), ft.getThrowable()); 64 | } 65 | } 66 | 67 | public void trace(String format, Object... argArray) { 68 | if (isTraceEnabled()) { 69 | FormattingTuple ft = MessageFormatter.arrayFormat(format, argArray); 70 | log(SELF, HILoggerLevelDebug, ft.getMessage(), ft.getThrowable()); 71 | } 72 | } 73 | 74 | public void trace(String msg, Throwable t) { 75 | if (isTraceEnabled()) { 76 | log(SELF, HILoggerLevelDebug, msg, t); 77 | } 78 | } 79 | 80 | public boolean isDebugEnabled() { 81 | return getLevel() <= HILoggerLevelDebug; 82 | } 83 | 84 | public void debug(String msg) { 85 | if (isDebugEnabled()) { 86 | log(SELF, HILoggerLevelDebug, msg, null); 87 | } 88 | } 89 | 90 | public void debug(String format, Object arg) { 91 | if (isDebugEnabled()) { 92 | FormattingTuple ft = MessageFormatter.format(format, arg); 93 | log(SELF, HILoggerLevelDebug, ft.getMessage(), ft.getThrowable()); 94 | } 95 | } 96 | 97 | public void debug(String format, Object arg1, Object arg2) { 98 | if (isDebugEnabled()) { 99 | FormattingTuple ft = MessageFormatter.format(format, arg1, arg2); 100 | log(SELF, HILoggerLevelDebug, ft.getMessage(), ft.getThrowable()); 101 | } 102 | } 103 | 104 | public void debug(String format, Object... argArray) { 105 | if (isDebugEnabled()) { 106 | FormattingTuple ft = MessageFormatter.arrayFormat(format, argArray); 107 | log(SELF, HILoggerLevelDebug, ft.getMessage(), ft.getThrowable()); 108 | } 109 | } 110 | 111 | public void debug(String msg, Throwable t) { 112 | if (isDebugEnabled()) { 113 | log(SELF, HILoggerLevelDebug, msg, t); 114 | } 115 | } 116 | 117 | public boolean isInfoEnabled() { 118 | return getLevel() <= HILoggerLevelInfo; 119 | } 120 | 121 | public void info(String msg) { 122 | if (isInfoEnabled()) { 123 | log(SELF, HILoggerLevelInfo, msg, null); 124 | } 125 | } 126 | 127 | public void info(String format, Object arg) { 128 | if (isInfoEnabled()) { 129 | FormattingTuple ft = MessageFormatter.format(format, arg); 130 | log(SELF, HILoggerLevelInfo, ft.getMessage(), ft.getThrowable()); 131 | } 132 | } 133 | 134 | public void info(String format, Object arg1, Object arg2) { 135 | if (isInfoEnabled()) { 136 | FormattingTuple ft = MessageFormatter.format(format, arg1, arg2); 137 | log(SELF, HILoggerLevelInfo, ft.getMessage(), ft.getThrowable()); 138 | } 139 | } 140 | 141 | public void info(String format, Object... argArray) { 142 | if (isInfoEnabled()) { 143 | FormattingTuple ft = MessageFormatter.arrayFormat(format, argArray); 144 | log(SELF, HILoggerLevelInfo, ft.getMessage(), ft.getThrowable()); 145 | } 146 | } 147 | 148 | public void info(String msg, Throwable t) { 149 | if (isInfoEnabled()) { 150 | log(SELF, HILoggerLevelInfo, msg, t); 151 | } 152 | } 153 | 154 | public boolean isWarnEnabled() { 155 | return getLevel() <= HILoggerLevelWarn; 156 | } 157 | 158 | public void warn(String msg) { 159 | if (isWarnEnabled()) { 160 | log(SELF, HILoggerLevelWarn, msg, null); 161 | } 162 | } 163 | 164 | public void warn(String format, Object arg) { 165 | if (isWarnEnabled()) { 166 | FormattingTuple ft = MessageFormatter.format(format, arg); 167 | log(SELF, HILoggerLevelWarn, ft.getMessage(), ft.getThrowable()); 168 | } 169 | } 170 | 171 | public void warn(String format, Object arg1, Object arg2) { 172 | if (isWarnEnabled()) { 173 | FormattingTuple ft = MessageFormatter.format(format, arg1, arg2); 174 | log(SELF, HILoggerLevelWarn, ft.getMessage(), ft.getThrowable()); 175 | } 176 | } 177 | 178 | public void warn(String format, Object... argArray) { 179 | if (isWarnEnabled()) { 180 | FormattingTuple ft = MessageFormatter.arrayFormat(format, argArray); 181 | log(SELF, HILoggerLevelWarn, ft.getMessage(), ft.getThrowable()); 182 | } 183 | } 184 | 185 | public void warn(String msg, Throwable t) { 186 | if (isWarnEnabled()) { 187 | log(SELF, HILoggerLevelWarn, msg, t); 188 | } 189 | } 190 | 191 | public boolean isErrorEnabled() { 192 | return getLevel() <= HILoggerLevelError; 193 | } 194 | 195 | public void error(String msg) { 196 | if (isErrorEnabled()) { 197 | log(SELF, HILoggerLevelError, msg, null); 198 | } 199 | } 200 | 201 | public void error(String format, Object arg) { 202 | if (isErrorEnabled()) { 203 | FormattingTuple ft = MessageFormatter.format(format, arg); 204 | log(SELF, HILoggerLevelError, ft.getMessage(), ft.getThrowable()); 205 | } 206 | } 207 | 208 | public void error(String format, Object arg1, Object arg2) { 209 | if (isErrorEnabled()) { 210 | FormattingTuple ft = MessageFormatter.format(format, arg1, arg2); 211 | log(SELF, HILoggerLevelError, ft.getMessage(), ft.getThrowable()); 212 | } 213 | } 214 | 215 | public void error(String format, Object... arguments) { 216 | if (isErrorEnabled()) { 217 | FormattingTuple ft = MessageFormatter.arrayFormat(format, arguments); 218 | log(SELF, HILoggerLevelError, ft.getMessage(), ft.getThrowable()); 219 | } 220 | } 221 | 222 | public void error(String msg, Throwable t) { 223 | if (isErrorEnabled()) { 224 | log(SELF, HILoggerLevelError, msg, t); 225 | } 226 | } 227 | 228 | private void log(String callerFQCN, int level, String msg, Throwable t) { 229 | String fileName = null; 230 | String methodName = null; 231 | int lineNumber = 0; 232 | 233 | StackTraceElement callerData = getCallerData(callerFQCN); 234 | if (callerData != null) { 235 | fileName = callerData.getFileName(); 236 | lineNumber = callerData.getLineNumber(); 237 | 238 | String className = callerData.getClassName().replaceAll(".*\\.", ""); 239 | methodName = "[" + className + " " + callerData.getMethodName() + "]"; 240 | } 241 | 242 | if (msg != null) { 243 | receiveLogFromJVM(fileName, methodName, lineNumber, level, msg); 244 | } 245 | 246 | if (t != null) { 247 | receiveLogFromJVM(fileName, methodName, lineNumber, level, "Exception logged: " + t); 248 | } 249 | } 250 | 251 | // TODO 252 | private StackTraceElement getCallerData(String callerFQCN) { 253 | StackTraceElement[] steArray = new Throwable().getStackTrace(); 254 | 255 | int selfIndex = -1; 256 | for (int i = 0; i < steArray.length; i++) { 257 | final String className = steArray[i].getClassName(); 258 | if (className.equals(callerFQCN) || className.equals(SUPER)) { 259 | selfIndex = i; 260 | break; 261 | } 262 | } 263 | 264 | int found = -1; 265 | for (int i = selfIndex + 1; i < steArray.length; i++) { 266 | final String className = steArray[i].getClassName(); 267 | if (!(className.equals(callerFQCN) || className.equals(SUPER))) { 268 | found = i; 269 | break; 270 | } 271 | } 272 | 273 | if (found != -1) { 274 | return steArray[found]; 275 | } else { 276 | return null; 277 | } 278 | } 279 | 280 | public native void receiveLogFromJVM(String fileName, String methodName, int lineNumber, int level, String msg); 281 | } 282 | -------------------------------------------------------------------------------- /BitcoinJKit/HIBitcoinManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // HIBitcoinManager.h 3 | // BitcoinKit 4 | // 5 | // Created by Bazyli Zygan on 11.07.2013. 6 | // Copyright (c) 2013 Hive Developers. All rights reserved. 7 | // 8 | 9 | // Transaction list update notification. Sent object is a NSString representation of the updated hash 10 | extern NSString * const kHIBitcoinManagerTransactionChangedNotification; 11 | 12 | // Manager start notification. Informs that manager is now ready to use 13 | extern NSString * const kHIBitcoinManagerStartedNotification; 14 | 15 | // Manager stop notification. Informs that manager is now stopped and can't be used anymore 16 | extern NSString * const kHIBitcoinManagerStoppedNotification; 17 | 18 | 19 | /** HIBitcoinManager is a class responsible for managing all Bitcoin actions app should do 20 | * 21 | * Word of warning. One should not create this object. All access should be done 22 | * via defaultManager class method that returns application-wide singleton to it. 23 | * 24 | * All properties are KVC enabled so one can register as an observer to them to monitor the changes. 25 | */ 26 | @interface HIBitcoinManager : NSObject 27 | 28 | // Specifies an URL path to a directory where HIBitcoinManager should store its data. 29 | // Warning! All changes to this property have to be made BEFORE start. 30 | @property (nonatomic, copy) NSURL *dataURL; 31 | 32 | // Specifies a path to a checkpoints file used to speed up blockchain sync (optional) 33 | // Warning! All changes to this property have to be made BEFORE start. 34 | // 35 | // To create a checkpoints file, use the BuildCheckpoints class from bitcoinj, e.g.: 36 | // java -cp target/bitcoinj-tools-0.11-SNAPSHOT.jar com.google.bitcoin.tools.BuildCheckpoints 37 | // Note: BuildCheckpoints only generates checkpoints for the main network (i.e. not for the testnet). 38 | @property (nonatomic, copy) NSString *checkpointsFilePath; 39 | 40 | // Specifies if a manager is running on the testing network. 41 | // Warning! All changes to this property have to be made BEFORE start. 42 | @property (nonatomic, assign) BOOL testingNetwork; 43 | 44 | // Flag indicating if HIBitcoinManager is currently running and connecting with the network 45 | @property (nonatomic, readonly) BOOL isRunning; 46 | 47 | // Flag indicating if HIBitcoinManager is currently syncing with the Bitcoin network 48 | @property (nonatomic, readonly) BOOL isSyncing; 49 | 50 | // Actual balance of the wallet 51 | @property (nonatomic, readonly) uint64_t availableBalance; 52 | 53 | // Balance calculated assuming all pending transactions are included into the best chain by miners 54 | @property (nonatomic, readonly) uint64_t estimatedBalance; 55 | 56 | // Float value indicating the progress of network sync. Values are from 0 to 100. 57 | @property (nonatomic, readonly) float syncProgress; 58 | 59 | // Various details about the wallet dumped into a single string, useful for debugging 60 | @property (nonatomic, readonly) NSString *walletDebuggingInfo; 61 | 62 | // Returns wallets main address. Creates one if none exists yet 63 | @property (nonatomic, readonly, getter = walletAddress) NSString *walletAddress; 64 | 65 | // Returns YES if wallet is encrypted. NO - otherwise 66 | @property (nonatomic, readonly, getter = isWalletEncrypted) BOOL isWalletEncrypted; 67 | 68 | // Returns YES if wallet is currently locked. NO - otherwise 69 | @property (nonatomic, readonly, getter = isWalletLocked) BOOL isWalletLocked; 70 | 71 | // Returns global transaction cound for current wallet 72 | @property (nonatomic, readonly, getter = transactionCount) NSUInteger transactionCount; 73 | 74 | // Returns number of connected peers 75 | @property (nonatomic, readonly) NSUInteger peerCount; 76 | 77 | // Tells if the manager is connected to at least one peer 78 | @property (nonatomic, readonly) BOOL isConnected; 79 | 80 | // Returns date when the wallet password was last changed, or when the wallet was created (might be null for old files) 81 | @property (nonatomic, readonly) NSDate *lastWalletChangeDate; 82 | 83 | // Block that will be called when an exception is thrown on a background thread in JVM (e.g. while processing an 84 | // incoming transaction or other blockchain update). If not set, the exception will just be thrown and will crash your 85 | // app unless you install a global uncaught exception handler. 86 | // Note: exceptions that are thrown while processing calls made from the Cocoa side will ignore this handler and will 87 | // simply be thrown directly in the same thread. 88 | @property (nonatomic, copy) void(^exceptionHandler)(NSException *exception); 89 | 90 | 91 | /** Class method returning application singleton to the manager. 92 | * 93 | * Please note not to create HIBitcoinManager objects in any other way. 94 | * This is due to bitcoind implementation that uses global variables that 95 | * currently allows us to create only one instance of this object. 96 | * Which should be more than enough anyway. 97 | * 98 | * @returns Initialized and ready manager object. 99 | */ 100 | + (HIBitcoinManager *)defaultManager; 101 | 102 | /** Starts the manager initializing all data and starting network sync. 103 | * 104 | * One should start the manager only once. After configuring the singleton. 105 | * Every time one will try to do that again - it will crash 106 | * This is due to bitcoind implementation that uses too many globals. 107 | * 108 | * @param error A pointer to an error object (or NULL to throw an exception on errors) 109 | * 110 | * @returns NO if an error prevented proper initialization. 111 | */ 112 | - (BOOL)start:(NSError **)error; 113 | 114 | /** 115 | * Creates a new unprotected wallet. 116 | * 117 | * Only call this if start returned kHIBitcoinManagerNoWallet. 118 | * It will fail if a wallet already exists. 119 | */ 120 | - (void)createWallet:(NSError **)error; 121 | 122 | /** 123 | * Creates a new wallet protected with a password. 124 | * 125 | * Only call this if start returned kHIBitcoinManagerNoWallet. 126 | * It will fail if a wallet already exists. 127 | * 128 | * @param password The user password as an UTF-16-encoded string. 129 | */ 130 | - (void)createWalletWithPassword:(NSData *)password 131 | error:(NSError **)error; 132 | 133 | /** Changes the wallet's password. 134 | * 135 | * @param fromPassword The current wallet password as an UTF-16-encoded string. 136 | * @param toPassword The new wallet password as an UTF-16-encoded string. 137 | * @param error A pointer to an error object (or NULL to throw an exception on errors) 138 | */ 139 | - (void)changeWalletPassword:(NSData *)fromPassword 140 | toPassword:(NSData *)toPassword 141 | error:(NSError **)error; 142 | 143 | /** Checks if the given password is correct. 144 | * 145 | * @param password The password to be checked. 146 | * @returns BOOL true if the password matches the one in the wallet 147 | */ 148 | - (BOOL)isPasswordCorrect:(NSData *)password; 149 | 150 | /** Signs a given text message with the user's private key and returns the signature. 151 | * 152 | * @param message Message to be signed. 153 | * @param password The wallet password (if any). 154 | * @param error A pointer to an error object (or NULL to throw an exception on errors) 155 | */ 156 | 157 | - (NSString *)signMessage:(NSString *)message 158 | withPassword:(NSData *)password 159 | error:(NSError **)error; 160 | 161 | /** Stops the manager and stores all up-to-date information in data folder 162 | * 163 | * One should stop the manager only once. At the shutdown procedure. 164 | * This is due to bitcoind implementation that uses too many globals. 165 | */ 166 | - (void)stop; 167 | 168 | /** Deletes the blockchain data file. */ 169 | - (void)deleteBlockchainDataFile:(NSError **)error; 170 | 171 | /** Deletes the blockchain data file, restarts the network layer and starts rebuilding the wallet from the blockchain. 172 | * 173 | * @param error A pointer to an error object (or NULL to throw an exception on errors) 174 | */ 175 | - (void)resetBlockchain:(NSError **)error; 176 | 177 | /** Returns transaction definition based on transaction hash 178 | * 179 | * @param hash NSString representation of transaction hash 180 | * 181 | * @returns NSDictionary definition of found transansaction. nil if not found 182 | */ 183 | - (NSDictionary *)transactionForHash:(NSString *)hash; 184 | 185 | /** Returns transaction definition based on transaction hash 186 | * 187 | * WARNING: Because transaction are kept in maps in bitcoind the only way 188 | * to find an element at requested index is to iterate through all of elements 189 | * in front. DO NOT USE too often or your app will get absurdely slow 190 | * 191 | * @param index Index of the searched transaction 192 | * 193 | * @returns NSDictionary definition of found transansaction. nil if not found 194 | */ 195 | - (NSDictionary *)transactionAtIndex:(NSUInteger)index; 196 | 197 | /** Returns an array of definitions of all transactions 198 | * 199 | * @returns Array of all transactions to this wallet 200 | */ 201 | - (NSArray *)allTransactions; 202 | 203 | /** Returns array of transactions from given range 204 | * 205 | * @param range Range of requested transactions 206 | * 207 | * @returns An array of transactions from requested range 208 | */ 209 | - (NSArray *)transactionsWithRange:(NSRange)range; 210 | 211 | /** Checks if given address is valid address 212 | * 213 | * @param address Address string to be checked 214 | * 215 | * @returns YES if address is valid. NO - otherwise 216 | */ 217 | - (BOOL)isAddressValid:(NSString *)address; 218 | 219 | /** Calculates the transaction fee when sending coins. 220 | * 221 | * @param coins Amount of coins for the recipient to receive in satoshis 222 | * @param recipient Target address 223 | */ 224 | - (uint64_t)calculateTransactionFeeForSendingCoins:(uint64_t)coins 225 | toRecipient:(NSString *)recipient 226 | error:(NSError **)error; 227 | 228 | /** Sends amount of coins to recipient 229 | * 230 | * @param coins Amount of coins to be sent in satoshis 231 | * @param recipient Recipient's address hash 232 | * @param comment optional comment string that will be bound to the transaction 233 | * @param complection Completion block where notification about created transaction hash will be sent 234 | * 235 | */ 236 | - (void)sendCoins:(uint64_t)coins 237 | toRecipient:(NSString *)recipient 238 | comment:(NSString *)comment 239 | password:(NSData *)password 240 | error:(NSError **)error 241 | completion:(void(^)(NSString *hash))completion; 242 | 243 | /** Loads a Payment Protocol compatible payment request from a local file. 244 | * 245 | * @param filename Path to the .bitcoinpaymentrequest file 246 | * @param outError Returned error if the payment request file can't be opened 247 | * @param callback Block that will be called when the request is loaded or an error occurs; 248 | * returns: error (if any), session id (to be returned later), request data dictionary 249 | * @return true if the request file was opened 250 | */ 251 | - (BOOL)openPaymentRequestFromFile:(NSString *)filename 252 | error:(NSError **)outError 253 | callback:(void(^)(NSError*, int, NSDictionary*))callback; 254 | 255 | /** Loads a Payment Protocol compatible payment request from a remote URL. 256 | * 257 | * @param URL Location of the payment request resource 258 | * @param outError Returned error if the URL is empty or invalid 259 | * @param callback Block that will be called when the request is loaded or an error occurs; 260 | * returns: error (if any), session id (to be returned later), request data dictionary 261 | * @return true if the URL was valid and the request is being loaded 262 | */ 263 | - (BOOL)openPaymentRequestFromURL:(NSString *)URL 264 | error:(NSError **)outError 265 | callback:(void(^)(NSError*, int, NSDictionary*))callback; 266 | 267 | /** Submits a payment using Payment Protocol. 268 | * 269 | * @param sessionId session id returned from one of the openPaymentRequest* methods 270 | * @param password wallet password, if any 271 | * @param outError Returned error if the transaction can't be completed 272 | * @param callback Block that will be called when the payment is accepted or an error occurs; 273 | * returns: error (if any), ack response data dictionary 274 | * @return true if the transaction was prepared successfully and the payment was submitted 275 | (though not necessarily confirmed) 276 | */ 277 | - (BOOL)sendPaymentRequest:(int)sessionId 278 | password:(NSData *)password 279 | error:(NSError **)outError 280 | callback:(void(^)(NSError*, NSDictionary*, NSString*))callback; 281 | 282 | /** Exports (backs up) the wallet to given file URL. 283 | * 284 | * @param exportURL NSURL to local file where the wallet file should be copied to 285 | * @param error reference to error variable where error info will be written if backup fails 286 | * 287 | */ 288 | - (void)exportWalletTo:(NSURL *)exportURL error:(NSError **)error; 289 | 290 | /** Exports the wallet's private key to a string to be saved in a file. 291 | * 292 | * @param password wallet password 293 | * @param error reference to error variable where error info will be written if export fails 294 | * @return wallet private key (in WIF format with a createdAt timestamp) 295 | * 296 | */ 297 | - (NSString *)exportPrivateKeyWithPassword:(NSData *)password 298 | error:(NSError **)error; 299 | 300 | @end 301 | -------------------------------------------------------------------------------- /BitcoinKit.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 24D21A63184D550A00FF296A /* HIBitcoinInternalErrorCodes.h in Headers */ = {isa = PBXBuildFile; fileRef = 24D21A61184D550A00FF296A /* HIBitcoinInternalErrorCodes.h */; }; 11 | 24D21A64184D550A00FF296A /* HIBitcoinInternalErrorCodes.m in Sources */ = {isa = PBXBuildFile; fileRef = 24D21A62184D550A00FF296A /* HIBitcoinInternalErrorCodes.m */; }; 12 | 583B1DC71860AA5A00817EF8 /* HILogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 583B1DC51860AA5A00817EF8 /* HILogger.h */; settings = {ATTRIBUTES = (Public, ); }; }; 13 | 583B1DC81860AA5A00817EF8 /* HILogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 583B1DC61860AA5A00817EF8 /* HILogger.m */; }; 14 | 58BE670D185B3DB3001AAB50 /* HIBitcoinErrorCodes.h in Headers */ = {isa = PBXBuildFile; fileRef = 58BE670A185B3DB3001AAB50 /* HIBitcoinErrorCodes.h */; settings = {ATTRIBUTES = (Public, ); }; }; 15 | 58BE670E185B3DB3001AAB50 /* HIBitcoinErrorCodes.m in Sources */ = {isa = PBXBuildFile; fileRef = 58BE670B185B3DB3001AAB50 /* HIBitcoinErrorCodes.m */; }; 16 | 58BE670F185B3DB3001AAB50 /* HIBitcoinManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 58BE670C185B3DB3001AAB50 /* HIBitcoinManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; 17 | D901F50017B279EA0071780D /* boot.jar in Resources */ = {isa = PBXBuildFile; fileRef = D901F49A17AF9DDB0071780D /* boot.jar */; }; 18 | D958B0AF17A29CE200B0EDD5 /* jni_md.h in Headers */ = {isa = PBXBuildFile; fileRef = D958B0AD17A29CE200B0EDD5 /* jni_md.h */; }; 19 | D958B0B017A29CE200B0EDD5 /* jni.h in Headers */ = {isa = PBXBuildFile; fileRef = D958B0AE17A29CE200B0EDD5 /* jni.h */; }; 20 | D958B0BC17A2A40600B0EDD5 /* HIBitcoinManager.m in Sources */ = {isa = PBXBuildFile; fileRef = D958B0BA17A2A40600B0EDD5 /* HIBitcoinManager.m */; }; 21 | D958B0BE17A2A41A00B0EDD5 /* BitcoinJKit.h in Headers */ = {isa = PBXBuildFile; fileRef = D958B0A617A29B0500B0EDD5 /* BitcoinJKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXFileReference section */ 25 | 24D21A61184D550A00FF296A /* HIBitcoinInternalErrorCodes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HIBitcoinInternalErrorCodes.h; sourceTree = ""; }; 26 | 24D21A62184D550A00FF296A /* HIBitcoinInternalErrorCodes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HIBitcoinInternalErrorCodes.m; sourceTree = ""; }; 27 | 583B1DC51860AA5A00817EF8 /* HILogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HILogger.h; sourceTree = ""; }; 28 | 583B1DC61860AA5A00817EF8 /* HILogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HILogger.m; sourceTree = ""; }; 29 | 58BE670A185B3DB3001AAB50 /* HIBitcoinErrorCodes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HIBitcoinErrorCodes.h; sourceTree = ""; }; 30 | 58BE670B185B3DB3001AAB50 /* HIBitcoinErrorCodes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HIBitcoinErrorCodes.m; sourceTree = ""; }; 31 | 58BE670C185B3DB3001AAB50 /* HIBitcoinManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HIBitcoinManager.h; sourceTree = ""; }; 32 | D901F49A17AF9DDB0071780D /* boot.jar */ = {isa = PBXFileReference; lastKnownFileType = archive.jar; path = boot.jar; sourceTree = SOURCE_ROOT; }; 33 | D901F4EC17B2479C0071780D /* BitcoinManager.java */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.java; name = BitcoinManager.java; path = java/bitcoinkit/src/main/java/com/hivewallet/bitcoinkit/BitcoinManager.java; sourceTree = ""; }; 34 | D958B09D17A29B0500B0EDD5 /* BitcoinJKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = BitcoinJKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 35 | D958B0A117A29B0500B0EDD5 /* BitcoinJKit-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "BitcoinJKit-Info.plist"; sourceTree = ""; }; 36 | D958B0A517A29B0500B0EDD5 /* BitcoinJKit-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BitcoinJKit-Prefix.pch"; sourceTree = ""; }; 37 | D958B0A617A29B0500B0EDD5 /* BitcoinJKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BitcoinJKit.h; sourceTree = ""; }; 38 | D958B0AD17A29CE200B0EDD5 /* jni_md.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = jni_md.h; sourceTree = ""; }; 39 | D958B0AE17A29CE200B0EDD5 /* jni.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = jni.h; sourceTree = ""; }; 40 | D958B0BA17A2A40600B0EDD5 /* HIBitcoinManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HIBitcoinManager.m; sourceTree = ""; }; 41 | /* End PBXFileReference section */ 42 | 43 | /* Begin PBXFrameworksBuildPhase section */ 44 | D958B09917A29B0500B0EDD5 /* Frameworks */ = { 45 | isa = PBXFrameworksBuildPhase; 46 | buildActionMask = 2147483647; 47 | files = ( 48 | ); 49 | runOnlyForDeploymentPostprocessing = 0; 50 | }; 51 | /* End PBXFrameworksBuildPhase section */ 52 | 53 | /* Begin PBXGroup section */ 54 | D91FE4E4178EB1F1001B37D8 = { 55 | isa = PBXGroup; 56 | children = ( 57 | D958B09F17A29B0500B0EDD5 /* BitcoinJKit */, 58 | D91FE4EF178EB1F1001B37D8 /* Products */, 59 | ); 60 | sourceTree = ""; 61 | }; 62 | D91FE4EF178EB1F1001B37D8 /* Products */ = { 63 | isa = PBXGroup; 64 | children = ( 65 | D958B09D17A29B0500B0EDD5 /* BitcoinJKit.framework */, 66 | ); 67 | name = Products; 68 | sourceTree = ""; 69 | }; 70 | D958B09F17A29B0500B0EDD5 /* BitcoinJKit */ = { 71 | isa = PBXGroup; 72 | children = ( 73 | D958B0A617A29B0500B0EDD5 /* BitcoinJKit.h */, 74 | D901F4EC17B2479C0071780D /* BitcoinManager.java */, 75 | D901F49A17AF9DDB0071780D /* boot.jar */, 76 | 58BE670A185B3DB3001AAB50 /* HIBitcoinErrorCodes.h */, 77 | 58BE670B185B3DB3001AAB50 /* HIBitcoinErrorCodes.m */, 78 | 24D21A61184D550A00FF296A /* HIBitcoinInternalErrorCodes.h */, 79 | 24D21A62184D550A00FF296A /* HIBitcoinInternalErrorCodes.m */, 80 | 58BE670C185B3DB3001AAB50 /* HIBitcoinManager.h */, 81 | D958B0BA17A2A40600B0EDD5 /* HIBitcoinManager.m */, 82 | 583B1DC51860AA5A00817EF8 /* HILogger.h */, 83 | 583B1DC61860AA5A00817EF8 /* HILogger.m */, 84 | D958B0AE17A29CE200B0EDD5 /* jni.h */, 85 | D958B0AD17A29CE200B0EDD5 /* jni_md.h */, 86 | D958B0A017A29B0500B0EDD5 /* Supporting Files */, 87 | ); 88 | path = BitcoinJKit; 89 | sourceTree = ""; 90 | }; 91 | D958B0A017A29B0500B0EDD5 /* Supporting Files */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | D958B0A117A29B0500B0EDD5 /* BitcoinJKit-Info.plist */, 95 | D958B0A517A29B0500B0EDD5 /* BitcoinJKit-Prefix.pch */, 96 | ); 97 | name = "Supporting Files"; 98 | sourceTree = ""; 99 | }; 100 | /* End PBXGroup section */ 101 | 102 | /* Begin PBXHeadersBuildPhase section */ 103 | D958B09A17A29B0500B0EDD5 /* Headers */ = { 104 | isa = PBXHeadersBuildPhase; 105 | buildActionMask = 2147483647; 106 | files = ( 107 | 583B1DC71860AA5A00817EF8 /* HILogger.h in Headers */, 108 | D958B0BE17A2A41A00B0EDD5 /* BitcoinJKit.h in Headers */, 109 | 58BE670F185B3DB3001AAB50 /* HIBitcoinManager.h in Headers */, 110 | 58BE670D185B3DB3001AAB50 /* HIBitcoinErrorCodes.h in Headers */, 111 | D958B0AF17A29CE200B0EDD5 /* jni_md.h in Headers */, 112 | 24D21A63184D550A00FF296A /* HIBitcoinInternalErrorCodes.h in Headers */, 113 | D958B0B017A29CE200B0EDD5 /* jni.h in Headers */, 114 | ); 115 | runOnlyForDeploymentPostprocessing = 0; 116 | }; 117 | /* End PBXHeadersBuildPhase section */ 118 | 119 | /* Begin PBXNativeTarget section */ 120 | D958B09C17A29B0500B0EDD5 /* BitcoinJKit */ = { 121 | isa = PBXNativeTarget; 122 | buildConfigurationList = D958B0AB17A29B0500B0EDD5 /* Build configuration list for PBXNativeTarget "BitcoinJKit" */; 123 | buildPhases = ( 124 | D958B0AC17A29C2C00B0EDD5 /* Run Maven */, 125 | D958B09817A29B0500B0EDD5 /* Sources */, 126 | D958B09917A29B0500B0EDD5 /* Frameworks */, 127 | D958B09A17A29B0500B0EDD5 /* Headers */, 128 | D958B09B17A29B0500B0EDD5 /* Resources */, 129 | ); 130 | buildRules = ( 131 | ); 132 | dependencies = ( 133 | ); 134 | name = BitcoinJKit; 135 | productName = BitcoinJKit; 136 | productReference = D958B09D17A29B0500B0EDD5 /* BitcoinJKit.framework */; 137 | productType = "com.apple.product-type.framework"; 138 | }; 139 | /* End PBXNativeTarget section */ 140 | 141 | /* Begin PBXProject section */ 142 | D91FE4E5178EB1F1001B37D8 /* Project object */ = { 143 | isa = PBXProject; 144 | attributes = { 145 | LastUpgradeCheck = 0500; 146 | ORGANIZATIONNAME = "Hive Developers"; 147 | }; 148 | buildConfigurationList = D91FE4E8178EB1F1001B37D8 /* Build configuration list for PBXProject "BitcoinKit" */; 149 | compatibilityVersion = "Xcode 3.2"; 150 | developmentRegion = English; 151 | hasScannedForEncodings = 0; 152 | knownRegions = ( 153 | en, 154 | ); 155 | mainGroup = D91FE4E4178EB1F1001B37D8; 156 | productRefGroup = D91FE4EF178EB1F1001B37D8 /* Products */; 157 | projectDirPath = ""; 158 | projectRoot = ""; 159 | targets = ( 160 | D958B09C17A29B0500B0EDD5 /* BitcoinJKit */, 161 | ); 162 | }; 163 | /* End PBXProject section */ 164 | 165 | /* Begin PBXResourcesBuildPhase section */ 166 | D958B09B17A29B0500B0EDD5 /* Resources */ = { 167 | isa = PBXResourcesBuildPhase; 168 | buildActionMask = 2147483647; 169 | files = ( 170 | D901F50017B279EA0071780D /* boot.jar in Resources */, 171 | ); 172 | runOnlyForDeploymentPostprocessing = 0; 173 | }; 174 | /* End PBXResourcesBuildPhase section */ 175 | 176 | /* Begin PBXShellScriptBuildPhase section */ 177 | D958B0AC17A29C2C00B0EDD5 /* Run Maven */ = { 178 | isa = PBXShellScriptBuildPhase; 179 | buildActionMask = 2147483647; 180 | files = ( 181 | ); 182 | inputPaths = ( 183 | ); 184 | name = "Run Maven"; 185 | outputPaths = ( 186 | ); 187 | runOnlyForDeploymentPostprocessing = 0; 188 | shellPath = /bin/sh; 189 | shellScript = "\ncd ${SRCROOT}\n\n# make sure homebrew bin path is included\nexport PATH=$PATH:/usr/local/bin\n\nif [ ! -f boot.jar ] || [ \"$(find . -name '*.java' -newer boot.jar)\" ]; then\n echo \"REBUILDING boot.jar...\"\n (cd BitcoinJKit/java/bitcoinkit; mvn clean package -Dmaven.test.skip=true) || exit 1\n cp BitcoinJKit/java/bitcoinkit/target/BitcoinKit-*.jar boot.jar\nfi"; 190 | }; 191 | /* End PBXShellScriptBuildPhase section */ 192 | 193 | /* Begin PBXSourcesBuildPhase section */ 194 | D958B09817A29B0500B0EDD5 /* Sources */ = { 195 | isa = PBXSourcesBuildPhase; 196 | buildActionMask = 2147483647; 197 | files = ( 198 | 24D21A64184D550A00FF296A /* HIBitcoinInternalErrorCodes.m in Sources */, 199 | 58BE670E185B3DB3001AAB50 /* HIBitcoinErrorCodes.m in Sources */, 200 | D958B0BC17A2A40600B0EDD5 /* HIBitcoinManager.m in Sources */, 201 | 583B1DC81860AA5A00817EF8 /* HILogger.m in Sources */, 202 | ); 203 | runOnlyForDeploymentPostprocessing = 0; 204 | }; 205 | /* End PBXSourcesBuildPhase section */ 206 | 207 | /* Begin XCBuildConfiguration section */ 208 | D91FE501178EB1F1001B37D8 /* Debug */ = { 209 | isa = XCBuildConfiguration; 210 | buildSettings = { 211 | ALWAYS_SEARCH_USER_PATHS = NO; 212 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 213 | CLANG_CXX_LIBRARY = "libc++"; 214 | CLANG_ENABLE_MODULES = YES; 215 | CLANG_WARN_CONSTANT_CONVERSION = YES; 216 | CLANG_WARN_EMPTY_BODY = YES; 217 | CLANG_WARN_ENUM_CONVERSION = YES; 218 | CLANG_WARN_INT_CONVERSION = YES; 219 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 220 | COPY_PHASE_STRIP = NO; 221 | GCC_C_LANGUAGE_STANDARD = gnu99; 222 | GCC_DYNAMIC_NO_PIC = NO; 223 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 224 | GCC_OPTIMIZATION_LEVEL = 0; 225 | GCC_PREPROCESSOR_DEFINITIONS = ( 226 | "DEBUG=1", 227 | "$(inherited)", 228 | ); 229 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 230 | GCC_TREAT_WARNINGS_AS_ERRORS = YES; 231 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 232 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 233 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 234 | GCC_WARN_UNUSED_VARIABLE = YES; 235 | MACOSX_DEPLOYMENT_TARGET = 10.8; 236 | ONLY_ACTIVE_ARCH = YES; 237 | SDKROOT = macosx; 238 | }; 239 | name = Debug; 240 | }; 241 | D91FE502178EB1F1001B37D8 /* Release */ = { 242 | isa = XCBuildConfiguration; 243 | buildSettings = { 244 | ALWAYS_SEARCH_USER_PATHS = NO; 245 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 246 | CLANG_CXX_LIBRARY = "libc++"; 247 | CLANG_ENABLE_MODULES = YES; 248 | CLANG_WARN_CONSTANT_CONVERSION = YES; 249 | CLANG_WARN_EMPTY_BODY = YES; 250 | CLANG_WARN_ENUM_CONVERSION = YES; 251 | CLANG_WARN_INT_CONVERSION = YES; 252 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 253 | COPY_PHASE_STRIP = YES; 254 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 255 | GCC_C_LANGUAGE_STANDARD = gnu99; 256 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 257 | GCC_TREAT_WARNINGS_AS_ERRORS = YES; 258 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 259 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 260 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 261 | GCC_WARN_UNUSED_VARIABLE = YES; 262 | MACOSX_DEPLOYMENT_TARGET = 10.8; 263 | SDKROOT = macosx; 264 | }; 265 | name = Release; 266 | }; 267 | D958B0A917A29B0500B0EDD5 /* Debug */ = { 268 | isa = XCBuildConfiguration; 269 | buildSettings = { 270 | CLANG_ENABLE_OBJC_ARC = YES; 271 | COMBINE_HIDPI_IMAGES = YES; 272 | DYLIB_COMPATIBILITY_VERSION = 1; 273 | DYLIB_CURRENT_VERSION = 1; 274 | FRAMEWORK_VERSION = A; 275 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 276 | GCC_PREFIX_HEADER = "BitcoinJKit/BitcoinJKit-Prefix.pch"; 277 | GCC_VERSION = ""; 278 | INFOPLIST_FILE = "BitcoinJKit/BitcoinJKit-Info.plist"; 279 | INSTALL_PATH = "@executable_path/../Frameworks"; 280 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 281 | MACOSX_DEPLOYMENT_TARGET = 10.6; 282 | PRODUCT_NAME = "$(TARGET_NAME)"; 283 | SKIP_INSTALL = YES; 284 | WRAPPER_EXTENSION = framework; 285 | }; 286 | name = Debug; 287 | }; 288 | D958B0AA17A29B0500B0EDD5 /* Release */ = { 289 | isa = XCBuildConfiguration; 290 | buildSettings = { 291 | CLANG_ENABLE_OBJC_ARC = YES; 292 | COMBINE_HIDPI_IMAGES = YES; 293 | DYLIB_COMPATIBILITY_VERSION = 1; 294 | DYLIB_CURRENT_VERSION = 1; 295 | FRAMEWORK_VERSION = A; 296 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 297 | GCC_PREFIX_HEADER = "BitcoinJKit/BitcoinJKit-Prefix.pch"; 298 | GCC_VERSION = ""; 299 | INFOPLIST_FILE = "BitcoinJKit/BitcoinJKit-Info.plist"; 300 | INSTALL_PATH = "@executable_path/../Frameworks"; 301 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 302 | MACOSX_DEPLOYMENT_TARGET = 10.6; 303 | PRODUCT_NAME = "$(TARGET_NAME)"; 304 | SKIP_INSTALL = YES; 305 | WRAPPER_EXTENSION = framework; 306 | }; 307 | name = Release; 308 | }; 309 | /* End XCBuildConfiguration section */ 310 | 311 | /* Begin XCConfigurationList section */ 312 | D91FE4E8178EB1F1001B37D8 /* Build configuration list for PBXProject "BitcoinKit" */ = { 313 | isa = XCConfigurationList; 314 | buildConfigurations = ( 315 | D91FE501178EB1F1001B37D8 /* Debug */, 316 | D91FE502178EB1F1001B37D8 /* Release */, 317 | ); 318 | defaultConfigurationIsVisible = 0; 319 | defaultConfigurationName = Release; 320 | }; 321 | D958B0AB17A29B0500B0EDD5 /* Build configuration list for PBXNativeTarget "BitcoinJKit" */ = { 322 | isa = XCConfigurationList; 323 | buildConfigurations = ( 324 | D958B0A917A29B0500B0EDD5 /* Debug */, 325 | D958B0AA17A29B0500B0EDD5 /* Release */, 326 | ); 327 | defaultConfigurationIsVisible = 0; 328 | defaultConfigurationName = Release; 329 | }; 330 | /* End XCConfigurationList section */ 331 | }; 332 | rootObject = D91FE4E5178EB1F1001B37D8 /* Project object */; 333 | } 334 | -------------------------------------------------------------------------------- /DemoApp/HIBitcoinKitDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 58BE6719185B417C001AAB50 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 58BE6712185B417C001AAB50 /* MainMenu.xib */; }; 11 | 58BE671A185B417C001AAB50 /* HIAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 58BE6715185B417C001AAB50 /* HIAppDelegate.m */; }; 12 | 58BE671B185B417C001AAB50 /* HISendWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 58BE6717185B417C001AAB50 /* HISendWindowController.m */; }; 13 | 58BE671C185B417C001AAB50 /* HISendWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 58BE6718185B417C001AAB50 /* HISendWindowController.xib */; }; 14 | D958B0C917A2A8FD00B0EDD5 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D91FE7C617900FCC001B37D8 /* Cocoa.framework */; }; 15 | D958B0CF17A2A8FD00B0EDD5 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = D958B0CD17A2A8FD00B0EDD5 /* InfoPlist.strings */; }; 16 | D958B0D117A2A8FD00B0EDD5 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = D958B0D017A2A8FD00B0EDD5 /* main.m */; }; 17 | D958B0D517A2A8FD00B0EDD5 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = D958B0D317A2A8FD00B0EDD5 /* Credits.rtf */; }; 18 | D958B11917A6820300B0EDD5 /* BitcoinJKit.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = D958B0E217A2A8FD00B0EDD5 /* BitcoinJKit.framework */; }; 19 | D958B11C17A6823600B0EDD5 /* BitcoinJKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D958B0E217A2A8FD00B0EDD5 /* BitcoinJKit.framework */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXContainerItemProxy section */ 23 | D958B0E117A2A8FD00B0EDD5 /* PBXContainerItemProxy */ = { 24 | isa = PBXContainerItemProxy; 25 | containerPortal = D91FE7E31790100A001B37D8 /* BitcoinKit.xcodeproj */; 26 | proxyType = 2; 27 | remoteGlobalIDString = D958B09D17A29B0500B0EDD5; 28 | remoteInfo = BitcoinJKit; 29 | }; 30 | D958B11717A681FF00B0EDD5 /* PBXContainerItemProxy */ = { 31 | isa = PBXContainerItemProxy; 32 | containerPortal = D91FE7E31790100A001B37D8 /* BitcoinKit.xcodeproj */; 33 | proxyType = 1; 34 | remoteGlobalIDString = D958B09C17A29B0500B0EDD5; 35 | remoteInfo = BitcoinJKit; 36 | }; 37 | /* End PBXContainerItemProxy section */ 38 | 39 | /* Begin PBXCopyFilesBuildPhase section */ 40 | D958B0E517A2A91B00B0EDD5 /* CopyFiles */ = { 41 | isa = PBXCopyFilesBuildPhase; 42 | buildActionMask = 8; 43 | dstPath = ""; 44 | dstSubfolderSpec = 10; 45 | files = ( 46 | D958B11917A6820300B0EDD5 /* BitcoinJKit.framework in CopyFiles */, 47 | ); 48 | runOnlyForDeploymentPostprocessing = 1; 49 | }; 50 | /* End PBXCopyFilesBuildPhase section */ 51 | 52 | /* Begin PBXFileReference section */ 53 | 58BE6713185B417C001AAB50 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/MainMenu.xib; sourceTree = ""; }; 54 | 58BE6714185B417C001AAB50 /* HIAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HIAppDelegate.h; sourceTree = ""; }; 55 | 58BE6715185B417C001AAB50 /* HIAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HIAppDelegate.m; sourceTree = ""; }; 56 | 58BE6716185B417C001AAB50 /* HISendWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HISendWindowController.h; sourceTree = ""; }; 57 | 58BE6717185B417C001AAB50 /* HISendWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HISendWindowController.m; sourceTree = ""; }; 58 | 58BE6718185B417C001AAB50 /* HISendWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = HISendWindowController.xib; sourceTree = ""; }; 59 | D91FE7C617900FCC001B37D8 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 60 | D91FE7C917900FCC001B37D8 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 61 | D91FE7CA17900FCC001B37D8 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 62 | D91FE7CB17900FCC001B37D8 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 63 | D91FE7E31790100A001B37D8 /* BitcoinKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = BitcoinKit.xcodeproj; path = ../BitcoinKit.xcodeproj; sourceTree = ""; }; 64 | D958B0C817A2A8FD00B0EDD5 /* HIBitcoinJKitDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = HIBitcoinJKitDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 65 | D958B0CC17A2A8FD00B0EDD5 /* HIBitcoinJKitDemo-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "HIBitcoinJKitDemo-Info.plist"; sourceTree = ""; }; 66 | D958B0CE17A2A8FD00B0EDD5 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 67 | D958B0D017A2A8FD00B0EDD5 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 68 | D958B0D217A2A8FD00B0EDD5 /* HIBitcoinJKitDemo-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "HIBitcoinJKitDemo-Prefix.pch"; sourceTree = ""; }; 69 | D958B0D417A2A8FD00B0EDD5 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = en; path = en.lproj/Credits.rtf; sourceTree = ""; }; 70 | /* End PBXFileReference section */ 71 | 72 | /* Begin PBXFrameworksBuildPhase section */ 73 | D958B0C517A2A8FD00B0EDD5 /* Frameworks */ = { 74 | isa = PBXFrameworksBuildPhase; 75 | buildActionMask = 2147483647; 76 | files = ( 77 | D958B0C917A2A8FD00B0EDD5 /* Cocoa.framework in Frameworks */, 78 | D958B11C17A6823600B0EDD5 /* BitcoinJKit.framework in Frameworks */, 79 | ); 80 | runOnlyForDeploymentPostprocessing = 0; 81 | }; 82 | /* End PBXFrameworksBuildPhase section */ 83 | 84 | /* Begin PBXGroup section */ 85 | D91FE7BA17900FCC001B37D8 = { 86 | isa = PBXGroup; 87 | children = ( 88 | D958B0CA17A2A8FD00B0EDD5 /* HIBitcoinJKitDemo */, 89 | D91FE7C517900FCC001B37D8 /* Frameworks */, 90 | D91FE7C417900FCC001B37D8 /* Products */, 91 | ); 92 | sourceTree = ""; 93 | }; 94 | D91FE7C417900FCC001B37D8 /* Products */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | D958B0C817A2A8FD00B0EDD5 /* HIBitcoinJKitDemo.app */, 98 | ); 99 | name = Products; 100 | sourceTree = ""; 101 | }; 102 | D91FE7C517900FCC001B37D8 /* Frameworks */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | D91FE7E31790100A001B37D8 /* BitcoinKit.xcodeproj */, 106 | D91FE7C617900FCC001B37D8 /* Cocoa.framework */, 107 | D91FE7C817900FCC001B37D8 /* Other Frameworks */, 108 | ); 109 | name = Frameworks; 110 | sourceTree = ""; 111 | }; 112 | D91FE7C817900FCC001B37D8 /* Other Frameworks */ = { 113 | isa = PBXGroup; 114 | children = ( 115 | D91FE7C917900FCC001B37D8 /* AppKit.framework */, 116 | D91FE7CA17900FCC001B37D8 /* CoreData.framework */, 117 | D91FE7CB17900FCC001B37D8 /* Foundation.framework */, 118 | ); 119 | name = "Other Frameworks"; 120 | sourceTree = ""; 121 | }; 122 | D91FE7E41790100A001B37D8 /* Products */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | D958B0E217A2A8FD00B0EDD5 /* BitcoinJKit.framework */, 126 | ); 127 | name = Products; 128 | sourceTree = ""; 129 | }; 130 | D958B0CA17A2A8FD00B0EDD5 /* HIBitcoinJKitDemo */ = { 131 | isa = PBXGroup; 132 | children = ( 133 | 58BE6712185B417C001AAB50 /* MainMenu.xib */, 134 | 58BE6714185B417C001AAB50 /* HIAppDelegate.h */, 135 | 58BE6715185B417C001AAB50 /* HIAppDelegate.m */, 136 | 58BE6716185B417C001AAB50 /* HISendWindowController.h */, 137 | 58BE6717185B417C001AAB50 /* HISendWindowController.m */, 138 | 58BE6718185B417C001AAB50 /* HISendWindowController.xib */, 139 | D958B0CB17A2A8FD00B0EDD5 /* Supporting Files */, 140 | ); 141 | path = HIBitcoinJKitDemo; 142 | sourceTree = ""; 143 | }; 144 | D958B0CB17A2A8FD00B0EDD5 /* Supporting Files */ = { 145 | isa = PBXGroup; 146 | children = ( 147 | D958B0CC17A2A8FD00B0EDD5 /* HIBitcoinJKitDemo-Info.plist */, 148 | D958B0CD17A2A8FD00B0EDD5 /* InfoPlist.strings */, 149 | D958B0D017A2A8FD00B0EDD5 /* main.m */, 150 | D958B0D217A2A8FD00B0EDD5 /* HIBitcoinJKitDemo-Prefix.pch */, 151 | D958B0D317A2A8FD00B0EDD5 /* Credits.rtf */, 152 | ); 153 | name = "Supporting Files"; 154 | sourceTree = ""; 155 | }; 156 | /* End PBXGroup section */ 157 | 158 | /* Begin PBXNativeTarget section */ 159 | D958B0C717A2A8FD00B0EDD5 /* HIBitcoinJKitDemo */ = { 160 | isa = PBXNativeTarget; 161 | buildConfigurationList = D958B0E317A2A8FD00B0EDD5 /* Build configuration list for PBXNativeTarget "HIBitcoinJKitDemo" */; 162 | buildPhases = ( 163 | D958B0C417A2A8FD00B0EDD5 /* Sources */, 164 | D958B0C517A2A8FD00B0EDD5 /* Frameworks */, 165 | D958B0C617A2A8FD00B0EDD5 /* Resources */, 166 | D958B0E517A2A91B00B0EDD5 /* CopyFiles */, 167 | ); 168 | buildRules = ( 169 | ); 170 | dependencies = ( 171 | D958B11817A681FF00B0EDD5 /* PBXTargetDependency */, 172 | ); 173 | name = HIBitcoinJKitDemo; 174 | productName = HIBitcoinJKitDemo; 175 | productReference = D958B0C817A2A8FD00B0EDD5 /* HIBitcoinJKitDemo.app */; 176 | productType = "com.apple.product-type.application"; 177 | }; 178 | /* End PBXNativeTarget section */ 179 | 180 | /* Begin PBXProject section */ 181 | D91FE7BB17900FCC001B37D8 /* Project object */ = { 182 | isa = PBXProject; 183 | attributes = { 184 | CLASSPREFIX = HI; 185 | LastUpgradeCheck = 0460; 186 | ORGANIZATIONNAME = "Hive Developers"; 187 | }; 188 | buildConfigurationList = D91FE7BE17900FCC001B37D8 /* Build configuration list for PBXProject "HIBitcoinKitDemo" */; 189 | compatibilityVersion = "Xcode 3.2"; 190 | developmentRegion = English; 191 | hasScannedForEncodings = 0; 192 | knownRegions = ( 193 | en, 194 | ); 195 | mainGroup = D91FE7BA17900FCC001B37D8; 196 | productRefGroup = D91FE7C417900FCC001B37D8 /* Products */; 197 | projectDirPath = ""; 198 | projectReferences = ( 199 | { 200 | ProductGroup = D91FE7E41790100A001B37D8 /* Products */; 201 | ProjectRef = D91FE7E31790100A001B37D8 /* BitcoinKit.xcodeproj */; 202 | }, 203 | ); 204 | projectRoot = ""; 205 | targets = ( 206 | D958B0C717A2A8FD00B0EDD5 /* HIBitcoinJKitDemo */, 207 | ); 208 | }; 209 | /* End PBXProject section */ 210 | 211 | /* Begin PBXReferenceProxy section */ 212 | D958B0E217A2A8FD00B0EDD5 /* BitcoinJKit.framework */ = { 213 | isa = PBXReferenceProxy; 214 | fileType = wrapper.framework; 215 | path = BitcoinJKit.framework; 216 | remoteRef = D958B0E117A2A8FD00B0EDD5 /* PBXContainerItemProxy */; 217 | sourceTree = BUILT_PRODUCTS_DIR; 218 | }; 219 | /* End PBXReferenceProxy section */ 220 | 221 | /* Begin PBXResourcesBuildPhase section */ 222 | D958B0C617A2A8FD00B0EDD5 /* Resources */ = { 223 | isa = PBXResourcesBuildPhase; 224 | buildActionMask = 2147483647; 225 | files = ( 226 | D958B0CF17A2A8FD00B0EDD5 /* InfoPlist.strings in Resources */, 227 | D958B0D517A2A8FD00B0EDD5 /* Credits.rtf in Resources */, 228 | 58BE671C185B417C001AAB50 /* HISendWindowController.xib in Resources */, 229 | 58BE6719185B417C001AAB50 /* MainMenu.xib in Resources */, 230 | ); 231 | runOnlyForDeploymentPostprocessing = 0; 232 | }; 233 | /* End PBXResourcesBuildPhase section */ 234 | 235 | /* Begin PBXSourcesBuildPhase section */ 236 | D958B0C417A2A8FD00B0EDD5 /* Sources */ = { 237 | isa = PBXSourcesBuildPhase; 238 | buildActionMask = 2147483647; 239 | files = ( 240 | 58BE671B185B417C001AAB50 /* HISendWindowController.m in Sources */, 241 | D958B0D117A2A8FD00B0EDD5 /* main.m in Sources */, 242 | 58BE671A185B417C001AAB50 /* HIAppDelegate.m in Sources */, 243 | ); 244 | runOnlyForDeploymentPostprocessing = 0; 245 | }; 246 | /* End PBXSourcesBuildPhase section */ 247 | 248 | /* Begin PBXTargetDependency section */ 249 | D958B11817A681FF00B0EDD5 /* PBXTargetDependency */ = { 250 | isa = PBXTargetDependency; 251 | name = BitcoinJKit; 252 | targetProxy = D958B11717A681FF00B0EDD5 /* PBXContainerItemProxy */; 253 | }; 254 | /* End PBXTargetDependency section */ 255 | 256 | /* Begin PBXVariantGroup section */ 257 | 58BE6712185B417C001AAB50 /* MainMenu.xib */ = { 258 | isa = PBXVariantGroup; 259 | children = ( 260 | 58BE6713185B417C001AAB50 /* en */, 261 | ); 262 | name = MainMenu.xib; 263 | sourceTree = ""; 264 | }; 265 | D958B0CD17A2A8FD00B0EDD5 /* InfoPlist.strings */ = { 266 | isa = PBXVariantGroup; 267 | children = ( 268 | D958B0CE17A2A8FD00B0EDD5 /* en */, 269 | ); 270 | name = InfoPlist.strings; 271 | sourceTree = ""; 272 | }; 273 | D958B0D317A2A8FD00B0EDD5 /* Credits.rtf */ = { 274 | isa = PBXVariantGroup; 275 | children = ( 276 | D958B0D417A2A8FD00B0EDD5 /* en */, 277 | ); 278 | name = Credits.rtf; 279 | sourceTree = ""; 280 | }; 281 | /* End PBXVariantGroup section */ 282 | 283 | /* Begin XCBuildConfiguration section */ 284 | D91FE7DE17900FCC001B37D8 /* Debug */ = { 285 | isa = XCBuildConfiguration; 286 | buildSettings = { 287 | ALWAYS_SEARCH_USER_PATHS = NO; 288 | ARCHS = "$(ARCHS_STANDARD_64_BIT)"; 289 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 290 | CLANG_CXX_LIBRARY = "libc++"; 291 | CLANG_ENABLE_OBJC_ARC = YES; 292 | CLANG_WARN_CONSTANT_CONVERSION = YES; 293 | CLANG_WARN_EMPTY_BODY = YES; 294 | CLANG_WARN_ENUM_CONVERSION = YES; 295 | CLANG_WARN_INT_CONVERSION = YES; 296 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 297 | COPY_PHASE_STRIP = NO; 298 | GCC_C_LANGUAGE_STANDARD = gnu99; 299 | GCC_DYNAMIC_NO_PIC = NO; 300 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 301 | GCC_OPTIMIZATION_LEVEL = 0; 302 | GCC_PREPROCESSOR_DEFINITIONS = ( 303 | "DEBUG=1", 304 | "$(inherited)", 305 | ); 306 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 307 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 308 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 309 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 310 | GCC_WARN_UNUSED_VARIABLE = YES; 311 | MACOSX_DEPLOYMENT_TARGET = 10.8; 312 | ONLY_ACTIVE_ARCH = YES; 313 | SDKROOT = macosx; 314 | }; 315 | name = Debug; 316 | }; 317 | D91FE7DF17900FCC001B37D8 /* Release */ = { 318 | isa = XCBuildConfiguration; 319 | buildSettings = { 320 | ALWAYS_SEARCH_USER_PATHS = NO; 321 | ARCHS = "$(ARCHS_STANDARD_64_BIT)"; 322 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 323 | CLANG_CXX_LIBRARY = "libc++"; 324 | CLANG_ENABLE_OBJC_ARC = YES; 325 | CLANG_WARN_CONSTANT_CONVERSION = YES; 326 | CLANG_WARN_EMPTY_BODY = YES; 327 | CLANG_WARN_ENUM_CONVERSION = YES; 328 | CLANG_WARN_INT_CONVERSION = YES; 329 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 330 | COPY_PHASE_STRIP = YES; 331 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 332 | GCC_C_LANGUAGE_STANDARD = gnu99; 333 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 334 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 335 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 336 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 337 | GCC_WARN_UNUSED_VARIABLE = YES; 338 | MACOSX_DEPLOYMENT_TARGET = 10.8; 339 | SDKROOT = macosx; 340 | }; 341 | name = Release; 342 | }; 343 | D958B0DC17A2A8FD00B0EDD5 /* Debug */ = { 344 | isa = XCBuildConfiguration; 345 | buildSettings = { 346 | COMBINE_HIDPI_IMAGES = YES; 347 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 348 | GCC_PREFIX_HEADER = "HIBitcoinJKitDemo/HIBitcoinJKitDemo-Prefix.pch"; 349 | INFOPLIST_FILE = "HIBitcoinJKitDemo/HIBitcoinJKitDemo-Info.plist"; 350 | MACOSX_DEPLOYMENT_TARGET = 10.7; 351 | PRODUCT_NAME = "$(TARGET_NAME)"; 352 | WRAPPER_EXTENSION = app; 353 | }; 354 | name = Debug; 355 | }; 356 | D958B0DD17A2A8FD00B0EDD5 /* Release */ = { 357 | isa = XCBuildConfiguration; 358 | buildSettings = { 359 | COMBINE_HIDPI_IMAGES = YES; 360 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 361 | GCC_PREFIX_HEADER = "HIBitcoinJKitDemo/HIBitcoinJKitDemo-Prefix.pch"; 362 | INFOPLIST_FILE = "HIBitcoinJKitDemo/HIBitcoinJKitDemo-Info.plist"; 363 | MACOSX_DEPLOYMENT_TARGET = 10.7; 364 | PRODUCT_NAME = "$(TARGET_NAME)"; 365 | WRAPPER_EXTENSION = app; 366 | }; 367 | name = Release; 368 | }; 369 | /* End XCBuildConfiguration section */ 370 | 371 | /* Begin XCConfigurationList section */ 372 | D91FE7BE17900FCC001B37D8 /* Build configuration list for PBXProject "HIBitcoinKitDemo" */ = { 373 | isa = XCConfigurationList; 374 | buildConfigurations = ( 375 | D91FE7DE17900FCC001B37D8 /* Debug */, 376 | D91FE7DF17900FCC001B37D8 /* Release */, 377 | ); 378 | defaultConfigurationIsVisible = 0; 379 | defaultConfigurationName = Release; 380 | }; 381 | D958B0E317A2A8FD00B0EDD5 /* Build configuration list for PBXNativeTarget "HIBitcoinJKitDemo" */ = { 382 | isa = XCConfigurationList; 383 | buildConfigurations = ( 384 | D958B0DC17A2A8FD00B0EDD5 /* Debug */, 385 | D958B0DD17A2A8FD00B0EDD5 /* Release */, 386 | ); 387 | defaultConfigurationIsVisible = 0; 388 | defaultConfigurationName = Release; 389 | }; 390 | /* End XCConfigurationList section */ 391 | }; 392 | rootObject = D91FE7BB17900FCC001B37D8 /* Project object */; 393 | } 394 | -------------------------------------------------------------------------------- /DemoApp/HIBitcoinJKitDemo/HISendWindowController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1070 5 | 12E55 6 | 3084 7 | 1187.39 8 | 626.00 9 | 10 | com.apple.InterfaceBuilder.CocoaPlugin 11 | 3084 12 | 13 | 14 | NSButton 15 | NSButtonCell 16 | NSCustomObject 17 | NSTextField 18 | NSTextFieldCell 19 | NSView 20 | NSWindowTemplate 21 | 22 | 23 | com.apple.InterfaceBuilder.CocoaPlugin 24 | 25 | 26 | PluginDependencyRecalculationVersion 27 | 28 | 29 | 30 | 31 | HISendWindowController 32 | 33 | 34 | FirstResponder 35 | 36 | 37 | NSApplication 38 | 39 | 40 | 23 41 | 2 42 | {{167, 107}, {404, 118}} 43 | -1535638528 44 | Window 45 | NSPanel 46 | 47 | 48 | 49 | 50 | 256 51 | 52 | 53 | 54 | 268 55 | {{17, 88}, {61, 17}} 56 | 57 | 58 | 59 | _NS:1535 60 | YES 61 | 62 | 68157504 63 | 272630784 64 | Receiver: 65 | 66 | LucidaGrande 67 | 13 68 | 1044 69 | 70 | _NS:1535 71 | 72 | 73 | 6 74 | System 75 | controlColor 76 | 77 | 3 78 | MC42NjY2NjY2NjY3AA 79 | 80 | 81 | 82 | 6 83 | System 84 | controlTextColor 85 | 86 | 3 87 | MAA 88 | 89 | 90 | 91 | NO 92 | 93 | 94 | 95 | 268 96 | {{17, 61}, {59, 17}} 97 | 98 | 99 | 100 | _NS:1535 101 | YES 102 | 103 | 68157504 104 | 272630784 105 | Amount: 106 | 107 | _NS:1535 108 | 109 | 110 | 111 | 112 | NO 113 | 114 | 115 | 116 | 268 117 | {{89, 83}, {295, 22}} 118 | 119 | 120 | 121 | _NS:9 122 | YES 123 | 124 | -1804599231 125 | 272630784 126 | 127 | 128 | _NS:9 129 | 130 | YES 131 | 132 | 6 133 | System 134 | textBackgroundColor 135 | 136 | 3 137 | MQA 138 | 139 | 140 | 141 | 6 142 | System 143 | textColor 144 | 145 | 146 | 147 | NO 148 | 149 | 150 | 151 | 268 152 | {{89, 58}, {295, 22}} 153 | 154 | 155 | 156 | _NS:9 157 | YES 158 | 159 | -1804599231 160 | 272630784 161 | 162 | 163 | _NS:9 164 | 165 | YES 166 | 167 | 168 | 169 | NO 170 | 171 | 172 | 173 | 268 174 | {{314, 13}, {71, 32}} 175 | 176 | 177 | 178 | _NS:9 179 | YES 180 | 181 | 67108864 182 | 134217728 183 | Send 184 | 185 | _NS:9 186 | 187 | -2038284288 188 | 129 189 | 190 | DQ 191 | 200 192 | 25 193 | 194 | NO 195 | 196 | 197 | 198 | 268 199 | {{233, 13}, {82, 32}} 200 | 201 | 202 | 203 | _NS:9 204 | YES 205 | 206 | 67108864 207 | 134217728 208 | Cancel 209 | 210 | _NS:9 211 | 212 | -2038284288 213 | 129 214 | 215 | Gw 216 | 200 217 | 25 218 | 219 | NO 220 | 221 | 222 | {404, 118} 223 | 224 | 225 | 226 | _NS:21 227 | 228 | {{0, 0}, {1680, 1028}} 229 | {10000000000000, 10000000000000} 230 | YES 231 | 232 | 233 | 234 | 235 | 236 | 237 | window 238 | 239 | 240 | 241 | 7 242 | 243 | 244 | 245 | addressField 246 | 247 | 248 | 249 | 23 250 | 251 | 252 | 253 | amountField 254 | 255 | 256 | 257 | 24 258 | 259 | 260 | 261 | cancelClicked: 262 | 263 | 264 | 265 | 25 266 | 267 | 268 | 269 | sendClicked: 270 | 271 | 272 | 273 | 26 274 | 275 | 276 | 277 | delegate 278 | 279 | 280 | 281 | 8 282 | 283 | 284 | 285 | 286 | 287 | 0 288 | 289 | 290 | 291 | 292 | 293 | -2 294 | 295 | 296 | File's Owner 297 | 298 | 299 | -1 300 | 301 | 302 | First Responder 303 | 304 | 305 | -3 306 | 307 | 308 | Application 309 | 310 | 311 | 5 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 6 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 9 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 10 341 | 342 | 343 | 344 | 345 | 13 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 14 354 | 355 | 356 | 357 | 358 | 15 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 16 367 | 368 | 369 | 370 | 371 | 17 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 18 380 | 381 | 382 | 383 | 384 | 19 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 20 393 | 394 | 395 | 396 | 397 | 21 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 22 406 | 407 | 408 | 409 | 410 | 411 | 412 | com.apple.InterfaceBuilder.CocoaPlugin 413 | com.apple.InterfaceBuilder.CocoaPlugin 414 | com.apple.InterfaceBuilder.CocoaPlugin 415 | com.apple.InterfaceBuilder.CocoaPlugin 416 | com.apple.InterfaceBuilder.CocoaPlugin 417 | com.apple.InterfaceBuilder.CocoaPlugin 418 | com.apple.InterfaceBuilder.CocoaPlugin 419 | com.apple.InterfaceBuilder.CocoaPlugin 420 | com.apple.InterfaceBuilder.CocoaPlugin 421 | com.apple.InterfaceBuilder.CocoaPlugin 422 | com.apple.InterfaceBuilder.CocoaPlugin 423 | com.apple.InterfaceBuilder.CocoaPlugin 424 | com.apple.InterfaceBuilder.CocoaPlugin 425 | com.apple.InterfaceBuilder.CocoaPlugin 426 | com.apple.InterfaceBuilder.CocoaPlugin 427 | 428 | com.apple.InterfaceBuilder.CocoaPlugin 429 | com.apple.InterfaceBuilder.CocoaPlugin 430 | 431 | 432 | 433 | 434 | 435 | 26 436 | 437 | 438 | 439 | 440 | HISendWindowController 441 | NSWindowController 442 | 443 | NSButton 444 | NSButton 445 | 446 | 447 | 448 | cancelClicked: 449 | NSButton 450 | 451 | 452 | sendClicked: 453 | NSButton 454 | 455 | 456 | 457 | NSTextField 458 | NSTextField 459 | 460 | 461 | 462 | addressField 463 | NSTextField 464 | 465 | 466 | amountField 467 | NSTextField 468 | 469 | 470 | 471 | IBProjectSource 472 | ./Classes/HISendWindowController.h 473 | 474 | 475 | 476 | 477 | 0 478 | IBCocoaFramework 479 | 480 | com.apple.InterfaceBuilder.CocoaPlugin.macosx 481 | 482 | 483 | YES 484 | 3 485 | 486 | 487 | -------------------------------------------------------------------------------- /BitcoinJKit/java/bitcoinkit/src/main/java/com/hivewallet/bitcoinkit/BitcoinManager.java: -------------------------------------------------------------------------------- 1 | package com.hivewallet.bitcoinkit; 2 | 3 | import com.google.bitcoin.core.*; 4 | import com.google.bitcoin.crypto.KeyCrypter; 5 | import com.google.bitcoin.crypto.KeyCrypterException; 6 | import com.google.bitcoin.crypto.KeyCrypterScrypt; 7 | import com.google.bitcoin.net.discovery.DnsDiscovery; 8 | import com.google.bitcoin.params.MainNetParams; 9 | import com.google.bitcoin.params.TestNet3Params; 10 | import com.google.bitcoin.protocols.payments.PaymentRequestException; 11 | import com.google.bitcoin.protocols.payments.PaymentSession; 12 | import com.google.bitcoin.script.Script; 13 | import com.google.bitcoin.store.BlockStore; 14 | import com.google.bitcoin.store.BlockStoreException; 15 | import com.google.bitcoin.store.SPVBlockStore; 16 | import com.google.bitcoin.store.UnreadableWalletException; 17 | import com.google.bitcoin.store.WalletProtobufSerializer; 18 | import com.google.bitcoin.utils.Threading; 19 | import com.google.common.base.Throwables; 20 | import com.google.common.collect.ImmutableList; 21 | import com.google.common.util.concurrent.FutureCallback; 22 | import com.google.common.util.concurrent.Futures; 23 | import com.google.common.util.concurrent.ListenableFuture; 24 | import org.bitcoinj.wallet.Protos; 25 | import org.codehaus.jettison.json.JSONArray; 26 | import org.codehaus.jettison.json.JSONException; 27 | import org.codehaus.jettison.json.JSONObject; 28 | import org.slf4j.Logger; 29 | import org.slf4j.LoggerFactory; 30 | import org.slf4j.impl.CocoaLogger; 31 | import org.spongycastle.crypto.params.KeyParameter; 32 | 33 | import java.io.File; 34 | import java.io.FileInputStream; 35 | import java.io.FileNotFoundException; 36 | import java.io.IOException; 37 | import java.math.BigInteger; 38 | import java.nio.CharBuffer; 39 | import java.text.SimpleDateFormat; 40 | import java.util.Arrays; 41 | import java.util.Date; 42 | import java.util.HashSet; 43 | import java.util.HashMap; 44 | import java.util.List; 45 | import java.util.TimeZone; 46 | import java.util.concurrent.TimeUnit; 47 | 48 | public class BitcoinManager implements Thread.UncaughtExceptionHandler, TransactionConfidence.Listener { 49 | private NetworkParameters networkParams; 50 | private Wallet wallet; 51 | private String dataDirectory; 52 | private String checkpointsFilePath; 53 | 54 | private PeerGroup peerGroup; 55 | private BlockStore blockStore; 56 | private File walletFile; 57 | private int blocksToDownload; 58 | private HashSet trackedTransactions; 59 | private HashMap paymentSessions; 60 | private int paymentSessionsSequenceId = 0; 61 | 62 | private static final Logger log = LoggerFactory.getLogger(BitcoinManager.class); 63 | 64 | 65 | /* --- Initialization & configuration --- */ 66 | 67 | public BitcoinManager() { 68 | Threading.uncaughtExceptionHandler = this; 69 | 70 | trackedTransactions = new HashSet(); 71 | paymentSessions = new HashMap(); 72 | 73 | ((CocoaLogger) log).setLevel(CocoaLogger.HILoggerLevelDebug); 74 | } 75 | 76 | public void setTestingNetwork(boolean testing) { 77 | if (testing) { 78 | this.networkParams = TestNet3Params.get(); 79 | } else { 80 | this.networkParams = MainNetParams.get(); 81 | } 82 | } 83 | 84 | public void setDataDirectory(String path) { 85 | dataDirectory = path; 86 | } 87 | 88 | public String getCheckpointsFilePath() { 89 | return checkpointsFilePath; 90 | } 91 | 92 | public void setCheckpointsFilePath(String path) { 93 | checkpointsFilePath = path; 94 | } 95 | 96 | 97 | /* --- Wallet lifecycle --- */ 98 | 99 | public void start() throws NoWalletException, UnreadableWalletException, IOException, BlockStoreException { 100 | if (networkParams == null) { 101 | setTestingNetwork(false); 102 | } 103 | 104 | // Try to read the wallet from storage, create a new one if not possible. 105 | wallet = null; 106 | walletFile = new File(dataDirectory + "/bitcoinkit.wallet"); 107 | 108 | if (!walletFile.exists()) { 109 | // Stop here, because the caller might want to create an encrypted wallet and needs to supply a password. 110 | throw new NoWalletException("No wallet file found at: " + walletFile); 111 | } 112 | 113 | try { 114 | useWallet(loadWalletFromFile(walletFile)); 115 | } catch (FileNotFoundException e) { 116 | throw new NoWalletException("No wallet file found at: " + walletFile); 117 | } 118 | } 119 | 120 | public void addExtensionsToWallet(Wallet wallet) { 121 | wallet.addExtension(new LastWalletChangeExtension()); 122 | } 123 | 124 | public Wallet loadWalletFromFile(File f) throws UnreadableWalletException { 125 | try { 126 | FileInputStream stream = null; 127 | 128 | try { 129 | stream = new FileInputStream(f); 130 | 131 | Wallet wallet = new Wallet(networkParams); 132 | addExtensionsToWallet(wallet); 133 | 134 | Protos.Wallet walletData = WalletProtobufSerializer.parseToProto(stream); 135 | new WalletProtobufSerializer().readWallet(walletData, wallet); 136 | 137 | if (!wallet.isConsistent()) { 138 | log.error("Loaded an inconsistent wallet"); 139 | } 140 | 141 | return wallet; 142 | } finally { 143 | if (stream != null) { 144 | stream.close(); 145 | } 146 | } 147 | } catch (IOException e) { 148 | throw new UnreadableWalletException("Could not open file", e); 149 | } 150 | } 151 | 152 | public void createWallet() 153 | throws IOException, BlockStoreException, ExistingWalletException, WrongPasswordException { 154 | createWallet(null); 155 | } 156 | 157 | public void createWallet(char[] utf16Password) 158 | throws IOException, BlockStoreException, ExistingWalletException, WrongPasswordException { 159 | if (walletFile == null) { 160 | throw new IllegalStateException("createWallet cannot be called before start"); 161 | } else if (walletFile.exists()) { 162 | throw new ExistingWalletException("Trying to create a wallet even though one exists: " + walletFile); 163 | } 164 | 165 | Wallet wallet = new Wallet(networkParams); 166 | addExtensionsToWallet(wallet); 167 | updateLastWalletChange(wallet); 168 | 169 | ECKey privateKey = new ECKey(); 170 | wallet.addKey(privateKey); 171 | long creationTime = privateKey.getCreationTimeSeconds(); 172 | 173 | if (utf16Password != null) { 174 | encryptWallet(utf16Password, wallet); 175 | 176 | // temporary fix for bitcoinj creation time clearing bug 177 | ECKey encryptedKey = wallet.getKeys().get(0); 178 | encryptedKey.setCreationTimeSeconds(creationTime); 179 | } 180 | 181 | wallet.saveToFile(walletFile); 182 | 183 | // just in case an old file exists there for some reason 184 | deleteBlockchainDataFile(); 185 | 186 | useWallet(wallet); 187 | } 188 | 189 | private void useWallet(Wallet wallet) throws BlockStoreException, IOException { 190 | this.wallet = wallet; 191 | 192 | //make wallet autosave 193 | wallet.autosaveToFile(walletFile, 1, TimeUnit.SECONDS, null); 194 | 195 | log.info("Opening wallet " + getWalletAddress()); 196 | 197 | wallet.addEventListener(new AbstractWalletEventListener() { 198 | // get notified when an incoming transaction is received 199 | @Override 200 | public void onCoinsReceived(Wallet w, Transaction tx, BigInteger prevBalance, BigInteger newBalance) { 201 | BitcoinManager.this.onCoinsReceived(w, tx, prevBalance, newBalance); 202 | } 203 | 204 | // get notified when we send a transaction, or when we restore an outgoing transaction from the blockchain 205 | @Override 206 | public void onCoinsSent(Wallet w, Transaction tx, BigInteger prevBalance, BigInteger newBalance) { 207 | BitcoinManager.this.onCoinsSent(w, tx, prevBalance, newBalance); 208 | } 209 | 210 | @Override 211 | public void onReorganize(Wallet wallet) { 212 | BitcoinManager.this.onBalanceChanged(); 213 | } 214 | }); 215 | 216 | startBlockchain(); 217 | 218 | wallet.cleanup(); 219 | } 220 | 221 | private File getBlockchainFile() { 222 | return new File(dataDirectory + "/bitcoinkit.spvchain"); 223 | } 224 | 225 | private void startBlockchain() throws BlockStoreException { 226 | // Load the block chain data file or generate a new one 227 | File chainFile = getBlockchainFile(); 228 | boolean chainExistedAlready = chainFile.exists(); 229 | blockStore = new SPVBlockStore(networkParams, chainFile); 230 | 231 | if (!chainExistedAlready) { 232 | // the blockchain will need to be replayed; if the wallet already contains transactions, this might 233 | // cause ugly inconsistent wallet exceptions, so clear all old transaction data first 234 | log.info("Chain file missing - wallet transactions list will be rebuilt now"); 235 | wallet.clearTransactions(0); 236 | 237 | String checkpointsFilePath = this.checkpointsFilePath; 238 | if (checkpointsFilePath == null) { 239 | checkpointsFilePath = dataDirectory + "/bitcoinkit.checkpoints"; 240 | } 241 | 242 | File checkpointsFile = new File(checkpointsFilePath); 243 | if (checkpointsFile.exists()) { 244 | long earliestKeyCreationTime = wallet.getEarliestKeyCreationTime(); 245 | 246 | if (earliestKeyCreationTime == 0) { 247 | // there was a bug in bitcoinj until recently that caused encrypted keys to lose their creation time 248 | // so if we have no data from the wallet, use the time of the first Hive commit (15.05.2013) instead 249 | earliestKeyCreationTime = 1368620845; 250 | } 251 | 252 | try { 253 | FileInputStream stream = new FileInputStream(checkpointsFile); 254 | CheckpointManager.checkpoint(networkParams, stream, blockStore, earliestKeyCreationTime); 255 | } catch (IOException e) { 256 | throw new BlockStoreException("Could not load checkpoints file"); 257 | } 258 | } 259 | } 260 | 261 | BlockChain chain = new BlockChain(networkParams, wallet, blockStore); 262 | 263 | peerGroup = new PeerGroup(networkParams, chain); 264 | peerGroup.setUserAgent("BitcoinJKit", "0.9"); 265 | peerGroup.addPeerDiscovery(new DnsDiscovery(networkParams)); 266 | peerGroup.addWallet(wallet); 267 | 268 | onBalanceChanged(); 269 | trackPendingTransactions(wallet); 270 | 271 | peerGroup.addEventListener(new AbstractPeerEventListener() { 272 | @Override 273 | public void onPeerConnected(Peer peer, int peerCount) { 274 | BitcoinManager.this.onPeerConnected(peer, peerCount); 275 | } 276 | 277 | @Override 278 | public void onPeerDisconnected(Peer peer, int peerCount) { 279 | BitcoinManager.this.onPeerDisconnected(peer, peerCount); 280 | } 281 | }); 282 | 283 | peerGroup.startAndWait(); 284 | 285 | // get notified about sync progress 286 | peerGroup.startBlockChainDownload(new AbstractPeerEventListener() { 287 | @Override 288 | public void onBlocksDownloaded(Peer peer, Block block, int blocksLeft) { 289 | BitcoinManager.this.onBlocksDownloaded(peer, block, blocksLeft); 290 | } 291 | 292 | @Override 293 | public void onChainDownloadStarted(Peer peer, int blocksLeft) { 294 | BitcoinManager.this.onChainDownloadStarted(peer, blocksLeft); 295 | } 296 | }); 297 | } 298 | 299 | private void trackPendingTransactions(Wallet wallet) { 300 | // we won't receive onCoinsReceived again for transactions that we already know about, 301 | // so we need to listen to confidence changes again after a restart 302 | for (Transaction tx : wallet.getPendingTransactions()) { 303 | trackTransaction(tx); 304 | } 305 | } 306 | 307 | private void trackTransaction(Transaction tx) { 308 | if (!trackedTransactions.contains(tx)) { 309 | log.debug("Tracking transaction " + tx.getHashAsString()); 310 | 311 | tx.getConfidence().addEventListener(this); 312 | trackedTransactions.add(tx); 313 | } 314 | } 315 | 316 | private void stopTrackingTransaction(Transaction tx) { 317 | if (trackedTransactions.contains(tx)) { 318 | log.debug("Stopped tracking transaction " + tx.getHashAsString()); 319 | 320 | tx.getConfidence().removeEventListener(this); 321 | trackedTransactions.remove(tx); 322 | } 323 | } 324 | 325 | public void deleteBlockchainDataFile() { 326 | log.info("Deleting blockchain data file..."); 327 | File chainFile = getBlockchainFile(); 328 | chainFile.delete(); 329 | } 330 | 331 | public void resetBlockchain() { 332 | try { 333 | shutdownBlockchain(); 334 | deleteBlockchainDataFile(); 335 | 336 | blocksToDownload = 0; 337 | 338 | for (Transaction tx : (HashSet) trackedTransactions.clone()) { 339 | stopTrackingTransaction(tx); 340 | } 341 | 342 | startBlockchain(); 343 | } catch (Exception e) { 344 | throw new RuntimeException(e); 345 | } 346 | } 347 | 348 | public void stop() { 349 | try { 350 | log.info("Shutting down BitcoinManager..."); 351 | 352 | shutdownBlockchain(); 353 | 354 | if (wallet != null) { 355 | wallet.saveToFile(walletFile); 356 | } 357 | 358 | log.info("Shutdown done."); 359 | } catch (Exception e) { 360 | throw new RuntimeException(e); 361 | } 362 | } 363 | 364 | private void shutdownBlockchain() throws BlockStoreException { 365 | log.info("Shutting down PeerGroup..."); 366 | 367 | if (peerGroup != null) { 368 | peerGroup.stopAndWait(); 369 | peerGroup.removeWallet(wallet); 370 | peerGroup = null; 371 | } 372 | 373 | log.info("Shutting down BlockStore..."); 374 | 375 | if (blockStore != null) { 376 | blockStore.close(); 377 | blockStore = null; 378 | } 379 | } 380 | 381 | public void exportWallet(String path) throws java.io.IOException { 382 | File backupFile = new File(path); 383 | wallet.saveToFile(backupFile); 384 | } 385 | 386 | public String exportPrivateKey(char[] utf16Password) throws WrongPasswordException { 387 | KeyParameter aesKey = null; 388 | ECKey decryptedKey = null; 389 | DumpedPrivateKey dumpedKey = null; 390 | 391 | try { 392 | ECKey ecKey = wallet.getKeys().get(0); 393 | Date creationDate = new Date(ecKey.getCreationTimeSeconds() * 1000); 394 | 395 | SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); 396 | format.setTimeZone(TimeZone.getTimeZone("UTC")); 397 | String timestamp = format.format(creationDate); 398 | 399 | aesKey = aesKeyForPassword(utf16Password); 400 | decryptedKey = ecKey.decrypt(wallet.getKeyCrypter(), aesKey); 401 | return decryptedKey.getPrivateKeyEncoded(networkParams).toString() + "\t" + timestamp; 402 | } catch (KeyCrypterException e) { 403 | throw new WrongPasswordException(e); 404 | } finally { 405 | wipeAesKey(aesKey); 406 | 407 | if (decryptedKey != null) { 408 | decryptedKey.clearPrivateKey(); 409 | } 410 | } 411 | } 412 | 413 | 414 | /* --- Reading wallet data --- */ 415 | 416 | public String getWalletAddress() { 417 | ECKey ecKey = wallet.getKeys().get(0); 418 | return ecKey.toAddress(networkParams).toString(); 419 | } 420 | 421 | public String getWalletDebuggingInfo() { 422 | return (wallet != null) ? wallet.toString() : null; 423 | } 424 | 425 | public long getAvailableBalance() { 426 | return (wallet != null) ? wallet.getBalance().longValue() : 0; 427 | } 428 | 429 | public long getEstimatedBalance() { 430 | return (wallet != null) ? wallet.getBalance(Wallet.BalanceType.ESTIMATED).longValue() : 0; 431 | } 432 | 433 | 434 | /* --- Reading transaction data --- */ 435 | 436 | private String getJSONFromTransaction(Transaction tx) throws ScriptException, JSONException { 437 | if (tx == null) { 438 | return null; 439 | } 440 | 441 | TransactionConfidence txConfidence = tx.getConfidence(); 442 | TransactionConfidence.ConfidenceType confidenceType = txConfidence.getConfidenceType(); 443 | String confidence; 444 | 445 | if (confidenceType == TransactionConfidence.ConfidenceType.BUILDING) { 446 | confidence = "building"; 447 | } else if (confidenceType == TransactionConfidence.ConfidenceType.PENDING) { 448 | confidence = "pending"; 449 | } else if (confidenceType == TransactionConfidence.ConfidenceType.DEAD) { 450 | confidence = "dead"; 451 | } else { 452 | confidence = "unknown"; 453 | } 454 | 455 | JSONArray inputs = new JSONArray(); 456 | 457 | for (TransactionInput input : tx.getInputs()) { 458 | JSONObject inputData = new JSONObject(); 459 | 460 | if (!input.isCoinBase()) { 461 | try { 462 | Script scriptSig = input.getScriptSig(); 463 | Address fromAddress = new Address(networkParams, Utils.sha256hash160(scriptSig.getPubKey())); 464 | inputData.put("address", fromAddress); 465 | } catch (ScriptException e) { 466 | // can't parse script, give up 467 | } 468 | } 469 | 470 | TransactionOutput source = input.getConnectedOutput(); 471 | if (source != null) { 472 | inputData.put("amount", source.getValue()); 473 | } 474 | 475 | inputs.put(inputData); 476 | } 477 | 478 | JSONArray outputs = new JSONArray(); 479 | 480 | for (TransactionOutput output : tx.getOutputs()) { 481 | JSONObject outputData = new JSONObject(); 482 | 483 | try { 484 | Script scriptPubKey = output.getScriptPubKey(); 485 | 486 | if (scriptPubKey.isSentToAddress() || scriptPubKey.isPayToScriptHash()) { 487 | Address toAddress = scriptPubKey.getToAddress(networkParams); 488 | outputData.put("address", toAddress); 489 | 490 | if (toAddress.toString().equals(getWalletAddress())) { 491 | outputData.put("type", "own"); 492 | } else { 493 | outputData.put("type", "external"); 494 | } 495 | } else if (scriptPubKey.isSentToRawPubKey()) { 496 | outputData.put("type", "pubkey"); 497 | } else if (scriptPubKey.isSentToMultiSig()) { 498 | outputData.put("type", "multisig"); 499 | } else { 500 | outputData.put("type", "unknown"); 501 | } 502 | } catch (ScriptException e) { 503 | // can't parse script, give up 504 | } 505 | 506 | outputData.put("amount", output.getValue()); 507 | outputs.put(outputData); 508 | } 509 | 510 | SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z"); 511 | dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); 512 | 513 | JSONObject result = new JSONObject(); 514 | result.put("amount", tx.getValue(wallet)); 515 | result.put("fee", getTransactionFee(tx)); 516 | result.put("txid", tx.getHashAsString()); 517 | result.put("time", dateFormat.format(tx.getUpdateTime())); 518 | result.put("confidence", confidence); 519 | result.put("peers", txConfidence.numBroadcastPeers()); 520 | result.put("confirmations", txConfidence.getDepthInBlocks()); 521 | result.put("inputs", inputs); 522 | result.put("outputs", outputs); 523 | 524 | return result.toString(); 525 | } 526 | 527 | public BigInteger getTransactionFee(Transaction tx) { 528 | // TODO: this will break once we do more complex transactions with multiple sources/targets (e.g. coinjoin) 529 | 530 | BigInteger v = BigInteger.ZERO; 531 | 532 | for (TransactionInput input : tx.getInputs()) { 533 | TransactionOutput connected = input.getConnectedOutput(); 534 | if (connected != null) { 535 | v = v.add(connected.getValue()); 536 | } else { 537 | // we can't calculate the fee amount without having all data 538 | return BigInteger.ZERO; 539 | } 540 | } 541 | 542 | for (TransactionOutput output : tx.getOutputs()) { 543 | v = v.subtract(output.getValue()); 544 | } 545 | 546 | return v; 547 | } 548 | 549 | public int getTransactionCount() { 550 | return wallet.getTransactionsByTime().size(); 551 | } 552 | 553 | public String getAllTransactions() throws ScriptException, JSONException { 554 | return getTransactions(0, getTransactionCount()); 555 | } 556 | 557 | public String getTransaction(String tx) throws ScriptException, JSONException { 558 | Sha256Hash hash = new Sha256Hash(tx); 559 | return getJSONFromTransaction(wallet.getTransaction(hash)); 560 | } 561 | 562 | public String getTransaction(int idx) throws ScriptException, JSONException { 563 | return getJSONFromTransaction(wallet.getTransactionsByTime().get(idx)); 564 | } 565 | 566 | public String getTransactions(int from, int count) throws ScriptException, JSONException { 567 | List transactions = wallet.getTransactionsByTime(); 568 | 569 | if (from >= transactions.size()) 570 | return null; 571 | 572 | int to = (from + count < transactions.size()) ? from + count : transactions.size(); 573 | 574 | StringBuffer txs = new StringBuffer(); 575 | txs.append("[\n"); 576 | boolean first = true; 577 | for (; from < to; from++) { 578 | if (first) 579 | first = false; 580 | else 581 | txs.append("\n,"); 582 | 583 | txs.append(getJSONFromTransaction(transactions.get(from))); 584 | } 585 | txs.append("]\n"); 586 | 587 | return txs.toString(); 588 | } 589 | 590 | 591 | /* --- Sending transactions --- */ 592 | 593 | public String feeForSendingCoins(String amount, String sendToAddressString) throws InsufficientMoneyException { 594 | try { 595 | BigInteger amountToSend = new BigInteger(amount); 596 | if (amountToSend.intValue() == 0 || sendToAddressString.equals("")) { 597 | // assume default value for now 598 | return Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.toString(); 599 | } 600 | 601 | Address sendToAddress = new Address(networkParams, sendToAddressString); 602 | Wallet.SendRequest request = Wallet.SendRequest.to(sendToAddress, amountToSend); 603 | 604 | try { 605 | wallet.completeTx(request); 606 | } catch (KeyCrypterException e) { 607 | // that's ok, we aren't sending this yet 608 | } 609 | 610 | return getTransactionFee(request.tx).toString(); 611 | } catch (AddressFormatException e) { 612 | // assume default value for now 613 | return Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.toString(); 614 | } 615 | } 616 | 617 | public boolean isAddressValid(String address) { 618 | try { 619 | Address addr = new Address(networkParams, address); 620 | return (addr != null); 621 | } catch (Exception e) { 622 | return false; 623 | } 624 | } 625 | 626 | public void sendCoins(String amount, final String sendToAddressString, char[] utf16Password) 627 | throws WrongPasswordException, AddressFormatException, InsufficientMoneyException { 628 | 629 | KeyParameter aesKey = null; 630 | 631 | try { 632 | BigInteger aToSend = new BigInteger(amount); 633 | Address sendToAddress = new Address(networkParams, sendToAddressString); 634 | final Wallet.SendRequest request = Wallet.SendRequest.to(sendToAddress, aToSend); 635 | 636 | aesKey = aesKeyForPassword(utf16Password); 637 | request.aesKey = aesKey; 638 | 639 | final Wallet.SendResult sendResult = wallet.sendCoins(peerGroup, request); 640 | Futures.addCallback(sendResult.broadcastComplete, new FutureCallback() { 641 | public void onSuccess(Transaction transaction) { 642 | onTransactionSuccess(sendResult.tx.getHashAsString()); 643 | } 644 | 645 | public void onFailure(Throwable throwable) { 646 | onTransactionFailed(); 647 | throwable.printStackTrace(); 648 | } 649 | }); 650 | } catch (KeyCrypterException e) { 651 | throw new WrongPasswordException(e); 652 | } finally { 653 | wipeAesKey(aesKey); 654 | } 655 | } 656 | 657 | 658 | /* --- Handling payment requests --- */ 659 | 660 | public void openPaymentRequestFromFile(String path, int callbackId) throws IOException { 661 | File requestFile = new File(path); 662 | FileInputStream stream = new FileInputStream(requestFile); 663 | 664 | try { 665 | org.bitcoin.protocols.payments.Protos.PaymentRequest paymentRequest = 666 | org.bitcoin.protocols.payments.Protos.PaymentRequest.parseFrom(stream); 667 | 668 | PaymentSession session = new PaymentSession(paymentRequest, false); 669 | 670 | validatePaymentRequest(session); 671 | 672 | int sessionId = ++paymentSessionsSequenceId; 673 | paymentSessions.put(sessionId, session); 674 | onPaymentRequestLoaded(callbackId, sessionId, getPaymentRequestDetails(session)); 675 | } catch (com.google.protobuf.InvalidProtocolBufferException e) { 676 | onPaymentRequestLoadFailed(callbackId, e); 677 | } catch (JSONException e) { 678 | // this should never happen 679 | onPaymentRequestLoadFailed(callbackId, e); 680 | } catch (PaymentRequestException e) { 681 | onPaymentRequestLoadFailed(callbackId, e); 682 | } 683 | } 684 | 685 | public void openPaymentRequestFromURL(String url, final int callbackId) throws PaymentRequestException { 686 | ListenableFuture future = PaymentSession.createFromUrl(url, false); 687 | 688 | Futures.addCallback(future, new FutureCallback() { 689 | public void onSuccess(PaymentSession session) { 690 | try { 691 | validatePaymentRequest(session); 692 | 693 | int sessionId = ++paymentSessionsSequenceId; 694 | paymentSessions.put(sessionId, session); 695 | onPaymentRequestLoaded(callbackId, sessionId, getPaymentRequestDetails(session)); 696 | } catch (Exception e) { 697 | onPaymentRequestLoadFailed(callbackId, e); 698 | } 699 | } 700 | 701 | public void onFailure(Throwable throwable) { 702 | onPaymentRequestLoadFailed(callbackId, throwable); 703 | } 704 | }); 705 | } 706 | 707 | private void validatePaymentRequest(PaymentSession session) throws PaymentRequestException { 708 | NetworkParameters params = session.getNetworkParameters(); 709 | 710 | if (params != networkParams) { 711 | throw new WrongNetworkException("This payment request is meant for a different Bitcoin network"); 712 | } 713 | 714 | if (session.isExpired()) { 715 | throw new PaymentRequestException.Expired("PaymentRequest is expired"); 716 | } 717 | 718 | try { 719 | session.verifyPki(); 720 | } catch (PaymentRequestException e) { 721 | // apparently we're supposed to just ignore these errors (?) 722 | log.warn("PKI Verification error: " + e); 723 | } 724 | } 725 | 726 | private String getPaymentRequestDetails(PaymentSession session) throws JSONException { 727 | JSONObject request = new JSONObject(); 728 | 729 | request.put("amount", session.getValue()); 730 | request.put("memo", session.getMemo()); 731 | request.put("paymentURL", session.getPaymentUrl()); 732 | 733 | if (session.pkiVerificationData != null) { 734 | request.put("pkiName", session.pkiVerificationData.name); 735 | request.put("pkiRootAuthorityName", session.pkiVerificationData.rootAuthorityName); 736 | } 737 | 738 | return request.toString(); 739 | } 740 | 741 | public void sendPaymentRequest(final int sessionId, char[] utf16Password, final int callbackId) 742 | throws WrongPasswordException, InsufficientMoneyException, PaymentRequestException, IOException { 743 | KeyParameter aesKey = null; 744 | 745 | try { 746 | PaymentSession session = paymentSessions.get(sessionId); 747 | final Wallet.SendRequest request = session.getSendRequest(); 748 | 749 | if (utf16Password != null) { 750 | aesKey = aesKeyForPassword(utf16Password); 751 | request.aesKey = aesKey; 752 | } 753 | 754 | wallet.completeTx(request); 755 | 756 | final Transaction tx = request.tx; 757 | Address refundAddress = wallet.getKeys().get(0).toAddress(networkParams); 758 | List transactions = ImmutableList.of(tx); 759 | 760 | ListenableFuture fack = session.sendPayment(transactions, refundAddress, null); 761 | 762 | if (fack != null) { 763 | Futures.addCallback(fack, new FutureCallback() { 764 | public void onSuccess(PaymentSession.Ack ack) { 765 | try { 766 | wallet.commitTx(tx); 767 | paymentSessions.remove(sessionId); 768 | 769 | onNewTransaction(tx); 770 | 771 | // we broadcast the transaction on our side anyway in case the server forgets to do it 772 | // but we don't need to wait for any responses in this case 773 | peerGroup.broadcastTransaction(tx); 774 | 775 | String ackDetails = getPaymentRequestAckDetails(ack); 776 | onPaymentRequestProcessed(callbackId, tx.getHashAsString(), ackDetails); 777 | } catch (JSONException e) { 778 | onPaymentRequestProcessingFailed(callbackId, e); 779 | } 780 | } 781 | 782 | public void onFailure(Throwable throwable) { 783 | onPaymentRequestProcessingFailed(callbackId, throwable); 784 | } 785 | }); 786 | } else { 787 | // no payment_url - we just need to broadcast the transaction as with a normal send 788 | 789 | wallet.commitTx(tx); 790 | ListenableFuture broadcastComplete = peerGroup.broadcastTransaction(tx); 791 | 792 | Futures.addCallback(broadcastComplete, new FutureCallback() { 793 | public void onSuccess(Transaction transaction) { 794 | paymentSessions.remove(sessionId); 795 | onPaymentRequestProcessed(callbackId, tx.getHashAsString(), "{}"); 796 | } 797 | 798 | public void onFailure(Throwable throwable) { 799 | onPaymentRequestProcessingFailed(callbackId, throwable); 800 | } 801 | }); 802 | } 803 | } catch (KeyCrypterException e) { 804 | throw new WrongPasswordException(e); 805 | } finally { 806 | wipeAesKey(aesKey); 807 | } 808 | } 809 | 810 | private String getPaymentRequestAckDetails(PaymentSession.Ack ack) throws JSONException { 811 | JSONObject json = new JSONObject(); 812 | json.put("memo", ack.getMemo()); 813 | return json.toString(); 814 | } 815 | 816 | 817 | /* --- Encryption/decryption --- */ 818 | 819 | private KeyParameter aesKeyForPassword(char[] utf16Password) throws WrongPasswordException { 820 | KeyCrypter keyCrypter = wallet.getKeyCrypter(); 821 | 822 | if (keyCrypter == null) { 823 | throw new WrongPasswordException("Wallet is not protected."); 824 | } 825 | 826 | return deriveKeyAndWipePassword(utf16Password, keyCrypter); 827 | } 828 | 829 | private KeyParameter deriveKeyAndWipePassword(char[] utf16Password, KeyCrypter keyCrypter) 830 | throws WrongPasswordException { 831 | 832 | if (utf16Password == null) { 833 | throw new WrongPasswordException("No password provided."); 834 | } 835 | 836 | try { 837 | return keyCrypter.deriveKey(CharBuffer.wrap(utf16Password)); 838 | } finally { 839 | Arrays.fill(utf16Password, '\0'); 840 | } 841 | } 842 | 843 | private void wipeAesKey(KeyParameter aesKey) { 844 | if (aesKey != null) { 845 | Arrays.fill(aesKey.getKey(), (byte) 0); 846 | } 847 | } 848 | 849 | public boolean isWalletEncrypted() { 850 | return wallet.getKeys().get(0).isEncrypted(); 851 | } 852 | 853 | public boolean isPasswordCorrect(char[] password) { 854 | KeyParameter aesKey = null; 855 | 856 | try { 857 | aesKey = aesKeyForPassword(password); 858 | return wallet.checkAESKey(aesKey); 859 | } catch (WrongPasswordException e) { 860 | return false; 861 | } finally { 862 | wipeAesKey(aesKey); 863 | } 864 | } 865 | 866 | public void changeWalletPassword(char[] oldUtf16Password, char[] newUtf16Password) throws WrongPasswordException { 867 | // check if aes key for new password can be generated before decrypting 868 | KeyCrypterScrypt keyCrypter = new KeyCrypterScrypt(); 869 | KeyParameter aesKey = deriveKeyAndWipePassword(newUtf16Password, keyCrypter); 870 | 871 | updateLastWalletChange(wallet); 872 | 873 | if (isWalletEncrypted()) { 874 | decryptWallet(oldUtf16Password); 875 | } 876 | 877 | try { 878 | wallet.encrypt(keyCrypter, aesKey); 879 | } finally { 880 | wipeAesKey(aesKey); 881 | } 882 | } 883 | 884 | private void decryptWallet(char[] oldUtf16Password) throws WrongPasswordException { 885 | KeyParameter oldAesKey = aesKeyForPassword(oldUtf16Password); 886 | 887 | try { 888 | wallet.decrypt(oldAesKey); 889 | } catch (KeyCrypterException e) { 890 | throw new WrongPasswordException(e); 891 | } finally { 892 | wipeAesKey(oldAesKey); 893 | } 894 | } 895 | 896 | private void encryptWallet(char[] utf16Password, Wallet wallet) throws WrongPasswordException { 897 | KeyCrypterScrypt keyCrypter = new KeyCrypterScrypt(); 898 | KeyParameter aesKey = deriveKeyAndWipePassword(utf16Password, keyCrypter); 899 | 900 | try { 901 | wallet.encrypt(keyCrypter, aesKey); 902 | } finally { 903 | wipeAesKey(aesKey); 904 | } 905 | } 906 | 907 | public String signMessage(String message, char[] utf16Password) throws WrongPasswordException { 908 | KeyParameter aesKey = null; 909 | ECKey decryptedKey = null; 910 | 911 | try { 912 | ECKey ecKey = wallet.getKeys().get(0); 913 | 914 | aesKey = aesKeyForPassword(utf16Password); 915 | decryptedKey = ecKey.decrypt(wallet.getKeyCrypter(), aesKey); 916 | return decryptedKey.signMessage(message); 917 | } catch (KeyCrypterException e) { 918 | throw new WrongPasswordException(e); 919 | } finally { 920 | wipeAesKey(aesKey); 921 | 922 | if (decryptedKey != null) { 923 | decryptedKey.clearPrivateKey(); 924 | } 925 | } 926 | } 927 | 928 | 929 | /* --- Handling exceptions --- */ 930 | 931 | public String getExceptionStackTrace(Throwable exception) { 932 | StringBuilder buffer = new StringBuilder(); 933 | 934 | for (Throwable e : Throwables.getCausalChain(exception)) { 935 | if (e != exception) { 936 | buffer.append("\ncaused by: " + e + "\n"); 937 | } 938 | 939 | for (StackTraceElement line : e.getStackTrace()) { 940 | buffer.append("at " + line.toString() + "\n"); 941 | } 942 | } 943 | 944 | return buffer.toString(); 945 | } 946 | 947 | 948 | /* --- Keeping last wallet change date --- */ 949 | 950 | public void updateLastWalletChange(Wallet wallet) { 951 | LastWalletChangeExtension ext = 952 | (LastWalletChangeExtension) wallet.getExtensions().get(LastWalletChangeExtension.EXTENSION_ID); 953 | 954 | ext.setLastWalletChangeDate(new Date()); 955 | } 956 | 957 | public Date getLastWalletChange() { 958 | if (wallet == null) { 959 | return null; 960 | } 961 | 962 | LastWalletChangeExtension ext = 963 | (LastWalletChangeExtension) wallet.getExtensions().get(LastWalletChangeExtension.EXTENSION_ID); 964 | 965 | return ext.getLastWalletChangeDate(); 966 | } 967 | 968 | public long getLastWalletChangeTimestamp() { 969 | Date date = getLastWalletChange(); 970 | return (date != null) ? date.getTime() : 0; 971 | } 972 | 973 | 974 | /* --- WalletEventListener --- */ 975 | 976 | public void onCoinsReceived(Wallet w, Transaction tx, BigInteger prevBalance, BigInteger newBalance) { 977 | onNewTransaction(tx); 978 | } 979 | 980 | public void onCoinsSent(Wallet w, Transaction tx, BigInteger prevBalance, BigInteger newBalance) { 981 | onNewTransaction(tx); 982 | } 983 | 984 | private void onNewTransaction(Transaction tx) { 985 | // avoid double updates if we get both sent + received 986 | if (!trackedTransactions.contains(tx)) { 987 | // update the UI 988 | onBalanceChanged(); 989 | 990 | String json = null; 991 | try { 992 | json = getJSONFromTransaction(tx); 993 | } catch (JSONException e) { 994 | // shouldn't happen 995 | } 996 | 997 | onTransactionChanged(tx.getHashAsString(), json); 998 | 999 | // get notified when transaction is confirmed 1000 | if (tx.isPending()) { 1001 | trackTransaction(tx); 1002 | } 1003 | } 1004 | } 1005 | 1006 | 1007 | /* --- TransactionConfidence.Listener --- */ 1008 | 1009 | public void onConfidenceChanged(final Transaction tx, TransactionConfidence.Listener.ChangeReason reason) { 1010 | if (reason != TransactionConfidence.Listener.ChangeReason.TYPE) { 1011 | // we don't need to notify the UI of peer count or confirmation amount changes 1012 | return; 1013 | } 1014 | 1015 | if (!tx.isPending()) { 1016 | // coins were confirmed (appeared in a block) - we don't need to listen anymore 1017 | stopTrackingTransaction(tx); 1018 | } 1019 | 1020 | // update the UI 1021 | onBalanceChanged(); 1022 | 1023 | String json = null; 1024 | try { 1025 | json = getJSONFromTransaction(tx); 1026 | } catch (JSONException e) { 1027 | // shouldn't happen 1028 | } 1029 | 1030 | onTransactionChanged(tx.getHashAsString(), json); 1031 | } 1032 | 1033 | 1034 | /* --- Thread.UncaughtExceptionHandler --- */ 1035 | 1036 | public void uncaughtException(Thread thread, Throwable exception) { 1037 | onException(exception); 1038 | } 1039 | 1040 | 1041 | /* PeerEventListener */ 1042 | 1043 | public void onPeerConnected(Peer peer, int peerCount) { 1044 | onPeerCountChanged(peerCount); 1045 | } 1046 | 1047 | public void onPeerDisconnected(Peer peer, int peerCount) { 1048 | onPeerCountChanged(peerCount); 1049 | } 1050 | 1051 | public void onBlocksDownloaded(Peer peer, Block block, int blocksLeft) { 1052 | updateBlocksLeft(blocksLeft); 1053 | } 1054 | 1055 | public void onChainDownloadStarted(Peer peer, int blocksLeft) { 1056 | if (blocksToDownload == 0) { 1057 | // remember the total amount 1058 | blocksToDownload = blocksLeft; 1059 | log.debug("Starting blockchain sync: blocksToDownload := " + blocksLeft); 1060 | } else { 1061 | // we've already set that once and we're only downloading the remaining part 1062 | log.debug("Restarting blockchain sync: blocksToDownload = " + blocksToDownload + ", left = " + blocksLeft); 1063 | } 1064 | 1065 | updateBlocksLeft(blocksLeft); 1066 | } 1067 | 1068 | private void updateBlocksLeft(int blocksLeft) { 1069 | if (blocksToDownload == 0) { 1070 | log.debug("Blockchain sync finished."); 1071 | onSynchronizationUpdate(100.0f); 1072 | } else { 1073 | int downloadedSoFar = blocksToDownload - blocksLeft; 1074 | onSynchronizationUpdate(100.0f * downloadedSoFar / blocksToDownload); 1075 | } 1076 | } 1077 | 1078 | 1079 | /* Native callbacks to pass data to the Cocoa side when something happens */ 1080 | 1081 | public native void onTransactionChanged(String txid, String json); 1082 | 1083 | public native void onTransactionFailed(); 1084 | 1085 | public native void onTransactionSuccess(String txid); 1086 | 1087 | public native void onSynchronizationUpdate(float percent); 1088 | 1089 | public native void onBalanceChanged(); 1090 | 1091 | public native void onPeerCountChanged(int peerCount); 1092 | 1093 | public native void onException(Throwable exception); 1094 | 1095 | public native void onPaymentRequestLoaded(int callbackId, int sessionId, String requestDetails); 1096 | public native void onPaymentRequestLoadFailed(int callbackId, Throwable error); 1097 | 1098 | public native void onPaymentRequestProcessed(int callbackId, String txid, String ackDetails); 1099 | public native void onPaymentRequestProcessingFailed(int callbackId, Throwable error); 1100 | } 1101 | -------------------------------------------------------------------------------- /BitcoinJKit/HIBitcoinManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // HIBitcoinManager.m 3 | // BitcoinKit 4 | // 5 | // Created by Bazyli Zygan on 26.07.2013. 6 | // Copyright (c) 2013 Hive Developers. All rights reserved. 7 | // 8 | 9 | #import "HIBitcoinManager.h" 10 | 11 | #import "HIBitcoinErrorCodes.h" 12 | #import "HIBitcoinInternalErrorCodes.h" 13 | #import "HILogger.h" 14 | #import "jni_md.h" 15 | 16 | @interface HIBitcoinManager () { 17 | JNIEnv *_jniEnv; 18 | jobject _managerObject; 19 | jclass _managerClass; 20 | NSDateFormatter *_dateFormatter; 21 | BOOL _sending; 22 | void(^sendCompletionBlock)(NSString *hash); 23 | uint64_t _lastAvailableBalance; 24 | uint64_t _lastEstimatedBalance; 25 | NSTimer *_balanceChecker; 26 | NSMutableDictionary *_callbacks; 27 | NSUInteger _callbackId; 28 | } 29 | 30 | - (void)onAvailableBalanceChanged; 31 | - (void)onEstimatedBalanceChanged; 32 | - (void)onPeerCountChanged:(NSUInteger)peerCount; 33 | - (void)onSynchronizationChanged:(float)percent; 34 | - (void)onTransactionChanged:(NSString *)txid transactionData:(NSString *)json; 35 | - (void)onTransactionSuccess:(NSString *)txid; 36 | - (void)onTransactionFailed; 37 | - (void)onPaymentRequestLoaded:(jint)sessionId details:(jstring)details callback:(NSUInteger)callbackId; 38 | - (void)onPaymentRequestLoadFailedWithError:(jthrowable)error callback:(NSUInteger)callbackId; 39 | - (void)onPaymentRequestProcessed:(jstring)details callback:(NSUInteger)callbackId transactionId:(NSString *)txid; 40 | - (void)onPaymentRequestProcessingFailedWithError:(jthrowable)error callback:(NSUInteger)callbackId; 41 | - (void)handleJavaException:(jthrowable)exception useExceptionHandler:(BOOL)useHandler error:(NSError **)returnedError; 42 | 43 | @property (nonatomic, assign) BOOL isSyncing; 44 | 45 | @end 46 | 47 | 48 | #pragma mark - Helper functions for conversion 49 | 50 | NSString * NSStringFromJString(JNIEnv *env, jstring javaString) { 51 | if (javaString) { 52 | const char *chars = (*env)->GetStringUTFChars(env, javaString, NULL); 53 | NSString *objcString = [NSString stringWithUTF8String:chars]; 54 | (*env)->ReleaseStringUTFChars(env, javaString, chars); 55 | 56 | return objcString; 57 | } else { 58 | return nil; 59 | } 60 | } 61 | 62 | jstring JStringFromNSString(JNIEnv *env, NSString *string) { 63 | return (*env)->NewStringUTF(env, [string UTF8String]); 64 | } 65 | 66 | jarray JCharArrayFromNSData(JNIEnv *env, NSData *data) { 67 | if (data) { 68 | jsize length = (jsize)(data.length / sizeof(jchar)); 69 | jcharArray charArray = (*env)->NewCharArray(env, length); 70 | (*env)->SetCharArrayRegion(env, charArray, 0, length, data.bytes); 71 | return charArray; 72 | } else { 73 | return NULL; 74 | } 75 | } 76 | 77 | char * CharsFromString(NSString *string) { 78 | return (char *) [string UTF8String]; 79 | } 80 | 81 | JavaVMInitArgs JavaVMInitArgsCreateFromOptions(NSArray *args) { 82 | JavaVMInitArgs vmArgs; 83 | JavaVMOption *options = malloc(args.count * sizeof(JavaVMOption)); 84 | 85 | for (NSInteger i = 0; i < args.count; i++) { 86 | options[i].optionString = CharsFromString(args[i]); 87 | } 88 | 89 | vmArgs.version = JNI_VERSION_1_2; 90 | vmArgs.nOptions = (jint) args.count; 91 | vmArgs.ignoreUnrecognized = JNI_TRUE; 92 | vmArgs.options = options; 93 | 94 | return vmArgs; 95 | } 96 | 97 | void JavaVMInitArgsRelease(JavaVMInitArgs vmArgs) { 98 | free(vmArgs.options); 99 | } 100 | 101 | 102 | #pragma mark - JNI callback functions 103 | 104 | JNIEXPORT void JNICALL onBalanceChanged(JNIEnv *env, jobject thisobject) { 105 | @autoreleasepool { 106 | [[HIBitcoinManager defaultManager] onAvailableBalanceChanged]; 107 | [[HIBitcoinManager defaultManager] onEstimatedBalanceChanged]; 108 | } 109 | } 110 | 111 | JNIEXPORT void JNICALL onPeerCountChanged(JNIEnv *env, jobject thisobject, jint peerCount) { 112 | @autoreleasepool { 113 | [[HIBitcoinManager defaultManager] onPeerCountChanged:peerCount]; 114 | } 115 | } 116 | 117 | JNIEXPORT void JNICALL onSynchronizationUpdate(JNIEnv *env, jobject thisobject, jfloat percent) { 118 | @autoreleasepool { 119 | [[HIBitcoinManager defaultManager] onSynchronizationChanged:(float)percent]; 120 | } 121 | } 122 | 123 | JNIEXPORT void JNICALL onTransactionChanged(JNIEnv *env, jobject thisobject, jstring txid, jstring json) { 124 | @autoreleasepool { 125 | if (txid) { 126 | [[HIBitcoinManager defaultManager] onTransactionChanged:NSStringFromJString(env, txid) 127 | transactionData:NSStringFromJString(env, json)]; 128 | } 129 | } 130 | } 131 | 132 | JNIEXPORT void JNICALL onTransactionSuccess(JNIEnv *env, jobject thisobject, jstring txid) { 133 | @autoreleasepool { 134 | if (txid) { 135 | [[HIBitcoinManager defaultManager] onTransactionSuccess:NSStringFromJString(env, txid)]; 136 | } 137 | } 138 | } 139 | 140 | JNIEXPORT void JNICALL onTransactionFailed(JNIEnv *env, jobject thisobject) { 141 | @autoreleasepool { 142 | [[HIBitcoinManager defaultManager] onTransactionFailed]; 143 | } 144 | } 145 | 146 | JNIEXPORT void JNICALL onException(JNIEnv *env, jobject thisobject, jthrowable jexception) { 147 | @autoreleasepool { 148 | [[HIBitcoinManager defaultManager] handleJavaException:jexception useExceptionHandler:YES error:NULL]; 149 | } 150 | } 151 | 152 | JNIEXPORT void JNICALL onPaymentRequestLoaded(JNIEnv *env, jobject thisobject, 153 | jint callbackId, jint sessionId, jstring requestDetails) { 154 | @autoreleasepool { 155 | [[HIBitcoinManager defaultManager] onPaymentRequestLoaded:sessionId 156 | details:requestDetails 157 | callback:callbackId]; 158 | } 159 | } 160 | 161 | JNIEXPORT void JNICALL onPaymentRequestLoadFailed(JNIEnv *env, jobject thisobject, jint callbackId, jthrowable error) { 162 | @autoreleasepool { 163 | [[HIBitcoinManager defaultManager] onPaymentRequestLoadFailedWithError:error 164 | callback:callbackId]; 165 | } 166 | } 167 | 168 | JNIEXPORT void JNICALL onPaymentRequestProcessed(JNIEnv *env, jobject thisobject, 169 | jint callbackId, jstring txid, jstring details) { 170 | @autoreleasepool { 171 | [[HIBitcoinManager defaultManager] onPaymentRequestProcessed:details 172 | callback:callbackId 173 | transactionId:NSStringFromJString(env, txid)]; 174 | } 175 | } 176 | 177 | JNIEXPORT void JNICALL onPaymentRequestProcessingFailed(JNIEnv *env, jobject thisobject, 178 | jint callbackId, jthrowable error) { 179 | @autoreleasepool { 180 | [[HIBitcoinManager defaultManager] onPaymentRequestProcessingFailedWithError:error 181 | callback:callbackId]; 182 | } 183 | } 184 | 185 | JNIEXPORT void JNICALL receiveLogFromJVM(JNIEnv *env, jobject thisobject, jstring fileName, jstring methodName, 186 | int lineNumber, jint level, jstring msg) { 187 | @autoreleasepool { 188 | const char *fileNameString = (*env)->GetStringUTFChars(env, fileName, NULL); 189 | const char *methodNameString = (*env)->GetStringUTFChars(env, methodName, NULL); 190 | 191 | HILoggerLog(fileNameString, methodNameString, lineNumber, level, @"%@", 192 | NSStringFromJString(env, msg)); 193 | 194 | (*env)->ReleaseStringUTFChars(env, fileName, fileNameString); 195 | (*env)->ReleaseStringUTFChars(env, methodName, methodNameString); 196 | } 197 | } 198 | 199 | 200 | static JNINativeMethod methods[] = { 201 | {"onBalanceChanged", "()V", (void *)&onBalanceChanged}, 202 | {"onPeerCountChanged", "(I)V", (void *)&onPeerCountChanged}, 203 | {"onTransactionChanged", "(Ljava/lang/String;Ljava/lang/String;)V", (void *)&onTransactionChanged}, 204 | {"onTransactionSuccess", "(Ljava/lang/String;)V", (void *)&onTransactionSuccess}, 205 | {"onTransactionFailed", "()V", (void *)&onTransactionFailed}, 206 | {"onPaymentRequestLoaded", "(IILjava/lang/String;)V", (void *)&onPaymentRequestLoaded}, 207 | {"onPaymentRequestLoadFailed", "(ILjava/lang/Throwable;)V", (void *)&onPaymentRequestLoadFailed}, 208 | {"onPaymentRequestProcessed", "(ILjava/lang/String;Ljava/lang/String;)V", (void *)&onPaymentRequestProcessed}, 209 | {"onPaymentRequestProcessingFailed", "(ILjava/lang/Throwable;)V", (void *)&onPaymentRequestProcessingFailed}, 210 | {"onSynchronizationUpdate", "(F)V", (void *)&onSynchronizationUpdate}, 211 | {"onException", "(Ljava/lang/Throwable;)V", (void *)&onException} 212 | }; 213 | 214 | NSString * const kHIBitcoinManagerTransactionChangedNotification = @"kJHIBitcoinManagerTransactionChangedNotification"; 215 | NSString * const kHIBitcoinManagerStartedNotification = @"kJHIBitcoinManagerStartedNotification"; 216 | NSString * const kHIBitcoinManagerStoppedNotification = @"kJHIBitcoinManagerStoppedNotification"; 217 | 218 | static NSString * const BitcoinJKitBundleIdentifier = @"com.hivewallet.BitcoinJKit"; 219 | 220 | @implementation HIBitcoinManager 221 | 222 | + (HIBitcoinManager *)defaultManager { 223 | static HIBitcoinManager *defaultManager = nil; 224 | static dispatch_once_t oncePredicate; 225 | 226 | if (!defaultManager) { 227 | dispatch_once(&oncePredicate, ^{ 228 | defaultManager = [[self alloc] init]; 229 | }); 230 | } 231 | 232 | return defaultManager; 233 | } 234 | 235 | 236 | #pragma mark - helper methods for JNI calls and conversion 237 | 238 | - (jclass)jClassForClass:(NSString *)class { 239 | jclass cls = (*_jniEnv)->FindClass(_jniEnv, [class UTF8String]); 240 | 241 | [self handleJavaExceptions:NULL]; 242 | 243 | return cls; 244 | } 245 | 246 | - (jmethodID)jMethodWithName:(char *)name signature:(char *)signature { 247 | jmethodID method = (*_jniEnv)->GetMethodID(_jniEnv, _managerClass, name, signature); 248 | 249 | if (method == NULL) { 250 | @throw [NSException exceptionWithName:@"Java exception" 251 | reason:[NSString stringWithFormat:@"Method not found: %s (%s)", name, signature] 252 | userInfo:nil]; 253 | } 254 | 255 | return method; 256 | } 257 | 258 | - (BOOL)callBooleanMethodWithName:(char *)name signature:(char *)signature, ... { 259 | jmethodID method = [self jMethodWithName:name signature:signature]; 260 | 261 | va_list args; 262 | va_start(args, signature); 263 | jboolean result = (*_jniEnv)->CallBooleanMethodV(_jniEnv, _managerObject, method, args); 264 | va_end(args); 265 | 266 | [self handleJavaExceptions:NULL]; 267 | 268 | return result; 269 | } 270 | 271 | - (int)callIntegerMethodWithName:(char *)name signature:(char *)signature, ... { 272 | jmethodID method = [self jMethodWithName:name signature:signature]; 273 | 274 | va_list args; 275 | va_start(args, signature); 276 | jint result = (*_jniEnv)->CallIntMethodV(_jniEnv, _managerObject, method, args); 277 | va_end(args); 278 | 279 | [self handleJavaExceptions:NULL]; 280 | 281 | return result; 282 | } 283 | 284 | - (long)callLongMethodWithName:(char *)name signature:(char *)signature, ... { 285 | jmethodID method = [self jMethodWithName:name signature:signature]; 286 | 287 | va_list args; 288 | va_start(args, signature); 289 | jlong result = (*_jniEnv)->CallLongMethodV(_jniEnv, _managerObject, method, args); 290 | va_end(args); 291 | 292 | [self handleJavaExceptions:NULL]; 293 | 294 | return result; 295 | } 296 | 297 | - (jobject)callObjectMethodWithName:(char *)name error:(NSError **)error signature:(char *)signature, ... { 298 | jmethodID method = [self jMethodWithName:name signature:signature]; 299 | 300 | va_list args; 301 | va_start(args, signature); 302 | jobject result = (*_jniEnv)->CallObjectMethodV(_jniEnv, _managerObject, method, args); 303 | va_end(args); 304 | 305 | [self handleJavaExceptions:error]; 306 | 307 | return result; 308 | } 309 | 310 | - (void)callVoidMethodWithName:(char *)name error:(NSError **)error signature:(char *)signature, ... { 311 | jmethodID method = [self jMethodWithName:name signature:signature]; 312 | 313 | va_list args; 314 | va_start(args, signature); 315 | (*_jniEnv)->CallVoidMethodV(_jniEnv, _managerObject, method, args); 316 | va_end(args); 317 | 318 | [self handleJavaExceptions:error]; 319 | } 320 | 321 | - (void)handleJavaExceptions:(NSError **)error { 322 | if ((*_jniEnv)->ExceptionCheck(_jniEnv)) { 323 | // get the exception object 324 | jthrowable exception = (*_jniEnv)->ExceptionOccurred(_jniEnv); 325 | 326 | [self handleJavaException:exception useExceptionHandler:NO error:error]; 327 | } else if (error) { 328 | *error = nil; 329 | } 330 | } 331 | 332 | - (void)handleJavaException:(jthrowable)exception useExceptionHandler:(BOOL)useHandler error:(NSError **)returnedError { 333 | BOOL callerWantsToHandleErrors = returnedError != nil; 334 | 335 | if (callerWantsToHandleErrors) { 336 | *returnedError = nil; 337 | } 338 | 339 | // exception has to be cleared if it exists 340 | (*_jniEnv)->ExceptionClear(_jniEnv); 341 | 342 | // try to get exception details from Java 343 | // 344 | // note: we need to do this on the main thread - if this is called from a background thread, 345 | // the toString() call returns nil and throws a new exception (java.lang.StackOverflowException) 346 | // 347 | // and it needs to be run synchronously, 348 | // otherwise Java GC can clean up the exception object and we get a memory access error 349 | 350 | [self runSynchronouslyOnMainThread:^{ 351 | NSError *error = [NSError errorWithDomain:@"BitcoinKit" 352 | code:[self errorCodeForJavaException:exception] 353 | userInfo:[self createUserInfoForJavaException:exception]]; 354 | 355 | NSString *exceptionClass = [self getJavaExceptionClassName:exception]; 356 | 357 | HILogWarn(@"Java exception caught (%@): %@\n%@", 358 | exceptionClass, 359 | error.userInfo[NSLocalizedFailureReasonErrorKey], 360 | error.userInfo[@"stackTrace"] ?: @""); 361 | 362 | if (callerWantsToHandleErrors && error.code != kHIBitcoinManagerUnexpectedError) { 363 | *returnedError = error; 364 | } else { 365 | NSException *exception = [NSException exceptionWithName:@"Java exception" 366 | reason:error.userInfo[NSLocalizedFailureReasonErrorKey] 367 | userInfo:error.userInfo]; 368 | if (useHandler && self.exceptionHandler) { 369 | self.exceptionHandler(exception); 370 | } else { 371 | @throw exception; 372 | } 373 | } 374 | }]; 375 | } 376 | 377 | - (void)runSynchronouslyOnMainThread:(void (^)())block { 378 | if (dispatch_get_current_queue() != dispatch_get_main_queue()) { 379 | dispatch_sync(dispatch_get_main_queue(), block); 380 | } else { 381 | // if this is the main thread, we can't use dispatch_sync or the whole thing will lock up 382 | block(); 383 | } 384 | } 385 | 386 | - (NSInteger)errorCodeForJavaException:(jthrowable)exception { 387 | NSString *exceptionClass = [self getJavaExceptionClassName:exception]; 388 | NSString *exceptionMessage = [self getJavaExceptionMessage:exception]; 389 | 390 | if ([exceptionClass isEqual:@"com.google.bitcoin.store.UnreadableWalletException"]) { 391 | return kHIBitcoinManagerUnreadableWallet; 392 | } else if ([exceptionClass isEqual:@"com.google.bitcoin.store.BlockStoreException"]) { 393 | if ([exceptionMessage rangeOfString:@"Store file is already locked"].location != NSNotFound) { 394 | return kHIBitcoinManagerBlockStoreLockError; 395 | } else { 396 | return kHIBitcoinManagerBlockStoreReadError; 397 | } 398 | } else if ([exceptionClass isEqual:@"com.google.bitcoin.protocols.payments.PaymentRequestException$Expired"]) { 399 | return kHIBitcoinManagerPaymentRequestExpiredError; 400 | } else if ([exceptionClass 401 | isEqual:@"com.google.bitcoin.protocols.payments.PaymentRequestException$InvalidNetwork"]) { 402 | return kHIBitcoinManagerPaymentRequestWrongNetworkError; 403 | } else if ([exceptionClass isEqual:@"com.google.bitcoin.core.InsufficientMoneyException"]) { 404 | return kHIBitcoinManagerInsufficientMoneyError; 405 | } else if ([exceptionClass isEqual:@"com.google.protobuf.InvalidProtocolBufferException"]) { 406 | return kHIBitcoinManagerInvalidProtocolBufferError; 407 | } else if ([exceptionClass isEqual:@"java.io.FileNotFoundException"]) { 408 | return kHIFileNotFoundException; 409 | } else if ([exceptionClass isEqual:@"java.lang.IllegalArgumentException"]) { 410 | if ([exceptionMessage rangeOfString:@"Tried to send dust"].location != NSNotFound) { 411 | return kHIBitcoinManagerSendingDustError; 412 | } else { 413 | return kHIIllegalArgumentException; 414 | } 415 | } else if ([exceptionClass isEqual:@"com.hivewallet.bitcoinkit.NoWalletException"]) { 416 | return kHIBitcoinManagerNoWallet; 417 | } else if ([exceptionClass isEqual:@"com.hivewallet.bitcoinkit.ExistingWalletException"]) { 418 | return kHIBitcoinManagerWalletExists; 419 | } else if ([exceptionClass isEqual:@"com.hivewallet.bitcoinkit.WrongPasswordException"]) { 420 | return kHIBitcoinManagerWrongPassword; 421 | } else if ([exceptionClass isEqual:@"com.hivewallet.bitcoinkit.WrongNetworkException"]) { 422 | return kHIBitcoinManagerPaymentRequestWrongNetworkError; 423 | } else { 424 | return kHIBitcoinManagerUnexpectedError; 425 | } 426 | } 427 | 428 | - (NSString *)getJavaExceptionClassName:(jthrowable)exception { 429 | jclass exceptionClass = (*_jniEnv)->GetObjectClass(_jniEnv, exception); 430 | jmethodID getClassMethod = (*_jniEnv)->GetMethodID(_jniEnv, exceptionClass, "getClass", "()Ljava/lang/Class;"); 431 | jobject classObject = (*_jniEnv)->CallObjectMethod(_jniEnv, exception, getClassMethod); 432 | jobject class = (*_jniEnv)->GetObjectClass(_jniEnv, classObject); 433 | jmethodID getNameMethod = (*_jniEnv)->GetMethodID(_jniEnv, class, "getName", "()Ljava/lang/String;"); 434 | jstring name = (*_jniEnv)->CallObjectMethod(_jniEnv, exceptionClass, getNameMethod); 435 | return NSStringFromJString(_jniEnv, name); 436 | } 437 | 438 | - (NSDictionary *)createUserInfoForJavaException:(jthrowable)exception { 439 | NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; 440 | userInfo[NSLocalizedFailureReasonErrorKey] = [self getJavaExceptionMessage:exception] ?: @"Java VM raised an exception"; 441 | 442 | NSString *stackTrace = [self getJavaExceptionStackTrace:exception]; 443 | if (stackTrace) { 444 | userInfo[@"stackTrace"] = stackTrace; 445 | } 446 | return userInfo; 447 | } 448 | 449 | - (NSString *)getJavaExceptionMessage:(jthrowable)exception { 450 | jclass exceptionClass = (*_jniEnv)->GetObjectClass(_jniEnv, exception); 451 | 452 | if (exceptionClass) { 453 | jmethodID toStringMethod = (*_jniEnv)->GetMethodID(_jniEnv, exceptionClass, "toString", "()Ljava/lang/String;"); 454 | 455 | if (toStringMethod) { 456 | jstring description = (*_jniEnv)->CallObjectMethod(_jniEnv, exception, toStringMethod); 457 | 458 | if ((*_jniEnv)->ExceptionCheck(_jniEnv)) { 459 | (*_jniEnv)->ExceptionDescribe(_jniEnv); 460 | (*_jniEnv)->ExceptionClear(_jniEnv); 461 | } 462 | 463 | if (description) { 464 | return NSStringFromJString(_jniEnv, description); 465 | } 466 | } 467 | } 468 | 469 | return nil; 470 | } 471 | 472 | - (NSString *)getJavaExceptionStackTrace:(jthrowable)exception { 473 | jmethodID stackTraceMethod = (*_jniEnv)->GetMethodID(_jniEnv, _managerClass, "getExceptionStackTrace", 474 | "(Ljava/lang/Throwable;)Ljava/lang/String;"); 475 | 476 | if (stackTraceMethod) { 477 | jstring stackTrace = (*_jniEnv)->CallObjectMethod(_jniEnv, _managerObject, stackTraceMethod, exception); 478 | 479 | if ((*_jniEnv)->ExceptionCheck(_jniEnv)) { 480 | (*_jniEnv)->ExceptionDescribe(_jniEnv); 481 | (*_jniEnv)->ExceptionClear(_jniEnv); 482 | } 483 | 484 | if (stackTrace) { 485 | return NSStringFromJString(_jniEnv, stackTrace); 486 | } 487 | } 488 | 489 | return nil; 490 | } 491 | 492 | - (id)objectFromJSONString:(NSString *)JSONString { 493 | NSError *error = nil; 494 | NSData *data = [JSONString dataUsingEncoding:NSUTF8StringEncoding]; 495 | id result = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; 496 | 497 | if (!error) { 498 | return result; 499 | } else { 500 | HILogWarn(@"Couldn't parse JSON string '%@' (%@)", JSONString, error); 501 | return nil; 502 | } 503 | } 504 | 505 | 506 | #pragma mark - Initialization 507 | 508 | - (instancetype)init { 509 | self = [super init]; 510 | 511 | if (self) { 512 | _dateFormatter = [[NSDateFormatter alloc] init]; 513 | _dateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_GB"]; 514 | _dateFormatter.dateFormat = @"yyyy-MM-dd HH:mm:ss z"; 515 | _sending = NO; 516 | _syncProgress = 0; 517 | _testingNetwork = NO; 518 | _isRunning = NO; 519 | _callbacks = [[NSMutableDictionary alloc] init]; 520 | _callbackId = 0; 521 | 522 | self.dataURL = [self defaultDataDirectoryURL]; 523 | 524 | NSMutableArray *options = [NSMutableArray array]; 525 | [options addObject:[NSString stringWithFormat:@"-Djava.class.path=%@", [self bootJarPath]]]; 526 | 527 | NSString *extensionPaths = [self javaExtensionPaths]; 528 | if (extensionPaths) { 529 | [options addObject:[NSString stringWithFormat:@"-Djava.ext.dirs=%@", extensionPaths]]; 530 | } 531 | 532 | #ifdef DEBUG 533 | const char *debugPort = getenv("HIVE_JAVA_DEBUG_PORT"); 534 | BOOL doDebug = debugPort && debugPort[0]; 535 | if (doDebug) { 536 | [options addObject:[NSString stringWithFormat: 537 | @"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=%s", 538 | debugPort]]; 539 | } 540 | #endif 541 | 542 | #pragma clang diagnostic push 543 | #pragma clang diagnostic ignored "-Wdeprecated" 544 | JavaVMInitArgs vmArgs = JavaVMInitArgsCreateFromOptions(options); 545 | JavaVM *vm = NULL; 546 | JNI_CreateJavaVM(&vm, (void *) &_jniEnv, &vmArgs); 547 | JavaVMInitArgsRelease(vmArgs); 548 | #pragma clang diagnostic pop 549 | 550 | [self handleJavaExceptions:NULL]; 551 | NSAssert(vm != NULL, @"JavaVM *vm should not be NULL"); 552 | NSAssert(_jniEnv != NULL, @"JNIEnv *_jniEnv should not be NULL"); 553 | 554 | #ifdef DEBUG 555 | // Optionally wait here to give the Java debugger a chance to attach before anything happens. 556 | const char *debugDelay = getenv("HIVE_JAVA_DEBUG_DELAY"); 557 | if (doDebug && debugDelay && debugDelay[0]) { 558 | long seconds = strtol(debugDelay, NULL, 10); 559 | HILogDebug(@"Waiting %ld seconds for Java debugger to connect...", seconds); 560 | sleep((int)seconds); 561 | } 562 | #endif 563 | 564 | _managerClass = [self jClassForClass:@"com/hivewallet/bitcoinkit/BitcoinManager"]; 565 | NSAssert(_managerClass != NULL, @"jclass _managerClass should not be NULL"); 566 | 567 | (*_jniEnv)->RegisterNatives(_jniEnv, _managerClass, methods, sizeof(methods)/sizeof(methods[0])); 568 | [self handleJavaExceptions:NULL]; 569 | 570 | JNINativeMethod loggerMethod; 571 | loggerMethod.name = "receiveLogFromJVM"; 572 | loggerMethod.signature = "(Ljava/lang/String;Ljava/lang/String;IILjava/lang/String;)V"; 573 | loggerMethod.fnPtr = &receiveLogFromJVM; 574 | 575 | jclass loggerClass = [self jClassForClass:@"org/slf4j/impl/CocoaLogger"]; 576 | NSAssert(loggerClass != NULL, @"jclass loggerClass should not be NULL"); 577 | 578 | (*_jniEnv)->RegisterNatives(_jniEnv, loggerClass, &loggerMethod, 1); 579 | [self handleJavaExceptions:NULL]; 580 | 581 | jmethodID constructorMethod = [self jMethodWithName:"" signature:"()V"]; 582 | NSAssert(constructorMethod != NULL, @"jmethodID constructorMethod should not be NULL"); 583 | 584 | _managerObject = (*_jniEnv)->NewObject(_jniEnv, _managerClass, constructorMethod); 585 | [self handleJavaExceptions:NULL]; 586 | NSAssert(_managerObject != NULL, @"jobject _managerObject should not be NULL"); 587 | 588 | _balanceChecker = [NSTimer scheduledTimerWithTimeInterval:1.0 589 | target:self 590 | selector:@selector(verifyBalance:) 591 | userInfo:nil 592 | repeats:YES]; 593 | 594 | // this is the same as default log level in default (JDK) logger, i.e. before adding CocoaLogger 595 | [self setLogLevel:HILoggerLevelInfo]; 596 | } 597 | 598 | return self; 599 | } 600 | 601 | - (NSURL *)defaultDataDirectoryURL { 602 | NSArray *applicationSupport = [[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory 603 | inDomains:NSUserDomainMask]; 604 | 605 | return [[applicationSupport lastObject] URLByAppendingPathComponent:BitcoinJKitBundleIdentifier]; 606 | } 607 | 608 | - (NSString *)bootJarPath { 609 | NSBundle *myBundle = [NSBundle bundleWithIdentifier:BitcoinJKitBundleIdentifier]; 610 | return [myBundle pathForResource:@"boot" ofType:@"jar"]; 611 | } 612 | 613 | - (NSString *)javaExtensionPaths { 614 | /* 615 | We want to remove /Library/Java/Extensions from java.ext.dirs, because otherwise some random libs from there 616 | will be loaded and might conflict with our classes. 617 | But we don't want to remove any system directories from there, because those might contain required JRE 618 | modules like crypto algorithm implementations, and e.g. password hashing with SHA256 might break... 619 | So to get a list of all system dirs from java.ext.dirs but without the user-accessible dirs, we need to run 620 | ExtensionPathsReader class first which will print the filtered paths. 621 | */ 622 | 623 | NSTask *task = [[NSTask alloc] init]; 624 | task.launchPath = @"/usr/libexec/java_home"; 625 | task.arguments = @[@"-t", @"JNI", @"--exec", @"java", @"-cp", [self bootJarPath], 626 | @"com.hivewallet.bitcoinkit.ExtensionPathsReader"]; 627 | task.standardOutput = [NSPipe pipe]; 628 | task.standardError = [NSPipe pipe]; 629 | 630 | [task launch]; 631 | [task waitUntilExit]; 632 | 633 | NSData *outputData = [[task.standardOutput fileHandleForReading] readDataToEndOfFile]; 634 | NSString *output = [[NSString alloc] initWithData:outputData encoding:NSUTF8StringEncoding]; 635 | output = [output stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 636 | 637 | if (task.terminationStatus == 0) { 638 | return output; 639 | } else { 640 | NSData *errorData = [[task.standardError fileHandleForReading] readDataToEndOfFile]; 641 | NSString *errorText = [[NSString alloc] initWithData:errorData encoding:NSUTF8StringEncoding]; 642 | 643 | HILogWarn(@"Can't read Java extension directory list: output = '%@', error = '%@', return code = %d", 644 | output, errorText, task.terminationStatus); 645 | 646 | return nil; 647 | } 648 | } 649 | 650 | - (void)dealloc { 651 | [self stop]; 652 | } 653 | 654 | - (BOOL)start:(NSError **)error { 655 | [[NSFileManager defaultManager] createDirectoryAtURL:self.dataURL 656 | withIntermediateDirectories:YES 657 | attributes:0 658 | error:NULL]; 659 | 660 | if (_testingNetwork) { 661 | [self callVoidMethodWithName:"setTestingNetwork" error:NULL signature:"(Z)V", true]; 662 | } 663 | 664 | // Now set the folder 665 | [self callVoidMethodWithName:"setDataDirectory" error:NULL signature:"(Ljava/lang/String;)V", 666 | JStringFromNSString(_jniEnv, self.dataURL.path)]; 667 | 668 | // We're ready! Let's start 669 | [self callVoidMethodWithName:"start" error:error signature:"()V"]; 670 | 671 | if (!error || !*error) { 672 | [self didStart]; 673 | return YES; 674 | } 675 | 676 | return NO; 677 | } 678 | 679 | - (void)createWallet:(NSError **)error { 680 | *error = nil; 681 | 682 | [self callVoidMethodWithName:"createWallet" error:error signature:"()V"]; 683 | 684 | if (!*error) { 685 | [self didStart]; 686 | } 687 | } 688 | 689 | - (void)createWalletWithPassword:(NSData *)password 690 | error:(NSError **)error { 691 | jarray charArray = JCharArrayFromNSData(_jniEnv, password); 692 | 693 | *error = nil; 694 | [self callVoidMethodWithName:"createWallet" 695 | error:error 696 | signature:"([C)V", charArray]; 697 | 698 | [self zeroCharArray:charArray size:(jsize)(password.length / sizeof(jchar))]; 699 | 700 | if (!*error) { 701 | [self didStart]; 702 | } 703 | } 704 | 705 | - (void)changeWalletPassword:(NSData *)fromPassword 706 | toPassword:(NSData *)toPassword 707 | error:(NSError **)error { 708 | jarray fromCharArray = JCharArrayFromNSData(_jniEnv, fromPassword); 709 | jarray toCharArray = JCharArrayFromNSData(_jniEnv, toPassword); 710 | 711 | [self callVoidMethodWithName:"changeWalletPassword" 712 | error:error 713 | signature:"([C[C)V", fromCharArray, toCharArray]; 714 | 715 | [self zeroCharArray:fromCharArray size:(jsize)(fromPassword.length / sizeof(jchar))]; 716 | [self zeroCharArray:toCharArray size:(jsize)(toPassword.length / sizeof(jchar))]; 717 | } 718 | 719 | - (NSString *)signMessage:(NSString *)message 720 | withPassword:(NSData *)password 721 | error:(NSError **)error { 722 | jarray passwordCharArray = JCharArrayFromNSData(_jniEnv, password); 723 | jstring messageJString = JStringFromNSString(_jniEnv, message); 724 | 725 | jstring signature = [self callObjectMethodWithName:"signMessage" 726 | error:error 727 | signature:"(Ljava/lang/String;[C)Ljava/lang/String;", 728 | messageJString, passwordCharArray]; 729 | 730 | [self zeroCharArray:passwordCharArray size:(jsize)(password.length / sizeof(jchar))]; 731 | 732 | if (signature) { 733 | return NSStringFromJString(_jniEnv, signature); 734 | } else { 735 | return NULL; 736 | } 737 | } 738 | 739 | - (BOOL)isPasswordCorrect:(NSData *)password { 740 | jarray charArray = JCharArrayFromNSData(_jniEnv, password); 741 | 742 | BOOL result = [self callBooleanMethodWithName:"isPasswordCorrect" signature:"([C)Z", charArray]; 743 | 744 | [self zeroCharArray:charArray size:(jsize)(password.length / sizeof(jchar))]; 745 | 746 | return result; 747 | } 748 | 749 | - (void)zeroCharArray:(jarray)charArray size:(jsize)size { 750 | if (charArray) { 751 | jchar zero[size]; 752 | memset(zero, 0, size * sizeof(jchar)); 753 | (*_jniEnv)->SetCharArrayRegion(_jniEnv, charArray, 0, size, zero); 754 | } 755 | } 756 | 757 | - (void)didStart { 758 | [self willChangeValueForKey:@"isRunning"]; 759 | _isRunning = YES; 760 | [self didChangeValueForKey:@"isRunning"]; 761 | 762 | [[NSNotificationCenter defaultCenter] postNotificationName:kHIBitcoinManagerStartedNotification object:self]; 763 | 764 | [self willChangeValueForKey:@"walletAddress"]; 765 | [self didChangeValueForKey:@"walletAddress"]; 766 | } 767 | 768 | - (NSString *)walletAddress { 769 | jstring address = [self callObjectMethodWithName:"getWalletAddress" error:NULL signature:"()Ljava/lang/String;"]; 770 | return NSStringFromJString(_jniEnv, address); 771 | } 772 | 773 | - (NSString *)checkpointsFilePath { 774 | jstring path = [self callObjectMethodWithName:"getCheckpointsFilePath" error:NULL signature:"()Ljava/lang/String;"]; 775 | return NSStringFromJString(_jniEnv, path); 776 | } 777 | 778 | - (void)setCheckpointsFilePath:(NSString *)checkpointsFilePath { 779 | [self callVoidMethodWithName:"setCheckpointsFilePath" 780 | error:NULL 781 | signature:"(Ljava/lang/String;)V", 782 | JStringFromNSString(_jniEnv, checkpointsFilePath)]; 783 | } 784 | 785 | - (void)stop { 786 | [_balanceChecker invalidate]; 787 | _balanceChecker = nil; 788 | 789 | if (_managerObject) { 790 | [self callVoidMethodWithName:"stop" error:NULL signature:"()V"]; 791 | } 792 | 793 | [self willChangeValueForKey:@"isRunning"]; 794 | _isRunning = NO; 795 | [self didChangeValueForKey:@"isRunning"]; 796 | 797 | [[NSNotificationCenter defaultCenter] postNotificationName:kHIBitcoinManagerStoppedNotification object:self]; 798 | } 799 | 800 | - (void)deleteBlockchainDataFile:(NSError **)error { 801 | [self callVoidMethodWithName:"deleteBlockchainDataFile" error:error signature:"()V"]; 802 | } 803 | 804 | - (void)resetBlockchain:(NSError **)error { 805 | [self callVoidMethodWithName:"resetBlockchain" error:error signature:"()V"]; 806 | } 807 | 808 | - (NSDictionary *)modifiedTransactionForTransaction:(NSDictionary *)transaction { 809 | if (!transaction) { 810 | return nil; 811 | } 812 | 813 | NSMutableDictionary *d = [NSMutableDictionary dictionaryWithDictionary:transaction]; 814 | NSDate *date = [_dateFormatter dateFromString:transaction[@"time"]]; 815 | 816 | if (date) { 817 | d[@"time"] = date; 818 | } else { 819 | d[@"time"] = [NSDate date]; 820 | } 821 | 822 | return d; 823 | } 824 | 825 | - (NSDictionary *)transactionForHash:(NSString *)hash { 826 | NSError *error = nil; 827 | jstring transactionJString = [self callObjectMethodWithName:"getTransaction" 828 | error:&error 829 | signature:"(Ljava/lang/String;)Ljava/lang/String;", 830 | JStringFromNSString(_jniEnv, hash)]; 831 | 832 | if (transactionJString && !error) { 833 | NSDictionary *transactionData = [self objectFromJSONString:NSStringFromJString(_jniEnv, transactionJString)]; 834 | return [self modifiedTransactionForTransaction:transactionData]; 835 | } 836 | 837 | return nil; 838 | } 839 | 840 | - (NSDictionary *)transactionAtIndex:(NSUInteger)index { 841 | jstring transactionJString = [self callObjectMethodWithName:"getTransaction" 842 | error:NULL 843 | signature:"(I)Ljava/lang/String;", index]; 844 | 845 | if (transactionJString) { 846 | NSDictionary *transactionData = [self objectFromJSONString:NSStringFromJString(_jniEnv, transactionJString)]; 847 | return [self modifiedTransactionForTransaction:transactionData]; 848 | } 849 | 850 | return nil; 851 | } 852 | 853 | - (NSArray *)allTransactions { 854 | jstring transactionsJString = [self callObjectMethodWithName:"getAllTransactions" 855 | error:NULL 856 | signature:"()Ljava/lang/String;"]; 857 | 858 | if (transactionsJString) { 859 | NSArray *transactionJSONs = [self objectFromJSONString:NSStringFromJString(_jniEnv, transactionsJString)]; 860 | if (!transactionJSONs) { 861 | return nil; 862 | } 863 | 864 | NSMutableArray *transactions = [NSMutableArray arrayWithCapacity:transactionJSONs.count]; 865 | 866 | for (NSDictionary *JSON in transactionJSONs) { 867 | [transactions addObject:[self modifiedTransactionForTransaction:JSON]]; 868 | } 869 | 870 | return transactions; 871 | } 872 | 873 | return nil; 874 | } 875 | 876 | - (NSArray *)transactionsWithRange:(NSRange)range { 877 | jstring transactionsJString = [self callObjectMethodWithName:"getTransaction" 878 | error:NULL 879 | signature:"(II)Ljava/lang/String;", 880 | range.location, range.length]; 881 | 882 | if (transactionsJString) { 883 | NSArray *transactionJSONs = [self objectFromJSONString:NSStringFromJString(_jniEnv, transactionsJString)]; 884 | if (!transactionJSONs) { 885 | return nil; 886 | } 887 | 888 | NSMutableArray *transactions = [NSMutableArray arrayWithCapacity:transactionJSONs.count]; 889 | 890 | for (NSDictionary *JSON in transactionJSONs) { 891 | [transactions addObject:[self modifiedTransactionForTransaction:JSON]]; 892 | } 893 | 894 | return transactions; 895 | } 896 | 897 | return nil; 898 | } 899 | 900 | - (NSString *)walletDebuggingInfo { 901 | jstring info = [self callObjectMethodWithName:"getWalletDebuggingInfo" error:NULL signature:"()Ljava/lang/String;"]; 902 | return NSStringFromJString(_jniEnv, info); 903 | } 904 | 905 | - (BOOL)isAddressValid:(NSString *)address { 906 | return [self callBooleanMethodWithName:"isAddressValid" signature:"(Ljava/lang/String;)Z", 907 | JStringFromNSString(_jniEnv, address)]; 908 | } 909 | 910 | - (uint64_t)calculateTransactionFeeForSendingCoins:(uint64_t)coins 911 | toRecipient:(NSString *)recipient 912 | error:(NSError **)error { 913 | 914 | if (error) { 915 | *error = nil; 916 | } 917 | 918 | jstring fee = 919 | [self callObjectMethodWithName:"feeForSendingCoins" 920 | error:error 921 | signature:"(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", 922 | JStringFromNSString(_jniEnv, [NSString stringWithFormat:@"%lld", coins]), 923 | JStringFromNSString(_jniEnv, recipient)]; 924 | 925 | if (*error) { 926 | return 0; 927 | } else { 928 | return [NSStringFromJString(_jniEnv, fee) longLongValue]; 929 | } 930 | } 931 | 932 | - (void)sendCoins:(uint64_t)coins 933 | toRecipient:(NSString *)recipient 934 | comment:(NSString *)comment 935 | password:(NSData *)password 936 | error:(NSError **)error 937 | completion:(void(^)(NSString *hash))completion { 938 | 939 | if (_sending) { 940 | if (completion) { 941 | completion(nil); 942 | } 943 | 944 | return; 945 | } 946 | 947 | _sending = YES; 948 | 949 | sendCompletionBlock = [completion copy]; 950 | 951 | jstring jAmount = JStringFromNSString(_jniEnv, [NSString stringWithFormat:@"%lld", coins]); 952 | jstring jRecipient = JStringFromNSString(_jniEnv, recipient); 953 | *error = nil; 954 | 955 | jarray jPassword = JCharArrayFromNSData(_jniEnv, password); 956 | 957 | [self callVoidMethodWithName:"sendCoins" 958 | error:error 959 | signature:"(Ljava/lang/String;Ljava/lang/String;[C)V", jAmount, jRecipient, jPassword]; 960 | 961 | [self zeroCharArray:jPassword size:(jsize)(password.length / sizeof(jchar))]; 962 | 963 | if (*error) { 964 | [self endSending]; 965 | } 966 | } 967 | 968 | - (BOOL)sendPaymentRequest:(int)sessionId 969 | password:(NSData *)password 970 | error:(NSError **)outError 971 | callback:(void(^)(NSError*, NSDictionary*, NSString*))callback { 972 | 973 | jarray jPassword = JCharArrayFromNSData(_jniEnv, password); 974 | NSError *error = nil; 975 | NSUInteger callbackId = [self storeCallback:callback]; 976 | 977 | [self callVoidMethodWithName:"sendPaymentRequest" 978 | error:&error 979 | signature:"(I[CI)V", sessionId, jPassword, callbackId]; 980 | 981 | [self zeroCharArray:jPassword size:(jsize)(password.length / sizeof(jchar))]; 982 | 983 | if (!error) { 984 | return YES; 985 | } else { 986 | [self retrieveCallback:callbackId]; 987 | 988 | *outError = error; 989 | return NO; 990 | } 991 | } 992 | 993 | - (NSUInteger)storeCallback:(id)callback { 994 | NSUInteger callbackId = ++_callbackId; 995 | _callbacks[@(callbackId)] = [callback copy]; 996 | return callbackId; 997 | } 998 | 999 | - (id)retrieveCallback:(NSUInteger)callbackId { 1000 | id callback = _callbacks[@(callbackId)]; 1001 | [_callbacks removeObjectForKey:@(callbackId)]; 1002 | return callback; 1003 | } 1004 | 1005 | - (BOOL)openPaymentRequestFromFile:(NSString *)filename 1006 | error:(NSError **)outError 1007 | callback:(void(^)(NSError*, int, NSDictionary*))callback { 1008 | 1009 | jstring requestFilename = JStringFromNSString(_jniEnv, filename); 1010 | NSUInteger callbackId = [self storeCallback:callback]; 1011 | NSError *error = nil; 1012 | 1013 | [self callVoidMethodWithName:"openPaymentRequestFromFile" 1014 | error:&error 1015 | signature:"(Ljava/lang/String;I)V", requestFilename, callbackId]; 1016 | 1017 | if (!error) { 1018 | return YES; 1019 | } else { 1020 | [self retrieveCallback:callbackId]; 1021 | 1022 | *outError = error; 1023 | return NO; 1024 | } 1025 | } 1026 | 1027 | - (BOOL)openPaymentRequestFromURL:(NSString *)URL 1028 | error:(NSError **)outError 1029 | callback:(void(^)(NSError*, int, NSDictionary*))callback { 1030 | 1031 | jstring requestURL = JStringFromNSString(_jniEnv, URL); 1032 | NSUInteger callbackId = [self storeCallback:callback]; 1033 | NSError *error = nil; 1034 | 1035 | [self callVoidMethodWithName:"openPaymentRequestFromURL" 1036 | error:&error 1037 | signature:"(Ljava/lang/String;I)V", requestURL, callbackId]; 1038 | 1039 | if (!error) { 1040 | return YES; 1041 | } else { 1042 | [self retrieveCallback:callbackId]; 1043 | 1044 | *outError = error; 1045 | return NO; 1046 | } 1047 | } 1048 | 1049 | - (BOOL)isWalletEncrypted { 1050 | return [self callBooleanMethodWithName:"isWalletEncrypted" signature:"()Z"]; 1051 | } 1052 | 1053 | - (void)exportWalletTo:(NSURL *)exportURL error:(NSError **)error { 1054 | jstring path = JStringFromNSString(_jniEnv, exportURL.path); 1055 | [self callVoidMethodWithName:"exportWallet" error:error signature:"(Ljava/lang/String;)V", path]; 1056 | } 1057 | 1058 | - (NSString *)exportPrivateKeyWithPassword:(NSData *)password 1059 | error:(NSError **)error { 1060 | jarray passwordCharArray = JCharArrayFromNSData(_jniEnv, password); 1061 | 1062 | jstring privateKey = [self callObjectMethodWithName:"exportPrivateKey" 1063 | error:error 1064 | signature:"([C)Ljava/lang/String;", passwordCharArray]; 1065 | 1066 | [self zeroCharArray:passwordCharArray size:(jsize)(password.length / sizeof(jchar))]; 1067 | 1068 | if (privateKey) { 1069 | return NSStringFromJString(_jniEnv, privateKey); 1070 | } else { 1071 | return NULL; 1072 | } 1073 | } 1074 | 1075 | - (uint64_t)availableBalance { 1076 | return [self callLongMethodWithName:"getAvailableBalance" signature:"()J"]; 1077 | } 1078 | 1079 | - (uint64_t)estimatedBalance { 1080 | return [self callLongMethodWithName:"getEstimatedBalance" signature:"()J"]; 1081 | } 1082 | 1083 | - (void)verifyBalance:(NSTimer *)timer { 1084 | // This shouldn't be necessary, but let's check if we missed anything. 1085 | if (self.availableBalance != _lastAvailableBalance) { 1086 | HILogError(@"BitcoinKit missed a change notification. This is a bug."); 1087 | [self onAvailableBalanceChanged]; 1088 | } 1089 | 1090 | if (self.estimatedBalance != _lastEstimatedBalance) { 1091 | HILogError(@"BitcoinKit missed a change notification. This is a bug."); 1092 | [self onEstimatedBalanceChanged]; 1093 | } 1094 | } 1095 | 1096 | - (NSDate *)lastWalletChangeDate { 1097 | long timestamp = [self callLongMethodWithName:"getLastWalletChangeTimestamp" signature:"()J"]; 1098 | return (timestamp > 0) ? [NSDate dateWithTimeIntervalSince1970:(timestamp / 1000.0)] : nil; 1099 | } 1100 | 1101 | - (NSUInteger)transactionCount { 1102 | return [self callIntegerMethodWithName:"getTransactionCount" signature:"()I"]; 1103 | } 1104 | 1105 | - (void)setLogLevel:(HILoggerLevel)level { 1106 | jclass loggerClass = [self jClassForClass:@"org/slf4j/impl/CocoaLogger"]; 1107 | jmethodID method = (*_jniEnv)->GetStaticMethodID(_jniEnv, loggerClass, "setGlobalLevel", "(I)V"); 1108 | 1109 | if (!method) { 1110 | @throw [NSException exceptionWithName:@"Java exception" 1111 | reason:@"Method not found: setLevel" 1112 | userInfo:nil]; 1113 | } 1114 | 1115 | (*_jniEnv)->CallStaticVoidMethod(_jniEnv, loggerClass, method, level); 1116 | 1117 | [self handleJavaExceptions:NULL]; 1118 | } 1119 | 1120 | - (void)setIsSyncing:(BOOL)isSyncing { 1121 | if (_isSyncing != isSyncing) { 1122 | [self willChangeValueForKey:@"isSyncing"]; 1123 | _isSyncing = isSyncing; 1124 | [self didChangeValueForKey:@"isSyncing"]; 1125 | } 1126 | } 1127 | 1128 | - (BOOL)isConnected { 1129 | return (_peerCount > 0); 1130 | } 1131 | 1132 | 1133 | #pragma mark - Callbacks 1134 | 1135 | - (void)onAvailableBalanceChanged { 1136 | dispatch_async(dispatch_get_main_queue(), ^{ 1137 | [self willChangeValueForKey:@"availableBalance"]; 1138 | _lastAvailableBalance = self.availableBalance; 1139 | [self didChangeValueForKey:@"availableBalance"]; 1140 | }); 1141 | } 1142 | 1143 | - (void)onEstimatedBalanceChanged { 1144 | dispatch_async(dispatch_get_main_queue(), ^{ 1145 | [self willChangeValueForKey:@"estimatedBalance"]; 1146 | _lastEstimatedBalance = self.estimatedBalance; 1147 | [self didChangeValueForKey:@"estimatedBalance"]; 1148 | }); 1149 | } 1150 | 1151 | - (void)onPeerCountChanged:(NSUInteger)peerCount { 1152 | dispatch_async(dispatch_get_main_queue(), ^{ 1153 | BOOL statusChange = (_peerCount > 0 && peerCount == 0) || (_peerCount == 0 && peerCount > 0); 1154 | 1155 | [self willChangeValueForKey:@"peerCount"]; 1156 | if (statusChange) { 1157 | [self willChangeValueForKey:@"isConnected"]; 1158 | } 1159 | 1160 | _peerCount = peerCount; 1161 | 1162 | [self didChangeValueForKey:@"peerCount"]; 1163 | if (statusChange) { 1164 | [self didChangeValueForKey:@"isConnected"]; 1165 | } 1166 | }); 1167 | } 1168 | 1169 | - (void)onSynchronizationChanged:(float)percent { 1170 | dispatch_async(dispatch_get_main_queue(), ^{ 1171 | [self willChangeValueForKey:@"syncProgress"]; 1172 | _syncProgress = percent; 1173 | [self didChangeValueForKey:@"syncProgress"]; 1174 | 1175 | self.isSyncing = (percent < 100.0); 1176 | }); 1177 | } 1178 | 1179 | - (void)onTransactionChanged:(NSString *)txid transactionData:(NSString *)json { 1180 | dispatch_async(dispatch_get_main_queue(), ^{ 1181 | NSDictionary *userInfo = nil; 1182 | 1183 | if (json) { 1184 | NSDictionary *transactionData = [self objectFromJSONString:json]; 1185 | userInfo = @{@"json": [self modifiedTransactionForTransaction:transactionData]}; 1186 | } 1187 | 1188 | [[NSNotificationCenter defaultCenter] postNotificationName:kHIBitcoinManagerTransactionChangedNotification 1189 | object:txid 1190 | userInfo:userInfo]; 1191 | }); 1192 | } 1193 | 1194 | - (void)onTransactionSuccess:(NSString *)txid { 1195 | dispatch_async(dispatch_get_main_queue(), ^{ 1196 | if (sendCompletionBlock) { 1197 | sendCompletionBlock(txid); 1198 | } 1199 | 1200 | [self endSending]; 1201 | }); 1202 | } 1203 | 1204 | - (void)onTransactionFailed { 1205 | dispatch_async(dispatch_get_main_queue(), ^{ 1206 | if (sendCompletionBlock) { 1207 | sendCompletionBlock(nil); 1208 | } 1209 | 1210 | [self endSending]; 1211 | }); 1212 | } 1213 | 1214 | - (void)onPaymentRequestLoaded:(jint)sessionId details:(jstring)details callback:(NSUInteger)callbackId { 1215 | void (^callback)(NSError*, int, NSDictionary*) = [self retrieveCallback:callbackId]; 1216 | 1217 | if (callback) { 1218 | [self runSynchronouslyOnMainThread:^{ 1219 | NSDictionary *data = [self objectFromJSONString:NSStringFromJString(_jniEnv, details)]; 1220 | callback(nil, sessionId, data); 1221 | }]; 1222 | } 1223 | } 1224 | 1225 | - (void)onPaymentRequestLoadFailedWithError:(jthrowable)exception callback:(NSUInteger)callbackId { 1226 | void (^callback)(NSError*, int, NSDictionary*) = [self retrieveCallback:callbackId]; 1227 | 1228 | if (callback) { 1229 | [self runSynchronouslyOnMainThread:^{ 1230 | NSError *error = [NSError errorWithDomain:@"BitcoinKit" 1231 | code:[self errorCodeForJavaException:exception] 1232 | userInfo:[self createUserInfoForJavaException:exception]]; 1233 | 1234 | callback(error, 0, nil); 1235 | }]; 1236 | }; 1237 | } 1238 | 1239 | - (void)onPaymentRequestProcessed:(jstring)details callback:(NSUInteger)callbackId transactionId:(NSString *)txid { 1240 | void (^callback)(NSError*, NSDictionary*, NSString*) = [self retrieveCallback:callbackId]; 1241 | 1242 | if (callback) { 1243 | [self runSynchronouslyOnMainThread:^{ 1244 | NSDictionary *data = [self objectFromJSONString:NSStringFromJString(_jniEnv, details)]; 1245 | callback(nil, data, txid); 1246 | }]; 1247 | } 1248 | } 1249 | 1250 | - (void)onPaymentRequestProcessingFailedWithError:(jthrowable)exception callback:(NSUInteger)callbackId { 1251 | void (^callback)(NSError*, NSDictionary*, NSString*) = [self retrieveCallback:callbackId]; 1252 | 1253 | if (callback) { 1254 | [self runSynchronouslyOnMainThread:^{ 1255 | NSError *error = [NSError errorWithDomain:@"BitcoinKit" 1256 | code:[self errorCodeForJavaException:exception] 1257 | userInfo:[self createUserInfoForJavaException:exception]]; 1258 | callback(error, nil, nil); 1259 | }]; 1260 | } 1261 | } 1262 | 1263 | - (void)endSending { 1264 | _sending = NO; 1265 | sendCompletionBlock = nil; 1266 | } 1267 | 1268 | @end 1269 | --------------------------------------------------------------------------------