├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── example ├── build.gradle └── src │ └── main │ └── java │ └── com │ └── firestack │ └── example │ ├── GenerateAddress.java │ ├── GetTransactionStatus.java │ ├── SmartContractDeposit.java │ ├── TransactionOperation.java │ ├── ValidChecksumAddress.java │ └── ValidateAddress.java ├── settings.gradle └── src ├── main └── java │ └── com │ └── firestack │ └── laksaj │ ├── account │ ├── Account.java │ └── Wallet.java │ ├── blockchain │ ├── BlockList.java │ ├── BlockShort.java │ ├── BlockType.java │ ├── BlockchainInfo.java │ ├── Contract.java │ ├── DsBlock.java │ ├── DsBlockHeader.java │ ├── EventLogEntry.java │ ├── EventParam.java │ ├── ShardingStructure.java │ ├── TransactionList.java │ ├── TransactionReceipt.java │ ├── TxBlock.java │ └── TxBlockHeader.java │ ├── contract │ ├── ABI.java │ ├── CallParams.java │ ├── Contract.java │ ├── ContractFactory.java │ ├── ContractStatus.java │ ├── DeployParams.java │ ├── Field.java │ ├── Transition.java │ └── Value.java │ ├── crypto │ ├── CustomSecureRandom.java │ ├── KDFParams.java │ ├── KDFType.java │ ├── KeyStore.java │ ├── KeyTools.java │ ├── LinuxSecureRandom.java │ ├── LinuxSecureRandomProvider.java │ ├── PBKDF2Params.java │ ├── PBKDF2Wrapper.java │ ├── Schnorr.java │ ├── ScryptParams.java │ ├── ScryptWrapper.java │ └── Signature.java │ ├── exception │ └── ZilliqaAPIException.java │ ├── jsonrpc │ ├── HttpProvider.java │ ├── Rep.java │ └── Req.java │ ├── proto │ ├── Message.java │ └── message.proto │ ├── transaction │ ├── PendingStatus.java │ ├── Transaction.java │ ├── TransactionFactory.java │ ├── TransactionPayload.java │ ├── TransactionStatus.java │ ├── TxParams.java │ ├── TxReceipt.java │ └── TxStatus.java │ └── utils │ ├── AddressFormatException.java │ ├── Base58.java │ ├── Bech32.java │ ├── ByteUtil.java │ ├── HashUtil.java │ ├── TransactionUtil.java │ └── Validation.java └── test └── java └── com └── firestack └── laksaj ├── account ├── AccountTest.java └── WalletTest.java ├── contract └── ContractTest.java ├── crypto ├── KeyStoreTest.java ├── KeyToolsTest.java └── SchnorrTest.java ├── jsonrpc └── ProviderTest.java ├── proto └── MessageTest.java └── utils ├── Base58Util.java ├── Bech32Test.java ├── ByteUtilTest.java ├── TransactionUtilTest.java └── ValidationTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle/ 2 | gradle/ 3 | out/ 4 | testaccount 5 | gradlew 6 | gradlew.bat 7 | .idea 8 | build/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LaksaJ 2 | 3 | LaksaJ -- Zilliqa Blockchain Java Library 4 | 5 | The project is still under development. 6 | 7 | ## Quick Start 8 | 9 | More docs can be found in https://apidocs.zilliqa.com/ 10 | 11 | ### Generate large amount of addresses 12 | 13 | ```java 14 | 15 | package com.firestack.example; 16 | 17 | import com.firestack.laksaj.crypto.KeyTools; 18 | import com.firestack.laksaj.utils.ByteUtil; 19 | import org.web3j.crypto.ECKeyPair; 20 | 21 | import java.math.BigInteger; 22 | import java.security.InvalidAlgorithmParameterException; 23 | import java.security.NoSuchAlgorithmException; 24 | import java.security.NoSuchProviderException; 25 | 26 | public class GenerateAddress { 27 | //How to generate large amount of addresses 28 | public static void main(String[] args) throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { 29 | int n = 0; 30 | while (n < 100) { 31 | System.out.println("--------------------------"); 32 | System.out.println("generate nth keypair:"); 33 | ECKeyPair keyPair = KeyTools.generateKeyPair(); 34 | BigInteger privateInteger = keyPair.getPrivateKey(); 35 | BigInteger publicInteger = keyPair.getPublicKey(); 36 | System.out.println("private key is: " + privateInteger.toString(16)); 37 | System.out.println("public key is: " + publicInteger.toString(16)); 38 | System.out.println("address is: " + KeyTools.getAddressFromPublicKey(ByteUtil.byteArrayToHexString(publicInteger.toByteArray()))); 39 | } 40 | } 41 | 42 | ``` 43 | 44 | ### Validate an address 45 | 46 | ```java 47 | package com.firestack.example; 48 | 49 | import com.firestack.laksaj.utils.Validation; 50 | 51 | public class ValidateAddress { 52 | public static void main(String[] args) { 53 | String address = "2624B9EA4B1CD740630F6BF2FEA82AAC0067070B"; 54 | boolean isAddress = Validation.isAddress(address); 55 | System.out.println("is address: " + isAddress); 56 | } 57 | } 58 | ``` 59 | 60 | ### Validate checksum address 61 | 62 | ```java 63 | package com.firestack.example; 64 | 65 | import com.firestack.laksaj.utils.Validation; 66 | 67 | public class ValidChecksumAddress { 68 | public static void main(String[] args) { 69 | String checksumAddress = "0x4BAF5faDA8e5Db92C3d3242618c5B47133AE003C"; 70 | boolean isChecksumAddress = Validation.isValidChecksumAddress(checksumAddress); 71 | System.out.println("is checksum address: " + isChecksumAddress); 72 | } 73 | } 74 | ``` 75 | 76 | ### Transaction operation (include construct transaction, calculate transaction fee, do serialization, sign a transaction, broadcast) 77 | 78 | ```java 79 | package com.firestack.example; 80 | 81 | import com.firestack.laksaj.account.Wallet; 82 | import com.firestack.laksaj.contract.Contract; 83 | import com.firestack.laksaj.contract.ContractFactory; 84 | import com.firestack.laksaj.contract.DeployParams; 85 | import com.firestack.laksaj.contract.Value; 86 | import com.firestack.laksaj.jsonrpc.HttpProvider; 87 | import com.firestack.laksaj.transaction.Transaction; 88 | import com.firestack.laksaj.transaction.TransactionFactory; 89 | import javafx.util.Pair; 90 | 91 | import java.io.IOException; 92 | import java.security.NoSuchAlgorithmException; 93 | import java.util.Arrays; 94 | import java.util.List; 95 | 96 | import static com.firestack.laksaj.account.Wallet.pack; 97 | 98 | public class TransactionOperation { 99 | public static void main(String[] args) throws IOException, NoSuchAlgorithmException { 100 | Wallet wallet = new Wallet(); 101 | String ptivateKey = "e19d05c5452598e24caad4a0d85a49146f7be089515c905ae6a19e8a578a6930"; 102 | // Populate the wallet with an account 103 | String address = wallet.addByPrivateKey(ptivateKey); 104 | System.out.println("address is: " + address); 105 | 106 | HttpProvider provider = new HttpProvider("https://api.zilliqa.com"); 107 | //get balance 108 | HttpProvider.BalanceResult balanceResult = provider.getBalance(address); 109 | System.out.println("balance is: " + balanceResult.getBalance()); 110 | 111 | //construct non-contract transaction 112 | Transaction transaction = Transaction.builder() 113 | .version(String.valueOf(pack(333, 1))) 114 | .toAddr("zil16jrfrs8vfdtc74yzhyy83je4s4c5sqrcasjlc4") 115 | .senderPubKey("0246e7178dc8253201101e18fd6f6eb9972451d121fc57aa2a06dd5c111e58dc6a") 116 | .amount("10000000") 117 | .gasPrice("1000000000") 118 | .gasLimit("1") 119 | .code("") 120 | .data("") 121 | .provider(new HttpProvider("https://api.zilliqa.com/")) 122 | .build(); 123 | 124 | //sign transaction 125 | transaction = wallet.sign(transaction); 126 | System.out.println("signature is: " + transaction.getSignature()); 127 | 128 | //broadcast transaction 129 | HttpProvider.CreateTxResult result = TransactionFactory.sendTransaction(transaction); 130 | System.out.println(result); 131 | 132 | //hello-world contract 133 | String code = "scilla_version 0\n" + 134 | "\n" + 135 | " (* HelloWorld contract *)\n" + 136 | "\n" + 137 | " import ListUtils\n" + 138 | "\n" + 139 | " (***************************************************)\n" + 140 | " (* Associated library *)\n" + 141 | " (***************************************************)\n" + 142 | " library HelloWorld\n" + 143 | "\n" + 144 | " let one_msg =\n" + 145 | " fun (msg : Message) =>\n" + 146 | " let nil_msg = Nil {Message} in\n" + 147 | " Cons {Message} msg nil_msg\n" + 148 | "\n" + 149 | " let not_owner_code = Int32 1\n" + 150 | " let set_hello_code = Int32 2\n" + 151 | "\n" + 152 | " (***************************************************)\n" + 153 | " (* The contract definition *)\n" + 154 | " (***************************************************)\n" + 155 | "\n" + 156 | " contract HelloWorld\n" + 157 | " (owner: ByStr20)\n" + 158 | "\n" + 159 | " field welcome_msg : String = \"\"\n" + 160 | "\n" + 161 | " transition setHello (msg : String)\n" + 162 | " is_owner = builtin eq owner _sender;\n" + 163 | " match is_owner with\n" + 164 | " | False =>\n" + 165 | " msg = {_tag : \"TransactionOperation\"; _recipient : _sender; _amount : Uint128 0; code : not_owner_code};\n" + 166 | " msgs = one_msg msg;\n" + 167 | " send msgs\n" + 168 | " | True =>\n" + 169 | " welcome_msg := msg;\n" + 170 | " msg = {_tag : \"TransactionOperation\"; _recipient : _sender; _amount : Uint128 0; code : set_hello_code};\n" + 171 | " msgs = one_msg msg;\n" + 172 | " send msgs\n" + 173 | " end\n" + 174 | " end\n" + 175 | "\n" + 176 | "\n" + 177 | " transition getHello ()\n" + 178 | " r <- welcome_msg;\n" + 179 | " e = {_eventname: \"getHello()\"; msg: r};\n" + 180 | " event e\n" + 181 | " end"; 182 | List init = Arrays.asList(Value.builder().vname("_scilla_version").type("Uint32").value("0").build(), Value.builder().vname("owner").type("ByStr20").value("0x9bfec715a6bd658fcb62b0f8cc9bfa2ade71434a").build()); 183 | 184 | ContractFactory factory = ContractFactory.builder().provider(new HttpProvider("https://api.zilliqa.com/")).signer(wallet).build(); 185 | Contract contract = factory.newContract(code, (Value[]) init.toArray(), ""); 186 | DeployParams deployParams = DeployParams.builder().version(String.valueOf(pack(2, 8))).gasPrice("1000000000").gasLimit("10000").senderPubKey("0246e7178dc8253201101e18fd6f6eb9972451d121fc57aa2a06dd5c111e58dc6a").build(); 187 | 188 | //deploy contract, this will take a while to track transaction util it has been confirmed or failed 189 | Pair deployResult = contract.deploy(deployParams, 300, 3); 190 | System.out.println("result is: " + deployResult); 191 | 192 | //calculate transaction fee 193 | String transactionFee = new BigInteger(deployResult.getKey().getReceipt().getCumulative_gas()).multiply(new BigInteger(deployResult.getKey().getGasPrice())).toString(); 194 | System.out.println("transaction fee is: " + transactionFee); 195 | } 196 | } 197 | 198 | ``` 199 | 200 | ### Know a smart contract deposit 201 | 202 | ```java 203 | 204 | package com.firestack.example; 205 | 206 | import com.firestack.laksaj.blockchain.Contract; 207 | import com.firestack.laksaj.jsonrpc.HttpProvider; 208 | 209 | import java.io.IOException; 210 | import java.util.List; 211 | 212 | public class SmartContractDeposit { 213 | public static void main(String[] args) throws IOException, InterruptedException { 214 | HttpProvider client = new HttpProvider("https://api.zilliqa.com/"); 215 | String lastEpoch = client.getNumTxnsTxEpoch(); 216 | List lastStateList = client.getSmartContractState("D6606D02DFF929593312D8D0D36105E376F95AA0"); 217 | 218 | System.out.println("last epoch is " + lastEpoch); 219 | System.out.println("last state:" + lastStateList); 220 | 221 | int n = 0; 222 | 223 | while (true) { 224 | String epoch = client.getNumTxnsTxEpoch(); 225 | System.out.println(n + "th current epoch is: " + epoch); 226 | if (!lastEpoch.equals(epoch)) { 227 | System.out.println("epoch hash changed"); 228 | List stateList = client.getSmartContractState("D6606D02DFF929593312D8D0D36105E376F95AA0"); 229 | System.out.println("last state: " + lastStateList); 230 | System.out.println("current state: " + stateList); 231 | lastEpoch = epoch; 232 | lastStateList = stateList; 233 | } 234 | Thread.sleep(3000); 235 | n += 1; 236 | } 237 | } 238 | } 239 | 240 | ``` 241 | 242 | ## Supports 243 | 244 | ### Account API 245 | 246 | - [x] fromFile 247 | - [x] toFile 248 | 249 | ### Wallet API 250 | 251 | - [x] createAccount 252 | - [x] addByPrivateKey addByKeyStore 253 | - [x] remove 254 | - [x] setDefault 255 | - [x] signTransaction (default account) 256 | - [x] signTransactionWith (specific account) 257 | 258 | ### TransactionFactory Transaction 259 | 260 | - [x] sendTransaction 261 | - [x] trackTx 262 | - [x] confirm 263 | - [x] isPending isInitialised isConfirmed isRejected 264 | 265 | ### ContractFactory Contract 266 | 267 | - [x] deploy 268 | - [x] call 269 | - [x] isInitialised isDeployed isRejected 270 | - [x] getState 271 | - [x] getAddressForContract 272 | 273 | ### Crypto API 274 | 275 | - [x] getDerivedKey (PBKDF2 and Scrypt) 276 | - [x] generatePrivateKey 277 | - [x] Schnorr.sign 278 | - [x] Schnorr.verify 279 | - [x] getPublicKeyFromPrivateKey 280 | - [x] getPublicKeyFromPrivateKey 281 | - [x] getAddressFromPublicKey 282 | - [x] getAddressFromPrivateKey 283 | - [x] encryptPrivateKey 284 | - [x] decryptPrivateKey 285 | 286 | ### JSON-RPC API 287 | 288 | #### Blockchain-related methods 289 | 290 | - [x] getNetworkId 291 | - [x] getBlockchainInfo 292 | - [x] getShardingStructure 293 | - [x] getDsBlock 294 | - [x] getLatestDsBlock 295 | - [x] getNumDSBlocks 296 | - [x] getDSBlockRate 297 | - [x] getDSBlockListing 298 | - [x] getTxBlock 299 | - [x] getLatestTxBlock 300 | - [x] getNumTxBlocks 301 | - [x] getTxBlockRate 302 | - [x] getTxBlockListing 303 | - [x] getNumTransactions 304 | - [x] getTransactionRate 305 | - [x] getCurrentMiniEpoch 306 | - [x] getCurrentDSEpoch 307 | - [x] getPrevDifficulty 308 | - [x] getPrevDSDifficulty 309 | 310 | #### Transaction-related methods 311 | 312 | - [x] createTransaction 313 | - [x] getTransaction 314 | - [x] getRecentTransactions 315 | - [x] getTransactionsForTxBlock 316 | - [x] getNumTxnsTxEpoch 317 | - [x] getNumTxnsDSEpoch 318 | - [x] getMinimumGasPrice 319 | 320 | #### Contract-related methods 321 | 322 | - [x] getSmartContractCode 323 | - [x] getSmartContractInit 324 | - [x] getSmartContractState 325 | - [x] getSmartContracts 326 | - [x] getContractAddressFromTransactionID 327 | 328 | #### Account-related methods 329 | 330 | - [x] getBalance 331 | 332 | ### Validation 333 | 334 | - [x] isAddress 335 | - [x] isPublicjKey 336 | - [x] isPrivateKey 337 | - [x] isSignature 338 | 339 | ### Util 340 | 341 | - [x] byteArrayToHexString 342 | - [x] hexStringToByteArray 343 | - [x] generateMac 344 | - [x] isByteString 345 | - [x] encodeTransactionProto 346 | - [x] toChecksumAddress 347 | - [x] isValidChecksumAddress 348 | - [x] base58.encode 349 | - [x] base58.decode 350 | - [x] isBase58 351 | - [x] bech32 encode decode 352 | - [x] fromBech32Address toBech32Address 353 | 354 | ## Build and Installation 355 | 356 | you can build jar using following command : 357 | 358 | ``` 359 | gradle build -x test 360 | ``` 361 | 362 | and you can also try our snapshot version by 363 | 364 | gradle: (please see our example project) 365 | 366 | ```groovy 367 | repositories { 368 | mavenCentral() 369 | maven { 370 | url "https://oss.sonatype.org/content/groups/public" 371 | } 372 | } 373 | 374 | dependencies { 375 | testCompile group: 'junit', name: 'junit', version: '4.12' 376 | compile group: 'org.firestack', name: 'laksaj', version: '0.4.5-SNAPSHOT' 377 | } 378 | 379 | ``` 380 | 381 | maven: 382 | 383 | ```xml 384 | 385 | org.firestack 386 | laksaj 387 | 1.0.5-SNAPSHOT 388 | 389 | ``` 390 | 391 | 392 | 393 | 394 | 395 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'maven-publish' 3 | apply plugin: 'signing' 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | 10 | group 'com.firestack' 11 | version '1.1.0-RELEASE' 12 | 13 | 14 | sourceCompatibility = 1.8 15 | 16 | 17 | dependencies { 18 | compile files(org.gradle.internal.jvm.Jvm.current().toolsJar) 19 | compile group: 'junit', name: 'junit', version: '4.12' 20 | compile "org.web3j:core:4.2.0" 21 | compileOnly("org.projectlombok:lombok:1.18.4") 22 | annotationProcessor "org.projectlombok:lombok:1.18.4" 23 | compile 'com.google.guava:guava:27.0.1-jre' 24 | compile group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.6.0' 25 | implementation 'com.google.code.gson:gson:2.8.5' 26 | compile group: 'com.google.protobuf', name: 'protobuf-java', version: '3.6.1' 27 | } 28 | 29 | test { 30 | // show standard out and standard error of the test JVM(s) on the console 31 | testLogging.showStandardStreams = true 32 | } 33 | 34 | //create a single Jar with all dependencies 35 | task fatJar(type: Jar) { 36 | manifest { 37 | } 38 | baseName = project.name + '-all' 39 | from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } } 40 | with jar 41 | } 42 | 43 | task sourceJar(type: Jar) { 44 | classifier "sources" 45 | from sourceSets.main.allJava 46 | } 47 | 48 | task javadocJar(type: Jar, dependsOn: javadoc) { 49 | classifier "javadoc" 50 | from javadoc.destinationDir 51 | } 52 | 53 | artifacts { 54 | archives jar 55 | archives sourceJar 56 | archives javadocJar 57 | } 58 | 59 | signing { 60 | sign configurations.archives 61 | } 62 | 63 | publishing { 64 | publications { 65 | mavenJava(MavenPublication) { 66 | customizePom(pom) 67 | groupId = 'org.firestack' 68 | artifactId = 'laksaj' 69 | version = '1.1.0-RELEASE' 70 | from components.java 71 | 72 | // create the sign pom artifact 73 | pom.withXml { 74 | def pomFile = file("${project.buildDir}/generated-pom.xml") 75 | writeTo(pomFile) 76 | def pomAscFile = signing.sign(pomFile).signatureFiles[0] 77 | artifact(pomAscFile) { 78 | classifier = null 79 | extension = 'pom.asc' 80 | } 81 | } 82 | 83 | artifact(sourceJar) { 84 | classifier = 'sources' 85 | } 86 | artifact(javadocJar) { 87 | classifier = 'javadoc' 88 | } 89 | 90 | // create the signed artifacts 91 | project.tasks.signArchives.signatureFiles.each { 92 | artifact(it) { 93 | def matcher = it.file =~ /-(sources|javadoc)\.jar\.asc$/ 94 | if (matcher.find()) { 95 | classifier = matcher.group(1) 96 | } else { 97 | classifier = null 98 | } 99 | extension = 'jar.asc' 100 | } 101 | } 102 | } 103 | } 104 | repositories { 105 | maven { 106 | // snapshot 107 | // url = "https://oss.sonatype.org/content/repositories/snapshots" 108 | // staging(release on page) 109 | url = "https://oss.sonatype.org/service/local/staging/deploy/maven2" 110 | credentials { 111 | username 'xiaohuo' 112 | password System.properties['mavenpassword'] 113 | } 114 | } 115 | 116 | } 117 | } 118 | 119 | def customizePom(pom) { 120 | pom.withXml { 121 | def root = asNode() 122 | 123 | // eliminate test-scoped dependencies (no need in maven central POMs) 124 | root.dependencies.removeAll { dep -> 125 | dep.scope == "test" 126 | } 127 | 128 | // add all items necessary for maven central publication 129 | root.children().last() + { 130 | resolveStrategy = Closure.DELEGATE_FIRST 131 | 132 | description 'Zilliqa java library' 133 | name 'zilliqa.com Java' 134 | url 'https://github.com/FireStack-Lab/LaksaJ' 135 | organization { 136 | name 'com.zilliqa' 137 | url 'https://github.com/zilliqa' 138 | } 139 | issueManagement { 140 | system 'GitHub' 141 | url 'https://github.com/FireStack-Lab/LaksaJ/issues' 142 | } 143 | licenses { 144 | license { 145 | name 'Apache License 2.0' 146 | url 'https://github.com/FireStack-Lab/LaksaJ/blob/master/LICENSE' 147 | distribution 'repo' 148 | } 149 | } 150 | scm { 151 | url 'https://github.com/FireStack-Lab/LaksaJ' 152 | connection 'scm:git:git://github.com/FireStack-Lab/LaksaJ.git' 153 | developerConnection 'scm:git:ssh://git@github.com:FireStack-Lab/LaksaJ.git' 154 | } 155 | developers { 156 | developer { 157 | name 'xiaohuo200@gmail.com' 158 | } 159 | } 160 | } 161 | } 162 | } 163 | 164 | model { 165 | tasks.generatePomFileForMavenJavaPublication { 166 | destination = file("$buildDir/generated-pom.xml") 167 | } 168 | tasks.publishMavenJavaPublicationToMavenLocal { 169 | dependsOn project.tasks.signArchives 170 | } 171 | tasks.publishMavenJavaPublicationToMavenRepository { 172 | dependsOn project.tasks.signArchives 173 | } 174 | } -------------------------------------------------------------------------------- /example/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | } 4 | 5 | group 'com.firestack' 6 | version '0.0.1-SNAPSHOT' 7 | 8 | sourceCompatibility = 1.8 9 | 10 | repositories { 11 | mavenCentral() 12 | maven { 13 | url "https://oss.sonatype.org/content/repositories/snapshots" 14 | } 15 | } 16 | 17 | dependencies { 18 | compile group: 'org.firestack', name: 'laksaj', version: '1.1.0-RELEASE' 19 | } 20 | -------------------------------------------------------------------------------- /example/src/main/java/com/firestack/example/GenerateAddress.java: -------------------------------------------------------------------------------- 1 | package com.firestack.example; 2 | 3 | import com.firestack.laksaj.crypto.KeyTools; 4 | import com.firestack.laksaj.utils.ByteUtil; 5 | import org.web3j.crypto.ECKeyPair; 6 | 7 | import java.math.BigInteger; 8 | import java.security.InvalidAlgorithmParameterException; 9 | import java.security.NoSuchAlgorithmException; 10 | import java.security.NoSuchProviderException; 11 | 12 | 13 | public class GenerateAddress { 14 | //How to generate large amount of addresses 15 | public static void main(String[] args) throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { 16 | int n = 0; 17 | while (n < 100) { 18 | System.out.println("--------------------------"); 19 | System.out.println("generate nth keypair:"); 20 | ECKeyPair keyPair = KeyTools.generateKeyPair(); 21 | BigInteger privateInteger = keyPair.getPrivateKey(); 22 | BigInteger publicInteger = keyPair.getPublicKey(); 23 | System.out.println("private key is: " + privateInteger.toString(16)); 24 | System.out.println("public key is: " + publicInteger.toString(16)); 25 | System.out.println("address is: " + KeyTools.getAddressFromPublicKey(ByteUtil.byteArrayToHexString(publicInteger.toByteArray()))); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /example/src/main/java/com/firestack/example/GetTransactionStatus.java: -------------------------------------------------------------------------------- 1 | package com.firestack.example; 2 | 3 | import com.firestack.laksaj.jsonrpc.HttpProvider; 4 | import com.firestack.laksaj.transaction.TransactionStatus; 5 | 6 | import java.io.IOException; 7 | 8 | public class GetTransactionStatus { 9 | public static void main(String[] args) throws IOException { 10 | HttpProvider provider = new HttpProvider("https://api.zilliqa.com"); 11 | TransactionStatus status = provider.getTransactionStatus("347a3d1f7393843b547b2d341a69b092473a26cb531eb8aabaffe1c790e1c70e").getResult(); 12 | System.out.println(status); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /example/src/main/java/com/firestack/example/SmartContractDeposit.java: -------------------------------------------------------------------------------- 1 | package com.firestack.example; 2 | 3 | import com.firestack.laksaj.exception.ZilliqaAPIException; 4 | import com.firestack.laksaj.jsonrpc.HttpProvider; 5 | 6 | import java.io.IOException; 7 | 8 | public class SmartContractDeposit { 9 | public static void main(String[] args) throws IOException, InterruptedException, ZilliqaAPIException { 10 | HttpProvider client = new HttpProvider("https://api.zilliqa.com/"); 11 | String lastEpoch = client.getNumTxnsTxEpoch().getResult(); 12 | String lastStateList = client.getSmartContractState("D6606D02DFF929593312D8D0D36105E376F95AA0"); 13 | 14 | System.out.println("last epoch is " + lastEpoch); 15 | System.out.println("last state:" + lastStateList); 16 | 17 | int n = 0; 18 | 19 | while (true) { 20 | String epoch = client.getNumTxnsTxEpoch().getResult(); 21 | System.out.println(n + "th current epoch is: " + epoch); 22 | if (!lastEpoch.equals(epoch)) { 23 | System.out.println("epoch hash changed"); 24 | String stateList = client.getSmartContractState("D6606D02DFF929593312D8D0D36105E376F95AA0"); 25 | System.out.println("last state: " + lastStateList); 26 | System.out.println("current state: " + stateList); 27 | lastEpoch = epoch; 28 | lastStateList = stateList; 29 | } 30 | Thread.sleep(3000); 31 | n += 1; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /example/src/main/java/com/firestack/example/TransactionOperation.java: -------------------------------------------------------------------------------- 1 | package com.firestack.example; 2 | 3 | import com.firestack.laksaj.account.Wallet; 4 | import com.firestack.laksaj.contract.Contract; 5 | import com.firestack.laksaj.contract.ContractFactory; 6 | import com.firestack.laksaj.contract.DeployParams; 7 | import com.firestack.laksaj.contract.Value; 8 | import com.firestack.laksaj.jsonrpc.HttpProvider; 9 | import com.firestack.laksaj.transaction.Transaction; 10 | import com.firestack.laksaj.transaction.TransactionFactory; 11 | import javafx.util.Pair; 12 | 13 | import java.math.BigInteger; 14 | import java.util.Arrays; 15 | import java.util.List; 16 | 17 | import static com.firestack.laksaj.account.Wallet.pack; 18 | 19 | public class TransactionOperation { 20 | public static void main(String[] args) throws Exception { 21 | Wallet wallet = new Wallet(); 22 | String ptivateKey = "e19d05c5452598e24caad4a0d85a49146f7be089515c905ae6a19e8a578a6930"; 23 | // Populate the wallet with an account 24 | String address = wallet.addByPrivateKey(ptivateKey); 25 | HttpProvider provider = new HttpProvider("https://dev-api.zilliqa.com"); 26 | //get balance 27 | HttpProvider.BalanceResult balanceResult = provider.getBalance(address).getResult(); 28 | System.out.println("balance is: " + balanceResult.getBalance()); 29 | 30 | //construct non-contract transaction 31 | Transaction transaction = Transaction.builder() 32 | .version(String.valueOf(pack(333, 1))) 33 | .toAddr("zil16jrfrs8vfdtc74yzhyy83je4s4c5sqrcasjlc4") 34 | .senderPubKey("0246e7178dc8253201101e18fd6f6eb9972451d121fc57aa2a06dd5c111e58dc6a") 35 | .amount("10000000") 36 | .gasPrice("1000000000") 37 | .gasLimit("1") 38 | .code("") 39 | .data("") 40 | .provider(new HttpProvider("https://api.zilliqa.com/")) 41 | .build(); 42 | 43 | //sign transaction 44 | transaction = wallet.sign(transaction); 45 | System.out.println("signature is: " + transaction.getSignature()); 46 | 47 | //broadcast transaction 48 | HttpProvider.CreateTxResult result = TransactionFactory.createTransaction(transaction); 49 | System.out.println(result); 50 | 51 | //hello-world contract 52 | String code = "scilla_version 0\n" + 53 | "\n" + 54 | " (* HelloWorld contract *)\n" + 55 | "\n" + 56 | " import ListUtils\n" + 57 | "\n" + 58 | " (***************************************************)\n" + 59 | " (* Associated library *)\n" + 60 | " (***************************************************)\n" + 61 | " library HelloWorld\n" + 62 | "\n" + 63 | " let one_msg =\n" + 64 | " fun (msg : Message) =>\n" + 65 | " let nil_msg = Nil {Message} in\n" + 66 | " Cons {Message} msg nil_msg\n" + 67 | "\n" + 68 | " let not_owner_code = Int32 1\n" + 69 | " let set_hello_code = Int32 2\n" + 70 | "\n" + 71 | " (***************************************************)\n" + 72 | " (* The contract definition *)\n" + 73 | " (***************************************************)\n" + 74 | "\n" + 75 | " contract HelloWorld\n" + 76 | " (owner: ByStr20)\n" + 77 | "\n" + 78 | " field welcome_msg : String = \"\"\n" + 79 | "\n" + 80 | " transition setHello (msg : String)\n" + 81 | " is_owner = builtin eq owner _sender;\n" + 82 | " match is_owner with\n" + 83 | " | False =>\n" + 84 | " msg = {_tag : \"TransactionOperation\"; _recipient : _sender; _amount : Uint128 0; code : not_owner_code};\n" + 85 | " msgs = one_msg msg;\n" + 86 | " send msgs\n" + 87 | " | True =>\n" + 88 | " welcome_msg := msg;\n" + 89 | " msg = {_tag : \"TransactionOperation\"; _recipient : _sender; _amount : Uint128 0; code : set_hello_code};\n" + 90 | " msgs = one_msg msg;\n" + 91 | " send msgs\n" + 92 | " end\n" + 93 | " end\n" + 94 | "\n" + 95 | "\n" + 96 | " transition getHello ()\n" + 97 | " r <- welcome_msg;\n" + 98 | " e = {_eventname: \"getHello()\"; msg: r};\n" + 99 | " event e\n" + 100 | " end"; 101 | List init = Arrays.asList(Value.builder().vname("_scilla_version").type("Uint32").value("0").build(), Value.builder().vname("owner").type("ByStr20").value("0x9bfec715a6bd658fcb62b0f8cc9bfa2ade71434a").build()); 102 | 103 | ContractFactory factory = ContractFactory.builder().provider(new HttpProvider("https://api.zilliqa.com/")).signer(wallet).build(); 104 | 105 | Contract contract = factory.newContract(code, (Value[]) init.toArray(), ""); 106 | 107 | DeployParams deployParams = DeployParams.builder().version(String.valueOf(pack(2, 8))).gasPrice("1000000000").gasLimit("10000").senderPubKey("0246e7178dc8253201101e18fd6f6eb9972451d121fc57aa2a06dd5c111e58dc6a").build(); 108 | 109 | //deploy contract, this will take a while to track transaction util it been confirmed or failed 110 | Pair deployResult = contract.deploy(deployParams, 300, 3); 111 | System.out.println("result is: " + deployResult); 112 | System.out.println("cumulative gas is: " + deployResult.getKey().getReceipt().getCumulative_gas()); 113 | 114 | //calculate transaction fee 115 | String transactionFee = new BigInteger(deployResult.getKey().getReceipt().getCumulative_gas()).multiply(new BigInteger(deployResult.getKey().getGasPrice())).toString(); 116 | System.out.println("transaction fee is: " + transactionFee); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /example/src/main/java/com/firestack/example/ValidChecksumAddress.java: -------------------------------------------------------------------------------- 1 | package com.firestack.example; 2 | 3 | import com.firestack.laksaj.utils.Validation; 4 | 5 | public class ValidChecksumAddress { 6 | public static void main(String[] args) { 7 | String checksumAddress = "0x4BAF5faDA8e5Db92C3d3242618c5B47133AE003C"; 8 | boolean isChecksumAddress = Validation.isValidChecksumAddress(checksumAddress); 9 | System.out.println("is checksum address: " + isChecksumAddress); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /example/src/main/java/com/firestack/example/ValidateAddress.java: -------------------------------------------------------------------------------- 1 | package com.firestack.example; 2 | 3 | import com.firestack.laksaj.utils.Validation; 4 | 5 | public class ValidateAddress { 6 | public static void main(String[] args) { 7 | String address = "2624B9EA4B1CD740630F6BF2FEA82AAC0067070B"; 8 | boolean isAddress = Validation.isAddress(address); 9 | System.out.println("is address: " + isAddress); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'laksaj' 2 | include 'example' 3 | 4 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/account/Account.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.account; 2 | 3 | import com.firestack.laksaj.contract.Contract; 4 | import com.firestack.laksaj.crypto.KDFType; 5 | import com.firestack.laksaj.crypto.KeyTools; 6 | import com.firestack.laksaj.utils.Bech32; 7 | import com.firestack.laksaj.utils.ByteUtil; 8 | import com.firestack.laksaj.utils.HashUtil; 9 | import com.firestack.laksaj.utils.Validation; 10 | import lombok.Data; 11 | import org.web3j.crypto.ECKeyPair; 12 | 13 | import java.math.BigInteger; 14 | import java.security.NoSuchAlgorithmException; 15 | 16 | @Data 17 | public class Account { 18 | private ECKeyPair keys; 19 | private String address; 20 | 21 | public Account(ECKeyPair keys) throws NoSuchAlgorithmException { 22 | this.keys = keys; 23 | this.address = KeyTools.getAddressFromPublicKey(this.keys.getPublicKey().toString(16)); 24 | } 25 | 26 | public Account(String privateKey) throws NoSuchAlgorithmException { 27 | String publicKey = KeyTools.getPublicKeyFromPrivateKey(privateKey, true); 28 | this.address = KeyTools.getAddressFromPublicKey(publicKey); 29 | this.keys = new ECKeyPair(new BigInteger(privateKey, 16), new BigInteger(publicKey, 16)); 30 | } 31 | 32 | ; 33 | 34 | public static Account fromFile(String file, String passphrase) throws Exception { 35 | String privateKey = KeyTools.decryptPrivateKey(file, passphrase); 36 | return new Account(privateKey); 37 | } 38 | 39 | public String toFile(String privateKey, String passphrase, KDFType type) throws Exception { 40 | return KeyTools.encryptPrivateKey(privateKey, passphrase, type); 41 | } 42 | 43 | public String getPublicKey() { 44 | return ByteUtil.byteArrayToHexString(this.keys.getPublicKey().toByteArray()); 45 | } 46 | 47 | public String getPrivateKey() { 48 | return ByteUtil.byteArrayToHexString(this.keys.getPrivateKey().toByteArray()); 49 | } 50 | 51 | public static String toCheckSumAddress(String address) { 52 | if (!Validation.isAddress(address)) { 53 | throw new RuntimeException("not a valid base 16 address"); 54 | } 55 | address = address.toLowerCase().replace("0x", ""); 56 | String hash = ByteUtil.byteArrayToHexString(HashUtil.sha256(ByteUtil.hexStringToByteArray(address))); 57 | StringBuilder ret = new StringBuilder("0x"); 58 | BigInteger v = new BigInteger(ByteUtil.hexStringToByteArray(hash)); 59 | for (int i = 0; i < address.length(); i++) { 60 | if ("1234567890".indexOf(address.charAt(i)) != -1) { 61 | ret.append(address.charAt(i)); 62 | } else { 63 | BigInteger checker = v.and(BigInteger.valueOf(2l).pow(255 - 6 * i)); 64 | ret.append(checker.compareTo(BigInteger.valueOf(1l)) < 0 ? String.valueOf(address.charAt(i)).toLowerCase() : String.valueOf(address.charAt(i)).toUpperCase()); 65 | } 66 | } 67 | return ret.toString(); 68 | } 69 | 70 | public static String normaliseAddress(String address) throws Exception { 71 | if (address.equals(Contract.NIL_ADDRESS)) { 72 | return address; 73 | } 74 | if (Validation.isBech32(address)) { 75 | return Bech32.fromBech32Address(address); 76 | } 77 | 78 | if (Validation.isAddress(address)) { 79 | return toCheckSumAddress(address).substring(2); 80 | } 81 | 82 | throw new Exception("Address format is invalid"); 83 | } 84 | 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/account/Wallet.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.account; 2 | 3 | import com.firestack.laksaj.crypto.KeyTools; 4 | import com.firestack.laksaj.crypto.Schnorr; 5 | import com.firestack.laksaj.crypto.Signature; 6 | import com.firestack.laksaj.jsonrpc.HttpProvider; 7 | import com.firestack.laksaj.transaction.Transaction; 8 | import com.firestack.laksaj.transaction.TxParams; 9 | import com.firestack.laksaj.utils.Bech32; 10 | import com.firestack.laksaj.utils.Validation; 11 | 12 | import java.io.IOException; 13 | import java.security.InvalidAlgorithmParameterException; 14 | import java.security.NoSuchAlgorithmException; 15 | import java.security.NoSuchProviderException; 16 | import java.util.*; 17 | 18 | 19 | /** 20 | * all address should be upper case 21 | */ 22 | public class Wallet { 23 | private Map accounts = new HashMap<>(); 24 | private HttpProvider provider; 25 | private Optional defaultAccount; 26 | 27 | public Wallet() { 28 | defaultAccount = Optional.empty(); 29 | provider = new HttpProvider("https://api.zilliqa.com/"); 30 | } 31 | 32 | public void setProvider(HttpProvider provider) { 33 | this.provider = provider; 34 | } 35 | 36 | public Wallet(Map accounts, HttpProvider provider) { 37 | this.accounts = accounts; 38 | this.provider = provider; 39 | 40 | if (accounts.size() > 0) { 41 | defaultAccount = Optional.of(accounts.values().iterator().next()); 42 | } else { 43 | Optional.empty(); 44 | } 45 | } 46 | 47 | public String createAccount() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { 48 | Account account = new Account(KeyTools.generateKeyPair()); 49 | this.accounts.put(account.getAddress().toUpperCase(), account); 50 | 51 | if (!defaultAccount.isPresent()) { 52 | defaultAccount = Optional.of(account); 53 | } 54 | return account.getAddress(); 55 | } 56 | 57 | public String addByPrivateKey(String privateKey) throws NoSuchAlgorithmException { 58 | Account account = new Account(privateKey); 59 | this.accounts.put(account.getAddress(), account); 60 | if (!defaultAccount.isPresent()) { 61 | defaultAccount = Optional.of(account); 62 | } 63 | return account.getAddress(); 64 | } 65 | 66 | public String addByKeyStore(String keystore, String passphrase) throws Exception { 67 | Account account = Account.fromFile(keystore, passphrase); 68 | this.accounts.put(account.getAddress(), account); 69 | 70 | if (!defaultAccount.isPresent()) { 71 | defaultAccount = Optional.of(account); 72 | } 73 | return account.getAddress(); 74 | } 75 | 76 | public void setDefault(String address) { 77 | this.defaultAccount = Optional.of(accounts.get(address)); 78 | } 79 | 80 | public void remove(String address) { 81 | Account toRemove = accounts.get(address); 82 | if (null != toRemove) { 83 | accounts.remove(address); 84 | if (defaultAccount.isPresent() && defaultAccount.get().getAddress().equals(toRemove.getAddress())) { 85 | if (!accounts.values().isEmpty()) { 86 | defaultAccount = Optional.of(accounts.values().iterator().next()); 87 | } else { 88 | defaultAccount = Optional.empty(); 89 | } 90 | } 91 | } 92 | } 93 | 94 | public List batchSign(List transactions) throws Exception { 95 | for (int i = 0; i < transactions.size(); i++) { 96 | this.sign(transactions.get(i)); 97 | } 98 | return transactions; 99 | } 100 | 101 | public Transaction sign(Transaction transaction) throws Exception { 102 | 103 | if (transaction.getToAddr().startsWith("0x") || transaction.getToAddr().startsWith("0X")) { 104 | transaction.setToAddr(transaction.getToAddr().substring(2)); 105 | } 106 | 107 | if (!Validation.isBech32(transaction.getToAddr()) && !Validation.isValidChecksumAddress("0x" + transaction.getToAddr())) { 108 | throw new Exception("not checksum address or bech32"); 109 | } 110 | 111 | 112 | if (Validation.isBech32(transaction.getToAddr())) { 113 | transaction.setToAddr(Bech32.fromBech32Address(transaction.getToAddr())); 114 | } 115 | 116 | if (Validation.isValidChecksumAddress("0x" + transaction.getToAddr())) { 117 | transaction.setToAddr("0x" + transaction.getToAddr()); 118 | } 119 | 120 | 121 | TxParams txParams = transaction.toTransactionParam(); 122 | 123 | if (Objects.nonNull(txParams) && !txParams.getSenderPubKey().isEmpty()) { 124 | String address = KeyTools.getAddressFromPublicKey(txParams.getSenderPubKey()).toUpperCase(); 125 | Account account = accounts.get(address); 126 | if (Objects.isNull(account)) { 127 | throw new IllegalArgumentException("Could not sign the transaction with" + address + " as it does not exist"); 128 | } 129 | return signWith(transaction, account); 130 | } 131 | 132 | if (!this.defaultAccount.isPresent()) { 133 | throw new IllegalArgumentException("This wallet has no default account."); 134 | } 135 | 136 | 137 | return this.signWith(transaction, this.defaultAccount.get()); 138 | 139 | } 140 | 141 | public Transaction signWith(Transaction tx, Account signer) throws IOException, NoSuchAlgorithmException { 142 | HttpProvider.BalanceResult result; 143 | if (Objects.isNull(signer)) { 144 | throw new IllegalArgumentException("account not exists"); 145 | } 146 | if (Objects.isNull(tx.getNonce()) || tx.getNonce().isEmpty()) { 147 | try { 148 | result = this.provider.getBalance(signer.getAddress()).getResult(); 149 | tx.setNonce(String.valueOf(Integer.valueOf(result.getNonce()) + 1)); 150 | } catch (IOException e) { 151 | throw new IllegalArgumentException("cannot get nonce", e); 152 | } 153 | } 154 | tx.setSenderPubKey(signer.getPublicKey()); 155 | byte[] message = tx.bytes(); 156 | Signature signature = Schnorr.sign(signer.getKeys(), message); 157 | tx.setSignature(signature.toString().toLowerCase()); 158 | return tx; 159 | } 160 | 161 | public static int pack(int a, int b) { 162 | return (a << 16) + b; 163 | } 164 | 165 | } 166 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/blockchain/BlockList.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.blockchain; 2 | 3 | 4 | import lombok.Builder; 5 | import lombok.Data; 6 | 7 | @Data 8 | @Builder 9 | public class BlockList { 10 | private BlockShort[] data; 11 | private int maxPages; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/blockchain/BlockShort.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.blockchain; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | @Data 7 | @Builder 8 | public class BlockShort { 9 | private int BlockNum; 10 | private String Hash; 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/blockchain/BlockType.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.blockchain; 2 | 3 | public enum BlockType { 4 | MICRO, FINAL 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/blockchain/BlockchainInfo.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.blockchain; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | @Data 7 | @Builder 8 | public class BlockchainInfo { 9 | private int NumPeers; 10 | private String NumTxBlocks; 11 | private String NumDSBlocks; 12 | private String NumTransactions; 13 | private String TransactionRate; 14 | private double TxBlockRate; 15 | private double DSBlockRate; 16 | private String CurrentMiniEpoch; 17 | private String CurrentDSEpoch; 18 | private String NumTxnsDSEpoch; 19 | private int NumTxnsTxEpoch; 20 | private ShardingStructure ShardingStructure; 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/blockchain/Contract.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.blockchain; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class Contract { 7 | private String address; 8 | private State[] state; 9 | 10 | @Data 11 | public static class State { 12 | private String type; 13 | private Object value; 14 | private String vname; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/blockchain/DsBlock.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.blockchain; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @Builder 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | public class DsBlock { 13 | private DsBlockHeader header; 14 | private String signature; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/blockchain/DsBlockHeader.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.blockchain; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | 7 | @Data 8 | @Builder 9 | public class DsBlockHeader { 10 | @SerializedName("BlockNum") 11 | private String blockNumber; 12 | @SerializedName("Difficulty") 13 | private int difficulty; 14 | @SerializedName("DifficultyDS") 15 | private int diffcultyDS; 16 | @SerializedName("GasPrice") 17 | private int gasPrice; 18 | @SerializedName("LeaderPubKey") 19 | private String leaderPublicKey; 20 | @SerializedName("PoWWinners") 21 | private String[] powWinners; 22 | @SerializedName("PrevHash") 23 | private String previousHash; 24 | @SerializedName("Timestamp") 25 | private String timestamp; 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/blockchain/EventLogEntry.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.blockchain; 2 | 3 | public class EventLogEntry { 4 | protected String address; 5 | protected String eventName; 6 | protected EventParam[] params; 7 | } 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/blockchain/EventParam.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.blockchain; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | @Data 7 | @Builder 8 | public class EventParam { 9 | private String vname; 10 | private String type; 11 | private String value; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/blockchain/ShardingStructure.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.blockchain; 2 | 3 | 4 | import lombok.Builder; 5 | import lombok.Data; 6 | 7 | @Data 8 | @Builder 9 | public class ShardingStructure { 10 | private int[] NumPeers; 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/blockchain/TransactionList.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.blockchain; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | @Data 7 | @Builder 8 | public class TransactionList { 9 | private int number; 10 | private String[] TxnHashes; 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/blockchain/TransactionReceipt.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.blockchain; 2 | 3 | 4 | import lombok.Builder; 5 | import lombok.Data; 6 | 7 | import java.util.List; 8 | 9 | @Data 10 | @Builder 11 | public class TransactionReceipt { 12 | private boolean success; 13 | private String cumulative_gas; 14 | private String epoch_num; 15 | //optional JsonArray 16 | private List event_logs; 17 | private List transitions; 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/blockchain/TxBlock.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.blockchain; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | @Data 7 | @Builder 8 | public class TxBlock { 9 | 10 | @Data 11 | @Builder 12 | public static class MicroBlockInfo { 13 | private String MicroBlockHash; 14 | private int MicroBlockShardId; 15 | private String MicroBlockTxnRootHash; 16 | } 17 | 18 | @Data 19 | @Builder 20 | public static class Body { 21 | private String BlockHash; 22 | private String HeaderSign; 23 | private MicroBlockInfo[] MicroBlockInfos; 24 | } 25 | 26 | private Body body; 27 | private TxBlockHeader header; 28 | } -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/blockchain/TxBlockHeader.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.blockchain; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | @Data 7 | @Builder 8 | public class TxBlockHeader { 9 | private String BlockNum; 10 | private String DSBlockNum; 11 | private String GasLimit; 12 | private String GasUsed; 13 | private String MbInfoHash; 14 | private String MinerPubKey; 15 | private int NumMicroBlocks; 16 | private int NumTxns; 17 | private String PrevBlockHash; 18 | private String Rewards; 19 | private String StateDeltaHash; 20 | private String StateRootHash; 21 | private String Timestamp; 22 | private int Type; 23 | private int Version; 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/contract/ABI.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.contract; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | @Data 7 | @Builder 8 | public class ABI { 9 | private String name; 10 | private Field[] fields; 11 | private Field[] params; 12 | private Transition[] transitions; 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/contract/CallParams.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.contract; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | @Data 7 | @Builder 8 | public class CallParams { 9 | private String ID; 10 | private String version; 11 | private String nonce; 12 | private String amount; 13 | private String gasPrice; 14 | private String gasLimit; 15 | private String senderPubKey; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/contract/Contract.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.contract; 2 | 3 | import com.firestack.laksaj.account.Account; 4 | import com.firestack.laksaj.account.Wallet; 5 | import com.firestack.laksaj.jsonrpc.HttpProvider; 6 | import com.firestack.laksaj.transaction.Transaction; 7 | import com.firestack.laksaj.transaction.TxStatus; 8 | import com.google.gson.Gson; 9 | import javafx.util.Pair; 10 | import lombok.Builder; 11 | import lombok.Data; 12 | 13 | import java.io.IOException; 14 | import java.util.List; 15 | 16 | 17 | @Data 18 | public class Contract { 19 | public static String NIL_ADDRESS = "0x0000000000000000000000000000000000000000"; 20 | 21 | private ContractFactory contractFactory; 22 | private Value[] init; 23 | private String abi; 24 | private List state; 25 | private String address; 26 | private String code; 27 | private ContractStatus contractStatus; 28 | 29 | private Wallet signer; 30 | private HttpProvider provider; 31 | 32 | public Contract(ContractFactory factory, String code, String abi, String address, Value[] init, List state) throws Exception { 33 | this.contractFactory = factory; 34 | this.provider = factory.getProvider(); 35 | this.signer = factory.getSigner(); 36 | if (null != address && !address.isEmpty()) { 37 | this.abi = abi; 38 | this.address = Account.normaliseAddress(address); 39 | this.init = init; 40 | this.state = state; 41 | this.code = code; 42 | this.contractStatus = ContractStatus.Deployed; 43 | } else { 44 | this.abi = abi; 45 | this.code = code; 46 | this.init = init; 47 | this.contractStatus = ContractStatus.Initialised; 48 | } 49 | } 50 | 51 | public Pair deploy(DeployParams params, int attempts, int interval) throws Exception { 52 | if (null == this.code || this.code.isEmpty() || null == this.init || this.init.length == 0) { 53 | throw new IllegalArgumentException("Cannot deploy without code or initialisation parameters."); 54 | } 55 | Gson gson = new Gson(); 56 | Transaction transaction = Transaction.builder() 57 | .ID(params.getID()) 58 | .version(params.getVersion()) 59 | .nonce(params.getNonce()) 60 | .gasPrice(params.getGasPrice()) 61 | .gasLimit(params.getGasLimit()) 62 | .senderPubKey(params.getSenderPubKey()) 63 | .toAddr(NIL_ADDRESS) 64 | .amount("0") 65 | .code(this.code.replace("/\\", "")) 66 | .data(gson.toJson(this.init).replace("/\\", "")) 67 | .provider(this.provider) 68 | .build(); 69 | transaction = this.prepareTx(transaction, attempts, interval); 70 | if (transaction.isRejected()) { 71 | this.contractStatus = ContractStatus.Rejected; 72 | Pair pair = new Pair<>(transaction, this); 73 | return pair; 74 | } 75 | 76 | this.contractStatus = ContractStatus.Deployed; 77 | this.address = ContractFactory.getAddressForContract(transaction); 78 | Pair pair = new Pair<>(transaction, this); 79 | return pair; 80 | } 81 | 82 | public HttpProvider.CreateTxResult deployWithoutConfirm(DeployParams params) throws Exception { 83 | if (null == this.code || this.code.isEmpty() || null == this.init || this.init.length == 0) { 84 | throw new IllegalArgumentException("Cannot deploy without code or initialisation parameters."); 85 | } 86 | Gson gson = new Gson(); 87 | Transaction transaction = Transaction.builder() 88 | .ID(params.getID()) 89 | .version(params.getVersion()) 90 | .nonce(params.getNonce()) 91 | .gasPrice(params.getGasPrice()) 92 | .gasLimit(params.getGasLimit()) 93 | .senderPubKey(params.getSenderPubKey()) 94 | .toAddr(NIL_ADDRESS) 95 | .amount("0") 96 | .code(this.code.replace("/\\", "")) 97 | .data(gson.toJson(this.init).replace("/\\", "")) 98 | .provider(this.provider) 99 | .build(); 100 | return this.prepareTx(transaction); 101 | } 102 | 103 | @Data 104 | @Builder 105 | private static class data { 106 | private String _tag; 107 | private Value[] params; 108 | } 109 | 110 | public Transaction call(String transition, Value[] args, CallParams params, int attempts, int interval) throws Exception { 111 | if (null == this.address || this.address.isEmpty()) { 112 | throw new IllegalArgumentException("Contract has not been deployed!"); 113 | } 114 | 115 | Gson gson = new Gson(); 116 | Transaction transaction = Transaction.builder() 117 | .ID(params.getID()) 118 | .version(params.getVersion()) 119 | .nonce(params.getNonce()) 120 | .amount(params.getAmount()) 121 | .gasPrice(params.getGasPrice()) 122 | .gasLimit(params.getGasLimit()) 123 | .senderPubKey(params.getSenderPubKey()) 124 | .data(gson.toJson(data.builder()._tag(transition).params(args).build())) 125 | .provider(this.provider) 126 | .toAddr(this.address) 127 | .code(this.code.replace("/\\", "")) 128 | .build(); 129 | return this.prepareTx(transaction, attempts, interval); 130 | 131 | } 132 | 133 | public HttpProvider.CreateTxResult prepareTx(Transaction tx) throws Exception { 134 | tx = signer.sign(tx); 135 | HttpProvider.CreateTxResult createTxResult = provider.createTransaction(tx.toTransactionPayload()).getResult(); 136 | return createTxResult; 137 | } 138 | 139 | public Transaction prepareTx(Transaction tx, int attempts, int interval) throws Exception { 140 | tx = signer.sign(tx); 141 | try { 142 | HttpProvider.CreateTxResult createTxResult = provider.createTransaction(tx.toTransactionPayload()).getResult(); 143 | tx.confirm(createTxResult.getTranID(), attempts, interval); 144 | } catch (IOException e) { 145 | e.printStackTrace(); 146 | tx.setStatus(TxStatus.Rejected); 147 | } catch (InterruptedException e) { 148 | e.printStackTrace(); 149 | } 150 | return tx; 151 | } 152 | 153 | public boolean isInitialised() { 154 | return ContractStatus.Initialised.equals(this.contractStatus); 155 | } 156 | 157 | public boolean isDeployed() { 158 | return ContractStatus.Deployed.equals(this.contractStatus); 159 | } 160 | 161 | public boolean isRejected() { 162 | return ContractStatus.Rejected.equals(this.contractStatus); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/contract/ContractFactory.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.contract; 2 | 3 | import com.firestack.laksaj.account.Wallet; 4 | import com.firestack.laksaj.crypto.KeyTools; 5 | import com.firestack.laksaj.jsonrpc.HttpProvider; 6 | import com.firestack.laksaj.transaction.Transaction; 7 | import com.firestack.laksaj.utils.ByteUtil; 8 | import com.firestack.laksaj.utils.Validation; 9 | import lombok.Builder; 10 | import lombok.Data; 11 | 12 | import java.security.MessageDigest; 13 | import java.security.NoSuchAlgorithmException; 14 | 15 | @Data 16 | @Builder 17 | public class ContractFactory { 18 | private Wallet signer; 19 | private HttpProvider provider; 20 | 21 | public static String getAddressForContract(Transaction tx) throws NoSuchAlgorithmException { 22 | MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); 23 | String senderAddress = KeyTools.getAddressFromPublicKey(tx.getSenderPubKey()); 24 | messageDigest.update(ByteUtil.hexStringToByteArray(senderAddress)); 25 | 26 | int nonce = 0; 27 | if (null != tx.getNonce() && !tx.getNonce().isEmpty()) { 28 | nonce = Integer.parseInt(tx.getNonce()); 29 | nonce--; 30 | } 31 | String hexNonce = Validation.intToHex(nonce, 16); 32 | 33 | messageDigest.update(ByteUtil.hexStringToByteArray(hexNonce)); 34 | 35 | byte[] bytes = messageDigest.digest(); 36 | 37 | return ByteUtil.byteArrayToHexString(bytes).substring(24); 38 | } 39 | 40 | public Contract newContract(String code, Value[] init, String abi) throws Exception { 41 | return new Contract(this, code, abi, null, init, null); 42 | } 43 | 44 | public Contract atContract(String address, String code, Value[] init, String abi) throws Exception { 45 | return new Contract(this, code, abi, address, init, null); 46 | } 47 | 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/contract/ContractStatus.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.contract; 2 | 3 | public enum ContractStatus { 4 | Deployed, 5 | Rejected, 6 | Initialised, 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/contract/DeployParams.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.contract; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | @Data 7 | @Builder 8 | public class DeployParams { 9 | private String ID; 10 | private String version; 11 | private String nonce; 12 | private String gasPrice; 13 | private String gasLimit; 14 | private String senderPubKey; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/contract/Field.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.contract; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | @Data 7 | @Builder 8 | public class Field { 9 | private String name; 10 | private String type; 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/contract/Transition.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.contract; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | @Data 7 | @Builder 8 | public class Transition { 9 | private String name; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/contract/Value.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.contract; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | @Data 7 | @Builder 8 | public class Value { 9 | private String vname; 10 | private String type; 11 | private Object value; 12 | } 13 | 14 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/crypto/CustomSecureRandom.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.crypto; 2 | 3 | import java.security.SecureRandom; 4 | 5 | public class CustomSecureRandom extends SecureRandom { 6 | 7 | public CustomSecureRandom() { 8 | super(new LinuxSecureRandom(), new LinuxSecureRandomProvider()); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/crypto/KDFParams.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.crypto; 2 | 3 | public interface KDFParams { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/crypto/KDFType.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.crypto; 2 | 3 | public enum KDFType { 4 | PBKDF2, Scrypt 5 | 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/crypto/KeyStore.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.crypto; 2 | 3 | import com.firestack.laksaj.utils.ByteUtil; 4 | import com.google.gson.Gson; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | 8 | import javax.crypto.Cipher; 9 | import javax.crypto.spec.IvParameterSpec; 10 | import javax.crypto.spec.SecretKeySpec; 11 | import java.io.UnsupportedEncodingException; 12 | import java.util.Arrays; 13 | import java.util.UUID; 14 | 15 | import static com.firestack.laksaj.utils.HashUtil.generateMac; 16 | 17 | public class KeyStore { 18 | private PBKDF2Wrapper pbkdf2Wrapper; 19 | private ScryptWrapper scryptWrapper; 20 | private Gson gson = new Gson(); 21 | 22 | public KeyStore(PBKDF2Wrapper pbkdf2Wrapper, ScryptWrapper scryptWrapper) { 23 | this.pbkdf2Wrapper = pbkdf2Wrapper; 24 | this.scryptWrapper = scryptWrapper; 25 | } 26 | 27 | public static KeyStore defaultKeyStore() { 28 | return new KeyStore(new PBKDF2Wrapper(), new ScryptWrapper()); 29 | } 30 | 31 | public byte[] getDerivedKey(byte[] password, KDFParams params) throws UnsupportedEncodingException { 32 | if (params instanceof PBKDF2Params) { 33 | PBKDF2Params pbkdf2Params = (PBKDF2Params) params; 34 | return pbkdf2Wrapper.getDerivedKey(password, ByteUtil.hexStringToByteArray(pbkdf2Params.getSalt()), pbkdf2Params.getCount(), pbkdf2Params.getDkLen()); 35 | } else if (params instanceof ScryptParams) { 36 | ScryptParams scryptParams = (ScryptParams) params; 37 | return scryptWrapper.getDerivedKey(password, ByteUtil.hexStringToByteArray(scryptParams.getSalt()), scryptParams.getN(), scryptParams.getR(), scryptParams.getP(), scryptParams.getDkLen()); 38 | } else { 39 | throw new IllegalArgumentException("unsupport kdf params"); 40 | } 41 | } 42 | 43 | public String encryptPrivateKey(String privateKey, String passphrase, KDFType type) throws Exception { 44 | String address = KeyTools.getAddressFromPrivateKey(privateKey); 45 | byte[] iv = KeyTools.generateRandomBytes(16); 46 | byte[] saltArray = KeyTools.generateRandomBytes(32); 47 | String salt = ByteUtil.byteArrayToHexString(saltArray); 48 | byte[] derivedKey; 49 | if (type.equals(KDFType.PBKDF2)) { 50 | PBKDF2Params pbkdf2Params = PBKDF2Params.builder() 51 | .salt(salt) 52 | .dkLen(32) 53 | .count(262144) 54 | .build(); 55 | derivedKey = getDerivedKey(passphrase.getBytes(), pbkdf2Params); 56 | } else { 57 | ScryptParams scryptParams = ScryptParams.builder() 58 | .salt(salt) 59 | .dkLen(32) 60 | .p(1) 61 | .r(8) 62 | .n(8192) 63 | .build(); 64 | derivedKey = getDerivedKey(passphrase.getBytes(), scryptParams); 65 | } 66 | 67 | byte[] encryptKey = Arrays.copyOfRange(derivedKey, 0, 16); 68 | 69 | //perform cipher operation 70 | IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); 71 | Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding"); 72 | SecretKeySpec secretKeySpec = new SecretKeySpec(encryptKey, "AES"); 73 | cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec); 74 | byte[] ciphertext = cipher.doFinal(ByteUtil.hexStringToByteArray(privateKey)); 75 | byte[] mac = generateMac(derivedKey, ciphertext, iv); 76 | 77 | //build struct 78 | CipherParams cipherParams = CipherParams.builder().iv(ByteUtil.byteArrayToHexString(iv)).build(); 79 | kdfparams kp = new kdfparams(salt); 80 | Crypto crypto = Crypto.builder() 81 | .cipher("aes-128-ctr") 82 | .cipherparams(cipherParams) 83 | .ciphertext(ByteUtil.byteArrayToHexString(ciphertext)) 84 | .kdf(type.equals(KDFType.PBKDF2) ? "pbkdf2" : "scrypt") 85 | .kdfparams(kp) 86 | .mac(ByteUtil.byteArrayToHexString(mac)) 87 | .build(); 88 | 89 | KeystoreV3 struct = KeystoreV3.builder() 90 | .address(address) 91 | .crypto(crypto) 92 | .id(UUID.randomUUID().toString()) 93 | .version(3) 94 | .build(); 95 | return gson.toJson(struct); 96 | } 97 | 98 | @Data 99 | @Builder 100 | public static class KeystoreV3 { 101 | private String address; 102 | private Crypto crypto; 103 | private String id; 104 | private int version; 105 | 106 | } 107 | 108 | @Data 109 | @Builder 110 | public static class Crypto { 111 | private String cipher; 112 | private CipherParams cipherparams; 113 | private String ciphertext; 114 | private String kdf; 115 | private kdfparams kdfparams; 116 | private String mac; 117 | 118 | } 119 | 120 | @Data 121 | @Builder 122 | private static class CipherParams { 123 | private String iv; 124 | } 125 | 126 | @Data 127 | private static class kdfparams { 128 | private int n = 8192; 129 | private int c = 262144; 130 | private int r = 8; 131 | private int p = 1; 132 | private int dklen = 32; 133 | private String salt; 134 | 135 | public kdfparams(String salt) { 136 | this.salt = salt; 137 | } 138 | } 139 | 140 | public String decryptPrivateKey(String encryptJson, String passphrase) throws Exception { 141 | KeystoreV3 keystoreV3 = gson.fromJson(encryptJson, KeystoreV3.class); 142 | byte[] ciphertext = ByteUtil.hexStringToByteArray(keystoreV3.crypto.ciphertext); 143 | byte[] iv = ByteUtil.hexStringToByteArray(keystoreV3.crypto.cipherparams.iv); 144 | kdfparams kp = keystoreV3.crypto.kdfparams; 145 | String kdf = keystoreV3.crypto.kdf; 146 | byte[] derivedKey; 147 | if (kdf.equals("pbkdf2")) { 148 | PBKDF2Params pbkdf2Params = PBKDF2Params.builder() 149 | .salt(kp.salt) 150 | .dkLen(32) 151 | .count(262144) 152 | .build(); 153 | derivedKey = getDerivedKey(passphrase.getBytes(), pbkdf2Params); 154 | } else { 155 | ScryptParams scryptParams = ScryptParams.builder() 156 | .salt(kp.salt) 157 | .dkLen(32) 158 | .p(1) 159 | .r(8) 160 | .n(8192) 161 | .build(); 162 | derivedKey = getDerivedKey(passphrase.getBytes(), scryptParams); 163 | } 164 | String mac = ByteUtil.byteArrayToHexString(generateMac(derivedKey, ciphertext, iv)); 165 | if (!mac.toUpperCase().equals(keystoreV3.crypto.mac.toUpperCase())) { 166 | throw new IllegalAccessException("Failed to decrypt."); 167 | } 168 | IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); 169 | Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding"); 170 | byte[] encryptKey = Arrays.copyOfRange(derivedKey, 0, 16); 171 | SecretKeySpec secretKeySpec = new SecretKeySpec(encryptKey, "AES"); 172 | cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec); 173 | return ByteUtil.byteArrayToHexString(cipher.doFinal(ciphertext)); 174 | 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/crypto/KeyTools.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.crypto; 2 | 3 | import com.firestack.laksaj.utils.ByteUtil; 4 | import com.firestack.laksaj.utils.HashUtil; 5 | import org.bouncycastle.asn1.x9.X9ECParameters; 6 | import org.bouncycastle.crypto.ec.CustomNamedCurves; 7 | import org.bouncycastle.crypto.params.ECDomainParameters; 8 | import org.bouncycastle.math.ec.ECPoint; 9 | import org.bouncycastle.math.ec.FixedPointCombMultiplier; 10 | import org.web3j.crypto.ECKeyPair; 11 | import org.web3j.utils.Numeric; 12 | 13 | import java.math.BigInteger; 14 | import java.security.InvalidAlgorithmParameterException; 15 | import java.security.NoSuchAlgorithmException; 16 | import java.security.NoSuchProviderException; 17 | import java.security.SecureRandom; 18 | import java.util.regex.Matcher; 19 | import java.util.regex.Pattern; 20 | 21 | public class KeyTools { 22 | 23 | /** 24 | * The parameters of the secp256k1 curve that Bitcoin uses. 25 | */ 26 | public static final ECDomainParameters CURVE; 27 | private static final X9ECParameters CURVE_PARAMS = CustomNamedCurves.getByName("secp256k1"); 28 | private static final KeyStore keystore = KeyStore.defaultKeyStore(); 29 | private static final Pattern pattern = Pattern.compile("^(0x)?[0-9a-f]"); 30 | 31 | 32 | static { 33 | CURVE = new ECDomainParameters(CURVE_PARAMS.getCurve(), CURVE_PARAMS.getG(), CURVE_PARAMS.getN(), 34 | CURVE_PARAMS.getH()); 35 | } 36 | 37 | public static String generatePrivateKey() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { 38 | ECKeyPair keys = Schnorr.generateKeyPair(); 39 | return Numeric.toHexStringNoPrefixZeroPadded(keys.getPrivateKey(), 64); 40 | } 41 | 42 | @Deprecated 43 | public static ECKeyPair generateKeyPair() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { 44 | while (true) { 45 | ECKeyPair keyPair = Schnorr.generateKeyPair(); 46 | if (keyPair.getPrivateKey().toString(16).length() == 64) { 47 | return keyPair; 48 | } 49 | } 50 | } 51 | 52 | 53 | public static String getAddressFromPrivateKey(String privateKey) { 54 | String publicKey = getPublicKeyFromPrivateKey(privateKey, true); 55 | return getAddressFromPublicKey(publicKey); 56 | } 57 | 58 | public static boolean isByteString(String address) { 59 | System.out.println(address); 60 | Matcher matcher = pattern.matcher(address); 61 | while (matcher.find()) { 62 | System.out.println(matcher.group()); 63 | System.out.print("start:" + matcher.start()); 64 | System.out.println(" end:" + matcher.end()); 65 | } 66 | return true; 67 | } 68 | 69 | /** 70 | * @param privateKey hex string without 0x 71 | * @return 72 | */ 73 | public static String getPublicKeyFromPrivateKey(String privateKey, boolean compressed) { 74 | byte[] pk = ByteUtil.hexStringToByteArray(privateKey); 75 | BigInteger bigInteger = new BigInteger(1, pk); 76 | ECPoint point = getPublicPointFromPrivate(bigInteger); 77 | return ByteUtil.byteArrayToHexString(point.getEncoded(compressed)); 78 | } 79 | 80 | public static String getAddressFromPublicKey(String publicKey) { 81 | byte[] address = getAddressFromPublicKey(Numeric.hexStringToByteArray(publicKey)); 82 | return ByteUtil.byteArrayToHexString(address).substring(24); 83 | } 84 | 85 | public static byte[] getAddressFromPublicKey(byte[] publicKey) { 86 | return HashUtil.sha256(publicKey); 87 | } 88 | 89 | public static byte[] generateRandomBytes(int size) { 90 | byte[] bytes = new byte[size]; 91 | new SecureRandom().nextBytes(bytes); 92 | return bytes; 93 | } 94 | 95 | private static ECPoint getPublicPointFromPrivate(BigInteger privateKeyPoint) { 96 | if (privateKeyPoint.bitLength() > CURVE.getN().bitLength()) { 97 | privateKeyPoint = privateKeyPoint.mod(CURVE.getN()); 98 | } 99 | return new FixedPointCombMultiplier().multiply(CURVE.getG(), privateKeyPoint); 100 | } 101 | 102 | public static String decryptPrivateKey(String file, String passphrase) throws Exception { 103 | return keystore.decryptPrivateKey(file, passphrase); 104 | } 105 | 106 | public static String encryptPrivateKey(String privateKey, String passphrase, KDFType type) throws Exception { 107 | return keystore.encryptPrivateKey(privateKey, passphrase, type); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/crypto/LinuxSecureRandom.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.crypto; 2 | 3 | import java.io.*; 4 | import java.security.SecureRandomSpi; 5 | 6 | public class LinuxSecureRandom extends SecureRandomSpi { 7 | private static final FileInputStream urandom; 8 | 9 | static { 10 | try { 11 | File file = new File("/dev/urandom"); 12 | urandom = new FileInputStream(file); 13 | if (urandom.read() == -1) { 14 | throw new RuntimeException("/dev/urandom not readable, please check your platform"); 15 | } 16 | } catch (FileNotFoundException e) { 17 | throw new RuntimeException(e); 18 | } catch (IOException e) { 19 | throw new RuntimeException(e); 20 | } 21 | } 22 | 23 | private final DataInputStream dis; 24 | 25 | public LinuxSecureRandom() { 26 | dis = new DataInputStream(urandom); 27 | } 28 | 29 | @Override 30 | protected void engineSetSeed(byte[] seed) { 31 | } 32 | 33 | @Override 34 | protected void engineNextBytes(byte[] bytes) { 35 | try { 36 | dis.readFully(bytes); 37 | } catch (IOException e) { 38 | throw new RuntimeException(e); 39 | } 40 | } 41 | 42 | @Override 43 | protected byte[] engineGenerateSeed(int numBytes) { 44 | byte[] bits = new byte[numBytes]; 45 | engineNextBytes(bits); 46 | return bits; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/crypto/LinuxSecureRandomProvider.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.crypto; 2 | 3 | import java.security.Provider; 4 | 5 | public class LinuxSecureRandomProvider extends Provider { 6 | 7 | public LinuxSecureRandomProvider() { 8 | super("LinuxSecureRandom", 1.0, "A Linux specific random number provider that uses /dev/urandom"); 9 | put("SecureRandom.LinuxSecureRandom", LinuxSecureRandom.class.getName()); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/crypto/PBKDF2Params.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.crypto; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | @Data 7 | @Builder 8 | public class PBKDF2Params implements KDFParams { 9 | private String salt; 10 | private int dkLen; 11 | private int count; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/crypto/PBKDF2Wrapper.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.crypto; 2 | 3 | 4 | import org.bouncycastle.crypto.digests.SHA256Digest; 5 | import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator; 6 | import org.bouncycastle.crypto.params.KeyParameter; 7 | 8 | import java.io.UnsupportedEncodingException; 9 | 10 | public class PBKDF2Wrapper { 11 | public byte[] getDerivedKey(byte[] password, byte[] salt, int iterationCount, int keySize) throws UnsupportedEncodingException { 12 | PKCS5S2ParametersGenerator generator = new PKCS5S2ParametersGenerator(new SHA256Digest()); 13 | generator.init(password, salt, iterationCount); 14 | return ((KeyParameter) generator.generateDerivedParameters(keySize * 8)).getKey(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/crypto/Schnorr.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.crypto; 2 | 3 | import com.firestack.laksaj.utils.HashUtil; 4 | import org.bouncycastle.crypto.digests.SHA256Digest; 5 | import org.bouncycastle.crypto.macs.HMac; 6 | import org.bouncycastle.crypto.prng.SP800SecureRandomBuilder; 7 | import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; 8 | import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; 9 | import org.bouncycastle.jce.ECNamedCurveTable; 10 | import org.bouncycastle.jce.provider.BouncyCastleProvider; 11 | import org.bouncycastle.jce.spec.ECNamedCurveGenParameterSpec; 12 | import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; 13 | import org.bouncycastle.math.ec.ECPoint; 14 | import org.web3j.crypto.ECKeyPair; 15 | 16 | import java.math.BigInteger; 17 | import java.security.*; 18 | import java.util.Arrays; 19 | 20 | public class Schnorr { 21 | static { 22 | if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { 23 | Security.addProvider(new BouncyCastleProvider()); 24 | } 25 | } 26 | 27 | static private final ECNamedCurveParameterSpec secp256k1 = ECNamedCurveTable.getParameterSpec("secp256k1"); 28 | 29 | static private final int PUBKEY_COMPRESSED_SIZE_BYTES = 33; 30 | 31 | static private final byte[] ALG = "Schnorr+SHA256 ".getBytes(); 32 | 33 | static private final int ENT_BITS = 256; // 32 bytes of entropy require for the k value 34 | 35 | static ECKeyPair generateKeyPair() throws NoSuchProviderException, 36 | NoSuchAlgorithmException, InvalidAlgorithmParameterException { 37 | KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME); 38 | keyPairGenerator.initialize(new ECNamedCurveGenParameterSpec("secp256k1")); 39 | KeyPair keyPair = keyPairGenerator.generateKeyPair(); 40 | BCECPrivateKey privateKey = (BCECPrivateKey) keyPair.getPrivate(); 41 | BCECPublicKey publicKey = (BCECPublicKey) keyPair.getPublic(); 42 | 43 | return new ECKeyPair(privateKey.getD(), new BigInteger(1, publicKey.getQ().getEncoded(true))); 44 | } 45 | 46 | public static Signature sign(ECKeyPair kp, byte[] message) { 47 | SecureRandom drbg = getDRBG(message); 48 | 49 | int len = secp256k1.getN().bitLength() / 8; 50 | byte[] bytes = new byte[len]; 51 | drbg.nextBytes(bytes); 52 | 53 | Signature signature = null; 54 | while (signature == null) { 55 | BigInteger k = new BigInteger(1, bytes); 56 | signature = trySign(kp, message, k); 57 | } 58 | 59 | return signature; 60 | } 61 | 62 | public static Signature trySign(ECKeyPair kp, byte[] msg, BigInteger k) throws IllegalArgumentException { 63 | BigInteger n = secp256k1.getN(); 64 | BigInteger privateKey = kp.getPrivateKey(); 65 | ECPoint publicKey = secp256k1.getCurve().decodePoint(kp.getPublicKey().toByteArray()); 66 | 67 | // 1a. check if private key is 0 68 | if (privateKey.equals(BigInteger.ZERO)) { 69 | throw new IllegalArgumentException("Private key must be >= 0"); 70 | } 71 | 72 | // 1b. check if private key is less than curve order, i.e., within [1...n-1] 73 | if (privateKey.compareTo(n) >= 0) { 74 | throw new IllegalArgumentException("Private key cannot be greater than curve order"); 75 | } 76 | 77 | // 2. Compute commitment Q = kG, where G is the base point 78 | ECPoint Q = secp256k1.getG().multiply(k); 79 | 80 | // 3. Compute the challenge r = H(Q || pubKey || msg) 81 | // mod reduce r by the order of secp256k1, n 82 | BigInteger r = hash(Q, publicKey, msg).mod(secp256k1.getN()); 83 | 84 | if (r.equals(BigInteger.ZERO)) { 85 | return null; 86 | } 87 | 88 | //4. Compute s = k - r * prv 89 | // 4a. Compute r * prv 90 | BigInteger s = r.multiply(privateKey).mod(n); 91 | // 4b. Compute s = k - r * prv mod n 92 | s = k.subtract(s).mod(n); 93 | 94 | if (s.equals(BigInteger.ZERO)) { 95 | return null; 96 | } 97 | 98 | return Signature.builder().r(r).s(s).build(); 99 | } 100 | 101 | static private BigInteger hash(ECPoint q, ECPoint pubKey, byte[] msg) { 102 | // 33 q + 33 pubKey + variable msgLen 103 | int totalLength = PUBKEY_COMPRESSED_SIZE_BYTES * 2 + msg.length; 104 | byte[] qCompressed = q.getEncoded(true); 105 | byte[] pubKeyCompressed = pubKey.getEncoded(true); 106 | byte[] hashInput = new byte[totalLength]; 107 | 108 | Arrays.fill(hashInput, (byte) 0); 109 | System.arraycopy(qCompressed, 0, hashInput, 0, PUBKEY_COMPRESSED_SIZE_BYTES); 110 | System.arraycopy(pubKeyCompressed, 0, hashInput, PUBKEY_COMPRESSED_SIZE_BYTES, PUBKEY_COMPRESSED_SIZE_BYTES); 111 | System.arraycopy(msg, 0, hashInput, PUBKEY_COMPRESSED_SIZE_BYTES * 2, msg.length); 112 | 113 | byte[] hash = HashUtil.sha256(hashInput); 114 | 115 | return new BigInteger(1, hash); 116 | } 117 | 118 | static boolean verify(byte[] msg, Signature sig, ECPoint publicKey) throws IllegalArgumentException { 119 | if (sig.getR().equals(BigInteger.ZERO) || sig.getS().equals(BigInteger.ZERO)) { 120 | throw new IllegalArgumentException("Invalid R or S value: cannot be zero."); 121 | } 122 | 123 | if (sig.getR().signum() == -1 || sig.getS().signum() == -1) { 124 | throw new IllegalArgumentException("Invalid R or S value: cannot be negative."); 125 | } 126 | 127 | if (!publicKey.getCurve().equals(secp256k1.getCurve())) { 128 | throw new IllegalArgumentException("The public key must be a point on secp256k1."); 129 | } 130 | 131 | if (!publicKey.isValid()) { 132 | throw new IllegalArgumentException("Invalid public key."); 133 | } 134 | 135 | ECPoint l = publicKey.multiply(sig.getR()); 136 | ECPoint r = secp256k1.getG().multiply(sig.getS()); 137 | ECPoint Q = l.add(r); 138 | 139 | if (Q.isInfinity() || !Q.isValid()) { 140 | throw new IllegalArgumentException("Invalid intermediate point."); 141 | } 142 | 143 | BigInteger r1 = hash(Q, publicKey, msg).mod(secp256k1.getN()); 144 | 145 | if (r1.equals(BigInteger.ZERO)) { 146 | throw new IllegalArgumentException("Invalid hash."); 147 | } 148 | 149 | return r1.equals(sig.getR()); 150 | } 151 | 152 | // use custom secure random function to solve blocked issue 153 | private static SecureRandom getDRBG(byte[] message) { 154 | SHA256Digest sha256 = new SHA256Digest(); 155 | HMac hMac = new HMac(sha256); 156 | return new SP800SecureRandomBuilder(new CustomSecureRandom(), false) 157 | .setEntropyBitsRequired(ENT_BITS) 158 | .setPersonalizationString(ALG) 159 | .buildHMAC(hMac, message, true); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/crypto/ScryptParams.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.crypto; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | @Data 7 | @Builder 8 | public class ScryptParams implements KDFParams { 9 | private String salt; 10 | private int dkLen; 11 | private int n; 12 | private int r; 13 | private int p; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/crypto/ScryptWrapper.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.crypto; 2 | 3 | 4 | import org.bouncycastle.crypto.generators.SCrypt; 5 | 6 | //http://tools.ietf.org/html/draft-josefsson-scrypt-kdf-01 7 | public class ScryptWrapper { 8 | public byte[] getDerivedKey( 9 | byte[] password, byte[] salt, int n, int r, int p, int dkLen) { 10 | return SCrypt.generate(password, salt, n, r, p, dkLen); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/crypto/Signature.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.crypto; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | import org.bouncycastle.util.encoders.Hex; 6 | 7 | import java.math.BigInteger; 8 | 9 | @Data 10 | @Builder 11 | public class Signature { 12 | private BigInteger r; 13 | private BigInteger s; 14 | 15 | @Override 16 | public String toString() { 17 | String rHex = Hex.toHexString(r.toByteArray()); 18 | while (rHex.length() < 64) { 19 | rHex = "0" + rHex; 20 | } 21 | 22 | while (rHex.length() > 64 && rHex.charAt(0) == '0') { 23 | rHex = rHex.substring(1); 24 | } 25 | 26 | String sHex = Hex.toHexString(s.toByteArray()); 27 | while (sHex.length() < 64) { 28 | sHex = "0" + sHex; 29 | } 30 | 31 | while (sHex.length() > 64 && sHex.charAt(0) == '0') { 32 | sHex = sHex.substring(1); 33 | } 34 | 35 | return rHex + sHex; 36 | } 37 | 38 | public boolean isNil() { 39 | return this.r.equals(BigInteger.ZERO) && this.s.equals(BigInteger.ZERO); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/exception/ZilliqaAPIException.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.exception; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class ZilliqaAPIException extends Exception { 7 | private String message; 8 | private int code; 9 | 10 | public ZilliqaAPIException(String message, int code) { 11 | this.message = message; 12 | this.code = code; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/jsonrpc/HttpProvider.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.jsonrpc; 2 | 3 | import com.firestack.laksaj.blockchain.*; 4 | import com.firestack.laksaj.exception.ZilliqaAPIException; 5 | import com.firestack.laksaj.transaction.*; 6 | import com.firestack.laksaj.utils.Bech32; 7 | import com.google.common.base.Strings; 8 | import com.google.common.reflect.TypeToken; 9 | import com.google.gson.Gson; 10 | import com.google.gson.JsonObject; 11 | import com.google.gson.JsonSyntaxException; 12 | import lombok.Builder; 13 | import lombok.Data; 14 | import okhttp3.*; 15 | 16 | import java.io.IOException; 17 | import java.lang.reflect.Type; 18 | import java.net.MalformedURLException; 19 | import java.net.URL; 20 | import java.util.*; 21 | import java.util.concurrent.TimeUnit; 22 | 23 | public class HttpProvider { 24 | 25 | private OkHttpClient client = new OkHttpClient(); 26 | private final Gson gson = new Gson(); 27 | private static final MediaType JSON 28 | = MediaType.parse("application/json; charset=utf-8"); 29 | private String url; 30 | private static final Map> transactionStatusMap = new HashMap<>(); 31 | private int retry = 3; 32 | 33 | static { 34 | transactionStatusMap.put(0, new HashMap() { 35 | { 36 | put(0, "Transaction not found"); 37 | put(1, "Pending - Dispatched"); 38 | } 39 | }); 40 | 41 | transactionStatusMap.put(1, new HashMap() { 42 | { 43 | put(2, "Pending - Soft-confirmed (awaiting Tx block generation)"); 44 | put(4, "Pending - Nonce is higher than expected"); 45 | put(5, "Pending - Microblock gas limit exceeded"); 46 | put(6, "Pending - Consensus failure in network"); 47 | } 48 | }); 49 | 50 | transactionStatusMap.put(2, new HashMap() { 51 | { 52 | put(2, "Confirmed"); 53 | put(10, "Rejected - Transaction caused math error"); 54 | put(11, "Rejected - Scilla invocation error"); 55 | put(12, "Rejected - Contract account initialization error"); 56 | put(13, "Rejected - Invalid source account"); 57 | put(14, "Rejected - Gas limit higher than shard gas limit"); 58 | put(15, "Rejected - Unknown transaction type"); 59 | put(16, "Rejected - Transaction sent to wrong shard"); 60 | put(17, "Rejected - Contract & source account cross-shard issue"); 61 | put(18, "Rejected - Code size exceeded limit"); 62 | put(19, "Rejected - Transaction verification failed"); 63 | put(20, "Rejected - Gas limit too low"); 64 | put(21, "Rejected - Insufficient balance"); 65 | put(22, "Rejected - Insufficient gas to invoke Scilla checker"); 66 | put(23, "Rejected - Duplicate transaction exists"); 67 | put(24, "Rejected - Transaction with higher gas price exists"); 68 | put(25, "Rejected - Invalid destination address"); 69 | put(26, "Rejected - Failed to add contract account to state"); 70 | put(27, "Rejected - Nonce is lower than expected"); 71 | put(255, "Rejected - Internal error"); 72 | } 73 | }); 74 | } 75 | 76 | public HttpProvider(String url) { 77 | this.url = url; 78 | } 79 | 80 | public HttpProvider(String url, OkHttpClient client) { 81 | this.url = url; 82 | this.client = client; 83 | } 84 | 85 | public HttpProvider(String url, int retry) { 86 | this.url = url; 87 | this.retry = retry; 88 | } 89 | 90 | //Blockchain-related methods 91 | public Rep getNetworkId() throws IOException, ZilliqaAPIException { 92 | Req req = Req.builder().id("1").jsonrpc("2.0").method("GetNetworkId").params(new String[]{""}).build(); 93 | Response response = client.newCall(buildRequest(req)).execute(); 94 | String resultString = Objects.requireNonNull(response.body()).string(); 95 | Type type = new TypeToken>() { 96 | }.getType(); 97 | Rep rep = gson.fromJson(resultString, type); 98 | if (null == rep.getResult()) { 99 | Pair pair = parseError(resultString); 100 | throw new ZilliqaAPIException(pair.getMessage(), pair.getCode()); 101 | } 102 | return rep; 103 | } 104 | 105 | public Rep getBlockchainInfo() throws IOException, ZilliqaAPIException { 106 | Req req = Req.builder().id("1").jsonrpc("2.0").method("GetBlockchainInfo").params(new String[]{""}).build(); 107 | Response response = client.newCall(buildRequest(req)).execute(); 108 | String resultString = Objects.requireNonNull(response.body()).string(); 109 | Type type = new TypeToken>() { 110 | }.getType(); 111 | Rep rep = gson.fromJson(resultString, type); 112 | if (null == rep.getResult()) { 113 | Pair pair = parseError(resultString); 114 | throw new ZilliqaAPIException(pair.getMessage(), pair.getCode()); 115 | } 116 | return rep; 117 | } 118 | 119 | public Rep getShardingStructure() throws IOException, ZilliqaAPIException { 120 | Req req = Req.builder().id("1").jsonrpc("2.0").method("GetShardingStructure").params(new String[]{""}).build(); 121 | Response response = client.newCall(buildRequest(req)).execute(); 122 | String resultString = Objects.requireNonNull(response.body()).string(); 123 | Type type = new TypeToken>() { 124 | }.getType(); 125 | Rep rep = gson.fromJson(resultString, type); 126 | if (null == rep.getResult()) { 127 | Pair pair = parseError(resultString); 128 | throw new ZilliqaAPIException(pair.getMessage(), pair.getCode()); 129 | } 130 | return rep; 131 | 132 | } 133 | 134 | public Rep getDSBlockListing(int pageNumber) throws IOException, ZilliqaAPIException { 135 | Req req = Req.builder().id("1").jsonrpc("2.0").method("DSBlockListing").params(new Integer[]{pageNumber}).build(); 136 | Response response = client.newCall(buildRequest(req)).execute(); 137 | String resultString = Objects.requireNonNull(response.body()).string(); 138 | Type type = new TypeToken>() { 139 | }.getType(); 140 | Rep rep = gson.fromJson(resultString, type); 141 | if (null == rep.getResult()) { 142 | Pair pair = parseError(resultString); 143 | throw new ZilliqaAPIException(pair.getMessage(), pair.getCode()); 144 | } 145 | return rep; 146 | } 147 | 148 | public Rep getTxBlockListing(int pageNumber) throws IOException, ZilliqaAPIException { 149 | Req req = Req.builder().id("1").jsonrpc("2.0").method("TxBlockListing").params(new Integer[]{pageNumber}).build(); 150 | Response response = client.newCall(buildRequest(req)).execute(); 151 | String resultString = Objects.requireNonNull(response.body()).string(); 152 | Type type = new TypeToken>() { 153 | }.getType(); 154 | Rep rep = gson.fromJson(resultString, type); 155 | if (null == rep.getResult()) { 156 | Pair pair = parseError(resultString); 157 | throw new ZilliqaAPIException(pair.getMessage(), pair.getCode()); 158 | } 159 | return rep; 160 | } 161 | 162 | public Rep getNumDSBlocks() throws IOException, ZilliqaAPIException { 163 | Req req = Req.builder().id("1").jsonrpc("2.0").method("GetNumDSBlocks").params(new String[]{""}).build(); 164 | Response response = client.newCall(buildRequest(req)).execute(); 165 | String resultString = Objects.requireNonNull(response.body()).string(); 166 | Type type = new TypeToken>() { 167 | }.getType(); 168 | Rep rep = gson.fromJson(resultString, type); 169 | if (null == rep.getResult()) { 170 | Pair pair = parseError(resultString); 171 | throw new ZilliqaAPIException(pair.getMessage(), pair.getCode()); 172 | } 173 | return rep; 174 | } 175 | 176 | public Rep getDSBlockRate() throws IOException, ZilliqaAPIException { 177 | Req req = Req.builder().id("1").jsonrpc("2.0").method("GetDSBlockRate").params(new String[]{""}).build(); 178 | Response response = client.newCall(buildRequest(req)).execute(); 179 | String resultString = Objects.requireNonNull(response.body()).string(); 180 | Type type = new TypeToken>() { 181 | }.getType(); 182 | Rep rep = gson.fromJson(resultString, type); 183 | if (null == rep.getResult()) { 184 | Pair pair = parseError(resultString); 185 | throw new ZilliqaAPIException(pair.getMessage(), pair.getCode()); 186 | } 187 | return rep; 188 | } 189 | 190 | public Rep getDSBlockListing() throws IOException, ZilliqaAPIException { 191 | Req req = Req.builder().id("1").jsonrpc("2.0").method("DSBlockListing").params(new Object[]{1}).build(); 192 | Response response = client.newCall(buildRequest(req)).execute(); 193 | String resultString = Objects.requireNonNull(response.body()).string(); 194 | Type type = new TypeToken>() { 195 | }.getType(); 196 | Rep rep = gson.fromJson(resultString, type); 197 | if (null == rep.getResult()) { 198 | Pair pair = parseError(resultString); 199 | throw new ZilliqaAPIException(pair.getMessage(), pair.getCode()); 200 | } 201 | return rep; 202 | } 203 | 204 | public Rep getDsBlock(String blockNumber) throws IOException, ZilliqaAPIException { 205 | Req req = Req.builder().id("1").jsonrpc("2.0").method("GetDsBlock").params(new String[]{blockNumber}).build(); 206 | Response response = client.newCall(buildRequest(req)).execute(); 207 | String resultString = Objects.requireNonNull(response.body()).string(); 208 | Type type = new TypeToken>() { 209 | }.getType(); 210 | Rep rep = gson.fromJson(resultString, type); 211 | if (null == rep.getResult()) { 212 | Pair pair = parseError(resultString); 213 | throw new ZilliqaAPIException(pair.getMessage(), pair.getCode()); 214 | } 215 | return rep; 216 | } 217 | 218 | public Rep getTxBlock(String blockNumber) throws IOException, ZilliqaAPIException { 219 | Req req = Req.builder().id("1").jsonrpc("2.0").method("GetTxBlock").params(new String[]{blockNumber}).build(); 220 | Response response = client.newCall(buildRequest(req)).execute(); 221 | String resultString = Objects.requireNonNull(response.body()).string(); 222 | Rep rep = gson.fromJson(resultString, new TypeToken>() { 223 | }.getType()); 224 | if (null == rep.getResult()) { 225 | Pair pair = parseError(resultString); 226 | throw new ZilliqaAPIException(pair.getMessage(), pair.getCode()); 227 | } 228 | return rep; 229 | } 230 | 231 | public Rep getNumTxBlocks() throws IOException, ZilliqaAPIException { 232 | Req req = Req.builder().id("1").jsonrpc("2.0").method("GetNumTxBlocks").params(new String[]{""}).build(); 233 | Response response = client.newCall(buildRequest(req)).execute(); 234 | String resultString = Objects.requireNonNull(response.body()).string(); 235 | Type type = new TypeToken>() { 236 | }.getType(); 237 | Rep rep = gson.fromJson(resultString, type); 238 | if (null == rep.getResult()) { 239 | Pair pair = parseError(resultString); 240 | throw new ZilliqaAPIException(pair.getMessage(), pair.getCode()); 241 | } 242 | return rep; 243 | } 244 | 245 | public Rep getTxBlockRate() throws IOException, ZilliqaAPIException { 246 | Req req = Req.builder().id("1").jsonrpc("2.0").method("GetTxBlockRate").params(new String[]{""}).build(); 247 | Response response = client.newCall(buildRequest(req)).execute(); 248 | String resultString = Objects.requireNonNull(response.body()).string(); 249 | Type type = new TypeToken>() { 250 | }.getType(); 251 | Rep rep = gson.fromJson(resultString, type); 252 | if (null == rep.getResult()) { 253 | Pair pair = parseError(resultString); 254 | throw new ZilliqaAPIException(pair.getMessage(), pair.getCode()); 255 | } 256 | return rep; 257 | } 258 | 259 | public Rep getLatestDsBlock() throws IOException, ZilliqaAPIException { 260 | Req req = Req.builder().id("1").jsonrpc("2.0").method("GetLatestDsBlock").params(new String[]{""}).build(); 261 | Response response = client.newCall(buildRequest(req)).execute(); 262 | String resultString = Objects.requireNonNull(response.body()).string(); 263 | Rep rep = gson.fromJson(resultString, new TypeToken>() { 264 | }.getType()); 265 | if (null == rep.getResult()) { 266 | Pair pair = parseError(resultString); 267 | throw new ZilliqaAPIException(pair.getMessage(), pair.getCode()); 268 | } 269 | return rep; 270 | } 271 | 272 | public Rep getLatestTxBlock() throws IOException, ZilliqaAPIException { 273 | Req req = Req.builder().id("1").jsonrpc("2.0").method("GetLatestTxBlock").params(new String[]{""}).build(); 274 | Response response = client.newCall(buildRequest(req)).execute(); 275 | String resultString = Objects.requireNonNull(response.body()).string(); 276 | Rep rep = gson.fromJson(resultString, new TypeToken>() { 277 | }.getType()); 278 | if (null == rep.getResult()) { 279 | Pair pair = parseError(resultString); 280 | throw new ZilliqaAPIException(pair.getMessage(), pair.getCode()); 281 | } 282 | return rep; 283 | } 284 | 285 | public Rep getNumTransactions() throws IOException, ZilliqaAPIException { 286 | Req req = Req.builder().id("1").jsonrpc("2.0").method("GetNumTransactions").params(new String[]{""}).build(); 287 | Response response = client.newCall(buildRequest(req)).execute(); 288 | String resultString = Objects.requireNonNull(response.body()).string(); 289 | Type type = new TypeToken>() { 290 | }.getType(); 291 | Rep rep = gson.fromJson(resultString, type); 292 | if (null == rep.getResult()) { 293 | Pair pair = parseError(resultString); 294 | throw new ZilliqaAPIException(pair.getMessage(), pair.getCode()); 295 | } 296 | return rep; 297 | } 298 | 299 | public Rep getTransactionRate() throws IOException, ZilliqaAPIException { 300 | Req req = Req.builder().id("1").jsonrpc("2.0").method("GetTransactionRate").params(new String[]{""}).build(); 301 | Response response = client.newCall(buildRequest(req)).execute(); 302 | String resultString = Objects.requireNonNull(response.body()).string(); 303 | Type type = new TypeToken>() { 304 | }.getType(); 305 | Rep rep = gson.fromJson(resultString, type); 306 | if (null == rep.getResult()) { 307 | Pair pair = parseError(resultString); 308 | throw new ZilliqaAPIException(pair.getMessage(), pair.getCode()); 309 | } 310 | return rep; 311 | } 312 | 313 | public Rep getCurrentMiniEpoch() throws IOException, ZilliqaAPIException { 314 | Req req = Req.builder().id("1").jsonrpc("2.0").method("GetCurrentMiniEpoch").params(new String[]{""}).build(); 315 | Response response = client.newCall(buildRequest(req)).execute(); 316 | String resultString = Objects.requireNonNull(response.body()).string(); 317 | Type type = new TypeToken>() { 318 | }.getType(); 319 | Rep rep = gson.fromJson(resultString, type); 320 | if (null == rep.getResult()) { 321 | Pair pair = parseError(resultString); 322 | throw new ZilliqaAPIException(pair.getMessage(), pair.getCode()); 323 | } 324 | return rep; 325 | } 326 | 327 | public Rep getCurrentDSEpoch() throws IOException, ZilliqaAPIException { 328 | Req req = Req.builder().id("1").jsonrpc("2.0").method("GetCurrentDSEpoch").params(new String[]{""}).build(); 329 | Response response = client.newCall(buildRequest(req)).execute(); 330 | String resultString = Objects.requireNonNull(response.body()).string(); 331 | Type type = new TypeToken>() { 332 | }.getType(); 333 | Rep rep = gson.fromJson(resultString, type); 334 | if (null == rep.getResult()) { 335 | Pair pair = parseError(resultString); 336 | throw new ZilliqaAPIException(pair.getMessage(), pair.getCode()); 337 | } 338 | return rep; 339 | } 340 | 341 | public Rep getPrevDifficulty() throws IOException, ZilliqaAPIException { 342 | Req req = Req.builder().id("1").jsonrpc("2.0").method("GetPrevDifficulty").params(new String[]{""}).build(); 343 | Response response = client.newCall(buildRequest(req)).execute(); 344 | String resultString = Objects.requireNonNull(response.body()).string(); 345 | Type type = new TypeToken>() { 346 | }.getType(); 347 | Rep rep = gson.fromJson(resultString, type); 348 | if (null == rep.getResult()) { 349 | Pair pair = parseError(resultString); 350 | throw new ZilliqaAPIException(pair.getMessage(), pair.getCode()); 351 | } 352 | return rep; 353 | } 354 | 355 | public Rep getPrevDSDifficulty() throws IOException, ZilliqaAPIException { 356 | Req req = Req.builder().id("1").jsonrpc("2.0").method("GetPrevDSDifficulty").params(new String[]{""}).build(); 357 | Response response = client.newCall(buildRequest(req)).execute(); 358 | String resultString = Objects.requireNonNull(response.body()).string(); 359 | Type type = new TypeToken>() { 360 | }.getType(); 361 | Rep rep = gson.fromJson(resultString, type); 362 | if (null == rep.getResult()) { 363 | Pair pair = parseError(resultString); 364 | throw new ZilliqaAPIException(pair.getMessage(), pair.getCode()); 365 | } 366 | return rep; 367 | } 368 | 369 | //Account-related methods 370 | public Rep getBalance(String address) throws IOException { 371 | Req req = Req.builder().id("1").jsonrpc("2.0").method("GetBalance").params(new String[]{address}).build(); 372 | Response response = client.newCall(buildRequest(req)).execute(); 373 | String resultString = Objects.requireNonNull(response.body()).string(); 374 | Type type = new TypeToken>() { 375 | }.getType(); 376 | 377 | try { 378 | Rep rep = gson.fromJson(resultString, type); 379 | if (null == rep.getResult()) { 380 | BalanceResult balanceResult = new BalanceResult(); 381 | balanceResult.setBalance("0"); 382 | balanceResult.setNonce("0"); 383 | rep.setResult(balanceResult); 384 | } 385 | return rep; 386 | } catch (JsonSyntaxException e) { 387 | throw new IOException("get wrong result: " + resultString); 388 | } 389 | 390 | } 391 | 392 | 393 | public Rep getBalanceWithRetry(String address) throws IOException, InterruptedException { 394 | int leftRetry = this.retry; 395 | double sleep = 1.0; 396 | while (leftRetry > 0) { 397 | try { 398 | Rep status = this.getBalance(address); 399 | return status; 400 | } catch (Exception e) { 401 | TimeUnit.SECONDS.sleep((long) Math.pow(2.0,sleep)); 402 | } 403 | leftRetry--; 404 | sleep++; 405 | } 406 | 407 | throw new IOException("failed after retry"); 408 | } 409 | 410 | 411 | public Rep getBalance32(String address) throws Exception { 412 | return getBalance(Bech32.fromBech32Address(address)); 413 | } 414 | 415 | //Contract-related methods todo need test 416 | public Rep getSmartContractCode(String address) throws IOException, ZilliqaAPIException { 417 | Req req = Req.builder().id("1").jsonrpc("2.0").method("GetSmartContractCode").params(new String[]{address}).build(); 418 | Response response = client.newCall(buildRequest(req)).execute(); 419 | String resultString = Objects.requireNonNull(response.body()).string(); 420 | Type type = new TypeToken>() { 421 | }.getType(); 422 | Rep rep = gson.fromJson(resultString, type); 423 | if (null == rep.getResult()) { 424 | Pair pair = parseError(resultString); 425 | throw new ZilliqaAPIException(pair.getMessage(), pair.getCode()); 426 | } 427 | return rep; 428 | } 429 | 430 | public Rep> getSmartContracts(String address) throws IOException, ZilliqaAPIException { 431 | Req req = Req.builder().id("1").jsonrpc("2.0").method("GetSmartContracts").params(new String[]{address}).build(); 432 | Response response = client.newCall(buildRequest(req)).execute(); 433 | String resultString = Objects.requireNonNull(response.body()).string(); 434 | Type type = new TypeToken>>() { 435 | }.getType(); 436 | Rep> rep = gson.fromJson(resultString, type); 437 | if (null == rep.getResult()) { 438 | Pair pair = parseError(resultString); 439 | throw new ZilliqaAPIException(pair.getMessage(), pair.getCode()); 440 | } 441 | return rep; 442 | } 443 | 444 | public Rep getContractAddressFromTransactionID(String address) throws IOException, ZilliqaAPIException { 445 | Req req = Req.builder().id("1").jsonrpc("2.0").method("GetContractAddressFromTransactionID").params(new String[]{address}).build(); 446 | Response response = client.newCall(buildRequest(req)).execute(); 447 | String resultString = Objects.requireNonNull(response.body()).string(); 448 | Type type = new TypeToken>() { 449 | }.getType(); 450 | Rep rep = gson.fromJson(resultString, type); 451 | if (null == rep.getResult()) { 452 | Pair pair = parseError(resultString); 453 | throw new ZilliqaAPIException(pair.getMessage(), pair.getCode()); 454 | } 455 | return rep; 456 | } 457 | 458 | public String getSmartContractState(String address) throws IOException, ZilliqaAPIException { 459 | Req req = Req.builder().id("1").jsonrpc("2.0").method("GetSmartContractState").params(new String[]{address}).build(); 460 | Response response = client.newCall(buildRequest(req)).execute(); 461 | String resultString = Objects.requireNonNull(response.body()).string(); 462 | return resultString; 463 | } 464 | 465 | public String getSmartContractSubState(List param) throws IOException { 466 | Req req = Req.builder().id("1").jsonrpc("2.0").method("GetSmartContractSubState").params(param.toArray()).build(); 467 | Response response = client.newCall(buildRequest(req)).execute(); 468 | return Objects.requireNonNull(response.body()).string(); 469 | } 470 | 471 | public Rep> getSmartContractInit(String address) throws IOException, ZilliqaAPIException { 472 | Req req = Req.builder().id("1").jsonrpc("2.0").method("GetSmartContractInit").params(new String[]{address}).build(); 473 | Response response = client.newCall(buildRequest(req)).execute(); 474 | String resultString = Objects.requireNonNull(response.body()).string(); 475 | Type type = new TypeToken>>() { 476 | }.getType(); 477 | Rep> rep = gson.fromJson(resultString, type); 478 | if (null == rep.getResult()) { 479 | Pair pair = parseError(resultString); 480 | throw new ZilliqaAPIException(pair.getMessage(), pair.getCode()); 481 | } 482 | return rep; 483 | } 484 | 485 | //Transaction-related methods 486 | public Rep createTransaction(TransactionPayload payload) throws IOException, ZilliqaAPIException { 487 | Req req = Req.builder().id("1").jsonrpc("2.0").method("CreateTransaction").params(new Object[]{payload}).build(); 488 | Response response = client.newCall(buildRequest(req)).execute(); 489 | String resultString = Objects.requireNonNull(response.body()).string(); 490 | Type type = new TypeToken>() { 491 | }.getType(); 492 | Rep rep = gson.fromJson(resultString, type); 493 | if (null == rep.getResult()) { 494 | Pair pair = parseError(resultString); 495 | throw new ZilliqaAPIException(pair.getMessage(), pair.getCode()); 496 | } 497 | return rep; 498 | } 499 | 500 | public List createTransactions(List transactions) throws Exception { 501 | List reqs = new ArrayList<>(); 502 | Map transactionMap = new HashMap<>(); 503 | for (int i = 0; i < transactions.size(); i++) { 504 | String index = Integer.toString(i); 505 | Transaction txn = transactions.get(i); 506 | transactionMap.put(index, txn); 507 | TransactionPayload payload = txn.toTransactionPayload(); 508 | Req req = Req.builder().id(index).jsonrpc("2.0").method("CreateTransaction").params(new Object[]{payload}).build(); 509 | reqs.add(req); 510 | } 511 | Response response = client.newCall(buildRequests(reqs)).execute(); 512 | String resultString = Objects.requireNonNull(response.body()).string(); 513 | Type type = new TypeToken>>() { 514 | }.getType(); 515 | List> reps = gson.fromJson(resultString, type); 516 | for (int i = 0; i < reps.size(); i++) { 517 | CreateTxResult createTxResult = reps.get(i).getResult(); 518 | if (null == createTxResult) { 519 | transactionMap.get(reps.get(i).getId()).setStatus(TxStatus.Rejected); 520 | transactionMap.get(reps.get(i).getId()).setInfo(reps.get(i).getError().message); 521 | } else if (Strings.isNullOrEmpty(createTxResult.TranID)) { 522 | transactionMap.get(reps.get(i).getId()).setStatus(TxStatus.Rejected); 523 | transactionMap.get(reps.get(i).getId()).setInfo(createTxResult.Info); 524 | } else { 525 | transactionMap.get(reps.get(i).getId()).setID(createTxResult.TranID); 526 | } 527 | } 528 | return transactions; 529 | } 530 | 531 | public Rep getMinimumGasPrice() throws IOException, ZilliqaAPIException { 532 | Req req = Req.builder().id("1").jsonrpc("2.0").method("GetMinimumGasPrice").params(new String[]{""}).build(); 533 | Response response = client.newCall(buildRequest(req)).execute(); 534 | String resultString = Objects.requireNonNull(response.body()).string(); 535 | Type type = new TypeToken>() { 536 | }.getType(); 537 | Rep rep = gson.fromJson(resultString, type); 538 | if (null == rep.getResult()) { 539 | Pair pair = parseError(resultString); 540 | throw new ZilliqaAPIException(pair.getMessage(), pair.getCode()); 541 | } 542 | return rep; 543 | } 544 | 545 | public Rep getTransactionStatus(String hash) throws IOException { 546 | Req req = Req.builder().id("1").jsonrpc("2.0").method("GetTransactionStatus").params(new String[]{hash}).build(); 547 | Response response = client.newCall(buildRequest(req)).execute(); 548 | String resultString = Objects.requireNonNull(response.body()).string(); 549 | Type type = new TypeToken>() { 550 | }.getType(); 551 | try { 552 | Rep rep = gson.fromJson(resultString, type); 553 | if (rep.getResult() == null) { 554 | throw new IOException("get result error = " + resultString); 555 | } 556 | TransactionStatus status = rep.getResult(); 557 | rep.getResult().setInfo(transactionStatusMap.get(status.getModificationState()).get(status.getStatus())); 558 | return rep; 559 | } catch (JsonSyntaxException e) { 560 | throw new IOException("get wrong result: " + resultString); 561 | } 562 | } 563 | 564 | public Rep getTransactionStatusWithRetry(String hash) throws IOException, InterruptedException { 565 | int leftRetry = this.retry; 566 | double sleep = 1.0; 567 | while (leftRetry > 0) { 568 | try { 569 | Rep status = this.getTransactionStatus(hash); 570 | return status; 571 | } catch (Exception e) { 572 | TimeUnit.SECONDS.sleep((long) Math.pow(2.0,sleep)); 573 | } 574 | leftRetry--; 575 | sleep++; 576 | } 577 | 578 | throw new IOException("failed after retry"); 579 | } 580 | 581 | public Rep getPendingTnx(String hash) throws IOException { 582 | Req req = Req.builder().id("1").jsonrpc("2.0").method("GetPendingTxn").params(new String[]{hash}).build(); 583 | Response response = client.newCall(buildRequest(req)).execute(); 584 | String resultString = Objects.requireNonNull(response.body()).string(); 585 | Type type = new TypeToken>() { 586 | }.getType(); 587 | Rep rep = gson.fromJson(resultString, type); 588 | if (rep.getResult() == null) { 589 | throw new IOException("get result error = " + resultString); 590 | } 591 | return rep; 592 | } 593 | 594 | public Rep getTransaction(String hash) throws IOException { 595 | Req req = Req.builder().id("1").jsonrpc("2.0").method("GetTransaction").params(new String[]{hash}).build(); 596 | Response response = client.newCall(buildRequest(req)).execute(); 597 | String resultString = Objects.requireNonNull(response.body()).string(); 598 | Type type = new TypeToken>() { 599 | }.getType(); 600 | try { 601 | Rep rep = gson.fromJson(resultString, type); 602 | if (rep.getResult() == null) { 603 | throw new IOException("get result error = " + resultString); 604 | } 605 | return rep; 606 | } catch (JsonSyntaxException e) { 607 | throw new IOException("get wrong result: " + resultString); 608 | } 609 | } 610 | 611 | public Rep getTransaction32(String hash) throws Exception { 612 | Rep rep = getTransaction(hash); 613 | rep.getResult().setToAddr(Bech32.toBech32Address(rep.getResult().getToAddr())); 614 | return rep; 615 | } 616 | 617 | 618 | public Rep getRecentTransactions() throws IOException, ZilliqaAPIException { 619 | Req req = Req.builder().id("1").jsonrpc("2.0").method("GetRecentTransactions").params(new String[]{""}).build(); 620 | Response response = client.newCall(buildRequest(req)).execute(); 621 | String resultString = Objects.requireNonNull(response.body()).string(); 622 | Type type = new TypeToken>() { 623 | }.getType(); 624 | Rep rep = gson.fromJson(resultString, type); 625 | if (null == rep.getResult()) { 626 | Pair pair = parseError(resultString); 627 | throw new ZilliqaAPIException(pair.getMessage(), pair.getCode()); 628 | } 629 | return rep; 630 | } 631 | 632 | public Rep>> getTransactionsForTxBlock(String blockNum) throws IOException { 633 | Req req = Req.builder().id("1").jsonrpc("2.0").method("GetTransactionsForTxBlock").params(new String[]{blockNum}).build(); 634 | Response response = client.newCall(buildRequest(req)).execute(); 635 | String resultString = Objects.requireNonNull(response.body()).string(); 636 | Type type = new TypeToken>>>() { 637 | }.getType(); 638 | Rep>> rep = gson.fromJson(resultString, type); 639 | if (rep.getResult() == null) { 640 | Rep>> res = new Rep<>(); 641 | res.setJsonrpc("2.0"); 642 | res.setId("1"); 643 | JsonObject jb = gson.fromJson(resultString, JsonObject.class); 644 | res.setErr(jb.getAsJsonObject("error").get("message").toString()); 645 | return res; 646 | } 647 | return rep; 648 | } 649 | 650 | 651 | public Rep getNumTxnsTxEpoch() throws IOException, ZilliqaAPIException { 652 | Req req = Req.builder().id("1").jsonrpc("2.0").method("GetNumTxnsTxEpoch").params(new String[]{""}).build(); 653 | Response response = client.newCall(buildRequest(req)).execute(); 654 | String resultString = Objects.requireNonNull(response.body()).string(); 655 | Type type = new TypeToken>() { 656 | }.getType(); 657 | Rep rep = gson.fromJson(resultString, type); 658 | if (null == rep.getResult()) { 659 | Pair pair = parseError(resultString); 660 | throw new ZilliqaAPIException(pair.getMessage(), pair.getCode()); 661 | } 662 | return rep; 663 | } 664 | 665 | public Rep getNumTxnsDSEpoch() throws IOException, ZilliqaAPIException { 666 | Req req = Req.builder().id("1").jsonrpc("2.0").method("GetNumTxnsDSEpoch").params(new String[]{""}).build(); 667 | Response response = client.newCall(buildRequest(req)).execute(); 668 | String resultString = Objects.requireNonNull(response.body()).string(); 669 | Type type = new TypeToken>() { 670 | }.getType(); 671 | Rep rep = gson.fromJson(resultString, type); 672 | if (null == rep.getResult()) { 673 | Pair pair = parseError(resultString); 674 | throw new ZilliqaAPIException(pair.getMessage(), pair.getCode()); 675 | } 676 | return rep; 677 | } 678 | 679 | 680 | private Request buildRequest(Req req) throws MalformedURLException { 681 | RequestBody body = RequestBody.create(JSON, gson.toJson(req)); 682 | return new Request.Builder() 683 | .post(body) 684 | .url(new URL(this.url)) 685 | .build(); 686 | } 687 | 688 | private Request buildRequests(List reqs) throws MalformedURLException { 689 | RequestBody body = RequestBody.create(JSON, gson.toJson(reqs)); 690 | return new Request.Builder() 691 | .post(body) 692 | .url(new URL(this.url)) 693 | .build(); 694 | } 695 | 696 | 697 | @Data 698 | public static class BalanceResult { 699 | private String balance; 700 | private String nonce; 701 | } 702 | 703 | @Data 704 | public static class ContractResult { 705 | private String code; 706 | } 707 | 708 | @Data 709 | public static class CreateTxResult { 710 | private String Info; 711 | private String TranID; 712 | private String ContractAddress; 713 | 714 | @Override 715 | public String toString() { 716 | return "CreateTxResult{" + 717 | "Info='" + Info + '\'' + 718 | ", TranID='" + TranID + '\'' + 719 | ", ContractAddress='" + ContractAddress + '\'' + 720 | '}'; 721 | } 722 | } 723 | 724 | @Data 725 | public static class CreateTxError { 726 | private Integer code; 727 | private Object data; 728 | private String message; 729 | } 730 | 731 | @Data 732 | @Builder 733 | public static class Pair { 734 | private String message; 735 | private int code; 736 | 737 | 738 | } 739 | 740 | public Pair parseError(String error) { 741 | JsonObject root = gson.fromJson(error, JsonObject.class); 742 | JsonObject err = root.getAsJsonObject("error"); 743 | return Pair.builder().code(err.get("code").getAsInt()).message(err.get("message").getAsString()).build(); 744 | 745 | } 746 | 747 | 748 | } 749 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/jsonrpc/Rep.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.jsonrpc; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class Rep { 7 | private String id; 8 | private String jsonrpc; 9 | private R result; 10 | private HttpProvider.CreateTxError error; 11 | private String err; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/jsonrpc/Req.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.jsonrpc; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | @Data 7 | @Builder 8 | public class Req { 9 | private String id; 10 | private String jsonrpc; 11 | private String method; 12 | private Object[] params; 13 | } -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/proto/message.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package com.firestack.laksaj.proto; 4 | 5 | // ============================================================================ 6 | // Primitives 7 | // ============================================================================ 8 | 9 | message ByteArray 10 | { 11 | required bytes data = 1; 12 | } 13 | 14 | message ProtoTransactionCoreInfo 15 | { 16 | optional uint32 version = 1; 17 | optional uint64 nonce = 2; 18 | optional bytes toaddr = 3; 19 | optional ByteArray senderpubkey = 4; 20 | optional ByteArray amount = 5; 21 | optional ByteArray gasprice = 6; 22 | optional uint64 gaslimit = 7; 23 | optional bytes code = 8; 24 | optional bytes data = 9; 25 | } 26 | 27 | message ProtoTransaction 28 | { 29 | optional bytes tranid = 1; 30 | optional ProtoTransactionCoreInfo info = 2; 31 | optional ByteArray signature = 3; 32 | } 33 | 34 | message ProtoTransactionReceipt 35 | { 36 | optional bytes receipt = 1; 37 | optional uint64 cumgas = 2; 38 | } 39 | 40 | message ProtoTransactionWithReceipt 41 | { 42 | optional ProtoTransaction transaction = 1; 43 | optional ProtoTransactionReceipt receipt = 2; 44 | } -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/transaction/PendingStatus.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.transaction; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | @Data 7 | @Builder 8 | public class PendingStatus { 9 | private Integer code; 10 | private boolean confirmed; 11 | private boolean pending; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/transaction/Transaction.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.transaction; 2 | 3 | 4 | import com.firestack.laksaj.account.Account; 5 | import com.firestack.laksaj.blockchain.TransactionReceipt; 6 | import com.firestack.laksaj.jsonrpc.HttpProvider; 7 | import com.firestack.laksaj.utils.Base58; 8 | import com.firestack.laksaj.utils.ByteUtil; 9 | import com.firestack.laksaj.utils.TransactionUtil; 10 | import com.google.gson.Gson; 11 | import lombok.Builder; 12 | import lombok.Data; 13 | 14 | import java.io.IOException; 15 | import java.time.Duration; 16 | 17 | import static java.time.temporal.ChronoUnit.SECONDS; 18 | 19 | @Data 20 | @Builder 21 | public class Transaction { 22 | private String ID; 23 | private String version; 24 | private String nonce; 25 | private String amount; 26 | private String gasPrice; 27 | private String gasLimit; 28 | private String signature; 29 | private TransactionReceipt receipt; 30 | private String senderPubKey; 31 | private String toAddr; 32 | private String code; 33 | private String data; 34 | 35 | private HttpProvider provider; 36 | private TxStatus status; 37 | // indicate error message returned by api while creating 38 | private String info; 39 | 40 | public TxParams toTransactionParam() throws IOException { 41 | return TxParams.builder() 42 | .ID(this.ID) 43 | .version(this.version) 44 | .nonce(this.nonce) 45 | .amount(this.amount) 46 | .gasPrice(this.gasPrice) 47 | .gasLimit(this.gasLimit) 48 | .signature(this.signature) 49 | .receipt(this.receipt) 50 | .senderPubKey(this.senderPubKey.toLowerCase()) 51 | .toAddr(this.toAddr == null ? "0000000000000000000000000000000000000000" : this.toAddr.toLowerCase()) 52 | .code(this.code) 53 | .data(this.data) 54 | .build(); 55 | } 56 | 57 | public TransactionPayload toTransactionPayload() throws Exception { 58 | return TransactionPayload.builder() 59 | .version(Integer.parseInt(this.version)) 60 | .nonce(Integer.valueOf(this.nonce)) 61 | .toAddr(Account.normaliseAddress(this.toAddr)) 62 | .amount(this.amount) 63 | .pubKey(this.senderPubKey.toLowerCase()) 64 | .gasPrice(this.gasPrice) 65 | .gasLimit(this.gasLimit) 66 | .code(this.code) 67 | .data(this.data) 68 | .signature(this.signature.toLowerCase()) 69 | .build(); 70 | } 71 | 72 | public void marshalToAddress() throws IOException { 73 | byte[] address = Base58.decode(this.getToAddr()); 74 | this.setToAddr(ByteUtil.byteArrayToHexString(address)); 75 | } 76 | 77 | public byte[] bytes() throws IOException { 78 | TxParams txParams = toTransactionParam(); 79 | TransactionUtil util = new TransactionUtil(); 80 | Gson gson = new Gson(); 81 | byte[] bytes = util.encodeTransactionProto(txParams); 82 | return bytes; 83 | } 84 | 85 | public boolean isPending() { 86 | return this.status.equals(TxStatus.Pending); 87 | } 88 | 89 | public boolean isInitialised() { 90 | return this.status.equals(TxStatus.Initialised); 91 | } 92 | 93 | public boolean isConfirmed() { 94 | return this.status.equals(TxStatus.Confirmed); 95 | } 96 | 97 | public boolean isRejected() { 98 | return this.status.equals(TxStatus.Rejected); 99 | } 100 | 101 | public Transaction confirm(String txHash, int maxAttempts, int interval) throws InterruptedException { 102 | this.setStatus(TxStatus.Pending); 103 | for (int i = 0; i < maxAttempts; i++) { 104 | boolean tracked = this.trackTx(txHash); 105 | Thread.sleep(Duration.of(interval, SECONDS).toMillis()); 106 | 107 | if (tracked) { 108 | this.setStatus(TxStatus.Confirmed); 109 | return this; 110 | } 111 | } 112 | this.status = TxStatus.Rejected; 113 | return this; 114 | } 115 | 116 | public boolean trackTx(String txHash) { 117 | System.out.println("tracking transaction: " + txHash); 118 | Transaction response; 119 | try { 120 | response = this.provider.getTransaction(txHash).getResult(); 121 | } catch (Exception e) { 122 | System.out.println("transaction not confirmed yet"); 123 | return false; 124 | } 125 | 126 | if (null == response) { 127 | System.out.println("transaction not confirmed yet"); 128 | return false; 129 | } 130 | 131 | 132 | this.setID(response.getID()); 133 | this.setReceipt(response.getReceipt()); 134 | if (response.getReceipt() != null && response.getReceipt().isSuccess()) { 135 | System.out.println("Transaction confirmed!"); 136 | this.setStatus(TxStatus.Confirmed); 137 | } else { 138 | this.setStatus(TxStatus.Rejected); 139 | System.out.println("Transaction rejected!"); 140 | 141 | } 142 | return true; 143 | } 144 | 145 | 146 | } 147 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/transaction/TransactionFactory.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.transaction; 2 | 3 | import com.firestack.laksaj.jsonrpc.HttpProvider; 4 | 5 | import java.time.Duration; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | import static java.time.temporal.ChronoUnit.SECONDS; 10 | 11 | 12 | public class TransactionFactory { 13 | public static Transaction buildTransaction(TxParams params, HttpProvider provider, TxStatus status) { 14 | return Transaction.builder() 15 | .ID(params.getID()) 16 | .version(params.getVersion()) 17 | .nonce(params.getNonce()) 18 | .amount(params.getAmount()) 19 | .gasPrice(params.getGasPrice()) 20 | .gasLimit(params.getGasLimit()) 21 | .signature(params.getSignature()) 22 | .receipt(params.getReceipt()) 23 | .senderPubKey(params.getSenderPubKey()) 24 | .toAddr(params.getToAddr()) 25 | .code(params.getCode()) 26 | .data(params.getData()) 27 | .provider(provider) 28 | .status(status) 29 | .build(); 30 | } 31 | 32 | public static HttpProvider.CreateTxResult createTransaction(Transaction signedTx) throws Exception { 33 | return signedTx.getProvider().createTransaction(signedTx.toTransactionPayload()).getResult(); 34 | } 35 | 36 | public static List toPayloads(List transactions) throws Exception { 37 | List payloads = new ArrayList<>(); 38 | for (int i = 0; i < transactions.size(); i++) { 39 | payloads.add(transactions.get(i).toTransactionPayload()); 40 | } 41 | return payloads; 42 | } 43 | 44 | public static List batchConfirm(List transactions, int maxAttempts, int interval) throws InterruptedException { 45 | for (int i = 0; i < transactions.size(); i++) { 46 | Transaction transaction = transactions.get(i); 47 | // if the status is rejected already, we don't track it at all 48 | if (TxStatus.Rejected != transaction.getStatus()) { 49 | transaction.setStatus(TxStatus.Pending); 50 | } 51 | 52 | } 53 | 54 | for (int i = 0; i < maxAttempts; i++) { 55 | Thread.sleep(Duration.of(interval, SECONDS).toMillis()); 56 | for (int j = 0; j < transactions.size(); j++) { 57 | Transaction transaction = transactions.get(j); 58 | // if transaction status is pending, track it, otherwise just ignore it 59 | if (TxStatus.Pending == transaction.getStatus()) { 60 | if (transaction.trackTx(transaction.getID())) { 61 | transaction.setStatus(TxStatus.Confirmed); 62 | } 63 | } 64 | } 65 | } 66 | 67 | for (int j = 0; j < transactions.size(); j++) { 68 | Transaction transaction = transactions.get(j); 69 | // if transaction status still pending, set it to reject 70 | if (TxStatus.Pending == transaction.getStatus()) { 71 | transaction.setStatus(TxStatus.Rejected); 72 | transaction.setInfo("cannot track after time limit"); 73 | } 74 | } 75 | 76 | return transactions; 77 | } 78 | 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/transaction/TransactionPayload.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.transaction; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | @Data 7 | @Builder 8 | public class TransactionPayload { 9 | private int version; 10 | private int nonce; 11 | private String toAddr; 12 | private String amount; 13 | private String pubKey; 14 | private String gasPrice; 15 | private String gasLimit; 16 | private String code; 17 | private String data; 18 | private String signature; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/transaction/TransactionStatus.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.transaction; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | @Data 7 | @Builder 8 | public class TransactionStatus { 9 | 10 | @Data 11 | @Builder 12 | public static class InternalId { 13 | private String $oid; 14 | } 15 | 16 | private String ID; 17 | private InternalId _id; 18 | private String amount; 19 | private String data; 20 | private String epochInserted; 21 | private String epochUpdated; 22 | private String gasLimit; 23 | private String gasPrice; 24 | private String lastModified; 25 | private Integer modificationState; 26 | private String nonce; 27 | private String senderAddr; 28 | private String signature; 29 | private Integer status; 30 | private Boolean success; 31 | private String toAddr; 32 | private String version; 33 | 34 | private String info; 35 | } 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/transaction/TxParams.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.transaction; 2 | 3 | import com.firestack.laksaj.blockchain.TransactionReceipt; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | 7 | @Data 8 | @Builder 9 | public class TxParams { 10 | private String ID; 11 | private String version; 12 | private String nonce; 13 | private String amount; 14 | private String gasPrice; 15 | private String gasLimit; 16 | private String signature; 17 | private TransactionReceipt receipt; 18 | private String senderPubKey; 19 | private String toAddr; 20 | private String code; 21 | private String data; 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/transaction/TxReceipt.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.transaction; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | @Data 7 | @Builder 8 | public class TxReceipt { 9 | private boolean success; 10 | private int cumulativeGas; 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/transaction/TxStatus.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.transaction; 2 | 3 | public enum TxStatus { 4 | Initialised, 5 | Pending, 6 | Confirmed, 7 | Rejected, 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/utils/AddressFormatException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. 3 | * Copyright 2015 Andreas Schildbach 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.firestack.laksaj.utils; 19 | 20 | @SuppressWarnings("serial") 21 | public class AddressFormatException extends IllegalArgumentException { 22 | public AddressFormatException() { 23 | super(); 24 | } 25 | 26 | public AddressFormatException(String message) { 27 | super(message); 28 | } 29 | 30 | 31 | public static class InvalidCharacter extends AddressFormatException { 32 | public final char character; 33 | public final int position; 34 | 35 | public InvalidCharacter(char character, int position) { 36 | super("Invalid character '" + Character.toString(character) + "' at position " + position); 37 | this.character = character; 38 | this.position = position; 39 | } 40 | } 41 | 42 | 43 | public static class InvalidDataLength extends AddressFormatException { 44 | public InvalidDataLength() { 45 | super(); 46 | } 47 | 48 | public InvalidDataLength(String message) { 49 | super(message); 50 | } 51 | } 52 | 53 | 54 | public static class InvalidChecksum extends AddressFormatException { 55 | public InvalidChecksum() { 56 | super("Checksum does not validate"); 57 | } 58 | 59 | public InvalidChecksum(String message) { 60 | super(message); 61 | } 62 | } 63 | 64 | 65 | public static class InvalidPrefix extends AddressFormatException { 66 | public InvalidPrefix() { 67 | super(); 68 | } 69 | 70 | public InvalidPrefix(String message) { 71 | super(message); 72 | } 73 | } 74 | 75 | 76 | public static class WrongNetwork extends InvalidPrefix { 77 | public WrongNetwork(int versionHeader) { 78 | super("Version code of address did not match acceptable versions for network: " + versionHeader); 79 | } 80 | 81 | public WrongNetwork(String hrp) { 82 | super("Human readable part of address did not match acceptable HRPs for network: " + hrp); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/utils/Base58.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.utils; 2 | 3 | import java.io.IOException; 4 | import java.math.BigInteger; 5 | import java.util.Arrays; 6 | 7 | public class Base58 { 8 | public static final char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray(); 9 | private static final char ENCODED_ZERO = ALPHABET[0]; 10 | private static final int[] INDEXES = new int[128]; 11 | 12 | static { 13 | Arrays.fill(INDEXES, -1); 14 | for (int i = 0; i < ALPHABET.length; i++) { 15 | INDEXES[ALPHABET[i]] = i; 16 | } 17 | } 18 | 19 | public static String encode(String input) { 20 | if (input.startsWith("0x") || input.startsWith("0X")) { 21 | input = input.substring(2); 22 | } 23 | return encode(ByteUtil.hexStringToByteArray(input)); 24 | } 25 | 26 | 27 | public static String encode(byte[] input) { 28 | if (input.length == 0) { 29 | return ""; 30 | } 31 | // Count leading zeros. 32 | int zeros = 0; 33 | while (zeros < input.length && input[zeros] == 0) { 34 | ++zeros; 35 | } 36 | // Convert base-256 digits to base-58 digits (plus conversion to ASCII characters) 37 | input = Arrays.copyOf(input, input.length); // since we modify it in-place 38 | char[] encoded = new char[input.length * 2]; // upper bound 39 | int outputStart = encoded.length; 40 | for (int inputStart = zeros; inputStart < input.length; ) { 41 | encoded[--outputStart] = ALPHABET[divmod(input, inputStart, 256, 58)]; 42 | if (input[inputStart] == 0) { 43 | ++inputStart; // optimization - skip leading zeros 44 | } 45 | } 46 | // Preserve exactly as many leading encoded zeros in output as there were leading zeros in input. 47 | while (outputStart < encoded.length && encoded[outputStart] == ENCODED_ZERO) { 48 | ++outputStart; 49 | } 50 | while (--zeros >= 0) { 51 | encoded[--outputStart] = ENCODED_ZERO; 52 | } 53 | // Return encoded string (including encoded leading zeros). 54 | return new String(encoded, outputStart, encoded.length - outputStart); 55 | } 56 | 57 | 58 | public static byte[] decode(String input) throws IOException { 59 | if (input.length() == 0) { 60 | return new byte[0]; 61 | } 62 | // Convert the base58-encoded ASCII chars to a base58 byte sequence (base58 digits). 63 | byte[] input58 = new byte[input.length()]; 64 | for (int i = 0; i < input.length(); ++i) { 65 | char c = input.charAt(i); 66 | int digit = c < 128 ? INDEXES[c] : -1; 67 | if (digit < 0) { 68 | throw new IOException("invalid character"); 69 | } 70 | input58[i] = (byte) digit; 71 | } 72 | // Count leading zeros. 73 | int zeros = 0; 74 | while (zeros < input58.length && input58[zeros] == 0) { 75 | ++zeros; 76 | } 77 | // Convert base-58 digits to base-256 digits. 78 | byte[] decoded = new byte[input.length()]; 79 | int outputStart = decoded.length; 80 | for (int inputStart = zeros; inputStart < input58.length; ) { 81 | decoded[--outputStart] = divmod(input58, inputStart, 58, 256); 82 | if (input58[inputStart] == 0) { 83 | ++inputStart; // optimization - skip leading zeros 84 | } 85 | } 86 | // Ignore extra leading zeroes that were added during the calculation. 87 | while (outputStart < decoded.length && decoded[outputStart] == 0) { 88 | ++outputStart; 89 | } 90 | // Return decoded data (including original number of leading zeros). 91 | return Arrays.copyOfRange(decoded, outputStart - zeros, decoded.length); 92 | } 93 | 94 | public static BigInteger decodeToBigInteger(String input) throws IOException { 95 | return new BigInteger(1, decode(input)); 96 | } 97 | 98 | private static byte divmod(byte[] number, int firstDigit, int base, int divisor) { 99 | // this is just long division which accounts for the base of the input digits 100 | int remainder = 0; 101 | for (int i = firstDigit; i < number.length; i++) { 102 | int digit = (int) number[i] & 0xFF; 103 | int temp = remainder * base + digit; 104 | number[i] = (byte) (temp / divisor); 105 | remainder = temp % divisor; 106 | } 107 | return (byte) remainder; 108 | } 109 | 110 | public static boolean isBase58(String v) { 111 | try { 112 | Base58.decode(v); 113 | } catch (Exception e) { 114 | return false; 115 | } 116 | return true; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/utils/Bech32.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Coinomi Ltd 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.firestack.laksaj.utils; 18 | 19 | import com.firestack.laksaj.account.Account; 20 | 21 | import java.util.ArrayList; 22 | import java.util.Arrays; 23 | import java.util.List; 24 | import java.util.Locale; 25 | 26 | import static com.google.common.base.Preconditions.checkArgument; 27 | 28 | public class Bech32 { 29 | /** 30 | * The Bech32 character set for encoding. 31 | */ 32 | private static final String CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; 33 | 34 | /** 35 | * The Bech32 character set for decoding. 36 | */ 37 | private static final byte[] CHARSET_REV = { 38 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 39 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 40 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 41 | 15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1, 42 | -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, 43 | 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1, 44 | -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, 45 | 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1 46 | }; 47 | 48 | public static class Bech32Data { 49 | public final String hrp; 50 | public final byte[] data; 51 | 52 | private Bech32Data(final String hrp, final byte[] data) { 53 | this.hrp = hrp; 54 | this.data = data; 55 | } 56 | } 57 | 58 | /** 59 | * Find the polynomial with value coefficients mod the generator as 30-bit. 60 | */ 61 | private static int polymod(final byte[] values) { 62 | int c = 1; 63 | for (byte v_i : values) { 64 | int c0 = (c >>> 25) & 0xff; 65 | c = ((c & 0x1ffffff) << 5) ^ (v_i & 0xff); 66 | if ((c0 & 1) != 0) c ^= 0x3b6a57b2; 67 | if ((c0 & 2) != 0) c ^= 0x26508e6d; 68 | if ((c0 & 4) != 0) c ^= 0x1ea119fa; 69 | if ((c0 & 8) != 0) c ^= 0x3d4233dd; 70 | if ((c0 & 16) != 0) c ^= 0x2a1462b3; 71 | } 72 | return c; 73 | } 74 | 75 | /** 76 | * Expand a HRP for use in checksum computation. 77 | */ 78 | private static byte[] expandHrp(final String hrp) { 79 | int hrpLength = hrp.length(); 80 | byte ret[] = new byte[hrpLength * 2 + 1]; 81 | for (int i = 0; i < hrpLength; ++i) { 82 | int c = hrp.charAt(i) & 0x7f; // Limit to standard 7-bit ASCII 83 | ret[i] = (byte) ((c >>> 5) & 0x07); 84 | ret[i + hrpLength + 1] = (byte) (c & 0x1f); 85 | } 86 | ret[hrpLength] = 0; 87 | return ret; 88 | } 89 | 90 | /** 91 | * Verify a checksum. 92 | */ 93 | private static boolean verifyChecksum(final String hrp, final byte[] values) { 94 | byte[] hrpExpanded = expandHrp(hrp); 95 | byte[] combined = new byte[hrpExpanded.length + values.length]; 96 | System.arraycopy(hrpExpanded, 0, combined, 0, hrpExpanded.length); 97 | System.arraycopy(values, 0, combined, hrpExpanded.length, values.length); 98 | return polymod(combined) == 1; 99 | } 100 | 101 | /** 102 | * Create a checksum. 103 | */ 104 | private static byte[] createChecksum(final String hrp, final byte[] values) { 105 | byte[] hrpExpanded = expandHrp(hrp); 106 | byte[] enc = new byte[hrpExpanded.length + values.length + 6]; 107 | System.arraycopy(hrpExpanded, 0, enc, 0, hrpExpanded.length); 108 | System.arraycopy(values, 0, enc, hrpExpanded.length, values.length); 109 | int mod = polymod(enc) ^ 1; 110 | byte[] ret = new byte[6]; 111 | for (int i = 0; i < 6; ++i) { 112 | ret[i] = (byte) ((mod >>> (5 * (5 - i))) & 31); 113 | } 114 | return ret; 115 | } 116 | 117 | /** 118 | * Encode a Bech32 string. 119 | */ 120 | public static String encode(final Bech32Data bech32) { 121 | return encode(bech32.hrp, bech32.data); 122 | } 123 | 124 | /** 125 | * Encode a Bech32 string. 126 | */ 127 | public static String encode(String hrp, final byte[] values) { 128 | checkArgument(hrp.length() >= 1, "Human-readable part is too short"); 129 | checkArgument(hrp.length() <= 83, "Human-readable part is too long"); 130 | hrp = hrp.toLowerCase(Locale.ROOT); 131 | byte[] checksum = createChecksum(hrp, values); 132 | byte[] combined = new byte[values.length + checksum.length]; 133 | System.arraycopy(values, 0, combined, 0, values.length); 134 | System.arraycopy(checksum, 0, combined, values.length, checksum.length); 135 | StringBuilder sb = new StringBuilder(hrp.length() + 1 + combined.length); 136 | sb.append(hrp); 137 | sb.append('1'); 138 | for (byte b : combined) { 139 | sb.append(CHARSET.charAt(b)); 140 | } 141 | return sb.toString(); 142 | } 143 | 144 | /** 145 | * Decode a Bech32 string. 146 | */ 147 | public static Bech32Data decode(final String str) throws AddressFormatException { 148 | boolean lower = false, upper = false; 149 | if (str.length() < 8) 150 | throw new AddressFormatException.InvalidDataLength("Input too short: " + str.length()); 151 | if (str.length() > 90) 152 | throw new AddressFormatException.InvalidDataLength("Input too long: " + str.length()); 153 | for (int i = 0; i < str.length(); ++i) { 154 | char c = str.charAt(i); 155 | if (c < 33 || c > 126) throw new AddressFormatException.InvalidCharacter(c, i); 156 | if (c >= 'a' && c <= 'z') { 157 | if (upper) 158 | throw new AddressFormatException.InvalidCharacter(c, i); 159 | lower = true; 160 | } 161 | if (c >= 'A' && c <= 'Z') { 162 | if (lower) 163 | throw new AddressFormatException.InvalidCharacter(c, i); 164 | upper = true; 165 | } 166 | } 167 | final int pos = str.lastIndexOf('1'); 168 | if (pos < 1) throw new AddressFormatException.InvalidPrefix("Missing human-readable part"); 169 | final int dataPartLength = str.length() - 1 - pos; 170 | if (dataPartLength < 6) 171 | throw new AddressFormatException.InvalidDataLength("Data part too short: " + dataPartLength); 172 | byte[] values = new byte[dataPartLength]; 173 | for (int i = 0; i < dataPartLength; ++i) { 174 | char c = str.charAt(i + pos + 1); 175 | if (CHARSET_REV[c] == -1) throw new AddressFormatException.InvalidCharacter(c, i + pos + 1); 176 | values[i] = CHARSET_REV[c]; 177 | } 178 | String hrp = str.substring(0, pos).toLowerCase(Locale.ROOT); 179 | if (!verifyChecksum(hrp, values)) throw new AddressFormatException.InvalidChecksum(); 180 | return new Bech32Data(hrp, Arrays.copyOfRange(values, 0, values.length - 6)); 181 | } 182 | 183 | 184 | public static String HRP = "zil"; 185 | 186 | 187 | public static List convertBits(byte[] data, int fromWidth, int toWidth, boolean pad) { 188 | int acc = 0; 189 | int bits = 0; 190 | int maxv = (1 << toWidth) - 1; 191 | List ret = new ArrayList<>(); 192 | 193 | for (int i = 0; i < data.length; i++) { 194 | int value = data[i] & 0xff; 195 | if (value < 0 || value >> fromWidth != 0) { 196 | return null; 197 | } 198 | acc = (acc << fromWidth) | value; 199 | bits += fromWidth; 200 | while (bits >= toWidth) { 201 | bits -= toWidth; 202 | ret.add((acc >> bits) & maxv); 203 | } 204 | } 205 | 206 | if (pad) { 207 | if (bits > 0) { 208 | ret.add((acc << (toWidth - bits)) & maxv); 209 | } else if (bits >= fromWidth || ((acc << (toWidth - bits)) & maxv) != 0) { 210 | return null; 211 | } 212 | } 213 | 214 | return ret; 215 | 216 | } 217 | 218 | public static String toBech32Address(String address) throws Exception { 219 | if (!Validation.isAddress(address)) { 220 | throw new Exception("Invalid address format."); 221 | } 222 | 223 | address = address.toLowerCase().replace("0x", ""); 224 | 225 | List bits = convertBits(ByteUtil.hexStringToByteArray(address), 8, 5, false); 226 | 227 | byte[] addrBz = new byte[bits.size()]; 228 | 229 | for (int i = 0; i < bits.size(); i++) { 230 | addrBz[i] = bits.get(i).byteValue(); 231 | } 232 | 233 | if (null == addrBz) { 234 | throw new Exception("Could not convert byte Buffer to 5-bit Buffer"); 235 | } 236 | 237 | return encode(HRP, addrBz); 238 | } 239 | 240 | public static String fromBech32Address(String address) throws Exception { 241 | Bech32Data data = decode(address); 242 | 243 | if (!data.hrp.equals(HRP)) { 244 | throw new Exception("Expected hrp to be zil"); 245 | } 246 | 247 | List bits = convertBits(data.data, 5, 8, false); 248 | byte[] buf = new byte[bits.size()]; 249 | for (int i = 0; i < bits.size(); i++) { 250 | buf[i] = bits.get(i).byteValue(); 251 | } 252 | if (null == buf || buf.length == 0) { 253 | throw new Exception("Could not convert buffer to bytes"); 254 | } 255 | 256 | return Account.toCheckSumAddress(ByteUtil.byteArrayToHexString(buf)).replace("0x", ""); 257 | } 258 | 259 | 260 | } 261 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/utils/ByteUtil.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.utils; 2 | 3 | public class ByteUtil { 4 | private final static char[] hexArray = "0123456789ABCDEF".toCharArray(); 5 | 6 | public static String byteArrayToHexString(byte[] bytes) { 7 | char[] hexChars = new char[bytes.length * 2]; 8 | for (int j = 0; j < bytes.length; j++) { 9 | int v = bytes[j] & 0xFF; 10 | hexChars[j * 2] = hexArray[v >>> 4]; 11 | hexChars[j * 2 + 1] = hexArray[v & 0x0F]; 12 | } 13 | return new String(hexChars); 14 | } 15 | 16 | public static byte[] hexStringToByteArray(String s) { 17 | s = s.toLowerCase().replace("0x", ""); 18 | int len = s.length(); 19 | byte[] data = new byte[len / 2]; 20 | for (int i = 0; i < len; i += 2) { 21 | data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) 22 | + Character.digit(s.charAt(i + 1), 16)); 23 | } 24 | return data; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/utils/HashUtil.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.utils; 2 | 3 | import com.google.common.hash.Hashing; 4 | 5 | import javax.crypto.Mac; 6 | import javax.crypto.spec.SecretKeySpec; 7 | import java.nio.ByteBuffer; 8 | import java.util.Arrays; 9 | 10 | public class HashUtil { 11 | 12 | public static String ALGO_IDENTIFIER = "aes-128-ctr"; 13 | 14 | 15 | public static byte[] hmacSha256(byte[] derivedKey, byte[] bytes) { 16 | return Hashing.hmacSha256(derivedKey).hashBytes(bytes).asBytes(); 17 | } 18 | 19 | public static byte[] sha256(byte[] bytes) { 20 | return Hashing.sha256().hashBytes(bytes).asBytes(); 21 | } 22 | 23 | public static byte[] generateMac(byte[] derivedKey, byte[] cipherText, byte[] iv) { 24 | ByteBuffer byteBuffer = ByteBuffer.allocate(16 + cipherText.length + iv.length + ALGO_IDENTIFIER.getBytes().length); 25 | byteBuffer.put(Arrays.copyOfRange(derivedKey, 16, 32)); 26 | byteBuffer.put(cipherText); 27 | byteBuffer.put(iv); 28 | byteBuffer.put(ALGO_IDENTIFIER.getBytes()); 29 | return HashUtil.hmacSha256(derivedKey, byteBuffer.array()); 30 | } 31 | 32 | public static byte[] encode(String key, String data) throws Exception { 33 | Mac sha256_HMAC = Mac.getInstance("HmacSHA256"); 34 | SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256"); 35 | sha256_HMAC.init(secret_key); 36 | 37 | return sha256_HMAC.doFinal(data.getBytes("UTF-8")); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/utils/TransactionUtil.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.utils; 2 | 3 | import com.firestack.laksaj.proto.Message; 4 | import com.firestack.laksaj.transaction.TxParams; 5 | import com.google.common.base.Strings; 6 | import com.google.protobuf.ByteString; 7 | import org.bouncycastle.util.BigIntegers; 8 | 9 | import java.math.BigInteger; 10 | 11 | public class TransactionUtil { 12 | 13 | public byte[] encodeTransactionProto(TxParams txParams) { 14 | BigInteger amount = new BigInteger(txParams.getAmount()); 15 | BigInteger gasPrice = new BigInteger(txParams.getGasPrice()); 16 | Message.ProtoTransactionCoreInfo.Builder builder = Message.ProtoTransactionCoreInfo.newBuilder() 17 | .setVersion(Integer.valueOf(txParams.getVersion())) 18 | .setNonce(Strings.isNullOrEmpty(txParams.getNonce()) ? 0 : Long.valueOf(txParams.getNonce())) 19 | .setToaddr(ByteString.copyFrom(ByteUtil.hexStringToByteArray(txParams.getToAddr().toLowerCase()))) 20 | .setSenderpubkey(Message.ByteArray.newBuilder().setData(ByteString.copyFrom(ByteUtil.hexStringToByteArray(txParams.getSenderPubKey()))).build()) 21 | .setAmount(Message.ByteArray.newBuilder().setData(ByteString.copyFrom(BigIntegers.asUnsignedByteArray(16, amount))).build()) 22 | .setGasprice(Message.ByteArray.newBuilder().setData(ByteString.copyFrom(BigIntegers.asUnsignedByteArray(16, gasPrice))).build()) 23 | .setGaslimit(Long.valueOf(txParams.getGasLimit())); 24 | if (null != txParams.getCode() && !txParams.getCode().isEmpty()) { 25 | builder.setCode(ByteString.copyFrom(txParams.getCode().getBytes())); 26 | } 27 | 28 | if (null != txParams.getData() && !txParams.getData().isEmpty()) { 29 | builder.setData(ByteString.copyFrom(txParams.getData().getBytes())); 30 | } 31 | 32 | return builder.build().toByteArray(); 33 | 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/firestack/laksaj/utils/Validation.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.utils; 2 | 3 | import com.firestack.laksaj.account.Account; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | public class Validation { 11 | //using low case 12 | public static boolean isByteString(String str, int len) { 13 | Pattern pattern = Pattern.compile("^(0x)?[0-9a-fA-F]{" + len + "}"); 14 | Matcher matcher = pattern.matcher(str); 15 | return matcher.matches(); 16 | } 17 | 18 | public static boolean isBech32(String str) { 19 | Pattern pattern = Pattern.compile("^zil1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{38}"); 20 | Matcher matcher = pattern.matcher(str); 21 | return matcher.matches(); 22 | } 23 | 24 | public static boolean isAddress(String address) { 25 | return isByteString(address, 40); 26 | } 27 | 28 | public static boolean isPublicKey(String publicKey) { 29 | return isByteString(publicKey, 66); 30 | } 31 | 32 | public static boolean isPrivateKey(String privateKey) { 33 | return isByteString(privateKey, 64); 34 | } 35 | 36 | public static boolean isSignature(String signature) { 37 | return isByteString(signature, 128); 38 | } 39 | 40 | 41 | public static boolean isValidChecksumAddress(String address) { 42 | return isAddress(address.replace("0x", "")) && Account.toCheckSumAddress(address).equals(address); 43 | } 44 | 45 | 46 | public static String intToHex(int value, int size) { 47 | String hexVal = Integer.toHexString(value); 48 | char[] hexRep = new char[hexVal.length()]; 49 | for (int i = 0; i < hexVal.length(); i++) { 50 | hexRep[i] = hexVal.charAt(i); 51 | } 52 | 53 | List hex = new ArrayList<>(); 54 | 55 | for (int i = 0; i < size - hexVal.length(); i++) { 56 | hex.add('0'); 57 | } 58 | 59 | for (int i = 0; i < hexVal.length(); i++) { 60 | hex.add(hexRep[i]); 61 | } 62 | 63 | StringBuilder builder = new StringBuilder(); 64 | for (Character c : hex) { 65 | builder.append(c); 66 | } 67 | return builder.toString(); 68 | 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/test/java/com/firestack/laksaj/account/AccountTest.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.account; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | public class AccountTest { 7 | @Test 8 | public void toCheckSumAddress() { 9 | Assert.assertEquals(Account.toCheckSumAddress("4BAF5FADA8E5DB92C3D3242618C5B47133AE003C"), "0x4BAF5faDA8e5Db92C3d3242618c5B47133AE003C"); 10 | Assert.assertEquals(Account.toCheckSumAddress("448261915A80CDE9BDE7C7A791685200D3A0BF4E"), "0x448261915a80cdE9BDE7C7a791685200D3A0bf4E"); 11 | Assert.assertEquals(Account.toCheckSumAddress("DED02FD979FC2E55C0243BD2F52DF022C40ADA1E"), "0xDed02fD979fC2e55c0243bd2F52df022c40ADa1E"); 12 | Assert.assertEquals(Account.toCheckSumAddress("13F06E60297BEA6A3C402F6F64C416A6B31E586E"), "0x13F06E60297bea6A3c402F6f64c416A6b31e586e"); 13 | Assert.assertEquals(Account.toCheckSumAddress("1A90C25307C3CC71958A83FA213A2362D859CF33"), "0x1a90C25307C3Cc71958A83fa213A2362D859CF33"); 14 | Assert.assertEquals(Account.toCheckSumAddress("625ABAEBD87DAE9AB128F3B3AE99688813D9C5DF"), "0x625ABAebd87daE9ab128f3B3AE99688813d9C5dF"); 15 | Assert.assertEquals(Account.toCheckSumAddress("36BA34097F861191C48C839C9B1A8B5912F583CF"), "0x36Ba34097f861191C48C839c9b1a8B5912f583cF"); 16 | Assert.assertEquals(Account.toCheckSumAddress("D2453AE76C9A86AAE544FCA699DBDC5C576AEF3A"), "0xD2453Ae76C9A86AAe544fca699DbDC5c576aEf3A"); 17 | Assert.assertEquals(Account.toCheckSumAddress("72220E84947C36118CDBC580454DFAA3B918CD97"), "0x72220e84947c36118cDbC580454DFaa3b918cD97"); 18 | Assert.assertEquals(Account.toCheckSumAddress("50F92304C892D94A385CA6CE6CD6950CE9A36839"), "0x50f92304c892D94A385cA6cE6CD6950ce9A36839"); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/com/firestack/laksaj/account/WalletTest.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.account; 2 | 3 | import com.firestack.laksaj.jsonrpc.HttpProvider; 4 | import com.firestack.laksaj.transaction.Transaction; 5 | import com.firestack.laksaj.transaction.TransactionFactory; 6 | import org.junit.Test; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | import static com.firestack.laksaj.account.Wallet.pack; 12 | 13 | public class WalletTest { 14 | @Test 15 | public void sendTransactionTest() throws Exception { 16 | Wallet wallet = new Wallet(); 17 | String ptivateKey = "e19d05c5452598e24caad4a0d85a49146f7be089515c905ae6a19e8a578a6930"; 18 | // Populate the wallet with an account 19 | String address = wallet.addByPrivateKey(ptivateKey); 20 | wallet.addByPrivateKey(ptivateKey); 21 | 22 | HttpProvider provider = new HttpProvider("https://dev-api.zilliqa.com/"); 23 | wallet.setProvider(provider); 24 | //get balance 25 | HttpProvider.BalanceResult balanceResult = provider.getBalance(address).getResult(); 26 | 27 | //construct non-contract transaction 28 | Transaction transaction = Transaction.builder() 29 | .version(String.valueOf(pack(333, 1))) 30 | // .toAddr("24A4zoHhcP4PGia5e5aCnEbq4fQw") 31 | // .toAddr("0x4baf5fada8e5db92c3d3242618c5b47133ae003c".toLowerCase()) 32 | // .toAddr("4BAF5faDA8e5Db92C3d3242618c5B47133AE003C") 33 | .toAddr("zil16jrfrs8vfdtc74yzhyy83je4s4c5sqrcasjlc4") 34 | .senderPubKey("0246E7178DC8253201101E18FD6F6EB9972451D121FC57AA2A06DD5C111E58DC6A") 35 | .amount("10000000") 36 | .gasPrice("2000000000") 37 | .gasLimit("1") 38 | .code("") 39 | .data("") 40 | .provider(new HttpProvider("https://dev-api.zilliqa.com/")) 41 | .build(); 42 | 43 | //sign transaction 44 | transaction = wallet.sign(transaction); 45 | 46 | //broadcast transaction 47 | HttpProvider.CreateTxResult result = TransactionFactory.createTransaction(transaction); 48 | transaction.confirm(result.getTranID(), 100, 10); 49 | } 50 | 51 | @Test 52 | public void sendTransactionsTest() throws Exception { 53 | Wallet wallet = new Wallet(); 54 | String ptivateKey = "e19d05c5452598e24caad4a0d85a49146f7be089515c905ae6a19e8a578a6930"; 55 | // Populate the wallet with an account 56 | String address = wallet.addByPrivateKey(ptivateKey); 57 | wallet.addByPrivateKey(ptivateKey); 58 | 59 | HttpProvider provider = new HttpProvider("https://dev-api.zilliqa.com/"); 60 | wallet.setProvider(provider); 61 | //get balance 62 | HttpProvider.BalanceResult balanceResult = provider.getBalance(address).getResult(); 63 | Integer nonce = Integer.parseInt(balanceResult.getNonce()); 64 | 65 | //construct non-contract transactions 66 | List transactions = new ArrayList<>(); 67 | Transaction tx1 = Transaction.builder() 68 | .version(String.valueOf(pack(333, 1))) 69 | .toAddr("zil16jrfrs8vfdtc74yzhyy83je4s4c5sqrcasjlc4") 70 | .senderPubKey("0246E7178DC8253201101E18FD6F6EB9972451D121FC57AA2A06DD5C111E58DC6A") 71 | .amount("10000000") 72 | .nonce(Integer.toString(nonce + 1)) 73 | .gasPrice("2000000000") 74 | .gasLimit("1") 75 | .code("") 76 | .data("") 77 | .provider(provider) 78 | .build(); 79 | 80 | Transaction tx2 = Transaction.builder() 81 | .version(String.valueOf(pack(333, 1))) 82 | .toAddr("zil16jrfrs8vfdtc74yzhyy83je4s4c5sqrcasjlc4") 83 | .senderPubKey("0246E7178DC8253201101E18FD6F6EB9972451D121FC57AA2A06DD5C111E58DC6A") 84 | .amount("10000000") 85 | .gasPrice("2000000000") 86 | .gasLimit("1") 87 | .nonce(Integer.toString(nonce + 2)) 88 | .code("") 89 | .data("") 90 | .provider(provider) 91 | .build(); 92 | 93 | Transaction tx3 = Transaction.builder() 94 | .version(String.valueOf(pack(333, 1))) 95 | .toAddr("zil1n0lvw9dxh4jcljmzkruvexl69t08zs62ds9ats") 96 | .senderPubKey("0246E7178DC8253201101E18FD6F6EB9972451D121FC57AA2A06DD5C111E58DC6A") 97 | .amount("10000000") 98 | .gasPrice("1000000000") 99 | .gasLimit("1") 100 | .nonce(Integer.toString(nonce + 2)) 101 | .code("") 102 | .data("") 103 | .provider(provider) 104 | .build(); 105 | 106 | transactions.add(tx1); 107 | transactions.add(tx2); 108 | transactions.add(tx3); 109 | 110 | 111 | wallet.batchSign(transactions); 112 | provider.createTransactions(transactions); 113 | TransactionFactory.batchConfirm(transactions, 9, 10); 114 | 115 | // do some post check, e.g check errors 116 | for (int i = 0; i < transactions.size(); i++) { 117 | // we expected transaction 3 is failed because of gas fee setting 118 | // (whose to address is zil1n0lvw9dxh4jcljmzkruvexl69t08zs62ds9ats) 119 | if (transactions.get(i).getToAddr().equals("0x9BFEC715a6bD658fCb62B0f8cc9BFa2ADE71434A")) { 120 | System.out.println(transactions.get(i).getInfo()); 121 | } 122 | } 123 | 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/test/java/com/firestack/laksaj/contract/ContractTest.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.contract; 2 | 3 | import com.firestack.laksaj.account.Wallet; 4 | import com.firestack.laksaj.jsonrpc.HttpProvider; 5 | import com.firestack.laksaj.transaction.Transaction; 6 | import org.junit.Assert; 7 | import org.junit.Test; 8 | 9 | import java.security.NoSuchAlgorithmException; 10 | import java.util.Arrays; 11 | import java.util.List; 12 | 13 | import static com.firestack.laksaj.account.Wallet.pack; 14 | 15 | public class ContractTest { 16 | 17 | @Test 18 | public void deploy() throws Exception { 19 | String code = "scilla_version 0\n" + 20 | "\n" + 21 | "(* This contract implements a fungible token interface a la ERC20.*)\n" + 22 | "\n" + 23 | "(***************************************************)\n" + 24 | "(* Associated library *)\n" + 25 | "(***************************************************)\n" + 26 | "library FungibleToken\n" + 27 | "\n" + 28 | "let one = Uint128 1\n" + 29 | "let zero = Uint128 0\n" + 30 | "\n" + 31 | "let min_int =\n" + 32 | " fun (a : Uint128) => fun (b : Uint128) =>\n" + 33 | " let alt = builtin lt a b in\n" + 34 | " match alt with\n" + 35 | " | True =>\n" + 36 | " a\n" + 37 | " | False =>\n" + 38 | " b\n" + 39 | " end\n" + 40 | "\n" + 41 | "let le_int =\n" + 42 | " fun (a : Uint128) => fun (b : Uint128) =>\n" + 43 | " let x = builtin lt a b in\n" + 44 | " match x with\n" + 45 | " | True => True\n" + 46 | " | False =>\n" + 47 | " let y = builtin eq a b in\n" + 48 | " match y with\n" + 49 | " | True => True\n" + 50 | " | False => False\n" + 51 | " end\n" + 52 | " end\n" + 53 | "\n" + 54 | "(* returns singleton List Message *)\n" + 55 | "let one_msg =\n" + 56 | " fun (msg : Message) =>\n" + 57 | " let nil_msg = Nil {Message} in\n" + 58 | " Cons {Message} msg nil_msg\n" + 59 | "\n" + 60 | "\n" + 61 | "(***************************************************)\n" + 62 | "(* The contract definition *)\n" + 63 | "(***************************************************)\n" + 64 | "\n" + 65 | "contract FungibleToken\n" + 66 | "(owner : ByStr20,\n" + 67 | " total_tokens : Uint128,\n" + 68 | " decimals : Uint32,\n" + 69 | " name : String,\n" + 70 | " symbol : String)\n" + 71 | "\n" + 72 | "(* Initial balance is not stated explicitly: it's initialized when creating the contract. *)\n" + 73 | "\n" + 74 | "field balances : Map ByStr20 Uint128 =\n" + 75 | " let m = Emp ByStr20 Uint128 in\n" + 76 | " builtin put m owner total_tokens\n" + 77 | "field allowed : Map ByStr20 (Map ByStr20 Uint128) = Emp ByStr20 (Map ByStr20 Uint128)\n" + 78 | "\n" + 79 | "transition BalanceOf (tokenOwner : ByStr20)\n" + 80 | " bal <- balances[tokenOwner];\n" + 81 | " match bal with\n" + 82 | " | Some v =>\n" + 83 | " msg = { _tag : \"BalanceOfResponse\"; _recipient : _sender; _amount : zero;\n" + 84 | " address : tokenOwner; balance : v};\n" + 85 | " msgs = one_msg msg;\n" + 86 | " send msgs\n" + 87 | " | None =>\n" + 88 | " msg = { _tag : \"BalanceOfResponse\"; _recipient : _sender; _amount : zero;\n" + 89 | " address : tokenOwner; balance : zero};\n" + 90 | " msgs = one_msg msg;\n" + 91 | " send msgs\n" + 92 | " end\n" + 93 | "end\n" + 94 | "\n" + 95 | "transition TotalSupply ()\n" + 96 | " msg = { _tag : \"TotalSupplyResponse\"; _recipient : _sender; _amount : zero;\n" + 97 | " caller : _sender; totalSupply : total_tokens};\n" + 98 | " msgs = one_msg msg;\n" + 99 | " send msgs\n" + 100 | "end\n" + 101 | "\n" + 102 | "transition Transfer (to : ByStr20, tokens : Uint128)\n" + 103 | " bal <- balances[_sender];\n" + 104 | " match bal with\n" + 105 | " | Some b =>\n" + 106 | " can_do = le_int tokens b;\n" + 107 | " match can_do with\n" + 108 | " | True =>\n" + 109 | " (* subtract tokens from _sender and add it to \"to\" *)\n" + 110 | " new_sender_bal = builtin sub b tokens;\n" + 111 | " balances[_sender] := new_sender_bal;\n" + 112 | "\n" + 113 | " (* Adds tokens to \"to\" address *)\n" + 114 | " to_bal <- balances[to];\n" + 115 | " new_to_bal = match to_bal with\n" + 116 | " | Some x => builtin add x tokens\n" + 117 | " | None => tokens\n" + 118 | " end;\n" + 119 | "\n" + 120 | " \t balances[to] := new_to_bal;\n" + 121 | " msg = { _tag : \"TransferSuccess\"; _recipient : _sender; _amount : zero;\n" + 122 | " sender : _sender; recipient : to; amount : tokens};\n" + 123 | " msgs = one_msg msg;\n" + 124 | " send msgs\n" + 125 | " | False =>\n" + 126 | " (* balance not sufficient. *)\n" + 127 | " msg = { _tag : \"TransferFailure\"; _recipient : _sender; _amount : zero;\n" + 128 | " sender : _sender; recipient : to; amount : zero};\n" + 129 | " msgs = one_msg msg;\n" + 130 | " send msgs\n" + 131 | " end\n" + 132 | " | None =>\n" + 133 | " (* no balance record, can't transfer *)\n" + 134 | " msg = { _tag : \"TransferFailure\"; _recipient : _sender; _amount : zero;\n" + 135 | " sender : _sender; recipient : to; amount : zero};\n" + 136 | " msgs = one_msg msg;\n" + 137 | " send msgs\n" + 138 | " end\n" + 139 | "end\n" + 140 | "\n" + 141 | "transition TransferFrom (from : ByStr20, to : ByStr20, tokens : Uint128)\n" + 142 | " bal <- balances[from];\n" + 143 | " (* Check if _sender has been authorized by \"from\" *)\n" + 144 | " sender_allowed_from <- allowed[from][_sender];\n" + 145 | " match bal with\n" + 146 | " | Some a =>\n" + 147 | " match sender_allowed_from with\n" + 148 | " | Some b =>\n" + 149 | " (* We can only transfer the minimum of available or authorized tokens *)\n" + 150 | " t = min_int a b;\n" + 151 | " can_do = le_int tokens t;\n" + 152 | " match can_do with\n" + 153 | " | True =>\n" + 154 | " (* tokens is what we should subtract from \"from\" and add to \"to\" *)\n" + 155 | " new_from_bal = builtin sub a tokens;\n" + 156 | " balances[from] := new_from_bal;\n" + 157 | " to_bal <- balances[to];\n" + 158 | " match to_bal with\n" + 159 | " | Some tb =>\n" + 160 | " new_to_bal = builtin add tb tokens;\n" + 161 | " balances[to] := new_to_bal\n" + 162 | " | None =>\n" + 163 | " (* \"to\" has no balance. So just set it to tokens *)\n" + 164 | " balances[to] := tokens\n" + 165 | " end;\n" + 166 | " (* reduce \"allowed\" by \"tokens\" *)\n" + 167 | " new_allowed = builtin sub b tokens;\n" + 168 | " allowed[from][_sender] := new_allowed;\n" + 169 | " msg = { _tag : \"TransferFromSuccess\"; _recipient : _sender; _amount : zero;\n" + 170 | " sender : from; recipient : to; amount : tokens };\n" + 171 | " msgs = one_msg msg;\n" + 172 | " send msgs\n" + 173 | " | False =>\n" + 174 | " msg = { _tag : \"TransferFromFailure\"; _recipient : _sender; _amount : zero;\n" + 175 | " sender : from; recipient : to; amount : zero };\n" + 176 | " msgs = one_msg msg;\n" + 177 | " send msgs\n" + 178 | " end\n" + 179 | " | None =>\n" + 180 | " msg = { _tag : \"TransferFromFailure\"; _recipient : _sender; _amount : zero;\n" + 181 | " sender : from; recipient : to; amount : zero };\n" + 182 | " msgs = one_msg msg;\n" + 183 | " send msgs\n" + 184 | " end\n" + 185 | " | None =>\n" + 186 | "\tmsg = { _tag : \"TransferFromFailure\"; _recipient : _sender; _amount : zero;\n" + 187 | " sender : from; recipient : to; amount : zero };\n" + 188 | " msgs = one_msg msg;\n" + 189 | " send msgs\n" + 190 | " end\n" + 191 | "end\n" + 192 | "\n" + 193 | "transition Approve (spender : ByStr20, tokens : Uint128)\n" + 194 | " allowed[_sender][spender] := tokens;\n" + 195 | " msg = { _tag : \"ApproveSuccess\"; _recipient : _sender; _amount : zero;\n" + 196 | " approver : _sender; spender : spender; amount : tokens };\n" + 197 | " msgs = one_msg msg;\n" + 198 | " send msgs\n" + 199 | "end\n" + 200 | "\n" + 201 | "transition Allowance (tokenOwner : ByStr20, spender : ByStr20)\n" + 202 | " spender_allowance <- allowed[tokenOwner][spender];\n" + 203 | " match spender_allowance with\n" + 204 | " | Some n =>\n" + 205 | " msg = { _tag : \"AllowanceResponse\"; _recipient : _sender; _amount : zero;\n" + 206 | " owner : tokenOwner; spender : spender; amount : n };\n" + 207 | " msgs = one_msg msg;\n" + 208 | " send msgs\n" + 209 | " | None =>\n" + 210 | " msg = { _tag : \"AllowanceResponse\"; _recipient : _sender; _amount : zero;\n" + 211 | " owner : tokenOwner; spender : spender; amount : zero };\n" + 212 | " msgs = one_msg msg;\n" + 213 | " send msgs\n" + 214 | " end\n" + 215 | "end"; 216 | List init = Arrays.asList( 217 | Value.builder().vname("_scilla_version").type("Uint32").value("0").build(), 218 | Value.builder().vname("owner").type("ByStr20").value("0x9bfec715a6bd658fcb62b0f8cc9bfa2ade71434a").build(), 219 | Value.builder().vname("total_tokens").type("Uint128").value("1000000000").build(), 220 | Value.builder().vname("decimals").type("Uint32").value("0").build(), 221 | Value.builder().vname("name").type("String").value("BobCoin").build(), 222 | Value.builder().vname("symbol").type("String").value("BOB").build()); 223 | Wallet wallet = new Wallet(); 224 | wallet.addByPrivateKey("e19d05c5452598e24caad4a0d85a49146f7be089515c905ae6a19e8a578a6930"); 225 | ContractFactory factory = ContractFactory.builder().provider(new HttpProvider("https://dev-api.zilliqa.com/")).signer(wallet).build(); 226 | Contract contract = factory.newContract(code, (Value[]) init.toArray(), ""); 227 | Integer nonce = Integer.valueOf(factory.getProvider().getBalance("9bfec715a6bd658fcb62b0f8cc9bfa2ade71434a").getResult().getNonce()); 228 | DeployParams deployParams = DeployParams.builder().version(String.valueOf(pack(333, 1))).gasPrice("1000000000").gasLimit("10000").nonce(String.valueOf(nonce + 1)).senderPubKey("0246e7178dc8253201101e18fd6f6eb9972451d121fc57aa2a06dd5c111e58dc6a").build(); 229 | System.out.println(contract.deployWithoutConfirm(deployParams)); 230 | // Pair result = contract.deploy(deployParams, 3000, 3); 231 | 232 | // System.out.println("result is: " + result.toString()); 233 | // 234 | // String transactionFee = new BigInteger(result.getKey().getReceipt().getCumulative_gas()).multiply(new BigInteger(result.getKey().getGasPrice())).toString(); 235 | // System.out.println("transaction fee is: " + transactionFee); 236 | } 237 | 238 | @Test 239 | public void call() throws Exception { 240 | 241 | List init = Arrays.asList( 242 | Value.builder().vname("_scilla_version").type("Uint32").value("0").build(), 243 | Value.builder().vname("owner").type("ByStr20").value("0x9bfec715a6bd658fcb62b0f8cc9bfa2ade71434a").build(), 244 | Value.builder().vname("total_tokens").type("Uint128").value("1000000000").build(), 245 | Value.builder().vname("decimals").type("Uint32").value("0").build(), 246 | Value.builder().vname("name").type("String").value("BobCoin").build(), 247 | Value.builder().vname("symbol").type("String").value("BOB").build()); 248 | Wallet wallet = new Wallet(); 249 | wallet.addByPrivateKey("e19d05c5452598e24caad4a0d85a49146f7be089515c905ae6a19e8a578a6930"); 250 | ContractFactory factory = ContractFactory.builder().provider(new HttpProvider("https://dev-api.zilliqa.com/")).signer(wallet).build(); 251 | Contract contract = factory.atContract("zil1h4cesgy498wyyvsdkj7g2zygp0xj920jw2hyx0", "", (Value[]) init.toArray(), ""); 252 | Integer nonce = Integer.valueOf(factory.getProvider().getBalance("9bfec715a6bd658fcb62b0f8cc9bfa2ade71434a").getResult().getNonce()); 253 | CallParams params = CallParams.builder().nonce(String.valueOf(nonce + 1)).version(String.valueOf(pack(333, 1))).gasPrice("1000000000").gasLimit("1000").senderPubKey("0246e7178dc8253201101e18fd6f6eb9972451d121fc57aa2a06dd5c111e58dc6a").amount("0").build(); 254 | List values = Arrays.asList(Value.builder().vname("to").type("ByStr20").value("0x381f4008505e940ad7681ec3468a719060caf796").build(), Value.builder().vname("tokens").type("Uint128").value("10").build()); 255 | contract.call("Transfer", (Value[]) values.toArray(), params, 3000, 3); 256 | } 257 | 258 | 259 | @Test 260 | public void getAddressForContract() throws NoSuchAlgorithmException { 261 | Transaction transaction = Transaction.builder().build(); 262 | transaction.setSenderPubKey("0246E7178DC8253201101E18FD6F6EB9972451D121FC57AA2A06DD5C111E58DC6A"); 263 | transaction.setNonce("19"); 264 | String address = ContractFactory.getAddressForContract(transaction); 265 | Assert.assertEquals(address.toLowerCase(), "8f14cb1735b2b5fba397bea1c223d65d12b9a887"); 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /src/test/java/com/firestack/laksaj/crypto/KeyStoreTest.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.crypto; 2 | 3 | import org.junit.Test; 4 | 5 | public class KeyStoreTest { 6 | @Test 7 | public void encryptPrivateKey() throws Exception { 8 | KeyStore keyStore = KeyStore.defaultKeyStore(); 9 | String result = keyStore.encryptPrivateKey("184e14d737356fc4598d371be70ae0d94d61bbd5643d7eb384faa0de7166c010", "dangerous", KDFType.PBKDF2); 10 | System.out.println(result); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/test/java/com/firestack/laksaj/crypto/KeyToolsTest.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.crypto; 2 | 3 | import com.firestack.laksaj.utils.ByteUtil; 4 | import org.bouncycastle.jce.ECNamedCurveTable; 5 | import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; 6 | import org.bouncycastle.math.ec.ECPoint; 7 | import org.junit.Assert; 8 | import org.junit.Test; 9 | import org.web3j.crypto.ECKeyPair; 10 | 11 | import java.math.BigInteger; 12 | import java.security.InvalidAlgorithmParameterException; 13 | import java.security.NoSuchAlgorithmException; 14 | import java.security.NoSuchProviderException; 15 | 16 | public class KeyToolsTest { 17 | static private final ECNamedCurveParameterSpec secp256k1 = ECNamedCurveTable.getParameterSpec("secp256k1"); 18 | 19 | @Test 20 | public void generateKeyPair() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { 21 | ECKeyPair keys = KeyTools.generateKeyPair(); 22 | ECPoint pubKey = secp256k1.getCurve().decodePoint(keys.getPublicKey().toByteArray()); 23 | Assert.assertEquals(keys.getPrivateKey().compareTo(BigInteger.ZERO), 1); 24 | Assert.assertTrue(pubKey.isValid()); 25 | } 26 | 27 | @Test 28 | public void generatePrivateKey() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { 29 | int i = 0; 30 | while (i < 10) { 31 | i++; 32 | String privateKey = KeyTools.generatePrivateKey(); 33 | if (privateKey.length() != 64) { 34 | throw new RuntimeException("generator err"); 35 | } 36 | System.out.println(privateKey); 37 | } 38 | } 39 | 40 | @Test 41 | public void generateRandomBytes() { 42 | byte[] bytes = KeyTools.generateRandomBytes(32); 43 | Assert.assertNotNull(bytes); 44 | Assert.assertTrue(32 == bytes.length); 45 | System.out.println(ByteUtil.byteArrayToHexString(bytes).toLowerCase()); 46 | } 47 | 48 | @Test 49 | public void getPublicKeyFromPrivateKey() { 50 | String privateKey = "24180e6b0c3021aedb8f5a86f75276ee6fc7ff46e67e98e716728326102e91c9"; 51 | String publicKey = KeyTools.getPublicKeyFromPrivateKey(privateKey, false); 52 | Assert.assertEquals(publicKey.toLowerCase(), "04163fa604c65aebeb7048c5548875c11418d6d106a20a0289d67b59807abdd299d4cf0efcf07e96e576732dae122b9a8ac142214a6bc133b77aa5b79ba46b3e20"); 53 | privateKey = "b776d8f068d11b3c3f5b94db0fb30efea05b73ddb9af1bbd5da8182d94245f0b"; 54 | publicKey = KeyTools.getPublicKeyFromPrivateKey(privateKey, false); 55 | Assert.assertEquals(publicKey.toLowerCase(), "04cfa555bb63231d167f643f1a23ba66e6ca1458d416ddb9941e95b5fd28df0ac513075403c996efbbc15d187868857e31cf7be4d109b4f8cb3fd40499839f150a"); 56 | privateKey = "e19d05c5452598e24caad4a0d85a49146f7be089515c905ae6a19e8a578a6930"; 57 | System.out.println(KeyTools.getPublicKeyFromPrivateKey(privateKey, true)); 58 | //3b7b12dc9fc38584e4baf021b1f37e4c41096dbde826762b27a7476f360a99c2a 59 | System.out.println(KeyTools.getPublicKeyFromPrivateKey("a7da3f73462264e44562f003e2dbcf7add319bf4bb0798a83d1009f651a5c803", true)); 60 | } 61 | 62 | @Test 63 | public void getAddressFromPrivateKey() { 64 | String privateKey = "e19d05c5452598e24caad4a0d85a49146f7be089515c905ae6a19e8a578a6930"; 65 | String address = KeyTools.getAddressFromPrivateKey(privateKey); 66 | System.out.println(address.toLowerCase()); 67 | } 68 | 69 | @Test 70 | public void getAddressFromPublicKey() throws Exception { 71 | String noPad = "38959e0b7b9c545dc055ab668f8fbaef207e845c590eca4b14993619fff0f723d"; 72 | String padded = "038959e0b7b9c545dc055ab668f8fbaef207e845c590eca4b14993619fff0f723d"; 73 | 74 | Assert.assertEquals(KeyTools.getAddressFromPublicKey(noPad), KeyTools.getAddressFromPublicKey(padded)); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/test/java/com/firestack/laksaj/jsonrpc/ProviderTest.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.jsonrpc; 2 | 3 | import com.firestack.laksaj.blockchain.*; 4 | import com.firestack.laksaj.exception.ZilliqaAPIException; 5 | import com.firestack.laksaj.transaction.PendingStatus; 6 | import com.firestack.laksaj.transaction.Transaction; 7 | import com.firestack.laksaj.transaction.TransactionStatus; 8 | import okhttp3.OkHttpClient; 9 | import org.junit.Assert; 10 | import org.junit.Test; 11 | 12 | import java.io.IOException; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.concurrent.TimeUnit; 16 | 17 | 18 | public class ProviderTest { 19 | 20 | @Test 21 | public void getNetWorkId() throws IOException, ZilliqaAPIException { 22 | HttpProvider client = new HttpProvider("https://api.zilliqa.com/"); 23 | String networkId = client.getNetworkId().getResult(); 24 | Assert.assertEquals("1", networkId); 25 | } 26 | 27 | @Test 28 | public void getDSBlockListing() throws IOException, ZilliqaAPIException { 29 | HttpProvider client = new HttpProvider("https://api.zilliqa.com/"); 30 | BlockList blockList = client.getDSBlockListing(1).getResult(); 31 | System.out.println(blockList); 32 | Assert.assertNotNull(blockList); 33 | } 34 | 35 | @Test 36 | public void getTxBlockListing() throws IOException, ZilliqaAPIException { 37 | HttpProvider client = new HttpProvider("https://api.zilliqa.com/"); 38 | BlockList blockList = client.getTxBlockListing(1).getResult(); 39 | System.out.println(blockList); 40 | Assert.assertNotNull(blockList); 41 | } 42 | 43 | @Test 44 | public void getBlockchainInfo() throws IOException, ZilliqaAPIException { 45 | HttpProvider client = new HttpProvider("https://api.zilliqa.com/"); 46 | BlockchainInfo blockchainInfo = client.getBlockchainInfo().getResult(); 47 | System.out.println(blockchainInfo); 48 | Assert.assertNotNull(blockchainInfo); 49 | } 50 | 51 | 52 | @Test 53 | public void getDsBlock() throws IOException, ZilliqaAPIException { 54 | HttpProvider client = new HttpProvider("https://api.zilliqa.com/"); 55 | DsBlock dsBlock = client.getDsBlock("1").getResult(); 56 | System.out.println(dsBlock); 57 | Assert.assertNotNull(dsBlock); 58 | Assert.assertTrue(dsBlock.getHeader().getDifficulty() == 3); 59 | } 60 | 61 | 62 | @Test 63 | public void getNumDSBlocks() throws IOException, ZilliqaAPIException { 64 | HttpProvider client = new HttpProvider("https://api.zilliqa.com/"); 65 | String result = client.getNumDSBlocks().getResult(); 66 | System.out.println(result); 67 | Assert.assertNotNull(result); 68 | } 69 | 70 | 71 | @Test 72 | public void getTxBlock() throws IOException, ZilliqaAPIException { 73 | HttpProvider client = new HttpProvider("https://api.zilliqa.com/"); 74 | TxBlock txBlock = client.getTxBlock("123").getResult(); 75 | System.out.println(txBlock); 76 | } 77 | 78 | @Test 79 | public void getLatestDsBlock() throws IOException, ZilliqaAPIException { 80 | OkHttpClient client = new OkHttpClient().newBuilder() 81 | .writeTimeout(1, TimeUnit.MINUTES) 82 | .readTimeout(1, TimeUnit.MINUTES) 83 | .connectTimeout(1, TimeUnit.MINUTES) 84 | .build(); 85 | HttpProvider provider = new HttpProvider("https://api.zilliqa.com/", client); 86 | DsBlock dsBlock = provider.getLatestDsBlock().getResult(); 87 | Assert.assertNotNull(dsBlock); 88 | System.out.println(dsBlock); 89 | } 90 | 91 | @Test 92 | public void getLatestTxBlock() throws IOException, ZilliqaAPIException { 93 | HttpProvider client = new HttpProvider("https://api.zilliqa.com/"); 94 | TxBlock txBlock = client.getLatestTxBlock().getResult(); 95 | System.out.println(txBlock); 96 | Assert.assertNotNull(txBlock); 97 | } 98 | 99 | @Test 100 | public void getBalance() throws IOException { 101 | HttpProvider client = new HttpProvider("https://api.zilliqa.com/"); 102 | HttpProvider.BalanceResult balance = client.getBalance("AE9C49CAF0D0BC9D7C769391E8BDA2028F824CF3F".toLowerCase()).getResult(); 103 | Assert.assertNotNull(balance.getBalance()); 104 | } 105 | 106 | @Test 107 | public void getBalanceWithRetry() throws IOException, InterruptedException { 108 | HttpProvider client = new HttpProvider("https://api.zilliqa.com/"); 109 | HttpProvider.BalanceResult balance = client.getBalanceWithRetry("AE9C49CAF0D0BC9D7C769391E8BDA2028F824CF3F".toLowerCase()).getResult(); 110 | Assert.assertNotNull(balance.getBalance()); 111 | } 112 | 113 | @Test 114 | public void getBalance32() throws Exception { 115 | HttpProvider client = new HttpProvider("https://api.zilliqa.com/"); 116 | HttpProvider.BalanceResult balance = client.getBalance32("zil1z6rpmumewzrmdz44wu9hgvdwrs5xgptlzd6kec").getResult(); 117 | Assert.assertNotNull(balance); 118 | } 119 | 120 | @Test 121 | public void getSmartContractCode() throws IOException { 122 | HttpProvider client = new HttpProvider("https://api.zilliqa.com/"); 123 | try { 124 | String code = client.getSmartContractCode("8cb841ef4f1f61d44271e167557e160434bd6d63").getResult().getCode(); 125 | System.out.println(code); 126 | } catch (ZilliqaAPIException e) { 127 | System.out.println(e.getMessage()); 128 | } 129 | } 130 | 131 | @Test 132 | public void getMinimumGasPrice() throws IOException, ZilliqaAPIException { 133 | HttpProvider client = new HttpProvider("https://api.zilliqa.com/"); 134 | String price = client.getMinimumGasPrice().getResult(); 135 | System.out.println(price); 136 | 137 | } 138 | 139 | @Test 140 | public void getTransactionStatus() throws IOException { 141 | HttpProvider client = new HttpProvider("https://api.zilliqa.com/"); 142 | TransactionStatus transaction = client.getTransactionStatus("db89c9998c5ba10b2ebd00116e0b5bd19339a54aed2f5d5bdc8b4e94ebd81f14").getResult(); 143 | System.out.println(transaction); 144 | } 145 | 146 | @Test 147 | public void getTransactionStatusWithRetry() throws IOException, InterruptedException { 148 | HttpProvider client = new HttpProvider("https://api.zilliqa.com/"); 149 | TransactionStatus transaction = client.getTransactionStatusWithRetry("db89c9998c5ba10b2ebd00116e0b5bd19339a54aed2f5d5bdc8b4e94ebd81f14").getResult(); 150 | System.out.println(transaction); 151 | } 152 | 153 | @Test 154 | public void getPendingTnx() throws IOException { 155 | HttpProvider client = new HttpProvider("https://api.zilliqa.com/"); 156 | PendingStatus status = client.getPendingTnx("a54d6bccd6cd172baab4e18a2b131c6d870cd778826eba71ff8d3a42819c078f").getResult(); 157 | System.out.println(status); 158 | } 159 | 160 | @Test 161 | public void getTransaction() throws IOException { 162 | HttpProvider client = new HttpProvider("https://api.zilliqa.com/"); 163 | Transaction transaction = client.getTransaction("055294ba67b3073d66ef078fb149dfb0490b2d46156479a9f2c9327fb762f4e9").getResult(); 164 | System.out.println(transaction); 165 | } 166 | 167 | @Test 168 | public void getTransactionsForTxBlock() throws IOException { 169 | HttpProvider client = new HttpProvider("https://api.zilliqa.com/"); 170 | Rep>> rep = client.getTransactionsForTxBlock("120951"); 171 | System.out.println(rep); 172 | } 173 | 174 | @Test 175 | public void getTransaction32() throws Exception { 176 | HttpProvider client = new HttpProvider("https://api.zilliqa.com/"); 177 | Transaction transaction = client.getTransaction32("ce918e4c77ed40f3a23588bd3c380458b43be168935d468e2e6f680724e71474").getResult(); 178 | System.out.println(transaction); 179 | } 180 | 181 | @Test 182 | public void getRecentTransactions() throws IOException, ZilliqaAPIException { 183 | HttpProvider client = new HttpProvider("https://api.zilliqa.com/"); 184 | TransactionList transactionList = client.getRecentTransactions().getResult(); 185 | System.out.println(transactionList); 186 | } 187 | 188 | @Test 189 | public void getSmartContractState() throws IOException, ZilliqaAPIException { 190 | HttpProvider client = new HttpProvider("https://mainnet-cashew-api.mainnet.aws.zilliqa.com"); 191 | String stateList = client.getSmartContractState("9611c53BE6d1b32058b2747bdeCECed7e1216793"); 192 | System.out.println(stateList); 193 | } 194 | 195 | @Test 196 | public void getSmartContractSubState() throws IOException { 197 | HttpProvider client = new HttpProvider("https://mainnet-cashew-api.mainnet.aws.zilliqa.com"); 198 | List param = new ArrayList<>(); 199 | param.add("9611c53BE6d1b32058b2747bdeCECed7e1216793"); 200 | param.add("admins"); 201 | param.add(new ArrayList<>()); 202 | String state = client.getSmartContractSubState(param); 203 | System.out.println(state); 204 | } 205 | 206 | @Test 207 | public void parseError() { 208 | HttpProvider client = new HttpProvider("https://api.zilliqa.com/"); 209 | HttpProvider.Pair pair = client.parseError("{\"error\":{\"code\":-8,\"data\":null,\"message\":\"Address size not appropriate\"},\"id\":\"1\",\"jsonrpc\":\"2.0\"}\n"); 210 | Assert.assertEquals("Address size not appropriate", pair.getMessage()); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/test/java/com/firestack/laksaj/proto/MessageTest.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.proto; 2 | 3 | public class MessageTest { 4 | } 5 | -------------------------------------------------------------------------------- /src/test/java/com/firestack/laksaj/utils/Base58Util.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.utils; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | public class Base58Util { 7 | @Test 8 | public void testEncode() { 9 | Assert.assertEquals("", Base58.encode("")); 10 | Assert.assertEquals("2g", Base58.encode("61")); 11 | Assert.assertEquals("a3gV", Base58.encode("626262")); 12 | Assert.assertEquals("aPEr", Base58.encode("636363")); 13 | Assert.assertEquals("2cFupjhnEsSn59qHXstmK2ffpLv2", Base58.encode("73696d706c792061206c6f6e6720737472696e67")); 14 | Assert.assertEquals("1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L", Base58.encode("00eb15231dfceb60925886b67d065299925915aeb172c06647")); 15 | Assert.assertEquals("ABnLTmg", Base58.encode("516b6fcd0f")); 16 | Assert.assertEquals("3SEo3LWLoPntC", Base58.encode("bf4f89001e670274dd")); 17 | Assert.assertEquals("3EFU7m", Base58.encode("572e4794")); 18 | Assert.assertEquals("EJDM8drfXA6uyA", Base58.encode("ecac89cad93923c02321")); 19 | Assert.assertEquals("Rt5zm", Base58.encode("10c8511e")); 20 | Assert.assertEquals("1111111111", Base58.encode("00000000000000000000")); 21 | Assert.assertEquals("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz", Base58.encode("000111d38e5fc9071ffcd20b4a763cc9ae4f252bb4e48fd66a835e252ada93ff480d6dd43dc62a641155a5")); 22 | Assert.assertEquals("1cWB5HCBdLjAuqGGReWE3R3CguuwSjw6RHn39s2yuDRTS5NsBgNiFpWgAnEx6VQi8csexkgYw3mdYrMHr8x9i7aEwP8kZ7vccXWqKDvGv3u1GxFKPuAkn8JCPPGDMf3vMMnbzm6Nh9zh1gcNsMvH3ZNLmP5fSG6DGbbi2tuwMWPthr4boWwCxf7ewSgNQeacyozhKDDQQ1qL5fQFUW52QKUZDZ5fw3KXNQJMcNTcaB723LchjeKun7MuGW5qyCBZYzA1KjofN1gYBV3NqyhQJ3Ns746GNuf9N2pQPmHz4xpnSrrfCvy6TVVz5d4PdrjeshsWQwpZsZGzvbdAdN8MKV5QsBDY", Base58.encode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff")); 23 | System.out.println(Base58.encode("0x4baf5fada8e5db92c3d3242618c5b47133ae003c")); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/com/firestack/laksaj/utils/Bech32Test.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.utils; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | public class Bech32Test { 7 | @Test 8 | public void toBech32Address() throws Exception { 9 | String bech32 = Bech32.toBech32Address("0x9bfec715a6bd658fcb62b0f8cc9bfa2ade71434a"); 10 | Assert.assertEquals(bech32.toLowerCase(), "zil1n0lvw9dxh4jcljmzkruvexl69t08zs62ds9ats"); 11 | System.out.println(Bech32.toBech32Address("4BAF5FADA8E5DB92C3D3242618C5B47133AE003C")); 12 | System.out.println(Bech32.toBech32Address("448261915A80CDE9BDE7C7A791685200D3A0BF4E")); 13 | System.out.println(Bech32.toBech32Address("DED02FD979FC2E55C0243BD2F52DF022C40ADA1E")); 14 | System.out.println(Bech32.toBech32Address("13F06E60297BEA6A3C402F6F64C416A6B31E586E")); 15 | System.out.println(Bech32.toBech32Address("1A90C25307C3CC71958A83FA213A2362D859CF33")); 16 | System.out.println(Bech32.toBech32Address("625ABAEBD87DAE9AB128F3B3AE99688813D9C5DF")); 17 | System.out.println(Bech32.toBech32Address("36BA34097F861191C48C839C9B1A8B5912F583CF")); 18 | System.out.println(Bech32.toBech32Address("D2453AE76C9A86AAE544FCA699DBDC5C576AEF3A")); 19 | System.out.println(Bech32.toBech32Address("72220E84947C36118CDBC580454DFAA3B918CD97")); 20 | System.out.println(Bech32.toBech32Address("50F92304C892D94A385CA6CE6CD6950CE9A36839")); 21 | 22 | 23 | Assert.assertEquals(Bech32.toBech32Address("1d19918a737306218b5cbb3241fcdcbd998c3a72"), "zil1r5verznnwvrzrz6uhveyrlxuhkvccwnju4aehf"); 24 | Assert.assertEquals(Bech32.toBech32Address("cc8ee24773e1b4b28b3cc5596bb9cfc430b48453"), "zil1ej8wy3mnux6t9zeuc4vkhww0csctfpznzt4s76"); 25 | Assert.assertEquals(Bech32.toBech32Address("e14576944443e9aeca6f12b454941884aa122938"), "zil1u9zhd9zyg056ajn0z269f9qcsj4py2fc89ru3d"); 26 | Assert.assertEquals(Bech32.toBech32Address("179361114cbfd53be4d3451edf8148cde4cfe774"), "zil1z7fkzy2vhl2nhexng50dlq2gehjvlem5w7kx8z"); 27 | Assert.assertEquals(Bech32.toBech32Address("5a2b667fdeb6356597681d08f6cd6636aed94784"), "zil1tg4kvl77kc6kt9mgr5y0dntxx6hdj3uy95ash8"); 28 | Assert.assertEquals(Bech32.toBech32Address("537342e5e0a6b402f281e2b4301b89123ae31117"), "zil12de59e0q566q9u5pu26rqxufzgawxyghq0vdk9"); 29 | Assert.assertEquals(Bech32.toBech32Address("5e61d42a952d2df1f4e5cbed7f7d1294e9744a52"), "zil1tesag25495klra89e0kh7lgjjn5hgjjj0qmu8l"); 30 | Assert.assertEquals(Bech32.toBech32Address("5f5db1c18ccde67e513b7f7ae820e569154976ba"), "zil1tawmrsvvehn8u5fm0aawsg89dy25ja46ndsrhq"); 31 | } 32 | 33 | 34 | @Test 35 | public void fromBech32Address() throws Exception { 36 | System.out.println(Bech32.fromBech32Address("zil18g9n36d4xkhda5r4cwq7q8kutu5atgh3g72qvj")); 37 | String address = Bech32.fromBech32Address("zil1n0lvw9dxh4jcljmzkruvexl69t08zs62ds9ats"); 38 | Assert.assertEquals(address.toLowerCase(), "9bfec715a6bd658fcb62b0f8cc9bfa2ade71434a"); 39 | 40 | Assert.assertEquals(Bech32.fromBech32Address("zil1fwh4ltdguhde9s7nysnp33d5wye6uqpugufkz7").toUpperCase(), "4BAF5FADA8E5DB92C3D3242618C5B47133AE003C"); 41 | Assert.assertEquals(Bech32.fromBech32Address("zil1gjpxry26srx7n008c7nez6zjqrf6p06wur4x3m").toUpperCase(), "448261915A80CDE9BDE7C7A791685200D3A0BF4E"); 42 | Assert.assertEquals(Bech32.fromBech32Address("zil1mmgzlktelsh9tspy80f02t0sytzq4ks79zdnkk").toUpperCase(), "DED02FD979FC2E55C0243BD2F52DF022C40ADA1E"); 43 | Assert.assertEquals(Bech32.fromBech32Address("zil1z0cxucpf004x50zq9ahkf3qk56e3ukrwaty4g8").toUpperCase(), "13F06E60297BEA6A3C402F6F64C416A6B31E586E"); 44 | Assert.assertEquals(Bech32.fromBech32Address("zil1r2gvy5c8c0x8r9v2s0azzw3rvtv9nnenynd33g").toUpperCase(), "1A90C25307C3CC71958A83FA213A2362D859CF33"); 45 | Assert.assertEquals(Bech32.fromBech32Address("zil1vfdt467c0khf4vfg7we6axtg3qfan3wlf9yc6y").toUpperCase(), "625ABAEBD87DAE9AB128F3B3AE99688813D9C5DF"); 46 | Assert.assertEquals(Bech32.fromBech32Address("zil1x6argztlscger3yvswwfkx5ttyf0tq703v7fre").toUpperCase(), "36BA34097F861191C48C839C9B1A8B5912F583CF"); 47 | Assert.assertEquals(Bech32.fromBech32Address("zil16fzn4emvn2r24e2yljnfnk7ut3tk4me6qx08ed").toUpperCase(), "D2453AE76C9A86AAE544FCA699DBDC5C576AEF3A"); 48 | Assert.assertEquals(Bech32.fromBech32Address("zil1wg3qapy50smprrxmckqy2n065wu33nvh35dn0v").toUpperCase(), "72220E84947C36118CDBC580454DFAA3B918CD97"); 49 | Assert.assertEquals(Bech32.fromBech32Address("zil12rujxpxgjtv55wzu5m8xe454pn56x6pedpl554").toUpperCase(), "50F92304C892D94A385CA6CE6CD6950CE9A36839"); 50 | 51 | 52 | Assert.assertEquals(Bech32.fromBech32Address("zil1r5verznnwvrzrz6uhveyrlxuhkvccwnju4aehf").toLowerCase(), "1d19918a737306218b5cbb3241fcdcbd998c3a72"); 53 | Assert.assertEquals(Bech32.fromBech32Address("zil1ej8wy3mnux6t9zeuc4vkhww0csctfpznzt4s76").toLowerCase(), "cc8ee24773e1b4b28b3cc5596bb9cfc430b48453"); 54 | Assert.assertEquals(Bech32.fromBech32Address("zil1u9zhd9zyg056ajn0z269f9qcsj4py2fc89ru3d").toLowerCase(), "e14576944443e9aeca6f12b454941884aa122938"); 55 | Assert.assertEquals(Bech32.fromBech32Address("zil1z7fkzy2vhl2nhexng50dlq2gehjvlem5w7kx8z").toLowerCase(), "179361114cbfd53be4d3451edf8148cde4cfe774"); 56 | Assert.assertEquals(Bech32.fromBech32Address("zil1tg4kvl77kc6kt9mgr5y0dntxx6hdj3uy95ash8").toLowerCase(), "5a2b667fdeb6356597681d08f6cd6636aed94784"); 57 | Assert.assertEquals(Bech32.fromBech32Address("zil12de59e0q566q9u5pu26rqxufzgawxyghq0vdk9").toLowerCase(), "537342e5e0a6b402f281e2b4301b89123ae31117"); 58 | Assert.assertEquals(Bech32.fromBech32Address("zil1tesag25495klra89e0kh7lgjjn5hgjjj0qmu8l").toLowerCase(), "5e61d42a952d2df1f4e5cbed7f7d1294e9744a52"); 59 | Assert.assertEquals(Bech32.fromBech32Address("zil1tawmrsvvehn8u5fm0aawsg89dy25ja46ndsrhq").toLowerCase(), "5f5db1c18ccde67e513b7f7ae820e569154976ba"); 60 | 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/test/java/com/firestack/laksaj/utils/ByteUtilTest.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.utils; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | public class ByteUtilTest { 7 | @Test 8 | public void hexStringToByteArray() { 9 | String hexString = "e19d05c5452598e24caad4a0d85a49146f7be089515c905ae6a19e8a578a6930"; 10 | String byteString = "225,157,5,197,69,37,152,226,76,170,212,160,216,90,73,20,111,123,224,137,81,92,144,90,230,161,158,138,87,138,105,48,"; 11 | byte[] bytes = ByteUtil.hexStringToByteArray(hexString); 12 | StringBuilder stringBuilder = new StringBuilder(); 13 | for (byte b : bytes) { 14 | stringBuilder.append(b & 0xff); 15 | stringBuilder.append(","); 16 | } 17 | System.out.println(stringBuilder.toString()); 18 | Assert.assertEquals(stringBuilder.toString(), byteString); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/com/firestack/laksaj/utils/TransactionUtilTest.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.utils; 2 | 3 | import com.firestack.laksaj.transaction.TxParams; 4 | import org.junit.Test; 5 | 6 | public class TransactionUtilTest { 7 | @Test 8 | public void encodeTransactionProto() { 9 | TxParams txParams = TxParams.builder() 10 | .version("0") 11 | .nonce("0") 12 | .toAddr("2E3C9B415B19AE4035503A06192A0FAD76E04243") 13 | .senderPubKey("0246e7178dc8253201101e18fd6f6eb9972451d121fc57aa2a06dd5c111e58dc6a") 14 | .amount("10000") 15 | .gasPrice("100") 16 | .gasLimit("1000") 17 | .code("") 18 | .data("") 19 | .build(); 20 | TransactionUtil util = new TransactionUtil(); 21 | byte[] bytes = util.encodeTransactionProto(txParams); 22 | System.out.println(ByteUtil.byteArrayToHexString(bytes)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/com/firestack/laksaj/utils/ValidationTest.java: -------------------------------------------------------------------------------- 1 | package com.firestack.laksaj.utils; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | public class ValidationTest { 7 | 8 | @Test 9 | public void isByteString() { 10 | boolean result = Validation.isByteString("e9c49caf0d0bc9d7c769391e8bda2028f824cf3d", 40); 11 | Assert.assertTrue(result); 12 | result = Validation.isByteString("e9c49caf0d0bc9d7c769391e8bda2028f824cf3", 40); 13 | Assert.assertFalse(result); 14 | result = Validation.isByteString("e9c49caf0d0bc9d7c76g391e8bda2028f824cf3d", 40); 15 | Assert.assertFalse(result); 16 | } 17 | 18 | @Test 19 | public void isValidChecksumAddress() { 20 | Assert.assertTrue(Validation.isValidChecksumAddress("0x4BAF5faDA8e5Db92C3d3242618c5B47133AE003C")); 21 | Assert.assertFalse(Validation.isValidChecksumAddress("0x4BAF5FaDA8e5Db92C3d3242618c5B47133AE003C")); 22 | } 23 | 24 | } 25 | --------------------------------------------------------------------------------