├── .gitignore ├── .gitmodules ├── .idea └── codeStyleSettings.xml ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE.md ├── Makefile ├── README.md ├── build.gradle ├── dependencies.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── kin-sdk-core ├── .gitignore ├── build.gradle ├── proguard-rules.pro ├── src │ ├── androidTest │ │ ├── assets │ │ │ └── testConfig.json │ │ └── java │ │ │ └── kin │ │ │ └── sdk │ │ │ └── core │ │ │ ├── BaseTest.java │ │ │ ├── Config.java │ │ │ ├── FileUtils.java │ │ │ ├── KinAccountTest.java │ │ │ ├── KinClientTest.java │ │ │ ├── RequestTest.java │ │ │ └── util │ │ │ └── KinConverterTest.java │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── kin │ │ │ └── sdk │ │ │ └── core │ │ │ ├── AbstractKinAccount.java │ │ │ ├── Balance.java │ │ │ ├── BalanceImpl.java │ │ │ ├── EthClientWrapper.java │ │ │ ├── KinAccount.java │ │ │ ├── KinAccountImpl.java │ │ │ ├── KinClient.java │ │ │ ├── KinConsts.java │ │ │ ├── KinSigner.java │ │ │ ├── PendingBalance.java │ │ │ ├── Request.java │ │ │ ├── ResultCallback.java │ │ │ ├── ServiceProvider.java │ │ │ ├── TransactionId.java │ │ │ ├── TransactionIdImpl.java │ │ │ ├── exception │ │ │ ├── AccountDeletedException.java │ │ │ ├── CreateAccountException.java │ │ │ ├── DeleteAccountException.java │ │ │ ├── EthereumClientException.java │ │ │ ├── OperationFailedException.java │ │ │ └── PassphraseException.java │ │ │ └── util │ │ │ ├── HexUtils.java │ │ │ └── KinConverter.java │ │ └── res │ │ └── values │ │ └── strings.xml └── truffle │ ├── contracts │ ├── BasicToken.sol │ ├── BasicTokenMock.sol │ ├── ERC20.sol │ ├── Migrations.sol │ └── SafeMath.sol │ ├── migrations │ └── 1_initial_migration.js │ ├── package-lock.json │ ├── package.json │ ├── scripts │ ├── prepare-tests.sh │ ├── testrpc-accounts.sh │ ├── testrpc-kill.sh │ ├── testrpc-run.sh │ └── truffle.sh │ ├── token-contract-address │ └── truffle.js ├── kin_android.png ├── sample ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── kin │ │ └── sdk │ │ └── core │ │ └── sample │ │ ├── BaseActivity.java │ │ ├── ChooseNetworkActivity.java │ │ ├── CreateWalletActivity.java │ │ ├── DisplayCallback.java │ │ ├── ExportKeystoreActivity.java │ │ ├── KinClientSampleApplication.java │ │ ├── TransactionActivity.java │ │ ├── Utils.java │ │ ├── WalletActivity.java │ │ ├── WebWrapperActivity.java │ │ └── kin │ │ └── sdk │ │ └── core │ │ └── sample │ │ └── dialog │ │ ├── KinAlertDialog.java │ │ └── OnConfirmedListener.java │ └── res │ ├── anim │ ├── slide_in_left.xml │ ├── slide_in_right.xml │ ├── slide_out_left.xml │ └── slide_out_right.xml │ ├── drawable-hdpi │ └── kin_small_icon.png │ ├── drawable-mdpi │ └── kin_small_icon.png │ ├── drawable-xhdpi │ └── kin_small_icon.png │ ├── drawable-xxhdpi │ └── kin_small_icon.png │ ├── drawable-xxxhdpi │ └── kin_small_icon.png │ ├── drawable │ ├── button_main_network_bg.xml │ ├── button_red_bg.xml │ ├── button_test_network_bg.xml │ ├── output_rectangle_bg.xml │ └── text_color.xml │ ├── layout │ ├── choose_network_activity.xml │ ├── create_wallet_activity.xml │ ├── export_key_store_activity.xml │ ├── transaction_activity.xml │ ├── wallet_activity.xml │ └── web_holder_activity.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_rounded.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_rounded.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_rounded.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_rounded.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_rounded.png │ ├── values-hdpi │ └── dimens.xml │ ├── values-xhdpi │ └── dimens.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── settings.gradle └── testfairy-uploader.sh /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Built application files 3 | *.apk 4 | *.ap_ 5 | 6 | # Files for the ART/Dalvik VM 7 | *.dex 8 | 9 | # Java class files 10 | *.class 11 | 12 | # Generated files 13 | bin/ 14 | gen/ 15 | out/ 16 | 17 | # Gradle files 18 | .gradle/ 19 | build/ 20 | 21 | # Local configuration file (sdk path, etc) 22 | local.properties 23 | 24 | # Proguard folder generated by Eclipse 25 | proguard/ 26 | 27 | # Log Files 28 | *.log 29 | 30 | # Android Studio Navigation editor temp files 31 | .navigation/ 32 | 33 | # Android Studio captures folder 34 | captures/ 35 | 36 | # Intellij - ignore most things 37 | *.iml 38 | .idea/* 39 | # ...but keep these 40 | !.idea/codeStyleSettings.xml 41 | 42 | # OSX Finder config files 43 | .DS_Store 44 | 45 | # Keystore files 46 | *.jks 47 | 48 | # External native build folder generated in Android Studio 2.2 and later 49 | .externalNativeBuild 50 | 51 | # Google Services (e.g. APIs or Firebase) 52 | google-services.json 53 | 54 | # Freeline 55 | freeline.py 56 | freeline/ 57 | freeline_project_description.json 58 | 59 | # test related files (ethereum testrpc): 60 | 61 | kin-sdk-core/truffle/node_modules 62 | kin-sdk-core/truffle/build 63 | 64 | kin-sdk-core/truffle/testrpc.log 65 | kin-sdk-core/truffle/testrpc.pid 66 | 67 | kin-sdk-core/truffle/truffle.log 68 | kin-sdk-core/truffle/token-contract-address 69 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "kin-sdk-core/truffle/kin-token"] 2 | path = kin-sdk-core/truffle/kin-token 3 | url = https://github.com/kinfoundation/kin-token.git 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: android 3 | 4 | before_cache: 5 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 6 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ 7 | 8 | cache: 9 | directories: 10 | - $HOME/.gradle/caches/ 11 | - $HOME/.gradle/wrapper/ 12 | - $HOME/.gradle/native 13 | - $HOME/.gradle/daemon 14 | - $HOME/.android/build-cache 15 | 16 | - node_modules 17 | 18 | env: 19 | global: 20 | - ANDROID_API_LEVEL=26 21 | - EMULATOR_API_LEVEL=16 22 | 23 | android: 24 | components: 25 | - tools 26 | - platform-tools 27 | - build-tools-26.0.2 28 | - android-$ANDROID_API_LEVEL 29 | - android-$EMULATOR_API_LEVEL 30 | - sys-img-armeabi-v7a-android-$ANDROID_API_LEVEL 31 | - sys-img-armeabi-v7a-android-$EMULATOR_API_LEVEL 32 | 33 | before_install: 34 | # install node (npm), truffle, testrpc 35 | - rm -rf ~/.nvm 36 | - git clone https://github.com/creationix/nvm.git ~/.nvm 37 | - source ~/.nvm/nvm.sh 38 | - nvm install 8.9.1 39 | - node --version 40 | - cd kin-sdk-core/truffle && npm install && cd ../../ 41 | 42 | before_script: 43 | # setup android emulator 44 | - echo no | android create avd --force -n test -t android-$EMULATOR_API_LEVEL --abi armeabi-v7a 45 | - emulator -avd test -no-skin -no-audio -no-window -camera-back none -camera-front none -verbose -memory 2048 & 46 | - android-wait-for-emulator 47 | - adb shell input keyevent 82 & 48 | 49 | script: 50 | - make test 51 | 52 | after_script: 53 | # print truffle and testrpc logs 54 | - cat kin-sdk-core/truffle/truffle.log 55 | - cat kin-sdk-core/truffle/testrpc.log 56 | 57 | deploy: 58 | skip_cleanup: true 59 | provider: script 60 | script: 61 | secure: ./testfairy-uploader.sh ./sample/build/outputs/apk/debug/sample-debug.apk 62 | on: 63 | branch: dev 64 | 65 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to kin-sdk-core-android 2 | 3 | Thank you very much for helping out with this project! 4 | Please read the following guidelines before making proposals: 5 | 6 | ## Guidelines 7 | 8 | * All suggestions should be raised as an issue or a pull request. 9 | * To submit your pull request, fork the repository, add your commit and submit a pull request to our `dev` branch. 10 | * Git commit messages should include a short title and a summary explaining the issue and how it has been handled. 11 | [Here is a handy guide on writing good commit messages](https://chris.beams.io/posts/git-commit/) 12 | * Please adhere to existing code style. We use the [Google Java style guide](https://google.github.io/styleguide/javaguide.html) 13 | to style our code with only exceptions of allowing a column limit of 120 instead of 100 and using 4 spaces for indentation instead of 2. 14 | * The project includes [codeStyleSettings.xml](.idea/codeStyleSettings.xml) under the .idea folder. We used 15 | [Google Java style guide for IntelliJ](https://github.com/google/styleguide/blob/gh-pages/intellij-java-google-style.xml) 16 | to generate this file, after adapting the column limit and identation as specified above. To format your code using the style 17 | guide on Android Studio, simply go to `Code -> Reformat Code` 18 | 19 | **We look forward to your contributions!** 20 | 21 | # Notes 22 | 23 | ## Git Flow 24 | 25 | The following describes our internal development team Git flow. 26 | If you're an outside collaborator - this section is provided here as general knowledge. 27 | However, you are not required to read or follow it. 28 | 29 | ### Merge vs. Rebase 30 | 31 | - When creating and starting work on a new feature branch - it is fine to 32 | squash (`rebase --interactive`), `amend`, or `push --force`. 33 | - Before opening a pull request, you should rebase from the primary `dev` branch. 34 | - Once a pull request has been opened, we stop `rebase`, `amend`, or `push --force`. 35 | - All changes to the branch from here on will be only via new commits added to the branch. 36 | - Specifically, fetching changes from other branches (i.e. `dev`) 37 | will be done via merging from it (`merge --no-ff`). 38 | - The reason behind this is that this allows you and everyone else reviewing 39 | the branch to see all changes that were made since the pull request was opened - 40 | updates following PR comments, merge conflicts, etc. Resolving contflicts using rebase, in contrast, 41 | are "hidden" and hard to track. 42 | - Avoid comitting binaries e.g. `.aar` files. 43 | - This increases the repository size by a large amount and is unnecessary. 44 | - Instead, you should create a bootstrap script that downloads these binaries 45 | from wherever they are stored. 46 | - Branch name structure should be `ISSUE NUMBER/feature name` e.g. `KIN-1234/my-new-feature`. 47 | Do not include your name in the branch name. There is no need for each branch to have a single owner. 48 | - Do not use `// created by` header comments from code files. This creates the wrong impression 49 | that there is a single owner for every file. 50 | - Do not use a license section in code files. We mostly use global [`LICENSE`](LICENSE) 51 | files for entire repositories. **Note** There might be some exceptions to this 52 | when dealing with 3rd-party libraries. 53 | 54 | 55 | ![Kin Token](kin_android.png) 56 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2017 Kin Foundation 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # default target does nothing 2 | .DEFAULT_GOAL: default 3 | default: ; 4 | 5 | # add truffle and testrpc to $PATH 6 | export PATH := ./kin-sdk-core/truffle/node_modules/.bin:$(PATH) 7 | export PATH := /usr/local/bin:$(PATH) 8 | 9 | test: 10 | ./gradlew :sample:assembleDebug 11 | ./gradlew :kin-sdk-core:connectedAndroidTest 12 | .PHONY: test 13 | 14 | prepare-tests: truffle 15 | kin-sdk-core/truffle/scripts/prepare-tests.sh 16 | .PHONY: test 17 | 18 | truffle: testrpc truffle-clean 19 | kin-sdk-core/truffle/scripts/truffle.sh 20 | .PHONY: truffle 21 | 22 | truffle-clean: 23 | rm -f kin-sdk-core/truffle/token-contract-address 24 | .PHONY: truffle-clean 25 | 26 | testrpc: testrpc-run # alias for testrpc-run 27 | .PHONY: testrpc 28 | 29 | testrpc-run: testrpc-kill 30 | kin-sdk-core/truffle/scripts/testrpc-run.sh 31 | .PHONY: testrpc-run 32 | 33 | testrpc-kill: 34 | kin-sdk-core/truffle/scripts/testrpc-kill.sh 35 | .PHONY: testrpc-kill 36 | 37 | clean: truffle-clean testrpc-kill 38 | rm -f kin-sdk-core/truffle/truffle.log 39 | rm -f kin-sdk-core/truffle/testrpc.log 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kin core SDK for Android 2 | Android library responsible for creating a new Ethereum account and managing KIN balance and transactions. 3 | ![Kin Token](kin_android.png) 4 | 5 | ## End-of-Life 6 | 7 | This SDK is no longer supported for new users. Please see [kin-core-android](https://github.com/kinfoundation/kin-core-android) for the current SDK. 8 | 9 | ## Build 10 | 11 | * Add this to your module's `build.gradle` file. 12 | ```gradle 13 | repositories { 14 | ... 15 | maven { 16 | url "https://dl.bintray.com/kinfoundation/go-ethereum" 17 | } 18 | maven { 19 | url 'https://jitpack.io' 20 | } 21 | } 22 | ... 23 | dependencies { 24 | ... 25 | compile "kinfoundation.ethereum:geth:1.0.2@aar" 26 | compile "com.github.kinfoundation:kin-sdk-core-android:LATEST-COMMIT-ON-DEV-BRANCH" 27 | } 28 | ``` 29 | In the above `build.gradle`: 30 | * LATEST-COMMIT-ON-DEV-BRANCH stands for the first 10 characters of our latest commit on dev branch. For example: d9bb37a7e2 31 | 32 | ## Usage 33 | ### Connecting to a service provider 34 | To start using our SDK, create a new `KinClient` with two arguments: an android `Context` and a `ServiceProvider`. 35 | 36 | A `ServiceProvider` is a service that provides access to the Ethereum network. 37 | The example below creates a `ServiceProvider` that will be used to connect to the main (production) Ethereum 38 | network, via Infura. To obtain an Infura token you can register [here](https://infura.io/register.html) 39 | ```java 40 | ServiceProvider infuraProvider = 41 | new ServiceProvider("https://main.infura.io/INFURA_TOKEN", ServiceProvider.NETWORK_ID_MAIN)); 42 | KinClient kinClient = new KinClient(context, infuraProvider); 43 | ``` 44 | 45 | To connect to a test ethereum network use the following ServiceProvider: 46 | ```java 47 | new ServiceProvider("https://ropsten.infura.io/INFURA_TOKEN", ServiceProvider.NETWORK_ID_ROPSTEN) 48 | ``` 49 | 50 | ### Parity service provider 51 | Unfortunately there is no guarantee that [`getPendingBalance`](#pendingBalance) will work when working with an Infura provider 52 | because of an existing known [issue](https://github.com/ethereum/go-ethereum/issues/15359) with the geth implementation of the ethereum protocol.\ 53 | In order for `getPendingBalance` to work as expected you will need to connect to a node running a parity implementation of the protocol. 54 | Let us know if you need help with that. 55 | 56 | ### Creating and retrieving a KIN account 57 | The first time you use `KinClient` you need to create a new account, using a passphrase. 58 | The details of the account created will be securely stored on the device. 59 | ```java 60 | KinAccount account; 61 | try { 62 | if (!kinClient.hasAccount()) { 63 | account = kinClient.createAccount("yourPassphrase"); 64 | } 65 | } catch (CreateAccountException e) { 66 | e.printStackTrace(); 67 | } 68 | ``` 69 | 70 | Once an account has been created there is no need to call `createAccount` again on the same device. 71 | From then on calling `getAccount` will retrieve the account stored on the device. 72 | ```java 73 | if (kinClient.hasAccount()) { 74 | account = kinClient.getAccount(); 75 | } 76 | ``` 77 | 78 | You can delete your account from the device using `deleteAccount` with the passphrase you used to create it as a parameter, 79 | but beware! Unless you export it first using `exportKeyStore` you will lose all your existing KIN if you do this. 80 | ```java 81 | kinClient.deleteAccount(String passphrase); 82 | ``` 83 | 84 | ### Public Address and JSON keystore 85 | Your account can be identified via it's public address. To retrieve the account public address use: 86 | ```java 87 | account.getPublicAddress(); 88 | ``` 89 | 90 | You can export the account keystore file as JSON using the `exportKeyStore` method 91 | ```java 92 | try { 93 | String oldPassphrase = "yourPassphrase"; 94 | String newPassphrase = "newPassphrase"; 95 | String json = account.exportKeyStore(oldPassphrase, newPassphrase); 96 | Log.d("example", "The keystore JSON: " + json); 97 | } 98 | catch (PassphraseException e){ 99 | e.printStackTrace(); 100 | } 101 | ``` 102 | 103 | ### Retrieving Balance 104 | To retrieve the balance of your account in KIN call the `getBalance` method: 105 | ```java 106 | Request balanceRequest = account.getBalance(); 107 | balanceRequest.run(new ResultCallback() { 108 | 109 | @Override 110 | public void onResult(Balance result) { 111 | Log.d("example", "The balance is: " + result.value(2)); 112 | } 113 | 114 | @Override 115 | public void onError(Exception e) { 116 | e.printStackTrace(); 117 | } 118 | }); 119 | ``` 120 | 121 | ### Transfering KIN to another account 122 | To transfer KIN to another account, you need the public address of the account you want 123 | to transfer the KIN to. 124 | 125 | The following code will transfer 20 KIN to account "#AB12349ACF123". 126 | ```java 127 | String toAddress = "#AB12349ACF123"; 128 | String passphrase = "yourPassphrase"; 129 | BigDecimal amountInKin = new BigDecimal("20"); 130 | 131 | 132 | transactionRequest = account.sendTransaction(toAddress, getPassphrase(), amount); 133 | transactionRequest.run(new ResultCallback() { 134 | 135 | @Override 136 | public void onResult(TransactionId result) { 137 | Log.d("example", "The transaction id: " + result.toString()); 138 | } 139 | 140 | @Override 141 | public void onError(Exception e) { 142 | e.printStackTrace(); 143 | } 144 | }); 145 | ``` 146 | 147 | ### Retrieving Pending Balance 148 | 149 | **This may not work with an infura ServiceProvider, as discussed [here](#parity) ** 150 | 151 | It takes some time for transactions to be confirmed. In the meantime you can call `getPendingBalance` 152 | to get the amount of KIN that you will have once all your pending transactions are confirmed. 153 | 154 | For example, if you have 40KIN and then transfer 5KIN to your friend, until the transaction of the 5KIN 155 | gets to be confirmed `getBalance` will return 40KIN and `getPendingBalance` will return 35KIN. 156 | 157 | Similarly if you have 30KIN and someone else transfer 2KIN to you, until the transaction gets to be confirmed 158 | `getBalance` will return 30KIN and `getPendingBalance` will return 32KIN. 159 | ```java 160 | Request balanceRequest = account.getPendingBalance(); 161 | balanceRequest.run(new ResultCallback() { 162 | 163 | @Override 164 | public void onResult(Balance result) { 165 | Log.d("example", "The balance is: " + result.toString()); 166 | } 167 | 168 | @Override 169 | public void onError(Exception e) { 170 | e.printStackTrace(); 171 | } 172 | }); 173 | ``` 174 | 175 | ### Sync vs Async 176 | 177 | Asynchronous requests are supported by our `Request` object. The `request.run()` method will perform the request on a serial 178 | background thread and notify success/failure using `ResultCallback` on the android main thread. 179 | In addition, `cancel(boolean)` method can be used to safely cancel requests and detach callbacks. 180 | 181 | 182 | A synchronous version of these methods is also provided. Make sure you call them in a background thread. 183 | 184 | ```java 185 | try { 186 | account.getBalanceSync(); 187 | } 188 | catch (OperationFailedException e) { 189 | // something went wrong - check the exception message 190 | } 191 | 192 | try { 193 | account.getPendingBalanceSync(); 194 | } 195 | catch (OperationFailedException e){ 196 | // something went wrong - check the exception message 197 | } 198 | 199 | try { 200 | account.sendTransactionSync(toAddress, passphrase, amountInKin); 201 | } 202 | catch (PassphraseException e){ 203 | // there passphrase used was wrong 204 | } 205 | catch (OperationFailedException e){ 206 | // something else went wrong - check the exception message 207 | } 208 | ``` 209 | 210 | ### Sample Application 211 | For a more detailed example on how to use the library please take a look at our [Sample App](sample/). 212 | 213 | ## Testing 214 | 215 | We use [ethereumjs/testrpc](https://github.com/trufflesuite/ganache-cli) and [Truffle framework](http://truffleframework.com/) unit tests. 216 | 217 | When running the SDK test target, pre-action and post-action tasks in build.gradle (Module: kin-sdk-core) 218 | will setup truffle and testrpc to run for the duration of the test. 219 | 220 | ### Requirements 221 | 222 | Node.js and npm. You can install these using homebrew: 223 | 224 | ```bash 225 | $ brew install node 226 | ``` 227 | Next, install specific npm packages using: 228 | 229 | ```bash 230 | $ cd kin-sdk-core/truffle 231 | $ npm install 232 | ``` 233 | 234 | Next, initialize and update git submodules. This will include `kin-sdk-core/truffle/kin-token`. 235 | 236 | ```bash 237 | $ git submodule init && git submodule update 238 | ``` 239 | 240 | ### How to run the tests 241 | 242 | 243 | * From command line
244 | Run the below command from the root directory.
245 | It will run all the tests and also clean testrpc at the end. 246 | 247 | ```bash 248 | $ make test 249 | ``` 250 | 251 | * From Android Studio
252 | Our test classes are [here](kin-sdk-core/src/androidTest/java/kin/sdk/core/). 253 | You can run the tests directly from Android Studio but will still have to run clean testrpc manually. 254 | 255 | ### Clean testrpc manually 256 | Run the below command from the root directory. 257 | ```bash 258 | $ make clean 259 | ``` 260 | 261 | 262 | ## Contributing 263 | Please review our [CONTRIBUTING.md](CONTRIBUTING.md) guide before opening issues and pull requests. 264 | 265 | ## License 266 | The kin-sdk-core-android library is licensed under [MIT license](LICENSE.md). 267 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | apply from: './dependencies.gradle' 3 | 4 | ext { 5 | projectName = ':kin-sdk-core' 6 | } 7 | 8 | buildscript { 9 | repositories { 10 | google() 11 | jcenter() 12 | } 13 | dependencies { 14 | classpath 'com.android.tools.build:gradle:3.0.0' 15 | // NOTE: Do not place your application dependencies here; they belong 16 | // in the individual module build.gradle files 17 | } 18 | } 19 | 20 | allprojects { 21 | repositories { 22 | jcenter() 23 | flatDir { 24 | dirs project(projectName).file('libs') 25 | } 26 | maven { 27 | url "https://dl.bintray.com/kinfoundation/go-ethereum" 28 | } 29 | } 30 | } 31 | 32 | task clean(type: Delete) { 33 | delete rootProject.buildDir 34 | } 35 | -------------------------------------------------------------------------------- /dependencies.gradle: -------------------------------------------------------------------------------- 1 | ext { 2 | 3 | //Version 4 | supportVersion = '26.+' 5 | junitVersion = '4.12' 6 | mockitoVersion = '2.10.0' 7 | jUnitParamsVersion = '1.1.1' 8 | gsonVersion = '2.8.2' 9 | 10 | //Packages 11 | supportPackage = 'com.android.support' 12 | mockitoPackage = 'org.mockito' 13 | junitPackage = 'junit' 14 | jUnitParamsPackage = 'pl.pragmatists' 15 | gsonPackage = 'com.google.code.gson' 16 | 17 | supportDependencies = [ 18 | appCompat: buildDependency(supportPackage, 'appcompat-v7', supportVersion), 19 | ] 20 | 21 | testingDependencies = [ 22 | junit : buildDependency(junitPackage, 'junit', junitVersion), 23 | mockitoAndroid: buildDependency(mockitoPackage, "mockito-android", mockitoVersion), 24 | junitParams : buildDependency(jUnitParamsPackage, "JUnitParams", jUnitParamsVersion), 25 | gson : buildDependency(gsonPackage, "gson", gsonVersion), 26 | ] 27 | } 28 | 29 | static String buildDependency(String pack, String dependency, String version) { 30 | return "${pack}:${dependency}:${version}" 31 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kinecosystem/kin-core-android-ethereum/6fc84185244c6bc9d3807197d4bc1baec6f8ad4e/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Oct 30 16:29:10 IST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /kin-sdk-core/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /kin-sdk-core/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | // maven plugin and group definition 4 | // needed for jitpack support 5 | apply plugin: 'maven' 6 | group = 'com.github.kinfoundation' 7 | 8 | android { 9 | compileSdkVersion 26 10 | buildToolsVersion "26.0.2" 11 | 12 | defaultConfig { 13 | minSdkVersion 16 14 | targetSdkVersion 26 15 | versionCode 1 16 | versionName "1.0" 17 | 18 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 19 | 20 | } 21 | buildTypes { 22 | release { 23 | minifyEnabled false 24 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 25 | } 26 | } 27 | 28 | compileOptions { 29 | sourceCompatibility JavaVersion.VERSION_1_8 30 | targetCompatibility JavaVersion.VERSION_1_8 31 | } 32 | } 33 | 34 | dependencies { 35 | 36 | implementation ('kinfoundation.ethereum:geth:1.0.2@aar') 37 | 38 | implementation supportDependencies.appCompat 39 | 40 | testImplementation testingDependencies.junit 41 | 42 | androidTestImplementation testingDependencies.mockitoAndroid 43 | androidTestImplementation testingDependencies.junitParams 44 | androidTestImplementation testingDependencies.gson 45 | androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', { 46 | exclude group: 'com.android.support', module: 'support-annotations' 47 | }) 48 | } 49 | 50 | task prepareRPC { 51 | doFirst { 52 | def testRpcOutput = new ByteArrayOutputStream() 53 | exec { 54 | workingDir '../' 55 | commandLine 'make', 'prepare-tests' 56 | standardOutput = testRpcOutput 57 | } 58 | testRpcOutput = testRpcOutput.toString().trim() 59 | printf("\n****** Prepare RPC ******\n" + testRpcOutput) 60 | } 61 | } 62 | 63 | task cleanRPC { 64 | doLast { 65 | def cleanRpcOutput = new ByteArrayOutputStream() 66 | exec { 67 | workingDir '../' 68 | commandLine 'make', 'testrpc-kill' 69 | standardOutput = cleanRpcOutput 70 | } 71 | cleanRpcOutput = cleanRpcOutput.toString().trim() 72 | printf("\n****** Clean RPC ******\n" + cleanRpcOutput) 73 | } 74 | } 75 | 76 | gradle.projectsEvaluated { 77 | preBuild.dependsOn prepareRPC 78 | connectedDebugAndroidTest.finalizedBy cleanRPC 79 | } 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /kin-sdk-core/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /kin-sdk-core/src/androidTest/assets/testConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "accounts": [{ 3 | "private_key":"0x11c98b8fa69354b26b5db98148a5bc4ef2ebae8187f651b82409f6cefc9bb0b8" 4 | }, 5 | { 6 | "private_key":"0xc5db67b3865454f6d129ec83204e845a2822d9fe5338ff46fe4c126859e1357e" 7 | }, 8 | { 9 | "private_key":"0x6ac1a8c98fa298af3884406fbd9468dca5200898f065e1283fc85dff95646c25" 10 | }, 11 | { 12 | "private_key":"0xbd9aebf18275f8074c53036818e8583b242f9bdfb7c0e79088007cb39a96e097" 13 | }, 14 | { 15 | "private_key":"0x8b727508230fda8e0ec96b7c9e51c89ff0e41ba30fad221c2f0fe942158571b1" 16 | }, 17 | { 18 | "private_key":"0x514111937962a290ba6afa3dd0044e0720148b46cd2dbc8045e811f8157b6b1a" 19 | }, 20 | { 21 | "private_key":"0x52f21c3eedc184eb13fcd5ec8e45e6741d97bca85a8703d733fab9c19f5e8518" 22 | }, 23 | { 24 | "private_key":"0xbca3035e18b3f87a38fa34fcc2561a023fe1f9b93354c04c772f37497ef08f3e" 25 | }, 26 | { 27 | "private_key":"0x2d8676754eb3d184f3e9428c5d52eacdf1d507593ba50c3ef2a59e1a3a46b578" 28 | }, 29 | { 30 | "private_key":"0xabf8c2dd52f5b14ea437325854048e5daadbca80f99f9d6f8e97ab5e05d4f0ab" 31 | } 32 | ], 33 | "token_contract_address":"0x8919486b0afaad4656e8f9667ce9adbb62c3f2b1" 34 | } -------------------------------------------------------------------------------- /kin-sdk-core/src/androidTest/java/kin/sdk/core/BaseTest.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | 6 | import com.google.gson.Gson; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import org.junit.After; 10 | import org.junit.Before; 11 | 12 | import java.io.File; 13 | 14 | public class BaseTest { 15 | 16 | /** 17 | * The Computer localhost on Android Studio emulator is 10.0.2.2 18 | * The Computer localhost on Genymotion emulator is 10.0.3.2 19 | */ 20 | private final String TESTRPC_PROVIDER_URL = "http://10.0.2.2:8545"; 21 | 22 | private Context context; 23 | private ServiceProvider serviceProvider; 24 | KinClient kinClient; 25 | Config config; 26 | 27 | @Before 28 | public void setUp() throws Exception { 29 | context = InstrumentationRegistry.getContext(); 30 | serviceProvider = new ServiceProvider(TESTRPC_PROVIDER_URL, ServiceProvider.NETWORK_ID_TRUFFLE); 31 | getConfigFile(); 32 | clearKeyStore(); 33 | kinClient = new KinClient(context, serviceProvider); 34 | } 35 | 36 | 37 | private void getConfigFile() { 38 | String json = null; 39 | try { 40 | InputStream is = context.getAssets().open("testConfig.json"); 41 | int size = is.available(); 42 | byte[] buffer = new byte[size]; 43 | is.read(buffer); 44 | is.close(); 45 | json = new String(buffer, "UTF-8"); 46 | } catch (IOException ex) { 47 | ex.printStackTrace(); 48 | } 49 | config = new Gson().fromJson(json, Config.class); 50 | // Set environment var of token contract address, to be used in EthClientWrapper; 51 | System.setProperty("TOKEN_CONTRACT_ADDRESS", config.getContractAddress()); 52 | } 53 | 54 | @After 55 | public void tearDown() throws Exception { 56 | clearKeyStore(); 57 | } 58 | 59 | private void clearKeyStore() { 60 | // Removes the previews KeyStore if exists 61 | String networkId = String.valueOf(serviceProvider.getNetworkId()); 62 | String keyStorePath = new StringBuilder(context.getFilesDir().getAbsolutePath()) 63 | .append(File.separator) 64 | .append("kin") 65 | .append(File.separator) 66 | .append("keystore") 67 | .append(File.separator) 68 | .append(networkId).toString(); 69 | 70 | File keystoreDir = new File(keyStorePath); 71 | FileUtils.deleteRecursive(keystoreDir); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /kin-sdk-core/src/androidTest/java/kin/sdk/core/Config.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | import java.util.List; 5 | 6 | public class Config { 7 | 8 | @SerializedName("accounts") 9 | private List accounts; 10 | 11 | @SerializedName("token_contract_address") 12 | private String contractAddress; 13 | 14 | public List getAccounts() { 15 | return accounts; 16 | } 17 | 18 | public String getContractAddress() { 19 | return contractAddress; 20 | } 21 | 22 | public class EcdsaAccount { 23 | 24 | @SerializedName("private_key") 25 | private String key; 26 | 27 | public String getKey() { 28 | return key; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /kin-sdk-core/src/androidTest/java/kin/sdk/core/FileUtils.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core; 2 | 3 | import java.io.File; 4 | 5 | public class FileUtils { 6 | 7 | public static void deleteRecursive(File fileOrDirectory) { 8 | if (fileOrDirectory == null || !fileOrDirectory.exists()) { 9 | return; 10 | } 11 | if (fileOrDirectory.isDirectory()) { 12 | for (File child : fileOrDirectory.listFiles()) { 13 | deleteRecursive(child); 14 | } 15 | } 16 | fileOrDirectory.delete(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /kin-sdk-core/src/androidTest/java/kin/sdk/core/KinAccountTest.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertNotNull; 5 | import static org.junit.Assert.assertThat; 6 | import static org.junit.Assert.assertTrue; 7 | 8 | import android.support.test.runner.AndroidJUnit4; 9 | import java.math.BigDecimal; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | import kin.sdk.core.Config.EcdsaAccount; 13 | import kin.sdk.core.exception.AccountDeletedException; 14 | import kin.sdk.core.exception.DeleteAccountException; 15 | import kin.sdk.core.exception.OperationFailedException; 16 | import kin.sdk.core.exception.PassphraseException; 17 | import org.hamcrest.CoreMatchers; 18 | import org.junit.Before; 19 | import org.junit.Rule; 20 | import org.junit.Test; 21 | import org.junit.rules.ExpectedException; 22 | import org.junit.runner.RunWith; 23 | 24 | @RunWith(AndroidJUnit4.class) 25 | public class KinAccountTest extends BaseTest { 26 | 27 | private final String PASSPHRASE = "testPassphrase"; 28 | private final String TO_ADDRESS = "0x82CdC15705CE9f4565DDa07d78c92ff3d2717854"; 29 | 30 | /** 31 | * First new account with 0 TOKEN and 0 ETH. 32 | */ 33 | private KinAccount kinAccount; 34 | 35 | /** 36 | * Imported account via private ECDSA Key, from testConfig.json 37 | * The first account (importedAccounts.get(0)) will have 1000 TOKEN and 100 ETH, and can sendTransactions. 38 | * All other accounts (1-9) will have only 100 ETH. 39 | */ 40 | private List importedAccounts = new ArrayList<>(10); 41 | 42 | @Rule 43 | public ExpectedException expectedEx = ExpectedException.none(); 44 | 45 | @Override 46 | @Before 47 | public void setUp() throws Exception { 48 | super.setUp(); 49 | kinAccount = kinClient.createAccount(PASSPHRASE); 50 | importAccounts(); 51 | } 52 | 53 | /** 54 | * Import all accounts from {@link Config}. 55 | */ 56 | private void importAccounts() throws OperationFailedException { 57 | List accounts = config.getAccounts(); 58 | for (EcdsaAccount account : accounts) { 59 | KinAccount importedAccount = kinClient.importAccount(account.getKey(), PASSPHRASE); 60 | importedAccounts.add(importedAccount); 61 | } 62 | } 63 | 64 | @Test 65 | public void testGetPublicAddress() { 66 | String address = kinAccount.getPublicAddress(); 67 | 68 | assertNotNull(address); 69 | assertThat(address, CoreMatchers.startsWith("0x")); 70 | assertEquals(40, address.substring(2, address.length()).length()); 71 | } 72 | 73 | @Test 74 | public void exportKeyStore() throws PassphraseException, OperationFailedException { 75 | String exportedKeyStore = kinAccount.exportKeyStore(PASSPHRASE, "newPassphrase"); 76 | 77 | assertNotNull(exportedKeyStore); 78 | assertThat(exportedKeyStore, CoreMatchers.containsString("address")); 79 | assertThat(exportedKeyStore, CoreMatchers.containsString("crypto")); 80 | assertThat(exportedKeyStore, CoreMatchers.containsString("cipher")); 81 | assertThat(exportedKeyStore, CoreMatchers.containsString("ciphertext")); 82 | assertThat(exportedKeyStore, CoreMatchers.containsString("cipherparams")); 83 | assertThat(exportedKeyStore, CoreMatchers.containsString("iv")); 84 | assertThat(exportedKeyStore, CoreMatchers.containsString("kdf")); 85 | assertThat(exportedKeyStore, CoreMatchers.containsString("kdfparams")); 86 | assertThat(exportedKeyStore, CoreMatchers.containsString("dklen")); 87 | assertThat(exportedKeyStore, CoreMatchers.containsString("salt")); 88 | } 89 | 90 | @Test(expected = PassphraseException.class) 91 | public void exportKeyStore_wrongPassphrase() throws Exception { 92 | kinAccount.exportKeyStore("wrongPassphrase", "newPassphrase"); 93 | } 94 | 95 | @Test(expected = AccountDeletedException.class) 96 | public void exportKeyStore_deletedAccount() throws Exception { 97 | kinClient.deleteAccount(PASSPHRASE); 98 | kinAccount.exportKeyStore(PASSPHRASE, "newPassphrase"); 99 | } 100 | 101 | @Test(expected = AccountDeletedException.class) 102 | public void transaction_deletedAccount() throws Exception { 103 | kinClient.deleteAccount(PASSPHRASE); 104 | kinAccount.sendTransactionSync(TO_ADDRESS, PASSPHRASE, new BigDecimal(1)); 105 | } 106 | 107 | public void getPublicKey_deletedAccount() 108 | throws PassphraseException, OperationFailedException, DeleteAccountException { 109 | kinClient.deleteAccount(PASSPHRASE); 110 | String publicAddress = kinAccount.getPublicAddress(); 111 | assertEquals("", publicAddress); 112 | } 113 | 114 | 115 | @Test 116 | public void sendTransactionSync_negativeAmount() throws Exception { 117 | expectedEx.expect(OperationFailedException.class); 118 | expectedEx.expectMessage("Amount can't be negative"); 119 | kinAccount.sendTransactionSync(TO_ADDRESS, PASSPHRASE, new BigDecimal(-1)); 120 | } 121 | 122 | @Test(expected = OperationFailedException.class) 123 | public void sendTransactionSync_nullPublicAddress() throws Exception { 124 | kinAccount.sendTransactionSync(null, PASSPHRASE, new BigDecimal(0)); 125 | } 126 | 127 | @Test(expected = OperationFailedException.class) 128 | public void sendTransactionSync_shortPublicAddress() throws Exception { 129 | kinAccount.sendTransactionSync("0xShortAddress", PASSPHRASE, new BigDecimal(0)); 130 | } 131 | 132 | @Test(expected = OperationFailedException.class) 133 | public void sendTransactionSync_longPublicAddress() throws Exception { 134 | kinAccount 135 | .sendTransactionSync("0xLongAddressVeryLongMoreThan40CharsYouCanCount!!", PASSPHRASE, new BigDecimal(0)); 136 | } 137 | 138 | @Test(expected = OperationFailedException.class) 139 | public void sendTransactionSync_illegalCharPublicAddress() throws Exception { 140 | kinAccount.sendTransactionSync("0xabababababababababababababababababababaX", PASSPHRASE, new BigDecimal(0)); 141 | } 142 | 143 | @Test(expected = OperationFailedException.class) 144 | public void sendTransactionSync_emptyPublicAddress() throws Exception { 145 | kinAccount.sendTransactionSync("", PASSPHRASE, new BigDecimal(0)); 146 | } 147 | 148 | @Test(expected = PassphraseException.class) 149 | public void sendTransactionSync_wrongPassphrase() throws Exception { 150 | kinAccount.sendTransactionSync(TO_ADDRESS, "wongPassphrase", new BigDecimal(0)); 151 | } 152 | 153 | @Test 154 | public void sendTransactionSync_SecondTimeEmptyPassphraseFails() throws Exception { 155 | KinAccount senderAccount = importedAccounts.get(0); 156 | Balance senderBalance = senderAccount.getBalanceSync(); 157 | BigDecimal amountToSend = new BigDecimal(10); 158 | 159 | senderAccount.sendTransactionSync(kinAccount.getPublicAddress(), PASSPHRASE, amountToSend); 160 | 161 | Balance kinAccountBalance = kinAccount.getBalanceSync(); 162 | assertTrue(kinAccountBalance.value(0).equals("10")); 163 | 164 | Balance afterBalance = senderAccount.getBalanceSync(); 165 | assertTrue((senderBalance.value().subtract(amountToSend).compareTo(afterBalance.value())) == 0); 166 | 167 | expectedEx.expect(PassphraseException.class); 168 | senderAccount.sendTransactionSync(kinAccount.getPublicAddress(), "", new BigDecimal(1)); 169 | } 170 | 171 | @Test 172 | public void sendTransactionSync_SecondTimeNullPassphraseFails() throws Exception { 173 | KinAccount senderAccount = importedAccounts.get(0); 174 | Balance senderBalance = senderAccount.getBalanceSync(); 175 | BigDecimal amountToSend = new BigDecimal(10); 176 | 177 | senderAccount.sendTransactionSync(kinAccount.getPublicAddress(), PASSPHRASE, amountToSend); 178 | 179 | Balance kinAccountBalance = kinAccount.getBalanceSync(); 180 | assertTrue(kinAccountBalance.value(0).equals("10")); 181 | Balance afterBalance = senderAccount.getBalanceSync(); 182 | assertTrue((senderBalance.value().subtract(amountToSend).compareTo(afterBalance.value())) == 0); 183 | 184 | expectedEx.expect(PassphraseException.class); 185 | senderAccount.sendTransactionSync(kinAccount.getPublicAddress(), null, new BigDecimal(1)); 186 | } 187 | 188 | @Test 189 | public void sendTransactionSync() throws Exception { 190 | KinAccount senderAccount = importedAccounts.get(0); 191 | Balance senderBalance = senderAccount.getBalanceSync(); 192 | BigDecimal amountToSend = new BigDecimal(10); 193 | 194 | senderAccount.sendTransactionSync(kinAccount.getPublicAddress(), PASSPHRASE, amountToSend); 195 | 196 | Balance kinAccountBalance = kinAccount.getBalanceSync(); 197 | assertTrue(kinAccountBalance.value(0).equals("10")); 198 | 199 | Balance afterBalance = senderAccount.getBalanceSync(); 200 | assertTrue((senderBalance.value().subtract(amountToSend).compareTo(afterBalance.value())) == 0); 201 | } 202 | 203 | @Test 204 | public void getBalanceSync() throws OperationFailedException { 205 | Balance balance = kinAccount.getBalanceSync(); 206 | 207 | assertNotNull(balance); 208 | assertEquals("0", balance.value(0)); 209 | assertEquals("0.0", balance.value(1)); 210 | } 211 | 212 | @Test 213 | public void getPendingBalanceSync_withNoTransaction() throws Exception { 214 | Balance balance = kinAccount.getBalanceSync(); 215 | Balance pendingBalance = kinAccount.getPendingBalanceSync(); 216 | 217 | assertNotNull(pendingBalance); 218 | assertEquals(balance.value(), pendingBalance.value()); 219 | } 220 | 221 | @Test 222 | public void getPendingBalanceSync() throws Exception { 223 | KinAccount senderAccount = importedAccounts.get(0); 224 | senderAccount.sendTransactionSync(kinAccount.getPublicAddress(), PASSPHRASE, new BigDecimal(10)); 225 | Balance kinAccountPendingBalance = kinAccount.getPendingBalanceSync(); 226 | 227 | assertNotNull(kinAccountPendingBalance); 228 | } 229 | } -------------------------------------------------------------------------------- /kin-sdk-core/src/androidTest/java/kin/sdk/core/KinClientTest.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertFalse; 5 | import static org.junit.Assert.assertNotNull; 6 | import static org.junit.Assert.assertNull; 7 | import static org.junit.Assert.assertTrue; 8 | 9 | import android.content.Context; 10 | import android.support.test.InstrumentationRegistry; 11 | import android.support.test.runner.AndroidJUnit4; 12 | import kin.sdk.core.exception.EthereumClientException; 13 | import org.junit.Rule; 14 | import org.junit.Test; 15 | import org.junit.rules.ExpectedException; 16 | import org.junit.runner.RunWith; 17 | 18 | 19 | @RunWith(AndroidJUnit4.class) 20 | public class KinClientTest extends BaseTest { 21 | 22 | private static final String PASSPHRASE = "testPassphrase"; 23 | 24 | @Rule 25 | public ExpectedException expectedEx = ExpectedException.none(); 26 | 27 | @Test 28 | public void testWrongServiceProvider() throws Exception { 29 | expectedEx.expect(EthereumClientException.class); 30 | expectedEx.expectMessage("provider - could not establish connection to the provider"); 31 | Context context = InstrumentationRegistry.getContext(); 32 | ServiceProvider wrongProvider = new ServiceProvider("wrongProvider", 12); 33 | kinClient = new KinClient(context, wrongProvider); 34 | } 35 | 36 | @Test 37 | public void testCreateAccount() throws Exception { 38 | // Create first account. 39 | KinAccount kinAccount = createAccount(); 40 | assertNotNull(kinAccount); 41 | } 42 | 43 | @Test 44 | public void testCreateSecondAccount() throws Exception { 45 | // Create account on the first time - should be ok 46 | KinAccount firstAccount = createAccount(); 47 | 48 | // Try to create second account 49 | // should return the same account (firstAccount). 50 | KinAccount secondAccount = createAccount(); 51 | 52 | assertEquals(firstAccount, secondAccount); 53 | } 54 | 55 | @Test 56 | public void testDeleteAccount() throws Exception { 57 | createAccount(); 58 | assertTrue(kinClient.hasAccount()); 59 | 60 | kinClient.deleteAccount(PASSPHRASE); 61 | assertFalse(kinClient.hasAccount()); 62 | assertNull(kinClient.getAccount()); 63 | } 64 | 65 | @Test 66 | public void testWipeAccount() throws Exception { 67 | createAccount(); 68 | assertTrue(kinClient.hasAccount()); 69 | 70 | kinClient.wipeoutAccount(); 71 | assertFalse(kinClient.hasAccount()); 72 | assertNull(kinClient.getAccount()); 73 | } 74 | 75 | @Test 76 | public void testGetAccount_isNull() throws Exception { 77 | // No account were created, thus the account is null 78 | KinAccount kinAccount = kinClient.getAccount(); 79 | assertNull(kinAccount); 80 | } 81 | 82 | @Test 83 | public void testGetAccount_notNull() throws Exception { 84 | // Create first account, should return same account. 85 | KinAccount kinAccount = createAccount(); 86 | KinAccount sameAccount = kinClient.getAccount(); 87 | 88 | assertNotNull(sameAccount); 89 | assertEquals(kinAccount, sameAccount); 90 | } 91 | 92 | @Test 93 | public void testHasAccounts_noAccount() throws Exception { 94 | // No account created 95 | // Check if has account 96 | assertFalse(kinClient.hasAccount()); 97 | } 98 | 99 | @Test 100 | public void testHasAccounts() throws Exception { 101 | // Create first account 102 | createAccount(); 103 | // Check if has account 104 | assertTrue(kinClient.hasAccount()); 105 | } 106 | 107 | private KinAccount createAccount() throws Exception { 108 | return kinClient.createAccount(PASSPHRASE); 109 | } 110 | } -------------------------------------------------------------------------------- /kin-sdk-core/src/androidTest/java/kin/sdk/core/RequestTest.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core; 2 | 3 | 4 | import static org.junit.Assert.assertEquals; 5 | import static org.junit.Assert.assertFalse; 6 | import static org.junit.Assert.assertNotEquals; 7 | import static org.junit.Assert.assertTrue; 8 | 9 | import android.os.Looper; 10 | import android.support.annotation.NonNull; 11 | import android.support.test.runner.AndroidJUnit4; 12 | import java.util.concurrent.Callable; 13 | import java.util.concurrent.CountDownLatch; 14 | import java.util.concurrent.TimeUnit; 15 | import java.util.concurrent.atomic.AtomicBoolean; 16 | import java.util.concurrent.atomic.AtomicLongArray; 17 | import org.junit.Test; 18 | import org.junit.runner.RunWith; 19 | 20 | @RunWith(AndroidJUnit4.class) 21 | public class RequestTest { 22 | 23 | private static final int TASK_DURATION_MILLIS = 10; 24 | private static final int TIMEOUT_DURATION_MILLIS = 100; 25 | 26 | public interface Consumer { 27 | 28 | void accept(T t); 29 | } 30 | 31 | private void runRequest(Callable task, Consumer onResultCallback, Consumer onErrorCallback) 32 | throws InterruptedException { 33 | CountDownLatch latch = new CountDownLatch(1); 34 | Request mockRequest = new Request<>(() -> { 35 | T result = task.call(); 36 | Thread.sleep(TASK_DURATION_MILLIS); 37 | return result; 38 | }); 39 | mockRequest.run(new ResultCallback() { 40 | @Override 41 | public void onResult(T result) { 42 | if (onResultCallback != null) { 43 | onResultCallback.accept(result); 44 | } 45 | latch.countDown(); 46 | } 47 | 48 | @Override 49 | public void onError(Exception e) { 50 | if (onErrorCallback != null) { 51 | onErrorCallback.accept(e); 52 | } 53 | latch.countDown(); 54 | } 55 | }); 56 | assertTrue(latch.await(TIMEOUT_DURATION_MILLIS, TimeUnit.MILLISECONDS)); 57 | } 58 | 59 | @Test 60 | public void run_verifyExpectedResult() throws InterruptedException { 61 | String expectedResult = "ExpectedResult"; 62 | runRequest( 63 | () -> expectedResult, 64 | result -> assertEquals(expectedResult, result), 65 | null 66 | ); 67 | } 68 | 69 | @Test 70 | public void run_verifyCorrectThreads() throws InterruptedException { 71 | AtomicLongArray threadIds = new AtomicLongArray(2); 72 | runRequest(() -> { 73 | threadIds.set(0, Thread.currentThread().getId()); 74 | return null; 75 | }, 76 | result -> threadIds.set(1, Thread.currentThread().getId()), 77 | null 78 | ); 79 | 80 | long mainThreadId = Looper.getMainLooper().getThread().getId(); 81 | assertNotEquals(mainThreadId, threadIds.get(0)); 82 | assertEquals(mainThreadId, threadIds.get(1)); 83 | } 84 | 85 | @Test 86 | public void run_verifyExceptionPropagation() throws InterruptedException { 87 | Exception expectedException = new Exception("some exception"); 88 | runRequest(() -> { 89 | throw expectedException; 90 | }, 91 | null 92 | , e -> assertEquals(expectedException, e)); 93 | } 94 | 95 | @Test 96 | public void run_cancelInterrupt() throws InterruptedException { 97 | threadInterruptTest(true); 98 | } 99 | 100 | @Test 101 | public void run_cancelDoNotInterrupt() throws InterruptedException { 102 | threadInterruptTest(false); 103 | } 104 | 105 | private void threadInterruptTest(boolean expectThreadInterruptted) throws InterruptedException { 106 | CountDownLatch latch = new CountDownLatch(1); 107 | CountDownLatch runLatch = new CountDownLatch(1); 108 | AtomicBoolean threadInterrupted = new AtomicBoolean(false); 109 | AtomicBoolean callbackExecuted = new AtomicBoolean(false); 110 | Request mockRequest = new Request<>(() -> { 111 | try { 112 | runLatch.countDown(); 113 | Thread.sleep(TASK_DURATION_MILLIS); 114 | } catch (InterruptedException ie) { 115 | threadInterrupted.set(true); 116 | } 117 | latch.countDown(); 118 | return new Object(); 119 | }); 120 | mockRequest.run(new ResultCallback() { 121 | @Override 122 | public void onResult(Object result) { 123 | callbackExecuted.set(true); 124 | } 125 | 126 | @Override 127 | public void onError(Exception e) { 128 | callbackExecuted.set(true); 129 | } 130 | }); 131 | assertTrue(runLatch.await(TIMEOUT_DURATION_MILLIS, TimeUnit.MILLISECONDS)); 132 | mockRequest.cancel(expectThreadInterruptted); 133 | assertTrue(latch.await(TIMEOUT_DURATION_MILLIS, TimeUnit.MILLISECONDS)); 134 | assertEquals(expectThreadInterruptted, threadInterrupted.get()); 135 | assertFalse(callbackExecuted.get()); 136 | } 137 | 138 | @Test(expected = IllegalArgumentException.class) 139 | public void request_nullCallable() { 140 | new Request<>(null); 141 | } 142 | 143 | @Test(expected = IllegalArgumentException.class) 144 | public void run_nullCallback() { 145 | Request request = new Request<>(() -> ""); 146 | request.run(null); 147 | } 148 | 149 | @Test(expected = IllegalStateException.class) 150 | public void run_twice() { 151 | Request request = new Request<>(() -> ""); 152 | request.run(getEmptyResultCallback()); 153 | request.run(getEmptyResultCallback()); 154 | } 155 | 156 | @Test(expected = IllegalStateException.class) 157 | public void runAfterCancel() { 158 | Request request = new Request<>(() -> ""); 159 | request.cancel(true); 160 | request.run(getEmptyResultCallback()); 161 | } 162 | 163 | @NonNull 164 | private ResultCallback getEmptyResultCallback() { 165 | return new ResultCallback() { 166 | @Override 167 | public void onResult(String result) { 168 | } 169 | 170 | @Override 171 | public void onError(Exception e) { 172 | } 173 | }; 174 | } 175 | } -------------------------------------------------------------------------------- /kin-sdk-core/src/androidTest/java/kin/sdk/core/util/KinConverterTest.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core.util; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import java.math.BigDecimal; 6 | import junitparams.JUnitParamsRunner; 7 | import junitparams.Parameters; 8 | import org.ethereum.geth.BigInt; 9 | import org.ethereum.geth.Geth; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | 13 | @RunWith(JUnitParamsRunner.class) 14 | public class KinConverterTest { 15 | 16 | @Test 17 | @Parameters({ 18 | // 1 Wei to Kin (smallest coin value) 19 | "1, 0.000000000000000001", 20 | //Max Kin Supply 21 | "10000000000000000000000000000000, 10000000000000.000000000000000000", 22 | //Invalid Wei (wei is integer) 23 | "0.1, 0.000000000000000000"}) 24 | public void toKinTest(String input, String output) throws Exception { 25 | BigDecimal bigDecimalWei = new BigDecimal(input); 26 | BigDecimal bigDecimalKin = KinConverter.toKin(bigDecimalWei); 27 | assertEquals(output, bigDecimalKin.toPlainString()); 28 | } 29 | 30 | @Test 31 | @Parameters({ 32 | // one wei (smallest coin value) 33 | "1, 0.000000000000000001", 34 | //max wei supply to kin 35 | "10000000000000000000000000000000, 10000000000000.000000000000000000", 36 | "1234567, 0.000000000001234567"}) 37 | public void toKinFromBigIntTest(String input, String output) throws Exception { 38 | BigInt bigIntWei = Geth.newBigInt(0L); 39 | bigIntWei.setString(input, 10); 40 | BigDecimal bigDecimalKin = KinConverter.toKin(bigIntWei); 41 | assertEquals(output, bigDecimalKin.toPlainString()); 42 | } 43 | 44 | @Test 45 | @Parameters({ 46 | // 1 kin to wei 47 | "1, 1000000000000000000", 48 | //max kin supply to wei 49 | "10000000000000.000000000000000000, 10000000000000000000000000000000", 50 | //discard too low value (less then wei) 51 | "0.0000000000012345678, 1234567"}) 52 | public void fromKinTest(String input, String output) throws Exception { 53 | BigDecimal bigDecimalKin = new BigDecimal(input); 54 | BigInt bigInt = KinConverter.fromKin(bigDecimalKin); 55 | assertEquals(output, bigInt.string()); 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /kin-sdk-core/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /kin-sdk-core/src/main/java/kin/sdk/core/AbstractKinAccount.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core; 2 | 3 | import java.math.BigDecimal; 4 | 5 | abstract class AbstractKinAccount implements KinAccount { 6 | 7 | @Override 8 | public Request sendTransaction(final String publicAddress, final String passphrase, 9 | final BigDecimal amount) { 10 | return new Request<>(() -> sendTransactionSync(publicAddress, passphrase, amount)); 11 | } 12 | 13 | @Override 14 | public Request getBalance() { 15 | return new Request<>(this::getBalanceSync); 16 | } 17 | 18 | @Override 19 | public Request getPendingBalance() { 20 | return new Request<>(this::getPendingBalanceSync); 21 | } 22 | 23 | @Override 24 | public boolean equals(Object obj) { 25 | if (this == obj) { 26 | return true; 27 | } 28 | if (obj == null || getClass() != obj.getClass()) { 29 | return false; 30 | } 31 | KinAccount account = (KinAccount) obj; 32 | return getPublicAddress().equals(account.getPublicAddress()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /kin-sdk-core/src/main/java/kin/sdk/core/Balance.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public interface Balance { 6 | 7 | /** 8 | * @return BigDecimal the balance value 9 | */ 10 | BigDecimal value(); 11 | 12 | /** 13 | * @param precision the number of decimals points 14 | * @return String the balance value as a string with specified precision 15 | */ 16 | String value(int precision); 17 | 18 | } -------------------------------------------------------------------------------- /kin-sdk-core/src/main/java/kin/sdk/core/BalanceImpl.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core; 2 | 3 | import java.math.BigDecimal; 4 | 5 | final class BalanceImpl implements Balance { 6 | 7 | private BigDecimal valueInKin; 8 | 9 | BalanceImpl(BigDecimal valueInKin) { 10 | this.valueInKin = valueInKin; 11 | } 12 | 13 | @Override 14 | public BigDecimal value() { 15 | return valueInKin; 16 | } 17 | 18 | @Override 19 | public String value(int precision) { 20 | return valueInKin.setScale(precision, BigDecimal.ROUND_FLOOR).toString(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /kin-sdk-core/src/main/java/kin/sdk/core/EthClientWrapper.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core; 2 | 3 | import java.io.File; 4 | import java.math.BigDecimal; 5 | import kin.sdk.core.exception.DeleteAccountException; 6 | import kin.sdk.core.exception.EthereumClientException; 7 | import kin.sdk.core.exception.OperationFailedException; 8 | import kin.sdk.core.exception.PassphraseException; 9 | import kin.sdk.core.util.HexUtils; 10 | import kin.sdk.core.util.KinConverter; 11 | import org.ethereum.geth.Account; 12 | import org.ethereum.geth.Address; 13 | import org.ethereum.geth.BigInt; 14 | import org.ethereum.geth.BoundContract; 15 | import org.ethereum.geth.CallOpts; 16 | import org.ethereum.geth.Context; 17 | import org.ethereum.geth.EthereumClient; 18 | import org.ethereum.geth.Geth; 19 | import org.ethereum.geth.Interface; 20 | import org.ethereum.geth.Interfaces; 21 | import org.ethereum.geth.KeyStore; 22 | import org.ethereum.geth.TransactOpts; 23 | import org.ethereum.geth.Transaction; 24 | 25 | /** 26 | * A Wrapper to the geth (go ethereum) library. 27 | * Responsible for account creation/storage/retrieval, connection to Kin contract 28 | * retrieving balance and sending transactions 29 | */ 30 | final class EthClientWrapper { 31 | 32 | private Context gethContext; 33 | private android.content.Context androidContext; 34 | private EthereumClient ethereumClient; 35 | private BoundContract boundContract; 36 | private KeyStore keyStore; 37 | private ServiceProvider serviceProvider; 38 | private final String kinContractAddress; 39 | private long nonce = -1; 40 | private BigInt gasPrice = null; 41 | private final PendingBalance pendingBalance; 42 | 43 | EthClientWrapper(android.content.Context androidContext, ServiceProvider serviceProvider) 44 | throws EthereumClientException { 45 | this.serviceProvider = serviceProvider; 46 | this.androidContext = androidContext.getApplicationContext(); 47 | this.gethContext = new Context(); 48 | this.kinContractAddress = KinConsts.getContractAddress(serviceProvider); 49 | initEthereumClient(); 50 | initKinContract(); 51 | initKeyStore(); 52 | this.pendingBalance = new PendingBalance(ethereumClient, gethContext, kinContractAddress); 53 | } 54 | 55 | /** 56 | * Create {@link EthereumClient}, that will be a connection to Ethereum network. 57 | * 58 | * @throws EthereumClientException if go-ethereum could not establish connection to the provider. 59 | */ 60 | private void initEthereumClient() throws EthereumClientException { 61 | try { 62 | this.ethereumClient = Geth.newEthereumClient(serviceProvider.getProviderUrl()); 63 | } catch (Exception e) { 64 | throw new EthereumClientException("provider - could not establish connection to the provider"); 65 | } 66 | } 67 | 68 | /** 69 | * Create {@link BoundContract}, that will handle all the calls to Kin smart-contract. 70 | * 71 | * @throws EthereumClientException if go-ethereum could not establish connection to Kin smart-contract. 72 | */ 73 | private void initKinContract() throws EthereumClientException { 74 | try { 75 | Address contractAddress = Geth.newAddressFromHex(kinContractAddress); 76 | this.boundContract = Geth.bindContract(contractAddress, KinConsts.ABI, ethereumClient); 77 | } catch (Exception e) { 78 | throw new EthereumClientException("contract - could not establish connection to Kin smart-contract"); 79 | } 80 | } 81 | 82 | /** 83 | * Create {@link KeyStore}, to have control over the account management. 84 | * And the ability to store accounts securely according to go-ethereum encryption protocol. 85 | * The keystore path is unique to each network id, 86 | * for example Ropsten network will be: ../data/kin/keystore/3/ 87 | * 88 | * @throws EthereumClientException if could not create directory to save the keystore. 89 | */ 90 | private void initKeyStore() throws EthereumClientException { 91 | // Make directories if necessary, the keystore will be saved there. 92 | File keystoreDir = new File(getKeyStorePath()); 93 | if (!keystoreDir.exists()) { 94 | if (!keystoreDir.mkdirs()) { 95 | throw new EthereumClientException("keystore - could not create directory"); 96 | } 97 | } 98 | // Create a keyStore instance according to go-ethereum encryption protocol. 99 | keyStore = Geth.newKeyStore(keystoreDir.getAbsolutePath(), Geth.LightScryptN, Geth.LightScryptP); 100 | } 101 | 102 | public String getKeyStorePath() { 103 | return new StringBuilder(androidContext.getFilesDir().getAbsolutePath()) 104 | .append(File.separator) 105 | .append("kin") 106 | .append(File.separator) 107 | .append("keystore") 108 | .append(File.separator) 109 | .append(serviceProvider.getNetworkId()) 110 | .toString(); 111 | } 112 | 113 | public void deleteAccount(Account account, String passphrase) throws DeleteAccountException { 114 | try { 115 | keyStore.deleteAccount(account, passphrase); 116 | } catch (Exception e) { 117 | throw new DeleteAccountException(e); 118 | } 119 | } 120 | 121 | public void wipeoutAccount() throws EthereumClientException { 122 | File keystoreDir = new File(getKeyStorePath()); 123 | if (keystoreDir.exists()) { 124 | deleteRecursive(keystoreDir); 125 | } 126 | // this will reset geth in-memory keystore 127 | initKeyStore(); 128 | } 129 | 130 | /** 131 | * @return {@link KeyStore} that will handle all operations related to accounts. 132 | */ 133 | KeyStore getKeyStore() { 134 | return keyStore; 135 | } 136 | 137 | /** 138 | * Transfer amount of KIN from account to the specified public address. 139 | * 140 | * @param from the sender {@link Account} 141 | * @param passphrase the passphrase used to create the account 142 | * @param publicAddress the address to send the KIN to 143 | * @param amount the amount of KIN to send 144 | * @return {@link TransactionId} of the transaction 145 | * @throws PassphraseException if the transaction could not be signed with the passphrase specified 146 | * @throws OperationFailedException another error occurred 147 | */ 148 | TransactionId sendTransaction(Account from, String passphrase, String publicAddress, BigDecimal amount) 149 | throws OperationFailedException, PassphraseException { 150 | Transaction transaction; 151 | Address toAddress; 152 | BigInt amountBigInt; 153 | 154 | // Verify public address is valid. 155 | if (publicAddress == null || publicAddress.isEmpty()) { 156 | throw new OperationFailedException("Addressee not valid - public address can't be null or empty"); 157 | } 158 | // Create the public Address. 159 | try { 160 | toAddress = Geth.newAddressFromHex(publicAddress); 161 | } catch (Exception e) { 162 | throw new OperationFailedException(e); 163 | } 164 | 165 | // Make sure the amount is positive 166 | if (amount.signum() != -1) { 167 | amountBigInt = KinConverter.fromKin(amount); 168 | } else { 169 | throw new OperationFailedException("Amount can't be negative"); 170 | } 171 | 172 | try { 173 | nonce = ethereumClient.getPendingNonceAt(gethContext, from.getAddress()); 174 | gasPrice = ethereumClient.suggestGasPrice(gethContext); 175 | } catch (Exception e) { 176 | throw new OperationFailedException(e); 177 | } 178 | 179 | // Create TransactionOps and send to Kin smart-contract with the required params. 180 | TransactOpts transactOpts = new TransactOpts(); 181 | transactOpts.setContext(gethContext); 182 | transactOpts.setGasLimit(KinConsts.getTransferKinGasLimit(serviceProvider)); 183 | transactOpts.setGasPrice(gasPrice); 184 | transactOpts.setNonce(nonce); 185 | transactOpts.setFrom(from.getAddress()); 186 | transactOpts.setSigner(new KinSigner(from, getKeyStore(), passphrase, serviceProvider.getNetworkId())); 187 | 188 | Interface paramToAddress = Geth.newInterface(); 189 | paramToAddress.setAddress(toAddress); 190 | 191 | Interface paramAmount = Geth.newInterface(); 192 | paramAmount.setBigInt(amountBigInt); 193 | Interfaces params = Geth.newInterfaces(2); 194 | try { 195 | params.set(0, paramToAddress); 196 | params.set(1, paramAmount); 197 | // Send transfer call to Kin smart-contract. 198 | transaction = boundContract.transact(transactOpts, "transfer", params); 199 | } catch (PassphraseException e) { 200 | throw e; 201 | } catch (Exception e) { 202 | // All other exception from go-ethereum 203 | throw new OperationFailedException(e); 204 | } 205 | 206 | return new TransactionIdImpl(transaction.getHash().getHex()); 207 | } 208 | 209 | /** 210 | * Get balance for the specified account. 211 | * 212 | * @param account the {@link Account} to check balance 213 | * @return the account {@link Balance} 214 | * @throws OperationFailedException if could not retrieve balance 215 | */ 216 | Balance getBalance(Account account) throws OperationFailedException { 217 | Interface balanceResult; 218 | try { 219 | Interface paramAddress = Geth.newInterface(); 220 | paramAddress.setAddress(account.getAddress()); 221 | 222 | Interfaces params = Geth.newInterfaces(1); 223 | params.set(0, paramAddress); 224 | 225 | balanceResult = Geth.newInterface(); 226 | balanceResult.setDefaultBigInt(); 227 | 228 | Interfaces results = Geth.newInterfaces(1); 229 | results.set(0, balanceResult); 230 | 231 | CallOpts opts = Geth.newCallOpts(); 232 | opts.setContext(gethContext); 233 | 234 | // Send balanceOf call to Kin smart-contract. 235 | boundContract.call(opts, results, "balanceOf", params); 236 | } catch (Exception e) { 237 | throw new OperationFailedException(e); 238 | } 239 | 240 | // Check for result, could be null if there was a problem with go-ethereum. 241 | if (balanceResult.getBigInt() != null) { 242 | BigDecimal valueInKin = KinConverter.toKin(balanceResult.getBigInt()); 243 | return new BalanceImpl(valueInKin); 244 | } else { 245 | throw new OperationFailedException("Could not retrieve balance"); 246 | } 247 | } 248 | 249 | Balance getPendingBalance(Account account) throws OperationFailedException { 250 | Balance balance = getBalance(account); 251 | return pendingBalance.calculate(account, balance); 252 | } 253 | 254 | ServiceProvider getServiceProvider() { 255 | return serviceProvider; 256 | } 257 | 258 | Account importAccount(String privateEcdsaKey, String passphrase) throws OperationFailedException { 259 | if (privateEcdsaKey == null || privateEcdsaKey.isEmpty()) { 260 | throw new OperationFailedException("private key not valid - can't be null or empty"); 261 | } else { 262 | if (privateEcdsaKey.startsWith("0x")) { 263 | privateEcdsaKey = privateEcdsaKey.substring(2, privateEcdsaKey.length()); 264 | } 265 | } 266 | try { 267 | byte[] hexBytes = HexUtils.hexStringToByteArray(privateEcdsaKey); 268 | return keyStore.importECDSAKey(hexBytes, passphrase); 269 | } catch (Exception e) { 270 | throw new OperationFailedException(e); 271 | } 272 | } 273 | 274 | private boolean hasEnoughBalance(Account account, BigDecimal amount) throws OperationFailedException { 275 | Balance balance = getBalance(account); 276 | // (> -1) means bigger than or equals to the amount. 277 | return balance.value().subtract(amount).compareTo(BigDecimal.ZERO) > -1; 278 | } 279 | 280 | private void deleteRecursive(File fileOrDirectory) { 281 | if (fileOrDirectory.isDirectory()) { 282 | for (File child : fileOrDirectory.listFiles()) { 283 | deleteRecursive(child); 284 | } 285 | } 286 | fileOrDirectory.delete(); 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /kin-sdk-core/src/main/java/kin/sdk/core/KinAccount.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core; 2 | 3 | import java.math.BigDecimal; 4 | import kin.sdk.core.exception.OperationFailedException; 5 | import kin.sdk.core.exception.PassphraseException; 6 | 7 | public interface KinAccount { 8 | 9 | /** 10 | * @return String the public address of the account 11 | */ 12 | String getPublicAddress(); 13 | 14 | /** 15 | * Exports the keystore json file 16 | * 17 | * @param passphrase the passphrase used to create the account 18 | * @param newPassphrase the exported json will be encrypted using this new passphrase. The original keystore and 19 | * passphrase will not change. 20 | * @return String the json string 21 | */ 22 | String exportKeyStore(String passphrase, String newPassphrase) throws PassphraseException, OperationFailedException; 23 | 24 | /** 25 | * Create {@link Request} for signing and sending a transaction of the given amount in kin to the specified public 26 | * address Ethereum gas will be handled internally. 27 | * 28 | * @param publicAddress the account address to send the specified kin amount 29 | * @param amount the amount of kin to transfer 30 | * @return {@code Request}, TransactionId - the transaction identifier 31 | */ 32 | Request sendTransaction(String publicAddress, String passphrase, BigDecimal amount); 33 | 34 | /** 35 | * Create, sign and send a transaction of the given amount in kin to the specified public address 36 | * Ethereum gas will be handled internally. 37 | * The method will accesses a blockchain 38 | * node on the network and should not be called on the android main thread. 39 | * 40 | * @param publicAddress the account address to send the specified kin amount 41 | * @param amount the amount of kin to transfer 42 | * @return TransactionId the transaction identifier 43 | */ 44 | TransactionId sendTransactionSync(String publicAddress, String passphrase, BigDecimal amount) 45 | throws OperationFailedException, PassphraseException; 46 | 47 | /** 48 | * Create {@link Request} for getting the current confirmed balance in kin 49 | * 50 | * @return {@code Request} Balance - the balance in kin 51 | */ 52 | Request getBalance(); 53 | 54 | /** 55 | * Get the current confirmed balance in kin 56 | * The method will accesses a blockchain 57 | * node on the network and should not be called on the android main thread. 58 | * 59 | * @return Balance the balance in kin 60 | */ 61 | Balance getBalanceSync() throws OperationFailedException; 62 | 63 | /** 64 | * Create {@link Request} for getting the pending balance in kin 65 | * 66 | * @return {@code Request} Balance - the pending balance in kin 67 | */ 68 | Request getPendingBalance(); 69 | 70 | /** 71 | * Get the pending balance in kin 72 | * The method will accesses a blockchain 73 | * node on the network and should not be called on the android main thread. 74 | * 75 | * @return Balance the balance amount in kin 76 | */ 77 | Balance getPendingBalanceSync() throws OperationFailedException; 78 | } 79 | -------------------------------------------------------------------------------- /kin-sdk-core/src/main/java/kin/sdk/core/KinAccountImpl.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core; 2 | 3 | import java.math.BigDecimal; 4 | import kin.sdk.core.exception.AccountDeletedException; 5 | import kin.sdk.core.exception.DeleteAccountException; 6 | import kin.sdk.core.exception.OperationFailedException; 7 | import kin.sdk.core.exception.PassphraseException; 8 | import org.ethereum.geth.Account; 9 | import org.ethereum.geth.KeyStore; 10 | 11 | final class KinAccountImpl extends AbstractKinAccount { 12 | 13 | private KeyStore keyStore; 14 | private EthClientWrapper ethClient; 15 | private Account account; 16 | private boolean isDeleted; 17 | 18 | /** 19 | * Creates a new {@link Account}. 20 | * 21 | * @param ethClientWrapper that will be use to call to Kin smart-contract. 22 | * @param passphrase that will be used to store the account private key securely. 23 | * @throws Exception if go-ethereum was unable to generate the account (unable to generate new key or store the 24 | * key). 25 | */ 26 | KinAccountImpl(EthClientWrapper ethClientWrapper, String passphrase) throws Exception { 27 | this.keyStore = ethClientWrapper.getKeyStore(); 28 | this.account = keyStore.newAccount(passphrase); 29 | this.ethClient = ethClientWrapper; 30 | isDeleted = false; 31 | } 32 | 33 | /** 34 | * Creates a {@link KinAccount} from existing {@link Account} 35 | * 36 | * @param ethClientWrapper that will be use to call to Kin smart-contract. 37 | * @param account the existing Account. 38 | */ 39 | KinAccountImpl(EthClientWrapper ethClientWrapper, Account account) { 40 | this.keyStore = ethClientWrapper.getKeyStore(); 41 | this.account = account; 42 | this.ethClient = ethClientWrapper; 43 | isDeleted = false; 44 | } 45 | 46 | @Override 47 | public String getPublicAddress() { 48 | if (!isDeleted) { 49 | return account.getAddress().getHex(); 50 | } 51 | return ""; 52 | } 53 | 54 | @Override 55 | public String exportKeyStore(String passphrase, String newPassphrase) 56 | throws PassphraseException, OperationFailedException { 57 | checkValidAccount(); 58 | String jsonKeyStore; 59 | try { 60 | byte[] keyInBytes = keyStore.exportKey(account, passphrase, newPassphrase); 61 | jsonKeyStore = new String(keyInBytes, "UTF-8"); 62 | } catch (Exception e) { 63 | throw new PassphraseException(); 64 | } 65 | return jsonKeyStore; 66 | } 67 | 68 | @Override 69 | public TransactionId sendTransactionSync(String publicAddress, String passphrase, BigDecimal amount) 70 | throws OperationFailedException, PassphraseException { 71 | checkValidAccount(); 72 | return ethClient.sendTransaction(account, passphrase, publicAddress, amount); 73 | } 74 | 75 | @Override 76 | public Balance getBalanceSync() throws OperationFailedException { 77 | checkValidAccount(); 78 | return ethClient.getBalance(account); 79 | } 80 | 81 | @Override 82 | public Balance getPendingBalanceSync() throws OperationFailedException { 83 | checkValidAccount(); 84 | return ethClient.getPendingBalance(account); 85 | } 86 | 87 | void delete(String passphrase) throws DeleteAccountException { 88 | ethClient.deleteAccount(account, passphrase); 89 | markAsDeleted(); 90 | } 91 | 92 | void markAsDeleted() { 93 | isDeleted = true; 94 | } 95 | 96 | private void checkValidAccount() throws AccountDeletedException { 97 | if (isDeleted) { 98 | throw new AccountDeletedException(); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /kin-sdk-core/src/main/java/kin/sdk/core/KinClient.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core; 2 | 3 | import android.content.Context; 4 | 5 | import org.ethereum.geth.Account; 6 | import org.ethereum.geth.Accounts; 7 | 8 | import kin.sdk.core.exception.OperationFailedException; 9 | import kin.sdk.core.exception.DeleteAccountException; 10 | import kin.sdk.core.exception.CreateAccountException; 11 | import kin.sdk.core.exception.EthereumClientException; 12 | 13 | public class KinClient { 14 | 15 | private KinAccount kinAccount; 16 | private EthClientWrapper ethClient; 17 | 18 | /** 19 | * KinClient is an account manager for a single {@link KinAccount} on 20 | * ethereum network. 21 | * 22 | * @param context the android application context 23 | * @param provider the service provider to use to connect to an ethereum node 24 | * @throws EthereumClientException if could not connect to service provider or connection problem with Kin 25 | * smart-contract problems. 26 | */ 27 | public KinClient(Context context, ServiceProvider provider) throws EthereumClientException { 28 | this.ethClient = new EthClientWrapper(context, provider); 29 | } 30 | 31 | /** 32 | * Create the account if it hasn't yet been created. 33 | * Multiple calls to this method will not create an additional account. 34 | * Once created, the account information will be stored securely on the device and can 35 | * be accessed again via the {@link #getAccount()} method. 36 | * 37 | * @param passphrase a passphrase provided by the user that will be used to store the account private key securely. 38 | * @return KinAccount the account created 39 | * @throws CreateAccountException if go-ethereum was unable to generate the account (unable to generate new key or 40 | * store the key). 41 | */ 42 | public KinAccount createAccount(String passphrase) throws CreateAccountException { 43 | if (!hasAccount()) { 44 | try { 45 | kinAccount = new KinAccountImpl(ethClient, passphrase); 46 | } catch (Exception e) { 47 | throw new CreateAccountException(e); 48 | } 49 | } 50 | return getAccount(); 51 | } 52 | 53 | /** 54 | * The method will return an account that has previously been create and stored on the device 55 | * via the {@link #createAccount(String)} method. 56 | * 57 | * @return the account if it has been created or null if there is no such account 58 | */ 59 | public KinAccount getAccount() { 60 | if (kinAccount != null) { 61 | return kinAccount; 62 | } else { 63 | Accounts accounts = ethClient.getKeyStore().getAccounts(); 64 | Account account; 65 | try { 66 | account = accounts.get(0); 67 | } catch (Exception e) { 68 | //There is no account 69 | return null; 70 | } 71 | // The Account is not null 72 | kinAccount = new KinAccountImpl(ethClient, account); 73 | } 74 | return kinAccount; 75 | } 76 | 77 | /** 78 | * @return true if there is an existing account 79 | */ 80 | public boolean hasAccount() { 81 | if (kinAccount != null) { 82 | return true; 83 | } else { 84 | Accounts accounts = ethClient.getKeyStore().getAccounts(); 85 | return accounts != null && accounts.size() > 0; 86 | } 87 | } 88 | 89 | /** 90 | * Deletes the account (if it exists) 91 | * WARNING - if you don't export the account before deleting it, you will lose all your Kin. 92 | * 93 | * @param passphrase the passphrase used when the account was created 94 | */ 95 | public void deleteAccount(String passphrase) throws DeleteAccountException { 96 | KinAccountImpl account = (KinAccountImpl) getAccount(); 97 | if (account != null) { 98 | account.delete(passphrase); 99 | kinAccount = null; 100 | } 101 | } 102 | 103 | /** 104 | * Delete all accounts. This will wipe out recursively the directory that holds all keystore files. 105 | * WARNING - if you don't export your account before deleting it, you will lose all your Kin. 106 | */ 107 | public void wipeoutAccount() throws EthereumClientException { 108 | ethClient.wipeoutAccount(); 109 | KinAccount account = getAccount(); 110 | if (account != null && account instanceof KinAccountImpl) { 111 | ((KinAccountImpl) account).markAsDeleted(); 112 | } 113 | kinAccount = null; 114 | } 115 | 116 | public ServiceProvider getServiceProvider() { 117 | return ethClient.getServiceProvider(); 118 | } 119 | 120 | KinAccount importAccount(String privateEcdsaKey, String passphrase) throws OperationFailedException { 121 | Account account = ethClient.importAccount(privateEcdsaKey, passphrase); 122 | KinAccount kinAccount = null; 123 | if (account != null) { 124 | kinAccount = new KinAccountImpl(ethClient, account); 125 | } 126 | return kinAccount; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /kin-sdk-core/src/main/java/kin/sdk/core/KinConsts.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core; 2 | 3 | 4 | final class KinConsts { 5 | 6 | private KinConsts() { 7 | } 8 | 9 | static final String ABI = "[{\"constant\":true,\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_newOwnerCandidate\",\"type\":\"address\"}],\"name\":\"requestOwnershipTransfer\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_spender\",\"type\":\"address\"},{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_from\",\"type\":\"address\"},{\"name\":\"_to\",\"type\":\"address\"},{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"isMinting\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"name\":\"\",\"type\":\"uint8\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_to\",\"type\":\"address\"},{\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"mint\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_owner\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"name\":\"balance\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_to\",\"type\":\"address\"},{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"newOwnerCandidate\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_tokenAddress\",\"type\":\"address\"},{\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"transferAnyERC20Token\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_owner\",\"type\":\"address\"},{\"name\":\"_spender\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"name\":\"remaining\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"endMinting\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"anonymous\":false,\"inputs\":[],\"name\":\"MintingEnded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"spender\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"_by\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"_to\",\"type\":\"address\"}],\"name\":\"OwnershipRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"_from\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"_to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"}]"; 10 | 11 | /** 12 | *

SHA3 (Keccak-256) hash of ERC-20's Transfer event:

13 | * {@code Transfer(address,address,uint256)} 14 | */ 15 | static final String TOPIC_EVENT_NAME_SHA3_TRANSFER = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"; 16 | 17 | static long getTransferKinGasLimit(ServiceProvider provider) { 18 | return NetworkConstants.fromProvider(provider).transferKinGasLimit; 19 | } 20 | 21 | static String getContractAddress(ServiceProvider provider) { 22 | return NetworkConstants.fromProvider(provider).contractAddress; 23 | } 24 | 25 | /* #enumsmatter */ 26 | enum NetworkConstants { 27 | NETWORK_MAIN(ServiceProvider.NETWORK_ID_MAIN, "0x818fc6c2ec5986bc6e2cbf00939d90556ab12ce5"), 28 | NETWORK_ROPSTEN(ServiceProvider.NETWORK_ID_ROPSTEN, "0xEF2Fcc998847DB203DEa15fC49d0872C7614910C"), 29 | NETWORK_RINKEBY(ServiceProvider.NETWORK_ID_RINKEBY, "0xEF2Fcc998847DB203DEa15fC49d0872C7614910C"), 30 | NETWORK_TRUFFLE(ServiceProvider.NETWORK_ID_TRUFFLE); 31 | 32 | int networkId; 33 | String contractAddress; 34 | long transferKinGasLimit; 35 | 36 | // gas limit for 'kin transfer' 37 | private static final long DEFAULT_TRANSFER_KIN_GAS_LIMIT = 60000; 38 | 39 | NetworkConstants(int id, String address){ 40 | networkId = id; 41 | contractAddress = address; 42 | transferKinGasLimit = DEFAULT_TRANSFER_KIN_GAS_LIMIT; 43 | } 44 | 45 | // This is used only for testing 46 | NetworkConstants(int id){ 47 | this(id, System.getProperty("TOKEN_CONTRACT_ADDRESS")); 48 | } 49 | 50 | static NetworkConstants fromProvider(ServiceProvider provider){ 51 | switch (provider.getNetworkId()) { 52 | case (ServiceProvider.NETWORK_ID_MAIN) : 53 | return NETWORK_MAIN; 54 | case (ServiceProvider.NETWORK_ID_RINKEBY) : 55 | return NETWORK_RINKEBY; 56 | case (ServiceProvider.NETWORK_ID_TRUFFLE) : 57 | return NETWORK_TRUFFLE; 58 | default: return NETWORK_ROPSTEN; 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /kin-sdk-core/src/main/java/kin/sdk/core/KinSigner.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core; 2 | 3 | import org.ethereum.geth.Account; 4 | import org.ethereum.geth.Address; 5 | import org.ethereum.geth.BigInt; 6 | import org.ethereum.geth.Geth; 7 | import org.ethereum.geth.KeyStore; 8 | import org.ethereum.geth.Signer; 9 | import org.ethereum.geth.Transaction; 10 | 11 | import kin.sdk.core.exception.PassphraseException; 12 | 13 | /** 14 | * Responsible for signing transactions with passphrase. 15 | */ 16 | class KinSigner implements Signer { 17 | 18 | private Account from; 19 | private KeyStore keyStore; 20 | private String passphrase; 21 | private BigInt networkId; 22 | 23 | KinSigner(Account from, KeyStore keyStore, String passphrase, int networkId) { 24 | this.from = from; 25 | this.keyStore = keyStore; 26 | this.passphrase = passphrase; 27 | this.networkId = Geth.newBigInt(networkId); 28 | } 29 | 30 | @Override 31 | public Transaction sign(Address address, Transaction transaction) throws Exception { 32 | try { 33 | transaction = keyStore.signTxPassphrase(from, passphrase, transaction, networkId); 34 | } catch (Exception e) { 35 | throw new PassphraseException(); 36 | } 37 | return transaction; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /kin-sdk-core/src/main/java/kin/sdk/core/PendingBalance.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core; 2 | 3 | 4 | import android.support.annotation.NonNull; 5 | import android.support.annotation.Nullable; 6 | import java.math.BigDecimal; 7 | import kin.sdk.core.exception.OperationFailedException; 8 | import kin.sdk.core.util.KinConverter; 9 | import org.ethereum.geth.Account; 10 | import org.ethereum.geth.Address; 11 | import org.ethereum.geth.Addresses; 12 | import org.ethereum.geth.BigInt; 13 | import org.ethereum.geth.Context; 14 | import org.ethereum.geth.EthereumClient; 15 | import org.ethereum.geth.FilterQuery; 16 | import org.ethereum.geth.Geth; 17 | import org.ethereum.geth.Hash; 18 | import org.ethereum.geth.Hashes; 19 | import org.ethereum.geth.Log; 20 | import org.ethereum.geth.Logs; 21 | import org.ethereum.geth.Topics; 22 | 23 | /** 24 | * Calculate pending balance based on current balance, using ethereum contracts events/logs mechanism to iterate all 25 | * relevant transactions both outgoing and incoming, and summing all transactions amounts. 26 | */ 27 | final class PendingBalance { 28 | 29 | private final EthereumClient ethereumClient; 30 | private final Context gethContext; 31 | private final String kinContractAddress; 32 | 33 | PendingBalance(EthereumClient ethereumClient, Context gethContext, String kinContractAddress) { 34 | this.ethereumClient = ethereumClient; 35 | this.gethContext = gethContext; 36 | this.kinContractAddress = kinContractAddress; 37 | } 38 | 39 | Balance calculate(Account account, Balance balance) throws OperationFailedException { 40 | try { 41 | String accountAddressHex = account.getAddress().getHex(); 42 | 43 | BigDecimal pendingSpentAmount = getPendingSpentAmount(accountAddressHex); 44 | BigDecimal pendingEarnAmount = getPendingEarnAmount(accountAddressHex); 45 | 46 | BigDecimal totalPendingAmountInKin = KinConverter.toKin(pendingEarnAmount.subtract(pendingSpentAmount)); 47 | return new BalanceImpl(balance.value().add(totalPendingAmountInKin)); 48 | } catch (Exception e) { 49 | throw new OperationFailedException(e); 50 | } 51 | } 52 | 53 | private BigDecimal getPendingSpentAmount(String accountAddressHex) throws Exception { 54 | Logs pendingSpentLogs = getPendingTransactionsLogs(accountAddressHex, null); 55 | return sumTransactionsAmount(pendingSpentLogs); 56 | } 57 | 58 | private BigDecimal getPendingEarnAmount(String accountAddressHex) throws Exception { 59 | Logs pendingEarnLogs = getPendingTransactionsLogs(null, accountAddressHex); 60 | return sumTransactionsAmount(pendingEarnLogs); 61 | } 62 | 63 | private Logs getPendingTransactionsLogs(@Nullable String fromHexAddress, @Nullable String toHexAddress) 64 | throws OperationFailedException { 65 | try { 66 | Address contractAddress = Geth.newAddressFromHex(kinContractAddress); 67 | Addresses addresses = Geth.newAddressesEmpty(); 68 | addresses.append(contractAddress); 69 | 70 | Topics topics = createFilterLogTopicsArray(fromHexAddress, toHexAddress); 71 | 72 | FilterQuery filterQuery = new FilterQuery(); 73 | filterQuery.setAddresses(addresses); 74 | filterQuery.setFromBlock(Geth.newBigInt(Geth.LatestBlockNumber)); 75 | filterQuery.setToBlock(Geth.newBigInt(Geth.PendingBlockNumber)); 76 | filterQuery.setTopics(topics); 77 | 78 | return ethereumClient.filterLogs(gethContext, filterQuery); 79 | } catch (Exception e) { 80 | throw new OperationFailedException(e); 81 | } 82 | } 83 | 84 | /** 85 | * @param fromHexAddress filter transaction by 'from' hex address, use null for any 'from' address (no filter) 86 | * @param toHexAddress filter transaction by 'to' hex address, use null for any 'to' address (no filter) 87 | */ 88 | @NonNull 89 | private Topics createFilterLogTopicsArray(@Nullable String fromHexAddress, @Nullable String toHexAddress) 90 | throws Exception { 91 | //Topics array is 32 bytes array, the first position is topic event signature hash, 92 | // the rest (up to 3 params) are indexed (= filterable) parameters for the desired event, in our case, transfer indexed params are 'from address' and 'to address' 93 | // in this order, param can be 32 bytes hex value representing address, or null, to allow any address (no filter). 94 | // so if we want to filter by To address only, the first parameter will be null, and the second param will be 32 byte To address. 95 | // see https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#events 96 | Topics topics = Geth.newTopicsEmpty(); 97 | Hashes hashes = Geth.newHashesEmpty(); 98 | hashes.append(Geth.newHashFromHex(KinConsts.TOPIC_EVENT_NAME_SHA3_TRANSFER)); 99 | topics.append(hashes); 100 | hashes = Geth.newHashesEmpty(); 101 | if (fromHexAddress != null) { 102 | hashes.append(hexAddressToTopicHash(fromHexAddress)); 103 | } 104 | topics.append(hashes); 105 | hashes = Geth.newHashesEmpty(); 106 | if (toHexAddress != null) { 107 | hashes.append(hexAddressToTopicHash(toHexAddress)); 108 | } 109 | topics.append(hashes); 110 | return topics; 111 | } 112 | 113 | private Hash hexAddressToTopicHash(String hexAddress) throws Exception { 114 | //add leading zeros to match 32 bytes 115 | // hex address are 40 chars (20 bytes), topic data is 64 chars (32 bytes) 116 | return Geth.newHashFromHex("0x000000000000000000000000" + hexAddress.substring(2)); 117 | } 118 | 119 | private BigDecimal sumTransactionsAmount(Logs logs) throws Exception { 120 | BigDecimal totalAmount = BigDecimal.ZERO; 121 | for (int i = 0; i < logs.size(); i++) { 122 | Log log = logs.get(i); 123 | String txHash = log.getTxHash().getHex(); 124 | if (txHash != null) { 125 | //getData returns raw data of non-indexed params of event 126 | //in our case it's the amount param of 'Transfer' event, the format is unsigned int of 32bytes, 127 | //so it can be converted safely to bigInt 128 | BigInt txAmount = Geth.newBigInt(0L); 129 | txAmount.setBytes(log.getData()); 130 | totalAmount = totalAmount.add(new BigDecimal(txAmount.string())); 131 | } 132 | } 133 | return totalAmount; 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /kin-sdk-core/src/main/java/kin/sdk/core/Request.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core; 2 | 3 | 4 | import android.os.Handler; 5 | import android.os.Looper; 6 | import java.util.concurrent.Callable; 7 | import java.util.concurrent.ExecutorService; 8 | import java.util.concurrent.Executors; 9 | import java.util.concurrent.Future; 10 | 11 | /** 12 | * Represents {@link KinAccount} method invocation, each request will run sequentially on background thread, 13 | * and will notify {@link ResultCallback} witch success or error on main thread. 14 | * 15 | * @param request result type 16 | */ 17 | public class Request { 18 | 19 | private static final ExecutorService executorService = Executors.newSingleThreadExecutor(); 20 | private final Handler mainHandler; 21 | private final Callable callable; 22 | private boolean cancelled; 23 | private boolean executed; 24 | private Future future; 25 | private ResultCallback resultCallback; 26 | 27 | Request(Callable callable) { 28 | checkNotNull(callable, "callable"); 29 | this.callable = callable; 30 | this.mainHandler = new Handler(Looper.getMainLooper()); 31 | } 32 | 33 | /** 34 | * Run request asynchronously, notify {@code callback} with successful result or error 35 | */ 36 | synchronized public void run(ResultCallback callback) { 37 | checkBeforeRun(callback); 38 | executed = true; 39 | submitFuture(callable, callback); 40 | } 41 | 42 | private void checkBeforeRun(ResultCallback callback) { 43 | checkNotNull(callback, "callback"); 44 | if (executed) { 45 | throw new IllegalStateException("Request already running."); 46 | } 47 | if (cancelled) { 48 | throw new IllegalStateException("Request already cancelled."); 49 | } 50 | } 51 | 52 | private void checkNotNull(Object param, String name) { 53 | if (param == null) { 54 | throw new IllegalArgumentException(name + " cannot be null."); 55 | } 56 | } 57 | 58 | private void submitFuture(final Callable callable, ResultCallback callback) { 59 | this.resultCallback = callback; 60 | future = executorService.submit(() -> { 61 | try { 62 | final T result = callable.call(); 63 | executeOnMainThreadIfNotCancelled(() -> resultCallback.onResult(result)); 64 | } catch (final Exception e) { 65 | executeOnMainThreadIfNotCancelled(() -> resultCallback.onError(e)); 66 | } 67 | }); 68 | } 69 | 70 | private synchronized void executeOnMainThreadIfNotCancelled(Runnable runnable) { 71 | if (!cancelled) { 72 | mainHandler.post(runnable); 73 | } 74 | } 75 | 76 | /** 77 | * Cancel {@code Request} and detach its callback, 78 | * an attempt will be made to cancel ongoing request, if request has not run yet it will never run. 79 | * 80 | * @param mayInterruptIfRunning true if the request should be interrupted; otherwise, 81 | * in-progress requests are allowed to complete 82 | */ 83 | synchronized public void cancel(boolean mayInterruptIfRunning) { 84 | if (!cancelled) { 85 | cancelled = true; 86 | if (future != null) { 87 | future.cancel(mayInterruptIfRunning); 88 | } 89 | future = null; 90 | mainHandler.removeCallbacksAndMessages(null); 91 | mainHandler.post(() -> resultCallback = null); 92 | } 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /kin-sdk-core/src/main/java/kin/sdk/core/ResultCallback.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core; 2 | 3 | public interface ResultCallback { 4 | 5 | /** 6 | * Method will be called when operation has completed successfully 7 | * 8 | * @param result the result received 9 | */ 10 | void onResult(T result); 11 | 12 | /** 13 | * Method will be called when operation has completed with error 14 | * 15 | * @param e the exception in case of error 16 | */ 17 | void onError(Exception e); 18 | } -------------------------------------------------------------------------------- /kin-sdk-core/src/main/java/kin/sdk/core/ServiceProvider.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core; 2 | 3 | public class ServiceProvider { 4 | 5 | /** 6 | * main ethereum network 7 | */ 8 | public static final int NETWORK_ID_MAIN = 1; 9 | 10 | /** 11 | * ropsten ethereum TEST network 12 | */ 13 | public static final int NETWORK_ID_ROPSTEN = 3; 14 | 15 | /** 16 | * rinkeby ethereum TEST network 17 | */ 18 | public static final int NETWORK_ID_RINKEBY = 4; 19 | 20 | /** 21 | * truffle testrpc network 22 | */ 23 | public static final int NETWORK_ID_TRUFFLE = 9; 24 | 25 | 26 | private String providerUrl; 27 | private int networkId; 28 | 29 | /** 30 | * A ServiceProvider used to connect to an ethereum node. 31 | *

32 | * For example to connect to an infura test node use 33 | * new ServiceProvider("https://ropsten.infura.io/YOURTOKEN", NETWORK_ID_ROPSTEN); 34 | * 35 | * @param providerUrl the provider to use 36 | * @param networkId for example see {@value #NETWORK_ID_MAIN} {@value NETWORK_ID_ROPSTEN} {@value 37 | * NETWORK_ID_RINKEBY} 38 | */ 39 | public ServiceProvider(String providerUrl, int networkId) { 40 | this.providerUrl = providerUrl; 41 | this.networkId = networkId; 42 | } 43 | 44 | public String getProviderUrl() { 45 | return providerUrl; 46 | } 47 | 48 | public int getNetworkId() { 49 | return networkId; 50 | } 51 | 52 | public boolean isMainNet(){ 53 | return networkId == NETWORK_ID_MAIN; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /kin-sdk-core/src/main/java/kin/sdk/core/TransactionId.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core; 2 | 3 | /** 4 | * Identifier of the transaction, can be use on etherscan.io 5 | * to find information about the transaction related to this TransactionId. 6 | */ 7 | public interface TransactionId { 8 | 9 | /** 10 | * @return the transaction id 11 | */ 12 | String id(); 13 | } -------------------------------------------------------------------------------- /kin-sdk-core/src/main/java/kin/sdk/core/TransactionIdImpl.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core; 2 | 3 | final class TransactionIdImpl implements TransactionId { 4 | 5 | private String id; 6 | 7 | TransactionIdImpl(String id) { 8 | this.id = id; 9 | } 10 | 11 | @Override 12 | public String id() { 13 | return id; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /kin-sdk-core/src/main/java/kin/sdk/core/exception/AccountDeletedException.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core.exception;/**/ 2 | 3 | public class AccountDeletedException extends OperationFailedException { 4 | 5 | public AccountDeletedException() { 6 | super("Account deleted, Create new account"); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /kin-sdk-core/src/main/java/kin/sdk/core/exception/CreateAccountException.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core.exception; 2 | 3 | 4 | public class CreateAccountException extends Exception { 5 | 6 | public CreateAccountException(Throwable cause) { 7 | super(cause); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /kin-sdk-core/src/main/java/kin/sdk/core/exception/DeleteAccountException.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core.exception; 2 | 3 | 4 | public class DeleteAccountException extends Exception { 5 | 6 | public DeleteAccountException(Throwable cause) { 7 | super(cause); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /kin-sdk-core/src/main/java/kin/sdk/core/exception/EthereumClientException.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core.exception; 2 | 3 | public class EthereumClientException extends Exception { 4 | 5 | public EthereumClientException(String message) { 6 | super(message); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /kin-sdk-core/src/main/java/kin/sdk/core/exception/OperationFailedException.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core.exception; 2 | 3 | public class OperationFailedException extends Exception { 4 | 5 | public OperationFailedException(Throwable cause) { 6 | super(cause); 7 | } 8 | 9 | public OperationFailedException(String message) { 10 | super(message); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /kin-sdk-core/src/main/java/kin/sdk/core/exception/PassphraseException.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core.exception; 2 | 3 | public class PassphraseException extends Exception { 4 | 5 | public PassphraseException() { 6 | super("Wrong passphrase - could not decrypt key with given passphrase"); 7 | } 8 | } 9 | 10 | -------------------------------------------------------------------------------- /kin-sdk-core/src/main/java/kin/sdk/core/util/HexUtils.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core.util; 2 | 3 | public class HexUtils { 4 | 5 | public static byte[] hexStringToByteArray(String s) { 6 | int len = s.length(); 7 | byte[] data = new byte[len / 2]; 8 | for (int i = 0; i < len; i += 2) { 9 | data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) 10 | + Character.digit(s.charAt(i + 1), 16)); 11 | } 12 | return data; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /kin-sdk-core/src/main/java/kin/sdk/core/util/KinConverter.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core.util; 2 | 3 | import java.math.BigDecimal; 4 | import org.ethereum.geth.BigInt; 5 | import org.ethereum.geth.Geth; 6 | 7 | /** 8 | * A Utility class used to convert currency values to/from Kin. 9 | */ 10 | public class KinConverter { 11 | 12 | private static final BigDecimal KIN = BigDecimal.TEN.pow(18); 13 | 14 | private static BigInt toBigInt(BigDecimal bigDecimal) { 15 | BigInt bigInt = Geth.newBigInt(0L); 16 | //to get ByteArray representation, convert to Java BigInteger (will discard the fractional part) 17 | //then extract ByteArray from the BigInteger, BigInteger representation is in two's complement, 18 | // but as we're not dealing with negative numbers, it's safe to init the Unsigned BigInt with it 19 | bigInt.setBytes(bigDecimal.toBigInteger().toByteArray()); 20 | return bigInt; 21 | } 22 | 23 | public static BigInt fromKin(BigDecimal value) { 24 | BigDecimal bigDecimal = value.multiply(KIN); 25 | return toBigInt(bigDecimal); 26 | } 27 | 28 | public static BigDecimal toKin(BigInt value) { 29 | return toKin(new BigDecimal(value.string())); 30 | } 31 | 32 | public static BigDecimal toKin(BigDecimal value) { 33 | return value.divide(KIN, 18, BigDecimal.ROUND_FLOOR); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /kin-sdk-core/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | kin-sdk-core-sample 3 | 4 | -------------------------------------------------------------------------------- /kin-sdk-core/truffle/contracts/BasicToken.sol: -------------------------------------------------------------------------------- 1 | ../kin-token/contracts/BasicToken.sol -------------------------------------------------------------------------------- /kin-sdk-core/truffle/contracts/BasicTokenMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.15; 2 | 3 | import './BasicToken.sol'; 4 | 5 | contract BasicTokenMock is BasicToken { 6 | function assign(address _account, uint _balance) { 7 | balances[_account] = _balance; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /kin-sdk-core/truffle/contracts/ERC20.sol: -------------------------------------------------------------------------------- 1 | ../kin-token/contracts/ERC20.sol -------------------------------------------------------------------------------- /kin-sdk-core/truffle/contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.15; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | modifier restricted() { 8 | if (msg.sender == owner) _; 9 | } 10 | 11 | function Migrations() public { 12 | owner = msg.sender; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /kin-sdk-core/truffle/contracts/SafeMath.sol: -------------------------------------------------------------------------------- 1 | ../kin-token/contracts/SafeMath.sol -------------------------------------------------------------------------------- /kin-sdk-core/truffle/migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | let Migrations = artifacts.require('../contracts/Migrations.sol'); 3 | let TestToken = artifacts.require('../contracts/BasicTokenMock.sol'); 4 | 5 | module.exports = (deployer, network, accounts) => { 6 | deployer.deploy(Migrations); 7 | deployer.deploy(TestToken).then(async() => { 8 | instance = await TestToken.deployed() 9 | console.log(`TestToken contract deployed at ${instance.address}`); 10 | 11 | // give tokens to the testing account 12 | let numTokens = 1000; 13 | ok = await instance.assign(accounts[0], web3.toWei(numTokens, "ether")); 14 | assert.ok(ok); 15 | 16 | // check resulting balance 17 | let balanceWei = (await instance.balanceOf(accounts[0])).toNumber(); 18 | assert.equal(web3.fromWei(balanceWei, "ether"), numTokens); 19 | console.log(`Assigned ${numTokens} tokens to account ${accounts[0]} ...`); 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /kin-sdk-core/truffle/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kin-sdk-core-android", 3 | "version": "0.0.1", 4 | "description": "Kin SDK Core Android", 5 | "repository": "git@github.com:kinfoundation/kin-sdk-core-android.git", 6 | "author": "Kik", 7 | "license": "", 8 | "main": "truffle.js", 9 | "scripts": { 10 | "testrpc": "scripts/testrpc.sh" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/kinfoundation/kin-sdk-core-android/issues" 14 | }, 15 | "homepage": "https://github.com/kinfoundation/kin-sdk-core-android", 16 | "dependencies": { 17 | "babel-polyfill": "^6.26.0", 18 | "babel-preset-es2015": "^6.24.1", 19 | "babel-preset-stage-2": "^6.24.1", 20 | "babel-preset-stage-3": "^6.24.1", 21 | "babel-register": "^6.26.0", 22 | "bignumber.js": "^4.0.2", 23 | "kinfoundation-ethereumjs-testrpc": "6.0.4", 24 | "lerna": "^2.0.0", 25 | "lodash": "^4.17.4", 26 | "truffle": "^4.0.1", 27 | "web3": "^1.0.0-beta.18", 28 | "xtend": "^4.0.1", 29 | "yargs": "^8.0.2" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /kin-sdk-core/truffle/scripts/prepare-tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # export account address environment variables 4 | # see this file for available variables 5 | cd kin-sdk-core/truffle 6 | 7 | source ./scripts/testrpc-accounts.sh 8 | 9 | # create variables 10 | configFile="../src/androidTest/assets/testConfig.json" 11 | 12 | # export token contract address environment variable 13 | export TOKEN_CONTRACT_ADDRESS=$(cat ./token-contract-address) 14 | test -n "${TOKEN_CONTRACT_ADDRESS}" 15 | echo "Set Contract Address ${TOKEN_CONTRACT_ADDRESS}" 16 | echo "" 17 | # write contract address to testConfig.json, will be read by androidTest 18 | printf '"token_contract_address":"%s"\n' "${TOKEN_CONTRACT_ADDRESS}" >> ${configFile} 19 | 20 | # write closing bracket to testConfig.json 21 | printf '}' >> ${configFile} 22 | -------------------------------------------------------------------------------- /kin-sdk-core/truffle/scripts/testrpc-accounts.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # create 10 accounts with balance 100 ETH each 4 | balance=100000000000000000000 5 | 6 | export ACCOUNT_0_PRIVATE_KEY=0x11c98b8fa69354b26b5db98148a5bc4ef2ebae8187f651b82409f6cefc9bb0b8 7 | export ACCOUNT_1_PRIVATE_KEY=0xc5db67b3865454f6d129ec83204e845a2822d9fe5338ff46fe4c126859e1357e 8 | export ACCOUNT_2_PRIVATE_KEY=0x6ac1a8c98fa298af3884406fbd9468dca5200898f065e1283fc85dff95646c25 9 | export ACCOUNT_3_PRIVATE_KEY=0xbd9aebf18275f8074c53036818e8583b242f9bdfb7c0e79088007cb39a96e097 10 | export ACCOUNT_4_PRIVATE_KEY=0x8b727508230fda8e0ec96b7c9e51c89ff0e41ba30fad221c2f0fe942158571b1 11 | export ACCOUNT_5_PRIVATE_KEY=0x514111937962a290ba6afa3dd0044e0720148b46cd2dbc8045e811f8157b6b1a 12 | export ACCOUNT_6_PRIVATE_KEY=0x52f21c3eedc184eb13fcd5ec8e45e6741d97bca85a8703d733fab9c19f5e8518 13 | export ACCOUNT_7_PRIVATE_KEY=0xbca3035e18b3f87a38fa34fcc2561a023fe1f9b93354c04c772f37497ef08f3e 14 | export ACCOUNT_8_PRIVATE_KEY=0x2d8676754eb3d184f3e9428c5d52eacdf1d507593ba50c3ef2a59e1a3a46b578 15 | export ACCOUNT_9_PRIVATE_KEY=0xabf8c2dd52f5b14ea437325854048e5daadbca80f99f9d6f8e97ab5e05d4f0ab 16 | 17 | account_array=( \ 18 | $ACCOUNT_0_PRIVATE_KEY \ 19 | $ACCOUNT_1_PRIVATE_KEY \ 20 | $ACCOUNT_2_PRIVATE_KEY \ 21 | $ACCOUNT_3_PRIVATE_KEY \ 22 | $ACCOUNT_4_PRIVATE_KEY \ 23 | $ACCOUNT_5_PRIVATE_KEY \ 24 | $ACCOUNT_6_PRIVATE_KEY \ 25 | $ACCOUNT_7_PRIVATE_KEY \ 26 | $ACCOUNT_8_PRIVATE_KEY \ 27 | $ACCOUNT_9_PRIVATE_KEY \ 28 | ) 29 | -------------------------------------------------------------------------------- /kin-sdk-core/truffle/scripts/testrpc-kill.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd kin-sdk-core/truffle 4 | 5 | if [ -f './testrpc.pid' ]; then 6 | echo "killing testrpc on port $(cat ./testrpc.pid)" 7 | # Don't fail if the process is already killed 8 | kill -SIGINT $(cat ./testrpc.pid) || true 9 | rm -f ./testrpc.pid 10 | else 11 | echo "./testrpc.pid not found, doing nothing" 12 | fi 13 | -------------------------------------------------------------------------------- /kin-sdk-core/truffle/scripts/testrpc-run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd kin-sdk-core/truffle 4 | # prepare testrpc accounts parameter string e.g. --account="0x11c..,1000" --account="0xc5d...,1000" .... 5 | source ./scripts/testrpc-accounts.sh 6 | 7 | # create variables 8 | configFile="../src/androidTest/assets/testConfig.json" 9 | accounts="" 10 | 11 | # clear config file 12 | > $configFile 13 | 14 | # copy output to testConfig.json, will be read by androidTest 15 | printf '{ \n "accounts": [' >> ${configFile} 16 | comma=',' 17 | for i in ${!account_array[@]}; do 18 | accounts+=$(printf '%saccount=%s,%s' "--" "${account_array[i]}" "${balance}") 19 | 20 | printf '{\n "private_key":"%s"\n }%s\n' "${account_array[i]}" "${comma}" >> ${configFile} 21 | 22 | if [ $i -lt 8 ]; then 23 | comma=',' 24 | else 25 | comma='' 26 | fi 27 | 28 | if [ $i -lt 10 ]; then 29 | accounts+=" " 30 | fi 31 | done 32 | # accounts closing bracket, contract address is added in prepare-tests.sh 33 | printf '], \n' >> ${configFile} 34 | 35 | if (nc -z localhost 8545); then 36 | echo "Using existing testrpc instance on port $(ps -fade | grep -e 'node.*testrpc' | head -n 1 | awk '{print $2}')" 37 | else 38 | echo -n "Starting testrpc instance on port ${port} " 39 | ./node_modules/.bin/testrpc ${accounts} -u 0 -u 1 -p "${port}" > testrpc.log 2>&1 & echo $! > testrpc.pid 40 | echo $(cat testrpc.pid) 41 | fi 42 | -------------------------------------------------------------------------------- /kin-sdk-core/truffle/scripts/truffle.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd kin-sdk-core/truffle 4 | 5 | ./node_modules/.bin/truffle deploy --reset > ./truffle.log 2>&1 6 | 7 | cat ./truffle.log | grep "Token contract deployed at" | tail -n 1 | awk '{print $5}' > ./token-contract-address 8 | -------------------------------------------------------------------------------- /kin-sdk-core/truffle/token-contract-address: -------------------------------------------------------------------------------- 1 | 0x8919486b0afaad4656e8f9667ce9adbb62c3f2b1 2 | -------------------------------------------------------------------------------- /kin-sdk-core/truffle/truffle.js: -------------------------------------------------------------------------------- 1 | require('babel-register'); 2 | require('babel-polyfill'); 3 | 4 | module.exports = { 5 | networks: { 6 | development: { 7 | host: 'localhost', 8 | port: 8545, 9 | network_id: '*', // Match any network id 10 | gas: 3500000 11 | } 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /kin_android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kinecosystem/kin-core-android-ethereum/6fc84185244c6bc9d3807197d4bc1baec6f8ad4e/kin_android.png -------------------------------------------------------------------------------- /sample/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /sample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 26 5 | buildToolsVersion "26.0.2" 6 | defaultConfig { 7 | applicationId "kin.sdk.core.sample" 8 | minSdkVersion 16 9 | targetSdkVersion 26 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | compileOptions { 21 | sourceCompatibility JavaVersion.VERSION_1_8 22 | targetCompatibility JavaVersion.VERSION_1_8 23 | } 24 | } 25 | 26 | dependencies { 27 | implementation fileTree(include: ['*.jar'], dir: 'libs') 28 | androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', { 29 | exclude group: 'com.android.support', module: 'support-annotations' 30 | }) 31 | implementation 'com.android.support:appcompat-v7:26.+' 32 | testImplementation 'junit:junit:4.12' 33 | api project(':kin-sdk-core') 34 | compile 'com.android.volley:volley:1.0.0' 35 | } 36 | -------------------------------------------------------------------------------- /sample/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 26 | 31 | 35 | 40 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /sample/src/main/java/kin/sdk/core/sample/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core.sample; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.view.MenuItem; 8 | import android.view.View; 9 | import android.view.inputmethod.InputMethodManager; 10 | import kin.sdk.core.KinClient; 11 | 12 | public abstract class BaseActivity extends AppCompatActivity { 13 | 14 | // ideally user should be asked for a passphrase when 15 | // creating an account and then the same passphrase 16 | // should be used when sending transactions 17 | // To make the UI simpler for the sample application 18 | // we are using a hardcoded passphrase. 19 | final static String PASSPHRASE1 = "12345"; 20 | final static int NO_ACTION_BAR_TITLE = -1; 21 | 22 | abstract Intent getBackIntent(); 23 | 24 | abstract int getActionBarTitleRes(); 25 | 26 | public boolean isMainNet() { 27 | if (getKinClient() != null && getKinClient().getServiceProvider() != null) { 28 | return getKinClient().getServiceProvider().isMainNet(); 29 | } 30 | return false; 31 | } 32 | 33 | protected boolean hasBack() { 34 | return true; 35 | } 36 | 37 | @Override 38 | protected void onCreate(Bundle savedInstanceState) { 39 | super.onCreate(savedInstanceState); 40 | int theme = isMainNet() ? R.style.AppTheme_Main : R.style.AppTheme_Test; 41 | setTheme(theme); 42 | initActionBar(); 43 | } 44 | 45 | private void initActionBar() { 46 | if (getActionBarTitleRes() != NO_ACTION_BAR_TITLE) { 47 | getSupportActionBar().setTitle(getActionBarTitleRes()); 48 | } 49 | getSupportActionBar().setDisplayHomeAsUpEnabled(hasBack()); 50 | } 51 | 52 | @Override 53 | public void onBackPressed() { 54 | Intent intent = getBackIntent(); 55 | if (intent != null) { 56 | startActivity(intent); 57 | overridePendingTransition(R.anim.slide_in_left, R.anim.slide_out_left); 58 | } 59 | finish(); 60 | } 61 | 62 | public KinClient getKinClient() { 63 | KinClientSampleApplication application = (KinClientSampleApplication) getApplication(); 64 | return application.getKinClient(); 65 | } 66 | 67 | @Override 68 | public boolean onOptionsItemSelected(MenuItem item) { 69 | onBackPressed(); 70 | return true; 71 | } 72 | 73 | @Override 74 | public void startActivity(Intent intent) { 75 | super.startActivity(intent); 76 | overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_right); 77 | finish(); 78 | } 79 | 80 | public String getPassphrase() { 81 | return PASSPHRASE1; 82 | } 83 | 84 | protected void hideKeyboard(View view) { 85 | InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Activity.INPUT_METHOD_SERVICE); 86 | inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /sample/src/main/java/kin/sdk/core/sample/ChooseNetworkActivity.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core.sample; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.graphics.Paint; 6 | import android.os.Bundle; 7 | import android.text.Html; 8 | import android.widget.TextView; 9 | import kin.sdk.core.KinClient; 10 | 11 | /** 12 | * User is given a choice to create or use an account on the MAIN or ROPSTEN(test) ethereum networks 13 | */ 14 | public class ChooseNetworkActivity extends BaseActivity { 15 | 16 | public static final String TAG = ChooseNetworkActivity.class.getSimpleName(); 17 | private static final String KIN_FOUNDATION_URL = "https://github.com/kinfoundation"; 18 | 19 | public static Intent getIntent(Context context) { 20 | return new Intent(context, ChooseNetworkActivity.class); 21 | } 22 | 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | setContentView(R.layout.choose_network_activity); 27 | initWidgets(); 28 | } 29 | 30 | @Override 31 | protected boolean hasBack() { 32 | return false; 33 | } 34 | 35 | private void initWidgets() { 36 | TextView urlTextView = (TextView) findViewById(R.id.kin_foundation_url); 37 | urlTextView.setPaintFlags(urlTextView.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG); 38 | urlTextView.setText(Html.fromHtml(KIN_FOUNDATION_URL)); 39 | urlTextView.setOnClickListener(view -> startWebWrapperActivity()); 40 | findViewById(R.id.kin_icon).setOnClickListener(view -> startWebWrapperActivity()); 41 | findViewById(R.id.btn_main_net).setOnClickListener( 42 | view -> createKinClient(KinClientSampleApplication.NetWorkType.MAIN)); 43 | 44 | findViewById(R.id.btn_test_net).setOnClickListener( 45 | view -> createKinClient(KinClientSampleApplication.NetWorkType.ROPSTEN)); 46 | } 47 | 48 | private void createKinClient(KinClientSampleApplication.NetWorkType netWorkType) { 49 | KinClientSampleApplication application = (KinClientSampleApplication) getApplication(); 50 | KinClient kinClient = application.createKinClient(netWorkType); 51 | if (kinClient.hasAccount()) { 52 | startActivity(WalletActivity.getIntent(this)); 53 | } else { 54 | startActivity(CreateWalletActivity.getIntent(this)); 55 | } 56 | } 57 | 58 | private void startWebWrapperActivity(){ 59 | startActivity(WebWrapperActivity.getIntent(this, KIN_FOUNDATION_URL)); 60 | } 61 | 62 | @Override 63 | Intent getBackIntent() { 64 | return null; 65 | } 66 | 67 | @Override 68 | int getActionBarTitleRes() { 69 | return R.string.app_name; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /sample/src/main/java/kin/sdk/core/sample/CreateWalletActivity.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core.sample; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.view.View; 7 | import kin.sdk.core.KinClient; 8 | import kin.sdk.core.exception.CreateAccountException; 9 | import kin.sdk.core.sample.kin.sdk.core.sample.dialog.KinAlertDialog; 10 | 11 | /** 12 | * This activity is displayed only if there is no existing account stored on device for the given network 13 | * The activity will just display a button to create an account 14 | */ 15 | public class CreateWalletActivity extends BaseActivity { 16 | 17 | public static final String TAG = CreateWalletActivity.class.getSimpleName(); 18 | 19 | public static Intent getIntent(Context context) { 20 | return new Intent(context, CreateWalletActivity.class); 21 | } 22 | 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | setContentView(R.layout.create_wallet_activity); 27 | initWidgets(); 28 | } 29 | 30 | private void initWidgets() { 31 | View createWallet = findViewById(R.id.btn_create_wallet); 32 | if (isMainNet()) { 33 | createWallet.setBackgroundResource(R.drawable.button_main_network_bg); 34 | } 35 | createWallet.setOnClickListener(view -> createAccount()); 36 | } 37 | 38 | private void createAccount() { 39 | try { 40 | final KinClient kinClient = getKinClient(); 41 | kinClient.createAccount(getPassphrase()); 42 | startActivity(WalletActivity.getIntent(this)); 43 | } catch (CreateAccountException e) { 44 | KinAlertDialog.createErrorDialog(this, e.getMessage()).show(); 45 | } 46 | } 47 | 48 | @Override 49 | Intent getBackIntent() { 50 | return ChooseNetworkActivity.getIntent(this); 51 | } 52 | 53 | @Override 54 | int getActionBarTitleRes() { 55 | return R.string.create_wallet; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /sample/src/main/java/kin/sdk/core/sample/DisplayCallback.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core.sample; 2 | 3 | import android.content.Context; 4 | import android.view.View; 5 | import kin.sdk.core.ResultCallback; 6 | import kin.sdk.core.sample.kin.sdk.core.sample.dialog.KinAlertDialog; 7 | 8 | /** 9 | * Will hide a progressBar and display result on a displayView passed at constructor 10 | * Holds the views as weakReferences and clears the references when canceled 11 | */ 12 | public abstract class DisplayCallback implements ResultCallback { 13 | 14 | private View progressBar; 15 | private View displayView; 16 | 17 | public DisplayCallback(View progressBar, View displayView) { 18 | this.progressBar = progressBar; 19 | this.displayView = displayView; 20 | } 21 | 22 | public DisplayCallback(View progressBar) { 23 | this.progressBar = progressBar; 24 | } 25 | 26 | /** 27 | * displayView will be null if DisplayCallback was constructed using the single parameter constructor. 28 | */ 29 | abstract public void displayResult(Context context, View displayView, T result); 30 | 31 | @Override 32 | public void onResult(T result) { 33 | progressBar.setVisibility(View.GONE); 34 | displayResult(progressBar.getContext(), displayView, result); 35 | } 36 | 37 | @Override 38 | public void onError(Exception e) { 39 | progressBar.setVisibility(View.GONE); 40 | KinAlertDialog.createErrorDialog(progressBar.getContext(), e.getMessage()).show(); 41 | } 42 | } -------------------------------------------------------------------------------- /sample/src/main/java/kin/sdk/core/sample/ExportKeystoreActivity.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core.sample; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.text.Editable; 7 | import android.text.TextUtils; 8 | import android.text.TextWatcher; 9 | import android.view.View; 10 | import android.widget.EditText; 11 | import android.widget.TextView; 12 | import kin.sdk.core.KinAccount; 13 | import kin.sdk.core.exception.AccountDeletedException; 14 | import kin.sdk.core.exception.OperationFailedException; 15 | import kin.sdk.core.exception.PassphraseException; 16 | import kin.sdk.core.sample.kin.sdk.core.sample.dialog.KinAlertDialog; 17 | import org.json.JSONException; 18 | import org.json.JSONObject; 19 | 20 | /** 21 | * Enter passphrase to generate Json content that can be used to access the current account 22 | */ 23 | public class ExportKeystoreActivity extends BaseActivity { 24 | 25 | public static final String TAG = ExportKeystoreActivity.class.getSimpleName(); 26 | 27 | public static Intent getIntent(Context context) { 28 | return new Intent(context, ExportKeystoreActivity.class); 29 | } 30 | 31 | private View exportBtn, copyBtn; 32 | private EditText passphraseInput; 33 | private TextView outputTextView; 34 | 35 | @Override 36 | protected void onCreate(Bundle savedInstanceState) { 37 | super.onCreate(savedInstanceState); 38 | setContentView(R.layout.export_key_store_activity); 39 | initWidgets(); 40 | } 41 | 42 | private void initWidgets() { 43 | copyBtn = findViewById(R.id.copy_btn); 44 | exportBtn = findViewById(R.id.generate_btn); 45 | passphraseInput = (EditText) findViewById(R.id.passphrase_input); 46 | outputTextView = (TextView) findViewById(R.id.output); 47 | 48 | findViewById(R.id.copy_btn).setOnClickListener(view -> { 49 | selectAll(); 50 | Utils.copyToClipboard(this, outputTextView.getText()); 51 | }); 52 | 53 | if (isMainNet()) { 54 | exportBtn.setBackgroundResource(R.drawable.button_main_network_bg); 55 | copyBtn.setBackgroundResource(R.drawable.button_main_network_bg); 56 | } 57 | passphraseInput.addTextChangedListener(new TextWatcher() { 58 | @Override 59 | public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { 60 | 61 | } 62 | 63 | @Override 64 | public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { 65 | if (!TextUtils.isEmpty(charSequence)) { 66 | clearOutput(); 67 | if (!exportBtn.isEnabled()) { 68 | exportBtn.setEnabled(true); 69 | } 70 | } else if (exportBtn.isEnabled()) { 71 | exportBtn.setEnabled(false); 72 | } 73 | } 74 | 75 | @Override 76 | public void afterTextChanged(Editable editable) { 77 | 78 | } 79 | }); 80 | 81 | passphraseInput.setOnFocusChangeListener((view, hasFocus) -> { 82 | if (!hasFocus) { 83 | hideKeyboard(view); 84 | } 85 | }); 86 | 87 | exportBtn.setOnClickListener(view -> { 88 | exportBtn.setEnabled(false); 89 | hideKeyboard(exportBtn); 90 | try { 91 | String jsonFormatString = generatePrivateKeyStoreJsonFormat(); 92 | updateOutput(jsonFormatString); 93 | copyBtn.setEnabled(true); 94 | } catch (PassphraseException e) { 95 | clearAll(); 96 | KinAlertDialog.createErrorDialog(this, e.getMessage()).show(); 97 | } catch (JSONException e) { 98 | clearAll(); 99 | KinAlertDialog.createErrorDialog(this, e.getMessage()).show(); 100 | } catch (OperationFailedException e) { 101 | clearAll(); 102 | KinAlertDialog.createErrorDialog(this, e.getMessage()).show(); 103 | } 104 | }); 105 | } 106 | 107 | private void clearAll() { 108 | clearOutput(); 109 | passphraseInput.setText(""); 110 | } 111 | 112 | private void selectAll() { 113 | outputTextView.setSelectAllOnFocus(true); 114 | outputTextView.clearFocus(); 115 | outputTextView.requestFocus(); 116 | outputTextView.setSelectAllOnFocus(false); 117 | } 118 | 119 | private String generatePrivateKeyStoreJsonFormat() 120 | throws PassphraseException, JSONException, OperationFailedException { 121 | KinAccount account = getKinClient().getAccount(); 122 | if (account == null) { 123 | throw new AccountDeletedException(); 124 | } 125 | String jsonString = account.exportKeyStore(getPassphrase(), passphraseInput.getText().toString()); 126 | JSONObject jsonObject = new JSONObject(jsonString); 127 | return jsonObject.toString(1); 128 | } 129 | 130 | private void updateOutput(String outputString) { 131 | if (TextUtils.isEmpty(outputString)) { 132 | outputTextView.setText(outputString); 133 | outputTextView.setTextIsSelectable(false); 134 | } else { 135 | outputTextView.setText(outputString); 136 | outputTextView.setTextIsSelectable(true); 137 | outputTextView.requestFocus(); 138 | } 139 | } 140 | 141 | private void clearOutput() { 142 | updateOutput(null); 143 | copyBtn.setEnabled(false); 144 | } 145 | 146 | @Override 147 | Intent getBackIntent() { 148 | return WalletActivity.getIntent(this); 149 | } 150 | 151 | @Override 152 | int getActionBarTitleRes() { 153 | return R.string.export_key_store; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /sample/src/main/java/kin/sdk/core/sample/KinClientSampleApplication.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core.sample; 2 | 3 | import android.app.Application; 4 | import kin.sdk.core.KinClient; 5 | import kin.sdk.core.ServiceProvider; 6 | import kin.sdk.core.exception.EthereumClientException; 7 | 8 | public class KinClientSampleApplication extends Application { 9 | 10 | //based on parity 11 | private final String ROPSTEN_TEST_NET_URL = "http://parity.rounds.video:8545"; 12 | private final String MAIN_NET_URL = "http://mainnet.rounds.video:8545"; 13 | 14 | 15 | public enum NetWorkType { 16 | MAIN, 17 | ROPSTEN; 18 | } 19 | 20 | private KinClient kinClient = null; 21 | 22 | public KinClient createKinClient(NetWorkType type) { 23 | String providerUrl; 24 | int netWorkId; 25 | switch (type) { 26 | case MAIN: 27 | providerUrl = MAIN_NET_URL; 28 | netWorkId = ServiceProvider.NETWORK_ID_MAIN; 29 | break; 30 | case ROPSTEN: 31 | providerUrl = ROPSTEN_TEST_NET_URL; 32 | netWorkId = ServiceProvider.NETWORK_ID_ROPSTEN; 33 | break; 34 | default: 35 | providerUrl = ROPSTEN_TEST_NET_URL; 36 | netWorkId = ServiceProvider.NETWORK_ID_ROPSTEN; 37 | } 38 | try { 39 | kinClient = new KinClient(this, 40 | new ServiceProvider(providerUrl, netWorkId)); 41 | } catch (EthereumClientException e) { 42 | e.printStackTrace(); 43 | } 44 | return kinClient; 45 | } 46 | 47 | public KinClient getKinClient() { 48 | return kinClient; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /sample/src/main/java/kin/sdk/core/sample/TransactionActivity.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core.sample; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.text.Editable; 7 | import android.text.TextUtils; 8 | import android.text.TextWatcher; 9 | import android.view.View; 10 | import android.widget.EditText; 11 | import java.math.BigDecimal; 12 | import kin.sdk.core.KinAccount; 13 | import kin.sdk.core.Request; 14 | import kin.sdk.core.TransactionId; 15 | import kin.sdk.core.exception.AccountDeletedException; 16 | import kin.sdk.core.exception.OperationFailedException; 17 | import kin.sdk.core.sample.kin.sdk.core.sample.dialog.KinAlertDialog; 18 | 19 | /** 20 | * Displays form to enter public address and amount and a button to send a transaction 21 | */ 22 | public class TransactionActivity extends BaseActivity { 23 | 24 | public static final String TAG = TransactionActivity.class.getSimpleName(); 25 | 26 | public static Intent getIntent(Context context) { 27 | return new Intent(context, TransactionActivity.class); 28 | } 29 | 30 | private View sendTransaction, progressBar; 31 | private EditText toAddressInput, amountInput; 32 | private Request transactionRequest; 33 | 34 | @Override 35 | protected void onCreate(Bundle savedInstanceState) { 36 | super.onCreate(savedInstanceState); 37 | setContentView(R.layout.transaction_activity); 38 | initWidgets(); 39 | } 40 | 41 | private void initWidgets() { 42 | sendTransaction = findViewById(R.id.send_transaction_btn); 43 | progressBar = findViewById(R.id.transaction_progress); 44 | toAddressInput = (EditText) findViewById(R.id.to_address_input); 45 | amountInput = (EditText) findViewById(R.id.amount_input); 46 | 47 | if (getKinClient().getServiceProvider().isMainNet()) { 48 | sendTransaction.setBackgroundResource(R.drawable.button_main_network_bg); 49 | } 50 | toAddressInput.addTextChangedListener(new TextWatcher() { 51 | @Override 52 | public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { 53 | 54 | } 55 | 56 | @Override 57 | public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { 58 | if (!TextUtils.isEmpty(charSequence) && !TextUtils.isEmpty(amountInput.getText())) { 59 | if (!sendTransaction.isEnabled()) { 60 | sendTransaction.setEnabled(true); 61 | } 62 | } else if (sendTransaction.isEnabled()) { 63 | sendTransaction.setEnabled(false); 64 | } 65 | } 66 | 67 | @Override 68 | public void afterTextChanged(Editable editable) { 69 | 70 | } 71 | }); 72 | 73 | amountInput.addTextChangedListener(new TextWatcher() { 74 | @Override 75 | public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { 76 | 77 | } 78 | 79 | @Override 80 | public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { 81 | if (!TextUtils.isEmpty(charSequence) && !TextUtils.isEmpty(toAddressInput.getText())) { 82 | if (!sendTransaction.isEnabled()) { 83 | sendTransaction.setEnabled(true); 84 | } 85 | } else if (sendTransaction.isEnabled()) { 86 | sendTransaction.setEnabled(false); 87 | } 88 | } 89 | 90 | @Override 91 | public void afterTextChanged(Editable editable) { 92 | 93 | } 94 | }); 95 | 96 | toAddressInput.setOnFocusChangeListener((view, hasFocus) -> { 97 | if (!hasFocus && !toAddressInput.hasFocus()) { 98 | hideKeyboard(view); 99 | } 100 | }); 101 | 102 | amountInput.setOnFocusChangeListener((view, hasFocus) -> { 103 | if (!hasFocus && !amountInput.hasFocus()) { 104 | hideKeyboard(view); 105 | } 106 | }); 107 | 108 | sendTransaction.setOnClickListener(view -> { 109 | BigDecimal amount = new BigDecimal(amountInput.getText().toString()); 110 | try { 111 | sendTransaction(toAddressInput.getText().toString(), amount); 112 | } catch (OperationFailedException e) { 113 | KinAlertDialog.createErrorDialog(TransactionActivity.this, e.getMessage()).show(); 114 | } 115 | }); 116 | } 117 | 118 | @Override 119 | Intent getBackIntent() { 120 | return WalletActivity.getIntent(this); 121 | } 122 | 123 | @Override 124 | int getActionBarTitleRes() { 125 | return R.string.transaction; 126 | } 127 | 128 | private void sendTransaction(String toAddress, BigDecimal amount) throws OperationFailedException { 129 | progressBar.setVisibility(View.VISIBLE); 130 | KinAccount account = getKinClient().getAccount(); 131 | if (account != null) { 132 | transactionRequest = account 133 | .sendTransaction(toAddress, getPassphrase(), amount); 134 | transactionRequest.run(new DisplayCallback(progressBar) { 135 | @Override 136 | public void displayResult(Context context, View view, TransactionId transactionId) { 137 | KinAlertDialog.createErrorDialog(context, "Transaction id " + transactionId.id()).show(); 138 | } 139 | }); 140 | } else { 141 | progressBar.setVisibility(View.GONE); 142 | throw new AccountDeletedException(); 143 | } 144 | } 145 | 146 | @Override 147 | protected void onDestroy() { 148 | super.onDestroy(); 149 | if (transactionRequest != null) { 150 | transactionRequest.cancel(false); 151 | } 152 | progressBar = null; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /sample/src/main/java/kin/sdk/core/sample/Utils.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core.sample; 2 | 3 | import android.content.Context; 4 | 5 | public class Utils { 6 | 7 | public static void copyToClipboard(Context context, CharSequence textToCopy) { 8 | int sdk = android.os.Build.VERSION.SDK_INT; 9 | if (sdk < android.os.Build.VERSION_CODES.HONEYCOMB) { 10 | android.text.ClipboardManager clipboard = (android.text.ClipboardManager) context.getSystemService( 11 | Context.CLIPBOARD_SERVICE); 12 | clipboard.setText(textToCopy); 13 | } else { 14 | android.content.ClipboardManager clipboard = (android.content.ClipboardManager) context.getSystemService( 15 | Context.CLIPBOARD_SERVICE); 16 | android.content.ClipData clip = android.content.ClipData 17 | .newPlainText("copied text", textToCopy); 18 | clipboard.setPrimaryClip(clip); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /sample/src/main/java/kin/sdk/core/sample/WalletActivity.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core.sample; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.view.View; 7 | import android.widget.TextView; 8 | import com.android.volley.Request; 9 | import com.android.volley.RequestQueue; 10 | import com.android.volley.toolbox.StringRequest; 11 | import com.android.volley.toolbox.Volley; 12 | import kin.sdk.core.Balance; 13 | import kin.sdk.core.KinAccount; 14 | import kin.sdk.core.exception.DeleteAccountException; 15 | import kin.sdk.core.sample.kin.sdk.core.sample.dialog.KinAlertDialog; 16 | 17 | /** 18 | * Responsible for presenting details about the account 19 | * Public address, account balance, account pending balance 20 | * and in future we will add here button to backup the account (show usage of exportKeyStore) 21 | * In addition there is "Send Transaction" button here that will navigate to TransactionActivity 22 | */ 23 | public class WalletActivity extends BaseActivity { 24 | 25 | public static final String TAG = WalletActivity.class.getSimpleName(); 26 | public static final String URL_GET_KIN = "http://kin-faucet.rounds.video/send?public_address="; 27 | 28 | public static Intent getIntent(Context context) { 29 | return new Intent(context, WalletActivity.class); 30 | } 31 | 32 | private TextView balance, pendingBalance, publicKey; 33 | private View getKinBtn; 34 | private View balanceProgress, pendingBalanceProgress; 35 | private kin.sdk.core.Request pendingBalanceRequest; 36 | private kin.sdk.core.Request balanceRequest; 37 | 38 | @Override 39 | protected void onCreate(Bundle savedInstanceState) { 40 | super.onCreate(savedInstanceState); 41 | setContentView(R.layout.wallet_activity); 42 | initWidgets(); 43 | } 44 | 45 | @Override 46 | protected void onResume() { 47 | super.onResume(); 48 | updatePublicKey(); 49 | updateBalance(); 50 | updatePendingBalance(); 51 | } 52 | 53 | private void initWidgets() { 54 | balance = (TextView) findViewById(R.id.balance); 55 | pendingBalance = (TextView) findViewById(R.id.pending_balance); 56 | publicKey = (TextView) findViewById(R.id.public_key); 57 | 58 | balanceProgress = findViewById(R.id.balance_progress); 59 | pendingBalanceProgress = findViewById(R.id.pending_balance_progress); 60 | 61 | final View transaction = findViewById(R.id.send_transaction_btn); 62 | final View refresh = findViewById(R.id.refresh_btn); 63 | getKinBtn = findViewById(R.id.get_kin_btn); 64 | final View exportKeyStore = findViewById(R.id.export_key_store_btn); 65 | final View deleteAccount = findViewById(R.id.delete_account_btn); 66 | 67 | if (isMainNet()) { 68 | transaction.setBackgroundResource(R.drawable.button_main_network_bg); 69 | refresh.setBackgroundResource(R.drawable.button_main_network_bg); 70 | exportKeyStore.setBackgroundResource(R.drawable.button_main_network_bg); 71 | getKinBtn.setVisibility(View.GONE); 72 | } else { 73 | getKinBtn.setVisibility(View.VISIBLE); 74 | getKinBtn.setOnClickListener(view -> { 75 | getKinBtn.setClickable(false); 76 | getKin(); 77 | }); 78 | } 79 | 80 | deleteAccount.setOnClickListener(view -> showDeleteAlert()); 81 | 82 | transaction.setOnClickListener(view -> startActivity(TransactionActivity.getIntent(WalletActivity.this))); 83 | refresh.setOnClickListener(view -> { 84 | updateBalance(); 85 | updatePendingBalance(); 86 | }); 87 | 88 | exportKeyStore.setOnClickListener(view -> startActivity(ExportKeystoreActivity.getIntent(this))); 89 | } 90 | 91 | private void showDeleteAlert() { 92 | KinAlertDialog.createConfirmationDialog(this, getResources().getString(R.string.delete_wallet_warning), 93 | getResources().getString(R.string.delete), this::deleteAccount).show(); 94 | } 95 | 96 | private void deleteAccount() { 97 | try { 98 | getKinClient().deleteAccount(getPassphrase()); 99 | onBackPressed(); 100 | } catch (DeleteAccountException e) { 101 | KinAlertDialog.createErrorDialog(this, e.getMessage()).show(); 102 | } 103 | } 104 | 105 | private void getKin() { 106 | final KinAccount account = getKinClient().getAccount(); 107 | if (account != null) { 108 | final String publicAddress = account.getPublicAddress(); 109 | final String url = URL_GET_KIN + publicAddress; 110 | final RequestQueue queue = Volley.newRequestQueue(this); 111 | final StringRequest stringRequest = new StringRequest(Request.Method.GET, url, 112 | response -> { 113 | updatePendingBalance(); 114 | getKinBtn.setClickable(true); 115 | }, 116 | e -> { 117 | KinAlertDialog.createErrorDialog(this, e.getMessage()).show(); 118 | getKinBtn.setClickable(true); 119 | }); 120 | stringRequest.setShouldCache(false); 121 | queue.add(stringRequest); 122 | } 123 | } 124 | 125 | private void updatePublicKey() { 126 | String publicKeyStr = ""; 127 | KinAccount account = getKinClient().getAccount(); 128 | if (account != null) { 129 | publicKeyStr = account.getPublicAddress(); 130 | } 131 | publicKey.setText(publicKeyStr); 132 | } 133 | 134 | private void updateBalance() { 135 | balanceProgress.setVisibility(View.VISIBLE); 136 | KinAccount account = getKinClient().getAccount(); 137 | if (account != null) { 138 | balanceRequest = account.getBalance(); 139 | balanceRequest.run(new DisplayCallback(balanceProgress, balance) { 140 | @Override 141 | public void displayResult(Context context, View view, Balance result) { 142 | ((TextView) view).setText(result.value(0)); 143 | } 144 | }); 145 | } else { 146 | balance.setText(""); 147 | } 148 | } 149 | 150 | private void updatePendingBalance() { 151 | pendingBalanceProgress.setVisibility(View.VISIBLE); 152 | KinAccount account = getKinClient().getAccount(); 153 | if (account != null) { 154 | pendingBalanceRequest = getKinClient().getAccount().getPendingBalance(); 155 | pendingBalanceRequest.run(new DisplayCallback(pendingBalanceProgress, pendingBalance) { 156 | @Override 157 | public void displayResult(Context context, View view, Balance result) { 158 | ((TextView) view).setText(result.value(0)); 159 | } 160 | }); 161 | } else { 162 | pendingBalance.setText(""); 163 | } 164 | } 165 | 166 | @Override 167 | Intent getBackIntent() { 168 | return ChooseNetworkActivity.getIntent(this); 169 | } 170 | 171 | @Override 172 | int getActionBarTitleRes() { 173 | return R.string.balance; 174 | } 175 | 176 | @Override 177 | protected void onDestroy() { 178 | super.onDestroy(); 179 | if (pendingBalanceRequest != null) { 180 | pendingBalanceRequest.cancel(true); 181 | } 182 | if (balanceRequest != null) { 183 | balanceRequest.cancel(true); 184 | } 185 | pendingBalance = null; 186 | balance = null; 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /sample/src/main/java/kin/sdk/core/sample/WebWrapperActivity.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core.sample; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.webkit.WebView; 7 | import android.webkit.WebViewClient; 8 | 9 | /** 10 | */ 11 | 12 | public class WebWrapperActivity extends BaseActivity { 13 | 14 | public static final String TAG = WebWrapperActivity.class.getSimpleName(); 15 | private static String ARGS_URL = "url"; 16 | 17 | public static Intent getIntent(Context context, String url) { 18 | Intent intent = new Intent(context, WebWrapperActivity.class); 19 | intent.putExtra(ARGS_URL, url); 20 | return intent; 21 | } 22 | 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | setContentView(R.layout.web_holder_activity); 27 | initWidgets(); 28 | } 29 | 30 | private void initWidgets() { 31 | WebView webView = (WebView) findViewById(R.id.web); 32 | webView.getSettings().setJavaScriptEnabled(true); 33 | webView.setWebViewClient(new WebViewClient() { 34 | @Override 35 | public boolean shouldOverrideUrlLoading(WebView view, String url) { 36 | view.loadUrl(url); 37 | return true; 38 | } 39 | }); 40 | final String url = getIntent().getStringExtra(ARGS_URL); 41 | webView.loadUrl(url); 42 | } 43 | 44 | @Override 45 | Intent getBackIntent() { 46 | return ChooseNetworkActivity.getIntent(this); 47 | } 48 | 49 | @Override 50 | int getActionBarTitleRes() { 51 | return R.string.kin_foundation; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /sample/src/main/java/kin/sdk/core/sample/kin/sdk/core/sample/dialog/KinAlertDialog.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core.sample.kin.sdk.core.sample.dialog; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.DialogInterface; 6 | import android.text.TextUtils; 7 | import android.view.Gravity; 8 | import android.view.View; 9 | import android.widget.TextView; 10 | import android.widget.Toast; 11 | import kin.sdk.core.sample.R; 12 | import kin.sdk.core.sample.Utils; 13 | 14 | public class KinAlertDialog { 15 | 16 | private Context context; 17 | private android.app.AlertDialog dialog; 18 | private OnConfirmedListener confirmedListener; 19 | private String positiveButtonText; 20 | 21 | public static KinAlertDialog createErrorDialog(Context context, String message) { 22 | KinAlertDialog dialog = new KinAlertDialog(context); 23 | dialog.setMessage(message); 24 | dialog.setConfirmButton(); 25 | return dialog; 26 | } 27 | 28 | public static KinAlertDialog createConfirmationDialog(Context context, String message, String confirmationText, 29 | OnConfirmedListener confirmedListener) { 30 | KinAlertDialog dialog = new KinAlertDialog(context); 31 | dialog.setPositiveButtonText(confirmationText); 32 | dialog.setOnConfirmedListener(confirmedListener); 33 | dialog.setMessage(message); 34 | dialog.setConfirmButton(); 35 | dialog.setCancelButton(); 36 | return dialog; 37 | } 38 | 39 | private KinAlertDialog(Context context) { 40 | this.context = context; 41 | final android.app.AlertDialog.Builder builder = new android.app.AlertDialog.Builder(context); 42 | dialog = builder.create(); 43 | dialog.setCancelable(true); 44 | positiveButtonText = context.getResources().getString(R.string.ok); 45 | } 46 | 47 | public void show() { 48 | if (context != null && !((Activity) context).isFinishing()) { 49 | dialog.show(); 50 | } 51 | } 52 | 53 | private void setPositiveButtonText(String text){ 54 | if(TextUtils.isEmpty(text)) { 55 | positiveButtonText = context.getResources().getString(R.string.ok); 56 | }else { 57 | positiveButtonText = text; 58 | } 59 | } 60 | 61 | protected void setMessage(String message) { 62 | if(TextUtils.isEmpty(message)){ 63 | message = context.getResources().getString(R.string.error_no_message); 64 | } 65 | dialog.setView(buildMessageView(message)); 66 | } 67 | 68 | protected void setOnConfirmedListener(OnConfirmedListener onConfirmedListener){ 69 | this.confirmedListener = onConfirmedListener; 70 | } 71 | 72 | protected void setConfirmButton() { 73 | dialog.setButton(DialogInterface.BUTTON_POSITIVE, positiveButtonText, 74 | (dialogInterface, i) -> { 75 | dialogInterface.dismiss(); 76 | onConfirmed(); 77 | }); 78 | } 79 | 80 | protected void setCancelButton() { 81 | dialog.setButton(DialogInterface.BUTTON_NEGATIVE, context.getResources().getString(R.string.cancel), 82 | (dialogInterface, i) -> dialogInterface.dismiss()); 83 | } 84 | 85 | protected void onConfirmed() { 86 | if (confirmedListener != null) { 87 | confirmedListener.onConfirm(); 88 | } 89 | } 90 | 91 | private View buildMessageView(String message) { 92 | TextView textView = new TextView(context); 93 | textView.setTextColor(R.drawable.text_color); 94 | textView.setTextIsSelectable(true); 95 | textView.setTextSize(18f); 96 | textView.setText(message); 97 | textView.setGravity(Gravity.LEFT); 98 | textView.setPadding(35, 35, 35, 0); 99 | textView.setOnLongClickListener(v -> { 100 | Utils.copyToClipboard(v.getContext(), message); 101 | Toast.makeText(v.getContext(), R.string.copied_to_clipboard, 102 | Toast.LENGTH_SHORT) 103 | .show(); 104 | return true; 105 | }); 106 | return textView; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /sample/src/main/java/kin/sdk/core/sample/kin/sdk/core/sample/dialog/OnConfirmedListener.java: -------------------------------------------------------------------------------- 1 | package kin.sdk.core.sample.kin.sdk.core.sample.dialog; 2 | 3 | /** 4 | * Created by shaybaz on 27/11/2017. 5 | */ 6 | 7 | public interface OnConfirmedListener { 8 | 9 | void onConfirm(); 10 | } 11 | -------------------------------------------------------------------------------- /sample/src/main/res/anim/slide_in_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /sample/src/main/res/anim/slide_in_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /sample/src/main/res/anim/slide_out_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /sample/src/main/res/anim/slide_out_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/kin_small_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kinecosystem/kin-core-android-ethereum/6fc84185244c6bc9d3807197d4bc1baec6f8ad4e/sample/src/main/res/drawable-hdpi/kin_small_icon.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-mdpi/kin_small_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kinecosystem/kin-core-android-ethereum/6fc84185244c6bc9d3807197d4bc1baec6f8ad4e/sample/src/main/res/drawable-mdpi/kin_small_icon.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/kin_small_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kinecosystem/kin-core-android-ethereum/6fc84185244c6bc9d3807197d4bc1baec6f8ad4e/sample/src/main/res/drawable-xhdpi/kin_small_icon.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/kin_small_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kinecosystem/kin-core-android-ethereum/6fc84185244c6bc9d3807197d4bc1baec6f8ad4e/sample/src/main/res/drawable-xxhdpi/kin_small_icon.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/kin_small_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kinecosystem/kin-core-android-ethereum/6fc84185244c6bc9d3807197d4bc1baec6f8ad4e/sample/src/main/res/drawable-xxxhdpi/kin_small_icon.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable/button_main_network_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/button_red_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/button_test_network_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/output_rectangle_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 10 | 11 | 13 | 14 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/text_color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/choose_network_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 16 | 17 | 18 |