OSDN Git Service

commit java sdk doc and source code
authorsuccessli <successli@outlook.com>
Mon, 28 May 2018 02:34:46 +0000 (10:34 +0800)
committersuccessli <successli@outlook.com>
Mon, 28 May 2018 02:34:46 +0000 (10:34 +0800)
44 files changed:
.gitignore [new file with mode: 0644]
doc/index.md [new file with mode: 0644]
pom.xml [new file with mode: 0644]
src/main/java/io/bytom/api/AccessToken.java [new file with mode: 0644]
src/main/java/io/bytom/api/Account.java [new file with mode: 0644]
src/main/java/io/bytom/api/Address.java [new file with mode: 0644]
src/main/java/io/bytom/api/Asset.java [new file with mode: 0644]
src/main/java/io/bytom/api/Balance.java [new file with mode: 0644]
src/main/java/io/bytom/api/Block.java [new file with mode: 0644]
src/main/java/io/bytom/api/Gas.java [new file with mode: 0644]
src/main/java/io/bytom/api/Key.java [new file with mode: 0644]
src/main/java/io/bytom/api/Message.java [new file with mode: 0644]
src/main/java/io/bytom/api/Miner.java [new file with mode: 0644]
src/main/java/io/bytom/api/NetInfo.java [new file with mode: 0644]
src/main/java/io/bytom/api/ParameterizedTypeImpl.java [new file with mode: 0644]
src/main/java/io/bytom/api/RawTransaction.java [new file with mode: 0644]
src/main/java/io/bytom/api/Receiver.java [new file with mode: 0644]
src/main/java/io/bytom/api/Transaction.java [new file with mode: 0644]
src/main/java/io/bytom/api/TransactionFeed.java [new file with mode: 0644]
src/main/java/io/bytom/api/UnconfirmedTransaction.java [new file with mode: 0644]
src/main/java/io/bytom/api/UnspentOutput.java [new file with mode: 0644]
src/main/java/io/bytom/api/Wallet.java [new file with mode: 0644]
src/main/java/io/bytom/common/Configuration.java [new file with mode: 0644]
src/main/java/io/bytom/common/Utils.java [new file with mode: 0644]
src/main/java/io/bytom/exception/APIException.java [new file with mode: 0644]
src/main/java/io/bytom/exception/BadURLException.java [new file with mode: 0644]
src/main/java/io/bytom/exception/BuildException.java [new file with mode: 0644]
src/main/java/io/bytom/exception/BytomException.java [new file with mode: 0644]
src/main/java/io/bytom/exception/ConfigurationException.java [new file with mode: 0644]
src/main/java/io/bytom/exception/ConnectivityException.java [new file with mode: 0644]
src/main/java/io/bytom/exception/HTTPException.java [new file with mode: 0644]
src/main/java/io/bytom/exception/JSONException.java [new file with mode: 0644]
src/main/java/io/bytom/http/BatchResponse.java [new file with mode: 0644]
src/main/java/io/bytom/http/Client.java [new file with mode: 0644]
src/main/java/io/bytom/http/SuccessMessage.java [new file with mode: 0644]
src/main/resources/config.properties [new file with mode: 0644]
src/main/resources/log4j.properties [new file with mode: 0644]
src/test/java/io/bytom/AppTest.java [new file with mode: 0644]
src/test/java/io/bytom/TestUtils.java [new file with mode: 0644]
src/test/java/io/bytom/integration/AccessTokenTest.java [new file with mode: 0644]
src/test/java/io/bytom/integration/AccountTest.java [new file with mode: 0644]
src/test/java/io/bytom/integration/AssetTest.java [new file with mode: 0644]
src/test/java/io/bytom/integration/KeyTest.java [new file with mode: 0644]
src/test/java/io/bytom/integration/TransactionTest.java [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..2e653db
--- /dev/null
@@ -0,0 +1,4 @@
+.idea/
+*.iml
+target/
+bytom.log
diff --git a/doc/index.md b/doc/index.md
new file mode 100644 (file)
index 0000000..1dabefd
--- /dev/null
@@ -0,0 +1,825 @@
+# Java SDK documentation
+
+This page will document the API classes and ways to properly use the API.
+Subsequent new releases also maintain backward compatibility with this class approach.
+
+## Basic Usage
+
+```
+public static Client generateClient() throws BytomException {
+    String coreURL = Configuration.getValue("bytom.api.url");
+    String accessToken = Configuration.getValue("client.access.token");
+    if (coreURL == null || coreURL.isEmpty()) {
+        coreURL = "http://127.0.0.1:9888/";
+    }
+    return new Client(coreURL, accessToken);
+}
+
+Client client = TestUtils.generateClient();
+```
+
+## Usage
+
+* [`Step 1: Create a key`](#create-a-key)
+* [`Step 2: Create an account`](#create-an-account)
+* [`Step 3: Create an receiver`](#create-an-receiver)
+* [`Step 4: Create an asset`](#create-an-asset)
+* [`Step 5: Issue asset`](#issue-asset)
+    * [`Firstly build the transaction`](#firstly-build-the-transaction)
+    * [`Secondly sign the transaction`](#secondly-sign-the-transaction)
+    * [`Finally submit the transaction`](#finally-submit-the-transaction)
+
+> For more details, see [`API methods`](#api-methods)
+
+## Create a key
+
+```java
+String alias = "test";
+String password = "123456";
+
+Key.Builder builder = new Key.Builder().setAlias(alias).setPassword(password);
+Key key = Key.create(client, builder);
+```
+
+## Create an account
+
+```java
+String alias = "sender-account";
+Integer quorum = 1;
+List<String> root_xpubs = new ArrayList<String>();
+root_xpubs.add(senderKey.xpub);
+
+Account.Builder builder = new Account.Builder().setAlias(alias).setQuorum(quorum).setRootXpub(root_xpubs);
+
+Account account = Account.create(client, builder);
+```
+
+## Create an receiver
+
+```java
+String alias = receiverAccount.alias;
+String id = receiverAccount.id;
+
+Account.ReceiverBuilder receiverBuilder = new Account.ReceiverBuilder().setAccountAlias(alias).setAccountId(id);
+Receiver receiver = receiverBuilder.create(client);
+```
+
+## Create an asset
+
+```java
+ String alias = "receiver-asset";
+
+List<String> xpubs = receiverAccount.xpubs;
+
+Asset.Builder builder = new Asset.Builder()
+                        .setAlias(alias)
+                        .setQuorum(1)
+                        .setRootXpubs(xpubs);
+receiverAsset = builder.create(client);
+```
+
+## Issue asset
+
+### Firstly build the transaction
+
+```java
+Transaction.Template controlAddress = new Transaction.Builder()
+        .addAction(
+                new Transaction.Action.SpendFromAccount()
+                        .setAccountId(senderAccount.id)
+                        .setAssetId(senderAsset.id)
+                        .setAmount(300000000)
+        )
+        .addAction(
+                new Transaction.Action.ControlWithAddress()
+                        .setAddress(receiverAddress.address)
+                        .setAssetId(senderAsset.id)
+                        .setAmount(200000000)
+        ).build(client);
+```
+
+### Secondly sign the transaction
+
+```java
+Transaction.Template singer = new Transaction.SignerBuilder().sign(client,
+        controlAddress, "123456");
+```
+
+### Finally submit the transaction
+
+```java
+Transaction.SubmitResponse txs = Transaction.submit(client, singer);
+```
+
+----
+
+## API methods
+
+* [`Key API`](#key-api)
+* [`Account API`](#account-api)
+* [`Asset API`](#asset-api)
+* [`Transaction API`](#transaction-api)
+* [`Wallet API`](#wallet-api)
+* [`Access Token API`](#access-token-api)
+* [`Block API`](#block-api)
+* [`Mining API`](#mining-api)
+* [`Other API`](#other-api)
+
+## Key API
+
+
+#### createKey
+
+```java
+Key create(Client client, Builder builder);
+```
+
+##### Parameters
+
+- `Client` - *client*, Client object that makes requests to the core.
+- `Key.Builder` - *builder*, Builder object that builds request parameters.
+
+##### Returns
+
+- `Key` - *key*, Key object.
+
+----
+
+#### listKeys
+
+```java
+List<Key> list(Client client);
+```
+
+##### Parameters
+
+- `Client` - *client*, Client object that makes requests to the core.
+
+##### Returns
+
+- `List of Key`, *List<Key>*, an ArrayList object contains Key objects.
+
+----
+
+#### deleteKey
+
+```java
+void delete(Client client, String xpub, String password);
+```
+
+##### Parameters
+
+- `Client` - *client*, Client object that makes requests to the core.
+- `String` - *xpub*, pubkey of the key.
+- `String` - *password*, password of the key.
+
+##### Returns
+
+none if the key is deleted successfully.
+
+----
+
+#### resetKeyPassword
+
+```java
+void resetPwd(Client client, String xpub, String oldPwd, String newPwd);
+```
+
+##### Parameters
+
+- `Client` - *client*, Client object that makes requests to the core.
+- `String` - *xpub*, pubkey of the key.
+- `String` - *oldPwd*, old password of the key.
+- `String` - *newPwd*, new password of the key.
+
+##### Returns
+
+none if the key password is reset successfully.
+
+
+## Account API
+
+
+#### createAccount
+
+```java
+Account create(Client client, Builder builder);
+```
+##### Parameters
+
+- `Client` - *client*, Client object that makes requests to the core.
+- `Account.Builder` - *builder*, Builder object that builds request parameters.
+
+##### Returns
+
+- `Account` - *account*, Account object.
+
+----
+
+#### listAccounts
+
+```java
+List<Account> list(Client client);
+```
+
+##### Parameters
+
+- `Client` - *client*, Client object that makes requests to the core.
+
+##### Returns
+
+- `List of Account`, *List<Account>*, an ArrayList object contains Account objects.
+
+----
+
+#### deleteAccount
+
+```java
+void delete(Client client, String account_info);
+```
+
+##### Parameters
+
+- `Client` - *client*, Client object that makes requests to the core.
+- `String` - *account_info*, alias or ID of account.
+
+##### Returns
+
+none if the account is deleted successfully.
+
+----
+
+#### createAccountReceiver
+
+```java
+Receiver create(Client client);
+```
+
+##### Parameters
+
+- `Client` - *client*, Client object that makes requests to the core.
+
+##### Returns
+
+- `Receiver` - *receiver*, Receiver object.
+
+
+----
+
+#### listAddresses
+
+```java
+List<Address> list(Client client);
+```
+
+##### Parameters
+
+- `Client` - *client*, Client object that makes requests to the core.
+
+##### Returns
+
+- `List of Address`, *List<Address>*, an ArrayList object contains Address objects.
+
+----
+
+#### validateAddress
+
+```java
+Address validate(Client client, String address);
+```
+
+##### Parameters
+
+- `Client` - *Client*, Client object that makes requests to the core.
+- `String` - *address*, address of account.
+
+##### Returns
+
+- `Address` - *address*, an Address object.
+
+
+## Asset API
+
+
+#### createAsset
+
+```java
+Asset create(Client client);
+```
+
+##### Parameters
+
+- `Client` - *Client*, Client object that makes requests to the core.
+
+##### Returns
+
+- `Asset` - *asset*, an Asset object.
+
+----
+
+#### getAsset
+
+```java
+Asset get(Client client);
+```
+
+##### Parameters
+
+- `Client` - *Client*, Client object that makes requests to the core.
+
+##### Returns
+
+- `Asset` - *asset*, an Asset object.
+
+----
+
+#### listAssets
+
+```java
+List<Asset> list(Client client);
+```
+
+##### Parameters
+
+- `Client` - *Client*, Client object that makes requests to the core.
+
+##### Returns
+
+- `List of Asset`, *List<Asset>*, an ArrayList object contains Asset objects.
+
+----
+
+#### updateAssetAlias
+
+```java
+void update(Client client);
+```
+
+##### Parameters
+
+- `Client` - *Client*, Client object that makes requests to the core.
+
+##### Returns
+
+none if the asset alias is updated success.
+
+----
+
+#### listBalances
+
+```java
+List<Balance> list(Client client);
+```
+
+##### Parameters
+
+- `Client` - *Client*, Client object that makes requests to the core.
+
+##### Returns
+
+- `List of Balance`, an ArrayList object contains Balance objects.
+
+----
+
+#### listUnspentOutPuts
+
+```java
+List<UnspentOutput> list(Client client);
+```
+
+##### Parameters
+
+- `Client` - *Client*, Client object that makes requests to the core.
+
+##### Returns
+
+- `List of UnspentOutput`, an ArrayList object contains UnspentOutput objects.
+
+
+## Transaction API
+
+
+#### buildTransaction
+
+```java
+Template build(Client client);
+```
+
+##### Parameters
+
+- `Client` - *Client*, Client object that makes requests to the core.
+
+##### Returns
+
+- `Template` - *template*, a template object.
+
+----
+
+#### signTransaction
+
+```java
+Template sign(Client client, Template template, String password);
+```
+
+##### Parameters
+
+`Object`:
+
+- `Client` - *Client*, Client object that makes requests to the core.
+- `Template` - *template*, a template object.
+- `String` - *password*, signature of the password.
+
+##### Returns
+
+- `Template` - *template*, a template object.
+
+----
+
+#### submitTransaction
+
+```java
+SubmitResponse submit(Client client, Template template);
+```
+
+##### Parameters
+
+- `Client` - *Client*, Client object that makes requests to the core.
+- `Template` - *template*, a template object.
+
+##### Returns
+
+- `SubmitResponse` - *submitResponse*, a SubmitResponse object
+
+----
+
+#### estimateTransactionGas
+
+```java
+TransactionGas estimateGas(Client client, Template template);
+```
+
+##### Parameters
+
+- `Client` - *Client*, Client object that makes requests to the core.
+- `Template` - *template*, a template object.
+
+##### Returns
+
+- `TransactionGas` - *transactionGas*, a TransactionGas object
+
+----
+
+#### getTransaction
+
+```java
+Transaction get(Client client);
+```
+
+##### Parameters
+
+- `Client` - *Client*, Client object that makes requests to the core.
+
+##### Returns
+
+- `Transaction` - *transaction*, a Transaction object
+
+----
+
+#### listTransactions
+
+```java
+List<Transaction> list(Client client);
+```
+
+##### Parameters
+
+- `Client` - *Client*, Client object that makes requests to the core.
+
+##### Returns
+
+- `List of Transaction`, *List<Transaction>*, an ArrayList object contains Transaction objects.
+
+## Wallet API
+
+
+#### backupWallet
+
+```java
+Wallet backupWallet(Client client);
+```
+
+##### Parameters
+
+- `Client` - *Client*, Client object that makes requests to the core.
+
+##### Returns
+
+- `Wallet` - *wallet*, a Wallet object
+
+----
+
+#### restoreWallet
+
+```java
+void restoreWallet(Client client ,Object accountImage, Object assetImage , Object keyImages);
+```
+
+##### Parameters
+
+`Object`:
+- `Client` - *Client*, Client object that makes requests to the core.
+- `Object` - *account_image*, account image.
+- `Object` - *asset_image*, asset image.
+- `Object` - *key_images*, key image.
+
+##### Returns
+
+none if restore wallet success.
+
+
+## Access Token API
+
+```java
+//example
+AccessToken accessToken = new AccessToken.Builder().setId("sheng").create(client);
+
+List<AccessToken> tokenList = AccessToken.list(client);
+
+String secret = "5e37378eb59de6b10e60f2247ebf71c4955002e75e0cd31ede3bf48813a0a799";
+AccessToken.check(client, "sheng", secret);
+```
+
+#### createAccessToken
+
+```java
+AccessToken create(Client client);
+```
+
+##### Parameters
+
+- `Client` - *Client*, Client object that makes requests to the core.
+
+##### Returns
+
+- `AccessToken` - *accessToken*, an AccessToken object.
+
+----
+
+#### listAccessTokens
+
+```java
+List<AccessToken> list(Client client);
+```
+
+##### Parameters
+
+- `Client` - *Client*, Client object that makes requests to the core.
+
+##### Returns
+
+- `Array of Object`, access token array.
+
+----
+
+#### deleteAccessToken
+
+```java
+void delete(Client client, String id);
+```
+
+##### Parameters
+
+- `Client` - *Client*, Client object that makes requests to the core.
+- `String` - *id*, token ID.
+
+##### Returns
+
+none if the access token is deleted successfully.
+
+----
+
+#### checkAccessToken
+
+```java
+void check(Client client, String id, String secret);
+```
+
+##### Parameters
+
+- `Client` - *Client*, Client object that makes requests to the core.
+- `String` - *id*, token ID.
+- `String` - *secret*, secret of token, the second part of the colon division for token.
+
+##### Returns
+
+none if the access token is checked valid.
+
+
+## Block API
+
+
+#### getBlockCount
+
+```java
+Integer getBlockCount(Client client);
+```
+
+##### Parameters
+
+none
+
+##### Returns
+
+- `Integer` - *block_count*, recent block height of the blockchain.
+
+----
+
+#### getBlockHash
+
+```java
+String getBlockHash(Client client);
+```
+
+##### Parameters
+
+none
+
+##### Returns
+
+- `String` - *block_hash*, recent block hash of the blockchain.
+
+----
+
+#### getBlock
+```php
+Block getBlock(Client client);
+```
+
+##### Parameters
+
+- `Client` - *Client*, Client object that makes requests to the core.
+
+##### Returns
+
+- `Block` - *block*, a Block object.
+
+----
+
+#### getBlockHeader
+
+```java
+BlockHeader getBlockHeader(Client client);
+```
+
+##### Parameters
+
+- `Client` - *Client*, Client object that makes requests to the core.
+
+##### Returns
+
+- `BlockHeader` - *blockHeader*, header of block.
+
+----
+
+#### getDifficulty
+
+```java
+BlockDifficulty getBlockDifficulty(Client client);
+```
+
+##### Parameters
+
+- `Client` - *Client*, Client object that makes requests to the core.
+
+##### Returns
+
+- `BlockDifficulty` - *blockDifficulty*, a BlockDifficulty object
+
+----
+
+#### getHashRate
+
+```java
+BlockHashRate getHashRate(Client client);
+```
+
+##### Parameters
+
+- `Client` - *Client*, Client object that makes requests to the core.
+
+##### Returns
+
+- `BlockHashRate` - *blockHashRate*, a BlockHashRate object
+
+## Mining API
+
+
+#### isMining
+
+```java
+Boolean isMining(Client client);
+```
+
+##### Parameters
+
+- `Client` - *Client*, Client object that makes requests to the core.
+
+##### Returns
+
+- `Boolean` - *is_mining*, whether the node is mining.
+
+----
+
+#### setMining
+
+```java
+void setMining(Client client, Boolean isMining);
+```
+
+##### Parameters
+
+- `Client` - *Client*, Client object that makes requests to the core.
+- `Boolean` - *is_mining*, whether the node is mining.
+
+
+## Other API
+
+
+#### netInfo
+
+```java
+NetInfo getNetInfo(Client client);
+```
+
+##### Parameters
+
+- `Client` - *Client*, Client object that makes requests to the core.
+
+##### Returns
+
+- `NetInfo` - *netInfo*, a NetInfo object.
+
+----
+
+#### gasRate
+
+```java
+Gas gasRate(Client client);
+```
+
+##### Parameters
+
+- `Client` - *Client*, Client object that makes requests to the core.
+
+##### Returns
+
+- `Gas` - *gas*, a Gas object.
+
+----
+
+#### verifyMessage
+
+```java
+Boolean verifyMessage(Client client);
+```
+
+##### Parameters
+
+- `Client` - *Client*, Client object that makes requests to the core.
+
+##### Returns
+
+- `Boolean` - *result*, verify result.
+
+----
+
+#### getWork
+
+```java
+MinerWork getWork(Client client);
+```
+
+##### Parameters
+
+- `Client` - *Client*, Client object that makes requests to the core.
+
+##### Returns
+
+- `MinerWork` - *minerWork*, a MinerWork object.
+
+----
+
+#### submitWork
+
+```java
+void submiWork(Client client, String blockHeader);
+```
+
+##### Parameters
+
+- `Client` - *Client*, Client object that makes requests to the core.
+- `String` - *block_header*, raw block header.
+
+##### Returns
+
+none if the work is submitted successfully.
diff --git a/pom.xml b/pom.xml
new file mode 100644 (file)
index 0000000..610804f
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>io.bytom</groupId>
+    <artifactId>bytom-sdk-java</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <packaging>jar</packaging>
+
+    <name>bytom-sdk-java</name>
+    <!-- FIXME change it to the project's website -->
+    <url>http://www.example.com</url>
+
+    <!--
+    <developers>
+        <developer>
+            <name>successli</name>
+            <email>successli@outlook.com</email>
+            <url>https://github.com/successli</url>
+        </developer>
+    </developers>
+    -->
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.12</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>log4j</groupId>
+            <artifactId>log4j</artifactId>
+            <version>1.2.17</version>
+        </dependency>
+        <dependency>
+            <groupId>com.squareup.okhttp</groupId>
+            <artifactId>okhttp</artifactId>
+            <version>2.5.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.squareup.okhttp</groupId>
+            <artifactId>mockwebserver</artifactId>
+            <version>2.5.0</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.google.code.gson</groupId>
+            <artifactId>gson</artifactId>
+            <version>2.8.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcpkix-jdk15on</artifactId>
+            <version>1.56</version>
+        </dependency>
+    </dependencies>
+
+    <distributionManagement>
+        <snapshotRepository>
+            <id>ossrh</id>
+            <url>https://oss.sonatype.org/content/repositories/snapshots</url>
+        </snapshotRepository>
+        <repository>
+            <id>ossrh</id>
+            <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
+        </repository>
+    </distributionManagement>
+
+    <build>
+        <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
+            <plugins>
+                <plugin>
+                    <artifactId>maven-clean-plugin</artifactId>
+                    <version>3.0.0</version>
+                </plugin>
+                <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
+                <plugin>
+                    <artifactId>maven-resources-plugin</artifactId>
+                    <version>3.0.2</version>
+                </plugin>
+                <plugin>
+                    <artifactId>maven-compiler-plugin</artifactId>
+                    <version>3.7.0</version>
+                </plugin>
+                <plugin>
+                    <artifactId>maven-surefire-plugin</artifactId>
+                    <version>2.20.1</version>
+                </plugin>
+                <plugin>
+                    <artifactId>maven-jar-plugin</artifactId>
+                    <version>3.0.2</version>
+                </plugin>
+                <plugin>
+                    <artifactId>maven-install-plugin</artifactId>
+                    <version>2.5.2</version>
+                </plugin>
+                <plugin>
+                    <artifactId>maven-deploy-plugin</artifactId>
+                    <version>2.8.2</version>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>7</source>
+                    <target>7</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/src/main/java/io/bytom/api/AccessToken.java b/src/main/java/io/bytom/api/AccessToken.java
new file mode 100644 (file)
index 0000000..d93ff2c
--- /dev/null
@@ -0,0 +1,144 @@
+package io.bytom.api;
+
+import com.google.gson.annotations.SerializedName;
+import io.bytom.common.Utils;
+import io.bytom.exception.BytomException;
+import io.bytom.http.Client;
+import org.apache.log4j.Logger;
+
+import java.lang.reflect.Type;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * <h1>AccessToken Class</h1>
+ */
+public class AccessToken {
+    /**
+     * Token id
+     */
+    public String id;
+    /**
+     * Token token
+     */
+    public String token;
+    /**
+     * Token type
+     */
+    public String type;
+    /**
+     * create time of token
+     */
+    @SerializedName(value = "created_at", alternate = {"create"})
+    public String createTime;
+
+    private static Logger logger = Logger.getLogger(AccessToken.class);
+
+    /**
+     * Serializes the AccessToken into a form that is safe to transfer over the wire.
+     *
+     * @return the JSON-serialized representation of the AccessToken object
+     */
+    public String toJson() {
+        return Utils.serializer.toJson(this);
+    }
+
+    public static class Builder {
+        /**
+         * id of Token
+         */
+        public String id;
+        /**
+         * type of Token
+         */
+        public String type;
+
+        public Builder() {
+        }
+
+        /**
+         * @param id the id to set
+         * @return Builder
+         */
+        public Builder setId(String id) {
+            this.id = id;
+            return this;
+        }
+
+        /**
+         * @param type the type to set
+         * @return Builder
+         */
+        public Builder setType(String type) {
+            this.type = type;
+            return this;
+        }
+
+        /**
+         * Call create-access-token api
+         *
+         * @param client client object that makes requests to the core
+         * @return AccessToken object
+         * @throws BytomException
+         */
+        public AccessToken create(Client client) throws BytomException {
+            AccessToken accessToken = client.request("create-access-token", this, AccessToken.class);
+
+            logger.info("create-access-token:");
+            logger.info(accessToken.toJson());
+
+            return accessToken;
+        }
+    }
+
+    /**
+     * Call check-access-token api
+     *
+     * @param client client object that makes requests to the core
+     * @param id id
+     * @param secret secret
+     * @throws BytomException
+     */
+    public static void check(Client client, String id, String secret) throws BytomException {
+        Map<String, Object> req = new HashMap<String, Object>();
+        req.put("id", id);
+        req.put("secret", secret);
+        client.request("check-access-token", req);
+        logger.info("check-access-token.");
+    }
+
+    /**
+     * Call delete-access-token api
+     *
+     * @param client client object that makes requests to the core
+     * @param id id
+     * @throws BytomException
+     */
+    public static void delete(Client client, String id) throws BytomException {
+        Map<String, Object> req = new HashMap<String, Object>();
+        req.put("id", id);
+        client.request("delete-access-token", req);
+        logger.info("delete-access-token.");
+    }
+
+    /**
+     * Call list-access-tokens api.<br>
+     * native method, can't rpc
+     *
+     * @param client client object that makes requests to the core
+     * @return list of AccessToken objects
+     * @throws BytomException
+     */
+    public static List<AccessToken> list(Client client) throws BytomException {
+        Type listType = new ParameterizedTypeImpl(List.class, new Class[]{AccessToken.class});
+        List<AccessToken> accessTokenList = client.request("list-access-tokens", null, listType);
+        // TODO: 2018/5/23 need to test
+        logger.info("list-access-tokens:");
+        logger.info("size of accessTokenList:" + accessTokenList.size());
+        logger.info(accessTokenList.get(0).toJson());
+        
+        return accessTokenList;
+    }
+
+}
diff --git a/src/main/java/io/bytom/api/Account.java b/src/main/java/io/bytom/api/Account.java
new file mode 100644 (file)
index 0000000..943515d
--- /dev/null
@@ -0,0 +1,287 @@
+package io.bytom.api;
+
+import com.google.gson.Gson;
+import com.google.gson.annotations.SerializedName;
+import io.bytom.common.Utils;
+import io.bytom.exception.*;
+import io.bytom.http.Client;
+import org.apache.log4j.Logger;
+
+import java.lang.reflect.Type;
+import java.util.*;
+
+/**
+ * <h1>Account Class</h1>
+ */
+public class Account {
+
+    @SerializedName("id")
+    public String id;
+
+    @SerializedName("alias")
+    public String alias;
+
+    @SerializedName("key_index")
+    public Integer key_index;
+
+    @SerializedName("quorum")
+    public Integer quorum;
+
+    @SerializedName("xpubs")
+    public List<String> xpubs;
+
+    private static Logger logger = Logger.getLogger(Account.class);
+
+    public String toJson() {
+        return Utils.serializer.toJson(this);
+    }
+
+    /**
+     * create-account
+     *
+     * @param client client object that makes requests to the core
+     * @param builder Account.Builder to make parameters
+     * @return Account return a account object
+     * @throws BytomException BytomException
+     */
+    public static Account create(Client client, Builder builder) throws BytomException {
+        Account account = client.request("create-account", builder, Account.class);
+        logger.info("create-account");
+        logger.info(account.toString());
+        return account;
+    }
+
+    /**
+     * list-accounts
+     *
+     * @param client client object that makes requests to the core
+     * @return return a list of account object
+     * @throws BytomException BytomException
+     */
+    public static List<Account> list(Client client) throws BytomException {
+        Type listType = new ParameterizedTypeImpl(List.class, new Class[]{Account.class});
+        List<Account> accountList = client.request("list-accounts", null, listType);
+        logger.info("list-accounts:");
+        logger.info("size of accountList:"+accountList.size());
+        logger.info(accountList);
+        return accountList;
+    }
+
+    /**
+     * delete-account
+     * @param client client object that makes requests to the core
+     * @param account_info account_info
+     * @throws BytomException BytomException
+     */
+    public static void delete(Client client, String account_info) throws BytomException {
+        Map<String, String> req = new HashMap<>();
+        req.put("account_info", account_info);
+        client.request("delete-account", req);
+    }
+
+    public static class Builder {
+
+        public List<String> root_xpubs;
+
+        public String alias;
+
+        public Integer quorum;
+
+        /**
+         * add a xpub to root_xpubs
+         *
+         * @param xpub xpub
+         * @return this Builder object
+         */
+        public Builder addRootXpub(String xpub) {
+            this.root_xpubs.add(xpub);
+            return this;
+        }
+
+        /**
+         * set xpubs to root_xpubs
+         *
+         * @param xpubs xpubs
+         * @return this Builder object
+         */
+        public Builder setRootXpub(List<String> xpubs) {
+            this.root_xpubs = new ArrayList<>(xpubs);
+            return this;
+        }
+
+        /**
+         * set alias to alias
+         * @param alias alias
+         * @return this Builder object
+         */
+        public Builder setAlias(String alias) {
+            this.alias = alias;
+            return this;
+        }
+
+        /**
+         * set quorum to quorum
+         *
+         * @param quorum quorum
+         * @return this Builder object
+         */
+        public Builder setQuorum(Integer quorum) {
+            this.quorum = quorum;
+            return this;
+        }
+
+    }
+
+    /**
+     * Use this class to create a {@link Receiver} under an account.
+     */
+    public static class ReceiverBuilder {
+
+        @SerializedName("account_alias")
+        public String accountAlias;
+
+        @SerializedName("account_id")
+        public String accountId;
+
+        /**
+         * Specifies the account under which the receiver is created. You must use
+         * this method or @{link ReceiverBuilder#setAccountId}, but not both.
+         *
+         * @param alias the unique alias of the account
+         * @return this ReceiverBuilder object
+         */
+        public ReceiverBuilder setAccountAlias(String alias) {
+            this.accountAlias = alias;
+            return this;
+        }
+
+        /**
+         * Specifies the account under which the receiver is created. You must use
+         * this method or @{link ReceiverBuilder#setAccountAlias}, but not both.
+         *
+         * @param id the unique ID of the account
+         * @return this ReceiverBuilder object
+         */
+        public ReceiverBuilder setAccountId(String id) {
+            this.accountId = id;
+            return this;
+        }
+
+        /**
+         * Creates a single Receiver object under an account.
+         *
+         * @param client the client object providing access to an instance of Chain Core
+         * @return a new Receiver object
+         * @throws APIException          This exception is raised if the api returns errors while creating the control programs.
+         * @throws BadURLException       This exception wraps java.net.MalformedURLException.
+         * @throws ConnectivityException This exception is raised if there are connectivity issues with the server.
+         * @throws HTTPException         This exception is raised when errors occur making http requests.
+         * @throws JSONException         This exception is raised due to malformed json requests or responses.
+         */
+        public Receiver create(Client client) throws BytomException {
+            Gson gson = new Gson();
+            Receiver receiver = client.request(
+                    "create-account-receiver", this, Receiver.class);
+            logger.info("create-account-receiver:");
+            logger.info(receiver.toJson());
+            return receiver;
+        }
+
+
+        @Override
+        public String toString() {
+            return "ReceiverBuilder{" +
+                    "accountAlias='" + accountAlias + '\'' +
+                    ", accountId='" + accountId + '\'' +
+                    '}';
+        }
+    }
+
+    /**
+     * Use this class to create a {@link Address} under an account.
+     */
+    public static class AddressBuilder {
+
+        @SerializedName("account_alias")
+        public String accountAlias;
+
+        @SerializedName("account_id")
+        public String accountId;
+
+        /**
+         * Specifies the account under which the address is created. You must use
+         * this method or @{link AddressBuilder#setAccountId}, but not both.
+         *
+         * @param alias the unique alias of the account
+         * @return this AddressBuilder object
+         */
+        public AddressBuilder setAccountAlias(String alias) {
+            this.accountAlias = alias;
+            return this;
+        }
+
+        /**
+         * Specifies the account under which the address is created. You must use
+         * this method or @{link AddressBuilder#setAccountAlias}, but not both.
+         *
+         * @param id the unique ID of the account
+         * @return this AddressBuilder object
+         */
+        public AddressBuilder setAccountId(String id) {
+            this.accountId = id;
+            return this;
+        }
+
+        /**
+         * list-addresses
+         * @param client client object that makes requests to the core
+         * @return list of address object
+         * @throws BytomException BytomException
+         */
+        public List<Address> list(Client client) throws BytomException {
+            Type listType = new ParameterizedTypeImpl(List.class, new Class[]{Address.class});
+            List<Address> addressList = client.request("list-addresses", this, listType);
+            logger.info("list-addresses:");
+            logger.info("size of addressList:" + addressList.size());
+            logger.info(addressList.get(0).toJson());
+
+            return addressList;
+        }
+
+        /**
+         * validate-address
+         * @param client client object that makes requests to the core
+         * @param address an address string
+         * @return an address object
+         * @throws BytomException BytomException
+         */
+        public Address validate(Client client, String address) throws BytomException {
+            Map<String, Object> req = new HashMap<>();
+            req.put("address", address);
+            Address addressResult = client.request("validate-address", req, Address.class);
+            logger.info("validate-address:");
+            logger.info(addressResult.toJson());
+
+            return addressResult;
+        }
+
+        @Override
+        public String toString() {
+            return "AddressBuilder{" +
+                    "accountAlias='" + accountAlias + '\'' +
+                    ", accountId='" + accountId + '\'' +
+                    '}';
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "Account{" +
+                "id='" + id + '\'' +
+                ", alias='" + alias + '\'' +
+                ", key_index=" + key_index +
+                ", quorum=" + quorum +
+                ", xpubs=" + xpubs +
+                '}';
+    }
+}
diff --git a/src/main/java/io/bytom/api/Address.java b/src/main/java/io/bytom/api/Address.java
new file mode 100644 (file)
index 0000000..76fd985
--- /dev/null
@@ -0,0 +1,50 @@
+package io.bytom.api;
+
+import com.google.gson.annotations.SerializedName;
+import io.bytom.common.Utils;
+import io.bytom.exception.JSONException;
+
+public class Address {
+    @SerializedName("account_alias")
+    public String accountAlias;
+
+    @SerializedName("account_id")
+    public String accountId;
+
+    @SerializedName("address")
+    public String address;
+
+    @SerializedName("change")
+    public Boolean change;
+
+    @SerializedName("vaild")
+    public Boolean vaild;
+
+    @SerializedName("is_local")
+    public Boolean is_local;
+
+    /**
+     * Serializes the Address into a form that is safe to transfer over the wire.
+     *
+     * @return the JSON-serialized representation of the Receiver object
+     */
+    public String toJson() {
+        return Utils.serializer.toJson(this);
+    }
+
+    /**
+     * Deserializes a Address from JSON.
+     *
+     * @param json a JSON-serialized Receiver object
+     * @return the deserialized Receiver object
+     * @throws JSONException Raised if the provided string is not valid JSON.
+     */
+    public static Address fromJson(String json) throws JSONException {
+        try {
+            return Utils.serializer.fromJson(json, Address.class);
+        } catch (IllegalStateException e) {
+            throw new JSONException("Unable to parse serialized receiver: " + e.getMessage());
+        }
+    }
+
+}
diff --git a/src/main/java/io/bytom/api/Asset.java b/src/main/java/io/bytom/api/Asset.java
new file mode 100644 (file)
index 0000000..3d2317e
--- /dev/null
@@ -0,0 +1,368 @@
+package io.bytom.api;
+
+import com.google.gson.annotations.SerializedName;
+import io.bytom.common.Utils;
+import io.bytom.exception.BytomException;
+import io.bytom.http.Client;
+import org.apache.log4j.Logger;
+
+import java.lang.reflect.Type;
+import java.util.*;
+
+/**
+ * <h1>Asset Class</h1>
+ * <br>
+ * String - id, asset id.<br>
+ * String - alias, name of the asset.<br>
+ * String - issuance_program, control program of the issuance of asset.<br>
+ * Array of Object - keys, information of asset pubkey.<br>
+ * String - definition, definition of asset.<br>
+ * Integer - quorum, threshold of keys that must sign a transaction to spend asset units controlled by the account.<br>
+ */
+public class Asset {
+
+    /**
+     * Globally unique identifier of the asset.<br>
+     * Asset version 1 specifies the asset id as the hash of:<br>
+     * - the asset version<br>
+     * - the asset's issuance program<br>
+     * - the core's VM version<br>
+     * - the hash of the network's initial block
+     */
+    public String id;
+
+    /**
+     * User specified, unique identifier.
+     */
+    public String alias;
+
+    /**
+     * A program specifying a predicate to be satisfied when issuing the asset.
+     */
+    @SerializedName(value = "issuance_program", alternate = {"issue_program"})
+    public String issuanceProgram;
+
+    /**
+     * The list of keys used to create the issuance program for the asset.<br>
+     * Signatures from these keys are required for issuing units of the asset.
+     */
+    public Key[] keys;
+
+    @SerializedName("key_index")
+    public Integer keyIndex;
+
+    @SerializedName("xpubs")
+    public List<String> xpubs;
+
+    /**
+     * The number of keys required to sign an issuance of the asset.
+     */
+    @SerializedName("quorum")
+    public int quorum;
+
+    /**
+     * User-specified, arbitrary/unstructured data visible across blockchain networks.<br>
+     * Version 1 assets specify the definition in their issuance programs, rendering the
+     * definition immutable.
+     */
+    @SerializedName("definition")
+    public Map<String, Object> definition;
+
+    /**
+     * version of VM.
+     */
+    @SerializedName("vm_version")
+    public int vmVersion;
+
+    /**
+     * type of asset.
+     */
+    @SerializedName("type")
+    public String type;
+
+    /**
+     * byte of asset definition.
+     */
+    @SerializedName("raw_definition_byte")
+    public String rawDefinitionByte;
+
+    public static Logger logger = Logger.getLogger(Asset.class);
+
+    public String toJson() {
+        return Utils.serializer.toJson(this);
+    }
+
+    public static class Key {
+        /**
+         * Hex-encoded representation of the root extended public key
+         */
+        @SerializedName("root_xpub")
+        public String rootXpub;
+
+        /**
+         * The derived public key, used in the asset's issuance program.
+         */
+        @SerializedName("asset_pubkey")
+        public String assetPubkey;
+
+        /**
+         * The derivation path of the derived key.
+         */
+        @SerializedName("asset_derivation_path")
+        public String[] assetDerivationPath;
+
+        @Override
+        public String toString() {
+            return "Key{" +
+                    "rootXpub='" + rootXpub + '\'' +
+                    ", assetPubkey='" + assetPubkey + '\'' +
+                    ", assetDerivationPath=" + Arrays.toString(assetDerivationPath) +
+                    '}';
+        }
+    }
+
+    /**
+     * <h2>Builder Class</h2>
+     */
+    public static class Builder {
+        /**
+         * User specified, unique identifier.
+         */
+        public String alias;
+
+        /**
+         * User-specified, arbitrary/unstructured data visible across blockchain networks.<br>
+         * Version 1 assets specify the definition in their issuance programs, rendering
+         * the definition immutable.
+         */
+        public Map<String, Object> definition;
+
+        /**
+         * The list of keys used to create the issuance program for the asset.<br>
+         * Signatures from these keys are required for issuing units of the asset.<br>
+         * <strong>Must set with {@link #addRootXpub(String)} or
+         * {@link #setRootXpubs(List)} before calling {@link #create(Client)}.</strong>
+         */
+        @SerializedName("root_xpubs")
+        public List<String> rootXpubs;
+
+        /**
+         * The number of keys required to sign an issuance of the asset.<br>
+         * <strong>Must set with {@link #setQuorum(int)} before calling
+         * {@link #create(Client)}.</strong>
+         */
+        public int quorum;
+
+        /**
+         * Unique identifier used for request idempotence.
+         */
+        @SerializedName("access_token")
+        private String access_token;
+
+        /**
+         * Default constructor initializes the list of keys.
+         */
+        public Builder() {
+            this.rootXpubs = new ArrayList<>();
+        }
+
+        /**
+         * Creates an asset object.
+         *
+         * @param client client object that makes request to the core
+         * @return an asset object
+         * @throws BytomException BytomException
+         */
+        public Asset create(Client client) throws BytomException {
+            Asset asset = client.request("create-asset", this, Asset.class);
+            logger.info("create-asset:");
+            logger.info(asset.toString());
+            return asset;
+        }
+
+        /**
+         * Sets the alias on the builder object.
+         * @param alias alias
+         * @return updated builder object
+         */
+        public Builder setAlias(String alias) {
+            this.alias = alias;
+            return this;
+        }
+
+        /**
+         * Adds a field to the existing definition object (initializing the object if it
+         * doesn't exist).
+         * @param key key of the definition field
+         * @param value value of the definition field
+         * @return updated builder object
+         */
+        public Builder addDefinitionField(String key, Object value) {
+            if (this.definition == null) {
+                this.definition = new HashMap<>();
+            }
+            this.definition.put(key, value);
+            return this;
+        }
+
+        /**
+         * Sets the asset definition object.<br>
+         * <strong>Note:</strong> any existing asset definition fields will be replaced.
+         * @param definition asset definition object
+         * @return updated builder object
+         */
+        public Builder setDefinition(Map<String, Object> definition) {
+            this.definition = definition;
+            return this;
+        }
+
+        /**
+         * Sets the quorum of the issuance program. <strong>Must be called before
+         * {@link #create(Client)}.</strong>
+         * @param quorum proposed quorum
+         * @return updated builder object
+         */
+        public Builder setQuorum(int quorum) {
+            this.quorum = quorum;
+            return this;
+        }
+
+        /**
+         * Adds a key to the builder's list.<br>
+         * <strong>Either this or {@link #setRootXpubs(List)} must be called before
+         * {@link #create(Client)}.</strong>
+         * @param xpub key
+         * @return updated builder object.
+         */
+        public Builder addRootXpub(String xpub) {
+            this.rootXpubs.add(xpub);
+            return this;
+        }
+
+        /**
+         * Sets the builder's list of keys.<br>
+         * <strong>Note:</strong> any existing keys will be replaced.<br>
+         * <strong>Either this or {@link #addRootXpub(String)} must be called before
+         * {@link #create(Client)}.</strong>
+         * @param xpubs list of xpubs
+         * @return updated builder object
+         */
+        public Builder setRootXpubs(List<String> xpubs) {
+            this.rootXpubs = new ArrayList<>(xpubs);
+            return this;
+        }
+
+        @Override
+        public String toString() {
+            return "Builder{" +
+                    "alias='" + alias + '\'' +
+                    ", definition=" + definition +
+                    ", rootXpubs=" + rootXpubs +
+                    ", quorum=" + quorum +
+                    ", access_token='" + access_token + '\'' +
+                    '}';
+        }
+    }
+
+    /**
+     * <h2>QueryBuilder Class</h2>
+     */
+    public static class QueryBuilder {
+
+        @SerializedName("id")
+        public String id;
+
+        public QueryBuilder setId(String assetId) {
+            this.id = assetId;
+            return this;
+        }
+
+        /**
+         * get-asset from bytomd
+         *
+         * @param client client object that makes requests to the core
+         * @return The Asset Object
+         * @throws BytomException BytomException
+         */
+        public Asset get(Client client) throws BytomException {
+            Asset asset = client.request("get-asset", this, Asset.class);
+            logger.info("get-asset:");
+            logger.info(asset.toJson());
+            return asset;
+        }
+
+        /**
+         * get all assets from bytomd
+         *
+         * @param client client object that makes requests to the core
+         * @return return list of asset object
+         * @throws BytomException BytomException
+         */
+        public List<Asset> list(Client client) throws BytomException {
+            Type listType = new ParameterizedTypeImpl(List.class, new Class[]{Asset.class});
+            List<Asset> assetList = client.request("list-assets", null, listType);
+            logger.info("list-assets:");
+            logger.info("size of assetList:"+assetList.size());
+            logger.info(assetList);
+            return assetList;
+        }
+
+    }
+
+    /**
+     * <h2>AliasUpdateBuilder Class</h2>
+     */
+    public static class AliasUpdateBuilder {
+        /**
+         * id of asset.
+         */
+        @SerializedName("id")
+        public String id;
+        /**
+         * new alias of asset
+         */
+        @SerializedName("alias")
+        public String alias;
+
+        public AliasUpdateBuilder setAssetId(String assetId) {
+            this.id = assetId;
+            return this;
+        }
+
+        public AliasUpdateBuilder setAlias(String alias) {
+            this.alias = alias;
+            return this;
+        }
+
+        /**
+         * update-asset-alias
+         *
+         * @param client client object that makes requests to the core
+         * @throws BytomException BytomException
+         */
+        public void update(Client client) throws BytomException {
+            client.request("update-asset-alias", this);
+            logger.info("update-asset-alias:");
+            logger.info("id:"+id);
+            logger.info("alias:"+alias);
+        }
+
+    }
+
+    @Override
+    public String toString() {
+        return "Asset{" +
+                "id='" + id + '\'' +
+                ", alias='" + alias + '\'' +
+                ", issuanceProgram='" + issuanceProgram + '\'' +
+                ", keys=" + Arrays.toString(keys) +
+                ", keyIndex=" + keyIndex +
+                ", xpubs=" + xpubs +
+                ", quorum=" + quorum +
+                ", definition=" + definition +
+                ", vmVersion=" + vmVersion +
+                ", type='" + type + '\'' +
+                ", rawDefinitionByte='" + rawDefinitionByte + '\'' +
+                '}';
+    }
+}
diff --git a/src/main/java/io/bytom/api/Balance.java b/src/main/java/io/bytom/api/Balance.java
new file mode 100644 (file)
index 0000000..319cbc9
--- /dev/null
@@ -0,0 +1,81 @@
+package io.bytom.api;
+
+import com.google.gson.annotations.SerializedName;
+import io.bytom.common.Utils;
+import io.bytom.exception.BytomException;
+import io.bytom.http.Client;
+import org.apache.log4j.Logger;
+
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Map;
+
+public class Balance {
+
+    /**
+     * account id
+     */
+    @SerializedName("account_id")
+    public String accountId;
+
+    /**
+     * name of account
+     */
+    @SerializedName("account_alias")
+    public String accountAlias;
+
+    /**
+     * sum of the unspent outputs.
+     * specified asset balance of account.
+     */
+    public long amount;
+
+    /**
+     * asset id
+     */
+    @SerializedName("asset_id")
+    public String assetId;
+
+    /**
+     * name of asset
+     */
+    @SerializedName("asset_alias")
+    public String assetAlias;
+
+    @SerializedName("asset_definition")
+    public Map<String, Object> definition;
+
+    private static Logger logger = Logger.getLogger(Balance.class);
+
+    /**
+     * Serializes the Balance into a form that is safe to transfer over the wire.
+     *
+     * @return the JSON-serialized representation of the Receiver object
+     */
+    public String toJson() {
+        return Utils.serializer.toJson(this);
+    }
+
+
+    public static class QueryBuilder {
+
+        /**
+         * Call list-Balances api
+         *
+         * @param client
+         * @return
+         * @throws BytomException
+         */
+        public List<Balance> list(Client client) throws BytomException {
+
+            // TODO: 2018/5/23 need tx and test
+            Type listType = new ParameterizedTypeImpl(List.class, new Class[]{Balance.class});
+            List<Balance> balanceList = client.request("list-balances", null, listType);
+            logger.info("list-balances:");
+            logger.info("size of :" + balanceList.size());
+            logger.info(balanceList.get(0).toJson());
+
+            return balanceList;
+        }
+    }
+}
diff --git a/src/main/java/io/bytom/api/Block.java b/src/main/java/io/bytom/api/Block.java
new file mode 100644 (file)
index 0000000..cde9c07
--- /dev/null
@@ -0,0 +1,320 @@
+package io.bytom.api;
+
+import com.google.gson.annotations.SerializedName;
+import io.bytom.common.Utils;
+import io.bytom.exception.BytomException;
+import io.bytom.http.Client;
+import org.apache.log4j.Logger;
+
+import java.util.List;
+import java.util.Map;
+
+public class Block {
+
+    public Integer bits;
+
+    public String difficulty;
+
+    public String hash;
+
+    public Integer height;
+
+    public Integer nonce;
+
+    @SerializedName("previous_block_hash")
+    public String previousBlockHash;
+
+    public Integer size;
+
+    public Integer timestamp;
+
+    @SerializedName("transaction_merkle_root")
+    public String transactionsMerkleRoot;
+
+    @SerializedName("transaction_status_hash")
+    public String transactionStatusHash;
+
+    public List<BlockTx> transactions;
+
+    public Integer version;
+
+    private static Logger logger = Logger.getLogger(Block.class);
+
+    public String toJson() {
+        return Utils.serializer.toJson(this);
+    }
+
+    /**
+     * Call get-block-count api
+     *
+     * @param client
+     * @return
+     * @throws BytomException
+     */
+    public static Integer getBlockCount(Client client) throws BytomException {
+        Integer blockCount =
+                client.requestGet("get-block-count", null, "block_count", Integer.class);
+
+        logger.info("get-block-count:"+blockCount);
+        return blockCount;
+    }
+
+    /**
+     * Call get-block-hash api
+     *
+     * @param client
+     * @return
+     * @throws BytomException
+     */
+    public static String getBlockHash(Client client) throws BytomException {
+        String blockHash =
+                client.requestGet("get-block-hash", null, "block_hash", String.class);
+
+        logger.info("get-block-hash:"+blockHash);
+
+        return blockHash;
+    }
+
+    public static class QueryBuilder {
+
+        /**
+         * block_height, height of block.
+         */
+        @SerializedName("block_height")
+        public int blockHeight;
+        /**
+         * block_hash, hash of block.
+         */
+        @SerializedName("block_hash")
+        public String blockHash;
+
+        public QueryBuilder setBlockHeight(int blockHeight) {
+            this.blockHeight = blockHeight;
+            return this;
+        }
+
+        public QueryBuilder setBlockHash(String blockHash) {
+            this.blockHash = blockHash;
+            return this;
+        }
+
+        /**
+         * Call get-block api
+         *
+         * @param client
+         * @return
+         * @throws BytomException
+         */
+        public Block getBlock(Client client) throws BytomException {
+            Block block = client.request("get-block", this, Block.class);
+
+            logger.info("get-block:");
+            logger.info(block.toJson());
+
+            return block;
+        }
+
+        /**
+         * Call get-block-header api
+         *
+         * @param client
+         * @return
+         * @throws BytomException
+         */
+        public BlockHeader getBlockHeader(Client client) throws BytomException {
+            BlockHeader blockHeader =
+                    client.request("get-block-header", this, BlockHeader.class);
+
+            logger.info("get-block-header:");
+            logger.info(blockHeader.toJson());
+
+            return blockHeader;
+        }
+
+        /**
+         * Call get-difficulty api
+         *
+         * @param client
+         * @return
+         * @throws BytomException
+         */
+        public BlockDifficulty getBlockDifficulty(Client client) throws BytomException {
+            BlockDifficulty blockDifficulty =
+                    client.request("get-difficulty", this, BlockDifficulty.class);
+
+            logger.info("get-difficulty:");
+            logger.info(blockDifficulty.toJson());
+
+            return blockDifficulty;
+        }
+
+        /**
+         * Call get-hash-rate api
+         *
+         * @param client
+         * @return
+         * @throws BytomException
+         */
+        public BlockHashRate getHashRate(Client client) throws BytomException {
+            BlockHashRate blockHashRate =
+                    client.request("get-hash-rate", this, BlockHashRate.class);
+
+            logger.info("get-hash-rate:");
+            logger.info(blockHashRate.toJson());
+
+            return blockHashRate;
+        }
+
+    }
+
+    public static class BlockTx {
+        /**
+         * Unique identifier, or transaction hash, of a transaction.
+         */
+        private String id;
+
+        /**
+         * version
+         */
+        private Integer version;
+
+        /**
+         * size
+         */
+        private Integer size;
+        /**
+         * time_range
+         */
+        @SerializedName("time_range")
+        private Integer timeRange;
+
+        /**
+         * status
+         */
+        @SerializedName("status_fail")
+        private boolean statusFail;
+
+        /**
+         * List of specified inputs for a transaction.
+         */
+        private List<AnnotatedInput> inputs;
+
+        /**
+         * List of specified outputs for a transaction.
+         */
+        private List<AnnotatedOutput> outputs;
+    }
+
+    public static class AnnotatedInput {
+
+        /**
+         * The number of units of the asset being issued or spent.
+         */
+        private Integer amount;
+
+        /**
+         * inputs param
+         */
+        private String arbitrary;
+
+        /**
+         * The definition of the asset being issued or spent (possibly null).
+         */
+        @SerializedName("asset_definition")
+        private String assetDefinition;
+
+        /**
+         * The id of the asset being issued or spent.
+         */
+        @SerializedName("asset_id")
+        private String assetId;
+
+        /**
+         * The type of the input.<br>
+         * Possible values are "issue" and "spend".
+         */
+        private String type;
+    }
+
+    public static class AnnotatedOutput {
+
+        /**
+         * The number of units of the asset being controlled.
+         */
+        private Integer amount;
+
+        /**
+         * The definition of the asset being controlled (possibly null).
+         */
+        @SerializedName("asset_definition")
+        private String assetDefinition;
+
+        /**
+         * The id of the asset being controlled.
+         */
+        @SerializedName("asset_id")
+        public String assetId;
+
+        /**
+         * The control program which must be satisfied to transfer this output.
+         */
+        @SerializedName("control_program")
+        private String controlProgram;
+
+        /**
+         * The id of the output.
+         */
+        @SerializedName("id")
+        private String id;
+
+        /**
+         * The output's position in a transaction's list of outputs.
+         */
+        private Integer position;
+
+        /**
+         * The type the output.<br>
+         * Possible values are "control" and "retire".
+         */
+        private String type;
+
+    }
+
+    public static class BlockHeader {
+
+        @SerializedName("block_header")
+        public String blockHeader;
+
+        @SerializedName("reward")
+        public Integer reward;
+
+        public String toJson() {
+            return Utils.serializer.toJson(this);
+        }
+
+    }
+
+    public static class BlockDifficulty {
+        public String hash;
+        public Integer height;
+        public Integer bits;
+        public String difficulty;
+
+        public String toJson() {
+            return Utils.serializer.toJson(this);
+        }
+
+
+    }
+
+    public static class BlockHashRate {
+        public String hash;
+        public Integer height;
+        public Integer hash_rate;
+
+        public String toJson() {
+            return Utils.serializer.toJson(this);
+        }
+
+    }
+}
diff --git a/src/main/java/io/bytom/api/Gas.java b/src/main/java/io/bytom/api/Gas.java
new file mode 100644 (file)
index 0000000..f9864db
--- /dev/null
@@ -0,0 +1,39 @@
+package io.bytom.api;
+
+import com.google.gson.annotations.SerializedName;
+import io.bytom.common.Utils;
+import io.bytom.exception.BytomException;
+import io.bytom.http.Client;
+import org.apache.log4j.Logger;
+
+/**
+ * <h1>Gas Class</h1>
+ */
+public class Gas {
+
+    @SerializedName("gas_rate")
+    public Integer gasRate;
+
+    private static Logger logger = Logger.getLogger(Gas.class);
+
+    public String toJson() {
+        return Utils.serializer.toJson(this);
+    }
+
+
+    /**
+     * Call gas-rate api
+     *
+     * @param client
+     * @return
+     * @throws BytomException
+     */
+    public static Gas gasRate(Client client) throws BytomException {
+        Gas gas = client.request("gas-rate", null, Gas.class);
+
+        logger.info("gas-rate:");
+        logger.info(gas.toJson());
+
+        return gas;
+    }
+}
diff --git a/src/main/java/io/bytom/api/Key.java b/src/main/java/io/bytom/api/Key.java
new file mode 100644 (file)
index 0000000..1c6b097
--- /dev/null
@@ -0,0 +1,144 @@
+package io.bytom.api;
+
+import com.google.gson.annotations.SerializedName;
+import io.bytom.common.Utils;
+import io.bytom.exception.*;
+import io.bytom.http.Client;
+import org.apache.log4j.Logger;
+
+import java.lang.reflect.Type;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * <h1>Key Class</h1>
+ *
+ * @version 1.0
+ * @since   2018-05-18
+ */
+public class Key {
+
+    @SerializedName("alias")
+    public String alias;
+
+    @SerializedName("xpub")
+    public String xpub;
+
+    @SerializedName("file")
+    public String file;
+
+    private static Logger logger = Logger.getLogger(Key.class);
+
+    public String toJson() {
+        return Utils.serializer.toJson(this);
+    }
+
+    /**
+     * Create a key object
+     *
+     * @param client client object that makes requests to the core
+     * @param builder  Key.Builder object that make parameters
+     * @return Key a key object
+     * @throws BytomException BytomException
+     */
+    public static Key create(Client client, Builder builder) throws BytomException {
+        Key key = client.request("create-key", builder, Key.class);
+        return key;
+    }
+
+    /**
+     * List all key objects
+     *
+     * @param client client object that makes requests to the core
+     * @return a list of key object
+     * @throws BytomException BytomException
+     */
+    public static List<Key> list(Client client) throws BytomException {
+        Type listType = new ParameterizedTypeImpl(List.class, new Class[]{Key.class});
+        List<Key> keyList = client.request("list-keys", null, listType);
+        logger.info("list-key:");
+        logger.info("size fo key:"+keyList.size());
+        logger.info(keyList);
+        return keyList;
+    }
+
+    /**
+     * delete a key
+     *
+     * @param client client object that makes requests to the core
+     * @param xpub the xpub is given when creates key
+     * @param password the password is given when creates key
+     * @throws BytomException BytomException
+     */
+    public static void delete(Client client, String xpub, String password) throws BytomException {
+        Map<String, String> req = new HashMap<>();
+        req.put("xpub", xpub);
+        req.put("password", password);
+        client.request("delete-key", req);
+    }
+
+    /**
+     * reset password
+     *
+     * @param client client object that makes requests to the core
+     * @param xpub the xpub is given when creates key
+     * @param oldPwd the old password is given when creates key
+     * @param newPwd new password used to set
+     * @throws BytomException BytomException
+     */
+    public static void resetPwd(Client client, String xpub, String oldPwd, String newPwd) throws BytomException {
+        Map<String, String> req = new HashMap<>();
+        req.put("xpub", xpub);
+        req.put("old_password", oldPwd);
+        req.put("new_password", newPwd);
+        client.request("reset-key-password", req);
+    }
+
+    /**
+     * <h1>Key.Builder Class</h1>
+     */
+    public static class Builder {
+        /**
+         * User specified, unique identifier.
+         */
+        public String alias;
+
+        /**
+         * User specified.
+         */
+        public String password;
+
+        /**
+         * Sets the alias on the builder object.
+         *
+         * @param alias alias
+         * @return updated builder object
+         */
+        public Builder setAlias(String alias) {
+            this.alias = alias;
+            return this;
+        }
+
+        /**
+         * Sets the alias on the builder object.
+         *
+         * @param password password
+         * @return updated builder object
+         */
+        public Builder setPassword(String password) {
+            this.password = password;
+            return this;
+        }
+
+    }
+
+    @Override
+    public String toString() {
+        return "Key{" +
+                "alias='" + alias + '\'' +
+                ", xpub='" + xpub + '\'' +
+                ", file='" + file + '\'' +
+                '}';
+    }
+}
diff --git a/src/main/java/io/bytom/api/Message.java b/src/main/java/io/bytom/api/Message.java
new file mode 100644 (file)
index 0000000..b55da44
--- /dev/null
@@ -0,0 +1,123 @@
+package io.bytom.api;
+
+import com.google.gson.annotations.SerializedName;
+import io.bytom.common.Utils;
+import io.bytom.exception.BytomException;
+import io.bytom.http.Client;
+import org.apache.log4j.Logger;
+
+public class Message {
+
+    @SerializedName("derived_xpub")
+    public String derivedXpub;
+    @SerializedName("signature")
+    public String signature;
+
+    private static Logger logger = Logger.getLogger(Message.class);
+
+    public String toJson() {
+        return Utils.serializer.toJson(this);
+    }
+
+    public static class SignBuilder {
+
+        public String address;
+        public String message;
+        public String password;
+
+        public SignBuilder setAddress(String address) {
+            this.address = address;
+            return this;
+        }
+
+        public SignBuilder setMessage(String message) {
+            this.message = message;
+            return this;
+        }
+
+        public SignBuilder setPassword(String password) {
+            this.password = password;
+            return this;
+        }
+
+        /**
+         * Call sign-message api
+         *
+         * @param client
+         * @return
+         * @throws BytomException
+         */
+        public Message sign(Client client) throws BytomException {
+            Message message = client.request("sign-message", this, Message.class);
+
+            logger.info("sign-message:");
+            logger.info(message.toJson());
+
+            return message;
+        }
+
+    }
+
+    public static class VerifyBuilder {
+
+        /**
+         * address, address for account.
+         */
+        public String address;
+
+        /**
+         * derived_xpub, derived xpub.
+         */
+        @SerializedName("derived_xpub")
+        public String derivedXpub;
+
+        /**
+         * message, message for signature by derived_xpub.
+         */
+        public String message;
+
+        /**
+         * signature, signature for message.
+         */
+        public String signature;
+
+
+        public VerifyBuilder setAddress(String address) {
+            this.address = address;
+            return this;
+        }
+
+        public VerifyBuilder setDerivedXpub(String derivedXpub) {
+            this.derivedXpub = derivedXpub;
+            return this;
+        }
+
+        public VerifyBuilder setMessage(String message) {
+            this.message = message;
+            return this;
+        }
+
+        public VerifyBuilder setSignature(String signature) {
+            this.signature = signature;
+            return this;
+        }
+
+        /**
+         * Call verify-message api
+         * @param client
+         * @return
+         * @throws BytomException
+         */
+        public Boolean verifyMessage(Client client) throws BytomException {
+            Boolean result = client.requestGet("verify-message", this, "result", Boolean.class);
+
+            logger.info("verify-message:");
+            logger.info(result);
+
+            return result;
+        }
+
+    }
+
+
+}
diff --git a/src/main/java/io/bytom/api/Miner.java b/src/main/java/io/bytom/api/Miner.java
new file mode 100644 (file)
index 0000000..c177f3f
--- /dev/null
@@ -0,0 +1,92 @@
+package io.bytom.api;
+
+import com.google.gson.annotations.SerializedName;
+import io.bytom.common.Utils;
+import io.bytom.exception.BytomException;
+import io.bytom.http.Client;
+import org.apache.log4j.Logger;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class Miner {
+
+    private static Logger logger = Logger.getLogger(Miner.class);
+
+    /**
+     * Call is-mining api
+     *
+     * @param client
+     * @return
+     * @throws BytomException
+     */
+    public static boolean isMining(Client client) throws BytomException {
+        return client.requestGet("is-mining", null, "is_mining", Boolean.class);
+    }
+
+    /**
+     * Call set-mining api
+     *
+     * @param client
+     * @param isMining
+     * @throws BytomException
+     */
+    public static void setMining(Client client, boolean isMining) throws BytomException {
+        Map<String, Object> req = new HashMap<String, Object>();
+        req.put("is_mining", isMining);
+        client.request("set-mining", req);
+        // TODO: 2018/5/22
+    }
+
+    /**
+     * Call get-work api
+     *
+     * @param client
+     * @return
+     * @throws BytomException
+     */
+    public static MinerWork getWork(Client client) throws BytomException {
+        MinerWork minerWork = client.request("get-work", null, MinerWork.class);
+
+        logger.info("get-work:");
+        logger.info(minerWork.toJson());
+
+        return minerWork;
+    }
+
+    /**
+     * Call submit-work api
+     *
+     * @param client
+     * @param blockHeader
+     * @throws BytomException
+     */
+    public static void submiWork(Client client, String blockHeader) throws BytomException
+    {
+        Map<String, Object> req = new HashMap<String, Object>();
+        req.put("block_header", blockHeader);
+        client.request("submit-work", req);
+        logger.info("submit-work.");
+    }
+
+    public static class MinerWork {
+
+        /**
+         * block_header, raw block header.
+         */
+        @SerializedName("block_header")
+        public String blockHeader;
+
+        /**
+         *  seed, seed.
+         */
+        @SerializedName("seed")
+        public String seed;
+
+        public String toJson() {
+            return Utils.serializer.toJson(this);
+        }
+
+
+    }
+}
diff --git a/src/main/java/io/bytom/api/NetInfo.java b/src/main/java/io/bytom/api/NetInfo.java
new file mode 100644 (file)
index 0000000..1331e0a
--- /dev/null
@@ -0,0 +1,76 @@
+package io.bytom.api;
+
+import com.google.gson.annotations.SerializedName;
+import io.bytom.common.Utils;
+import io.bytom.exception.BytomException;
+import io.bytom.http.Client;
+import org.apache.log4j.Logger;
+
+public class NetInfo {
+
+    /**
+     * listening, whether the node is listening.
+     */
+    public boolean listening;
+
+    /**
+     * syncing, whether the node is syncing.
+     */
+    public boolean syncing;
+
+    /**
+     * mining, whether the node is mining.
+     */
+    public boolean mining;
+
+    /**
+     * peer_count, current count of connected peers.
+     */
+    @SerializedName("peer_count")
+    public int peerCount;
+
+    /**
+     * current_block, current block height in the node's blockchain.
+     */
+    @SerializedName("current_block")
+    public long currentBlock;
+
+    /**
+     * highest_block, current highest block of the connected peers.
+     */
+    @SerializedName("highest_block")
+    public long highestBlock;
+
+    /**
+     * network_id, network id.
+     */
+    @SerializedName("network_id")
+    public String networkID;
+
+    /**
+     * version, bytom version.
+     */
+    @SerializedName("version")
+    public String version;
+
+    private static Logger logger = Logger.getLogger(NetInfo.class);
+
+    public String toJson() {
+        return Utils.serializer.toJson(this);
+    }
+
+    /**
+     * Call net-info api
+     *
+     * @param client
+     * @return
+     * @throws BytomException
+     */
+    public static NetInfo getNetInfo(Client client) throws BytomException {
+        NetInfo netInfo = client.request("net-info", null, NetInfo.class);
+
+        logger.info("net-info:");
+        logger.info(netInfo.toJson());
+        return netInfo;
+    }
+}
diff --git a/src/main/java/io/bytom/api/ParameterizedTypeImpl.java b/src/main/java/io/bytom/api/ParameterizedTypeImpl.java
new file mode 100644 (file)
index 0000000..a706e71
--- /dev/null
@@ -0,0 +1,30 @@
+package io.bytom.api;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+
+public class ParameterizedTypeImpl implements ParameterizedType {
+
+    private final Class raw;
+    private final Type[] args;
+
+    public ParameterizedTypeImpl(Class raw, Type[] args) {
+        this.raw = raw;
+        this.args = args != null ? args : new Type[0];
+    }
+
+    @Override
+    public Type[] getActualTypeArguments() {
+        return args;
+    }
+
+    @Override
+    public Type getRawType() {
+        return raw;
+    }
+
+    @Override
+    public Type getOwnerType() {
+        return null;
+    }
+}
diff --git a/src/main/java/io/bytom/api/RawTransaction.java b/src/main/java/io/bytom/api/RawTransaction.java
new file mode 100644 (file)
index 0000000..f09e006
--- /dev/null
@@ -0,0 +1,154 @@
+package io.bytom.api;
+
+import com.google.gson.annotations.SerializedName;
+import io.bytom.common.Utils;
+import io.bytom.exception.BytomException;
+import io.bytom.http.Client;
+import org.apache.log4j.Logger;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class RawTransaction {
+    /**
+     * version
+     */
+    public Integer version;
+    /**
+     * size
+     */
+    public Integer size;
+    /**
+     * time_range
+     */
+    @SerializedName("time_range")
+    public Integer timeRange;
+
+    /**
+     * status
+     */
+    public Integer fee;
+
+    /**
+     * List of specified inputs for a transaction.
+     */
+    public List<AnnotatedInput> inputs;
+
+    /**
+     * List of specified outputs for a transaction.
+     */
+    public List<AnnotatedOutput> outputs;
+
+    private static Logger logger = Logger.getLogger(RawTransaction.class);
+
+    public String toJson() {
+        return Utils.serializer.toJson(this);
+    }
+
+
+    public static RawTransaction decode(Client client, String txId) throws BytomException {
+        Map<String, Object> req = new HashMap<String, Object>();
+        req.put("raw_transaction", txId);
+        RawTransaction rawTransaction =
+                client.request("decode-raw-transaction", req, RawTransaction.class);
+
+        logger.info("decode-raw-transaction:");
+        logger.info(rawTransaction.toJson());
+
+        return rawTransaction;
+    }
+
+    public static class AnnotatedInput {
+
+        /**
+         * address
+         */
+        private String address;
+
+        /**
+         * The number of units of the asset being issued or spent.
+         */
+        private Integer amount;
+
+        /**
+         * The definition of the asset being issued or spent (possibly null).
+         */
+        @SerializedName("asset_definition")
+        private Map<String, Object> assetDefinition;
+
+        /**
+         * The id of the asset being issued or spent.
+         */
+        @SerializedName("asset_id")
+        private String assetId;
+
+        /**
+         * The control program which must be satisfied to transfer this output.
+         */
+        @SerializedName("control_program")
+        private String controlProgram;
+
+        /**
+         * The id of the output consumed by this input. Null if the input is an
+         * issuance.
+         */
+        @SerializedName("spent_output_id")
+        private String spentOutputId;
+
+        /**
+         * The type of the input.<br>
+         * Possible values are "issue" and "spend".
+         */
+        private String type;
+    }
+
+    public static class AnnotatedOutput {
+
+        /**
+         * address
+         */
+        private String address;
+
+        /**
+         * The number of units of the asset being controlled.
+         */
+        private Integer amount;
+
+        /**
+         * The definition of the asset being controlled (possibly null).
+         */
+        @SerializedName("asset_definition")
+        private Map<String, Object> assetDefinition;
+
+        /**
+         * The id of the asset being controlled.
+         */
+        @SerializedName("asset_id")
+        public String assetId;
+
+        /**
+         * The control program which must be satisfied to transfer this output.
+         */
+        @SerializedName("control_program")
+        private String controlProgram;
+
+        /**
+         * The id of the output.
+         */
+        @SerializedName("id")
+        private String id;
+
+        /**
+         * The output's position in a transaction's list of outputs.
+         */
+        private Integer position;
+
+        /**
+         * The type the output.<br>
+         * Possible values are "control" and "retire".
+         */
+        private String type;
+
+    }
+}
diff --git a/src/main/java/io/bytom/api/Receiver.java b/src/main/java/io/bytom/api/Receiver.java
new file mode 100644 (file)
index 0000000..add4a6a
--- /dev/null
@@ -0,0 +1,49 @@
+package io.bytom.api;
+
+import com.google.gson.annotations.SerializedName;
+import io.bytom.common.Utils;
+import io.bytom.exception.JSONException;
+
+/**
+ * Receivers are used to facilitate payments between accounts on different
+ * cores. They contain a control program and an expiration date. In the future,
+ * more payment-related metadata may be placed here.
+ * <p>
+ * Receivers are typically created under accounts via the
+ * {@link Account.ReceiverBuilder} class.
+ */
+public class Receiver {
+
+    @SerializedName("address")
+    public String address;
+    /**
+     * Hex-encoded string representation of the control program.
+     */
+    @SerializedName("control_program")
+    public String controlProgram;
+
+
+    /**
+     * Serializes the receiver into a form that is safe to transfer over the wire.
+     *
+     * @return the JSON-serialized representation of the Receiver object
+     */
+    public String toJson() {
+        return Utils.serializer.toJson(this);
+    }
+
+    /**
+     * Deserializes a Receiver from JSON.
+     *
+     * @param json a JSON-serialized Receiver object
+     * @return the deserialized Receiver object
+     * @throws JSONException Raised if the provided string is not valid JSON.
+     */
+    public static Receiver fromJson(String json) throws JSONException {
+        try {
+            return Utils.serializer.fromJson(json, Receiver.class);
+        } catch (IllegalStateException e) {
+            throw new JSONException("Unable to parse serialized receiver: " + e.getMessage());
+        }
+    }
+}
diff --git a/src/main/java/io/bytom/api/Transaction.java b/src/main/java/io/bytom/api/Transaction.java
new file mode 100644 (file)
index 0000000..329c7df
--- /dev/null
@@ -0,0 +1,982 @@
+package io.bytom.api;
+
+import com.google.gson.annotations.SerializedName;
+import io.bytom.common.Utils;
+import io.bytom.exception.BytomException;
+import io.bytom.http.Client;
+import org.apache.log4j.Logger;
+
+import java.lang.reflect.Type;
+import java.util.*;
+
+/**
+ * <h1>Transaction Class</h1>
+ */
+public class Transaction {
+    /**
+     * Unique identifier, or transaction hash, of a transaction.
+     */
+    @SerializedName("tx_id")
+    public String txId;
+
+    /**
+     * Time of transaction.
+     */
+    @SerializedName("block_time")
+    public String blockTime;
+
+    /**
+     * Unique identifier, or block hash, of the block containing a transaction.
+     */
+    @SerializedName("block_hash")
+    public String blockHash;
+
+    /**
+     * Index of a transaction within the block.
+     */
+    @SerializedName("block_index")
+    public String blockIndex;
+
+    @SerializedName("block_transactions_count")
+    public String blockTransactionsCount;
+
+    /**
+     * Height of the block containing a transaction.
+     */
+    @SerializedName("block_height")
+    public int blockHeight;
+
+    /**
+     * whether the state of the request has failed.
+     */
+    @SerializedName("status_fail")
+    public boolean statusFail;
+
+    /**
+     * List of specified inputs for a transaction.
+     */
+    public List<Input> inputs;
+
+    /**
+     * List of specified outputs for a transaction.
+     */
+    public List<Output> outputs;
+
+    private static Logger logger = Logger.getLogger(Transaction.class);
+
+    /**
+     * Serializes the Address into a form that is safe to transfer over the wire.
+     *
+     * @return the JSON-serialized representation of the Receiver object
+     */
+    public String toJson() {
+        return Utils.serializer.toJson(this);
+    }
+
+    public static class Builder {
+        /**
+         * Hex-encoded serialization of a transaction to add to the current template.
+         */
+        @SerializedName("base_transaction")
+        protected String baseTransaction;
+
+        /**
+         * List of actions in a transaction.
+         */
+        protected List<Action> actions;
+
+        /**
+         * A time duration in milliseconds. If the transaction is not fully signed and
+         * submitted within this time, it will be rejected by the blockchain.
+         * Additionally, any outputs reserved when building this transaction will remain
+         * reserved for this duration.
+         */
+        protected long ttl;
+
+        /**
+         * Call build-transaction api.<br>
+         *
+         * Builds a single transaction template.
+         *
+         * @param client client object which makes requests to the server
+         * @return a transaction template
+         */
+        public Template build(Client client) throws BytomException {
+            return client.request("build-transaction", this, Template.class);
+        }
+
+        /**
+         * Default constructor initializes actions list.
+         */
+        public Builder() {
+            this.actions = new ArrayList<>();
+        }
+
+        /**
+         * Sets the baseTransaction field and initializes the actions lists.<br>
+         * This constructor can be used when executing an atomic swap and the counter
+         * party has sent an initialized tx template.
+         */
+        public Builder(String baseTransaction) {
+            this.setBaseTransaction(baseTransaction);
+            this.actions = new ArrayList<>();
+        }
+
+        /**
+         * Sets the base transaction that will be added to the current template.
+         */
+        public Builder setBaseTransaction(String baseTransaction) {
+            this.baseTransaction = baseTransaction;
+            return this;
+        }
+
+        /**
+         * Adds an action to a transaction builder.
+         * @param action action to add
+         * @return updated builder object
+         */
+        public Builder addAction(Action action) {
+            this.actions.add(action);
+            return this;
+        }
+
+        /**
+         * Sets a transaction's time-to-live, which indicates how long outputs will be
+         * reserved for, and how long the transaction will remain valid. Passing zero will
+         * use the default TTL, which is 300000ms (5 minutes).
+         * @param ms the duration of the TTL, in milliseconds.
+         * @return updated builder object
+         */
+        public Builder setTtl(long ms) {
+            this.ttl = ms;
+            return this;
+        }
+    }
+
+    public static class QueryBuilder {
+
+        public String txId;
+
+        public QueryBuilder setTxId(String txId) {
+            this.txId = txId;
+            return this;
+        }
+
+        /**
+         * call list-transactions api
+         *
+         * @param client client object that makes requests to the core
+         * @return Transaction Info
+         * @throws BytomException BytomException
+         */
+        public List<Transaction> list(Client client) throws BytomException {
+
+            Type listType = new ParameterizedTypeImpl(List.class, new Class[]{Transaction.class});
+            List<Transaction> transactionList = client.request("list-transactions", this, listType);
+            logger.info("list-transactions:");
+            logger.info("size of transactionList:" + transactionList.size());
+            logger.info(transactionList.get(0).toJson());
+
+            return transactionList;
+        }
+
+        /**
+         * call get-transaction api
+         *
+         * @param client
+         * @return
+         * @throws BytomException
+         */
+        public Transaction get(Client client) throws BytomException {
+            // TODO: 2018/5/23 need to test 
+            Transaction transaction = client.request("get-transaction", this, Transaction.class);
+            logger.info("get-transaction:");
+            logger.info(transaction.toJson());
+            return transaction;
+        }
+
+    }
+
+    public static class SignerBuilder {
+        /**
+         * call sign-transaction api
+         *
+         * Sends a transaction template to a remote password for signing.
+         *
+         * @param client
+         * @param template a signed transaction template
+         * @param password
+         * @return
+         * @throws BytomException
+         */
+        public Template sign(Client client, Template template,
+                                         String password) throws BytomException {
+            // TODO: 2018/5/23 need to test
+            HashMap<String, Object> req = new HashMap<String, Object>();
+            req.put("transaction", template);
+            req.put("password", password);
+
+            Template templateResult = client.requestGet("sign-transaction", req, "transaction",
+                    Transaction.Template.class);
+
+            logger.info("sign-transaction:");
+            logger.info(templateResult.toJson());
+
+            return templateResult;
+        }
+
+    }
+
+    /**
+     * A single input included in a transaction.
+     */
+    public static class Input {
+        /**
+         * The alias of the account transferring the asset (possibly null if the input is
+         * an issuance or an unspent output is specified).
+         */
+        @SerializedName("account_alias")
+        public String accountAlias;
+
+        /**
+         * The id of the account transferring the asset (possibly null if the input is an
+         * issuance or an unspent output is specified).
+         */
+        @SerializedName("account_id")
+        public String accountId;
+
+        @SerializedName("address")
+        public String address;
+
+        /**
+         * The number of units of the asset being issued or spent.
+         */
+        public long amount;
+
+        /**
+         * The alias of the asset being issued or spent (possibly null).
+         */
+        @SerializedName("asset_alias")
+        public String assetAlias;
+
+        /**
+         * The definition of the asset being issued or spent (possibly null).
+         */
+        @SerializedName("asset_definition")
+        public Map<String, Object> assetDefinition;
+
+        /**
+         * The id of the asset being issued or spent.
+         */
+        @SerializedName("asset_id")
+        public String assetId;
+
+        /**
+         * The id of the output consumed by this input. Null if the input is an issuance.
+         */
+        @SerializedName("spent_output_id")
+        public String spentOutputId;
+
+        /**
+         * The type of the input.<br>
+         * Possible values are "issue" and "spend".
+         */
+        public String type;
+
+        public String arbitrary;
+
+        @SerializedName("control_program")
+        public String controlProgram;
+
+    }
+
+    /**
+     * A single output included in a transaction.
+     */
+    public static class Output {
+        /**
+         * The id of the output.
+         */
+        @SerializedName("id")
+        public String id;
+
+        /**
+         * The type the output.<br>
+         * Possible values are "control" and "retire".
+         */
+        public String type;
+
+        /**
+         * The output's position in a transaction's list of outputs.
+         */
+        public int position;
+
+        /**
+         * The control program which must be satisfied to transfer this output.
+         */
+        @SerializedName("control_program")
+        public String controlProgram;
+
+        /**
+         * The id of the asset being controlled.
+         */
+        @SerializedName("asset_id")
+        public String assetId;
+
+        /**
+         * The alias of the asset being controlled.
+         */
+        @SerializedName("asset_alias")
+        public String assetAlias;
+
+        /**
+         * The definition of the asset being controlled (possibly null).
+         */
+        @SerializedName("asset_definition")
+        public Map<String, Object> assetDefinition;
+
+        /**
+         * The number of units of the asset being controlled.
+         */
+        public long amount;
+
+        /**
+         * The id of the account controlling this output (possibly null if a control
+         * program is specified).
+         */
+        @SerializedName("account_id")
+        public String accountId;
+
+        /**
+         * The alias of the account controlling this output (possibly null if a control
+         * program is specified).
+         */
+        @SerializedName("account_alias")
+        public String accountAlias;
+
+        @SerializedName("address")
+        public String address;
+
+    }
+
+    /**
+     * Base class representing actions that can be taken within a transaction.
+     */
+    public static class Action extends HashMap<String, Object> {
+        /**
+         *
+         */
+        private static final long serialVersionUID = 7948250382060074590L;
+
+        /**
+         * Default constructor initializes list and sets the client token.
+         */
+        public Action() {
+            // Several action types require client_token as an idempotency key.
+            // It's safest to include a default value for this param.
+            this.put("client_token", UUID.randomUUID().toString());
+        }
+
+        /**
+         * Represents an issuance action.
+         */
+        public static class Issue extends Action {
+            /**
+             *
+             */
+            private static final long serialVersionUID = -6296543909434749786L;
+
+            /**
+             * Default constructor defines the action type as "issue"
+             */
+            public Issue() {
+                this.put("type", "issue");
+            }
+
+            /**
+             * Specifies the asset to be issued using its alias.<br>
+             * <strong>Either this or {@link Issue#setAssetId(String)} must be
+             * called.</strong>
+             * @param alias alias of the asset to be issued
+             * @return updated action object
+             */
+            public Issue setAssetAlias(String alias) {
+                this.put("asset_alias", alias);
+                return this;
+            }
+
+            /**
+             * Specifies the asset to be issued using its id.<br>
+             * <strong>Either this or {@link Issue#setAssetAlias(String)} must be
+             * called.</strong>
+             * @param id id of the asset to be issued
+             * @return updated action object
+             */
+            public Issue setAssetId(String id) {
+                this.put("asset_id", id);
+                return this;
+            }
+
+            /**
+             * Specifies the amount of the asset to be issued.<br>
+             * <strong>Must be called.</strong>
+             * @param amount number of units of the asset to be issued
+             * @return updated action object
+             */
+            public Issue setAmount(long amount) {
+                this.put("amount", amount);
+                return this;
+            }
+        }
+
+        /**
+         * Represents a spend action taken on a particular account.
+         */
+        public static class SpendFromAccount extends Action {
+            /**
+             *
+             */
+            private static final long serialVersionUID = 6444162327409625893L;
+
+            /**
+             * Default constructor defines the action type as "spend_account"
+             */
+            public SpendFromAccount() {
+                this.put("type", "spend_account");
+            }
+
+            /**
+             * Specifies the spending account using its alias.<br>
+             * <strong>Either this or {@link SpendFromAccount#setAccountId(String)} must
+             * be called.</strong><br>
+             * <strong>Must be used with {@link SpendFromAccount#setAssetAlias(String)}
+             * .</strong>
+             * @param alias alias of the spending account
+             * @return updated action object
+             */
+            public SpendFromAccount setAccountAlias(String alias) {
+                this.put("account_alias", alias);
+                return this;
+            }
+
+            /**
+             * Specifies the spending account using its id.<br>
+             * <strong>Either this or {@link SpendFromAccount#setAccountAlias(String)}
+             * must be called.</strong><br>
+             * <strong>Must be used with {@link SpendFromAccount#setAssetId(String)}
+             * .</strong>
+             * @param id id of the spending account
+             * @return updated action object
+             */
+            public SpendFromAccount setAccountId(String id) {
+                this.put("account_id", id);
+                return this;
+            }
+
+            /**
+             * Specifies the asset to be spent using its alias.<br>
+             * <strong>Either this or {@link SpendFromAccount#setAssetId(String)} must be
+             * called.</strong><br>
+             * <strong>Must be used with {@link SpendFromAccount#setAccountAlias(String)}
+             * .</strong>
+             * @param alias alias of the asset to be spent
+             * @return updated action object
+             */
+            public SpendFromAccount setAssetAlias(String alias) {
+                this.put("asset_alias", alias);
+                return this;
+            }
+
+            /**
+             * Specifies the asset to be spent using its id.<br>
+             * <strong>Either this or {@link SpendFromAccount#setAssetAlias(String)} must
+             * be called.</strong><br>
+             * <strong>Must be used with {@link SpendFromAccount#setAccountId(String)}
+             * .</strong><br>
+             * @param id id of the asset to be spent
+             * @return updated action object
+             */
+            public SpendFromAccount setAssetId(String id) {
+                this.put("asset_id", id);
+                return this;
+            }
+
+            /**
+             * Specifies the amount of asset to be spent.<br>
+             * <strong>Must be called.</strong>
+             * @param amount number of units of the asset to be spent
+             * @return updated action object
+             */
+            public SpendFromAccount setAmount(long amount) {
+                this.put("amount", amount);
+                return this;
+            }
+        }
+
+        /**
+         * Represents a control action taken on a particular account.
+         */
+        public static class ControlWithAccount extends Action {
+            /**
+             *
+             */
+            private static final long serialVersionUID = -1067464339402520620L;
+
+            /**
+             * Default constructor defines the action type as "control_account"
+             */
+            public ControlWithAccount() {
+                this.put("type", "control_account");
+            }
+
+            /**
+             * Specifies the controlling account using its alias.<br>
+             * <strong>Either this or {@link ControlWithAccount#setAccountId(String)} must
+             * be called.</strong><br>
+             * <strong>Must be used with {@link ControlWithAccount#setAssetAlias(String)}
+             * .</strong>
+             * @param alias alias of the controlling account
+             * @return updated action object
+             */
+            public ControlWithAccount setAccountAlias(String alias) {
+                this.put("account_alias", alias);
+                return this;
+            }
+
+            /**
+             * Specifies the controlling account using its id.<br>
+             * <strong>Either this or {@link ControlWithAccount#setAccountAlias(String)}
+             * must be called.</strong><br>
+             * <strong>Must be used with {@link ControlWithAccount#setAssetId(String)}
+             * .</strong>
+             * @param id id of the controlling account
+             * @return updated action object
+             */
+            public ControlWithAccount setAccountId(String id) {
+                this.put("account_id", id);
+                return this;
+            }
+
+            /**
+             * Specifies the asset to be controlled using its alias.<br>
+             * <strong>Either this or {@link ControlWithAccount#setAssetId(String)} must
+             * be called.</strong><br>
+             * <strong>Must be used with
+             * {@link ControlWithAccount#setAccountAlias(String)}.</strong>
+             * @param alias alias of the asset to be controlled
+             * @return updated action object
+             */
+            public ControlWithAccount setAssetAlias(String alias) {
+                this.put("asset_alias", alias);
+                return this;
+            }
+
+            /**
+             * Specifies the asset to be controlled using its id.<br>
+             * <strong>Either this or {@link ControlWithAccount#setAssetAlias(String)}
+             * must be called.</strong><br>
+             * <strong>Must be used with {@link ControlWithAccount#setAccountId(String)}
+             * .</strong>
+             * @param id id of the asset to be controlled
+             * @return updated action object
+             */
+            public ControlWithAccount setAssetId(String id) {
+                this.put("asset_id", id);
+                return this;
+            }
+
+            /**
+             * Specifies the amount of the asset to be controlled.<br>
+             * <strong>Must be called.</strong>
+             * @param amount number of units of the asset to be controlled
+             * @return updated action object
+             */
+            public ControlWithAccount setAmount(long amount) {
+                this.put("amount", amount);
+                return this;
+            }
+
+        }
+
+        /**
+         * Represents a control action taken on a particular address.
+         */
+        public static class ControlWithAddress extends Action {
+            /**
+             *
+             */
+            private static final long serialVersionUID = 1292007349260961499L;
+
+            /**
+             * Default constructor defines the action type as "control_address"
+             */
+            public ControlWithAddress() {
+                this.put("type", "control_address");
+            }
+
+            public ControlWithAddress setAddress(String address) {
+                this.put("address", address);
+                return this;
+            }
+
+            /**
+             * Specifies the asset to be controlled using its alias.<br>
+             * <strong>Either this or {@link ControlWithAddress#setAssetId(String)} must
+             * be called.</strong><br>
+             * @param alias alias of the asset to be controlled
+             * @return updated action object
+             */
+            public ControlWithAddress setAssetAlias(String alias) {
+                this.put("asset_alias", alias);
+                return this;
+            }
+
+            /**
+             * Specifies the asset to be controlled using its id.<br>
+             * <strong>Either this or {@link ControlWithAccount#setAssetAlias(String)}
+             * must be called.</strong><br>
+             * @param id id of the asset to be controlled
+             * @return updated action object
+             */
+            public ControlWithAddress setAssetId(String id) {
+                this.put("asset_id", id);
+                return this;
+            }
+
+            /**
+             * Specifies the amount of the asset to be controlled.<br>
+             * <strong>Must be called.</strong>
+             * @param amount number of units of the asset to be controlled
+             * @return updated action object
+             */
+            public ControlWithAddress setAmount(long amount) {
+                this.put("amount", amount);
+                return this;
+            }
+
+        }
+
+        /**
+         * Use this action to pay assets into a {@link Receiver}.
+         */
+        public static class ControlWithReceiver extends Action {
+            /**
+             *
+             */
+            private static final long serialVersionUID = 7280759134960453401L;
+
+            /**
+             * Default constructor.
+             */
+            public ControlWithReceiver() {
+                this.put("type", "control_receiver");
+            }
+
+            /**
+             * Specifies the receiver that is being paid to.
+             *
+             * @param receiver the receiver being paid to
+             * @return this ControlWithReceiver object
+             */
+            public ControlWithReceiver setReceiver(Receiver receiver) {
+                this.put("receiver", receiver);
+                return this;
+            }
+
+            /**
+             * Specifies the asset to be controlled using its alias.
+             * <p>
+             * <strong>Either this or {@link ControlWithReceiver#setAssetId(String)} must
+             * be called.</strong>
+             * @param alias unique alias of the asset to be controlled
+             * @return this ControlWithReceiver object
+             */
+            public ControlWithReceiver setAssetAlias(String alias) {
+                this.put("asset_alias", alias);
+                return this;
+            }
+
+            /**
+             * Specifies the asset to be controlled using its id.
+             * <p>
+             * <strong>Either this or {@link ControlWithReceiver#setAssetAlias(String)}
+             * must be called.</strong>
+             * @param id unique ID of the asset to be controlled
+             * @return this ControlWithReceiver object
+             */
+            public ControlWithReceiver setAssetId(String id) {
+                this.put("asset_id", id);
+                return this;
+            }
+
+            /**
+             * Specifies the amount of the asset to be controlled.
+             * <p>
+             * <strong>Must be called.</strong>
+             * @param amount the number of units of the asset to be controlled
+             * @return this ControlWithReceiver object
+             */
+            public ControlWithReceiver setAmount(long amount) {
+                this.put("amount", amount);
+                return this;
+            }
+
+        }
+
+        /**
+         * Represents a retire action.
+         */
+        public static class Retire extends Action {
+            /**
+             *
+             */
+            private static final long serialVersionUID = -8434272436211832706L;
+
+            /**
+             * Default constructor defines the action type as "control_program"
+             */
+            public Retire() {
+                this.put("type", "retire");
+            }
+
+            /**
+             * Specifies the amount of the asset to be retired.<br>
+             * <strong>Must be called.</strong>
+             * @param amount number of units of the asset to be retired
+             * @return updated action object
+             */
+            public Retire setAmount(long amount) {
+                this.put("amount", amount);
+                return this;
+            }
+
+            /**
+             * Specifies the asset to be retired using its alias.<br>
+             * <strong>Either this or {@link Retire#setAssetId(String)} must be
+             * called.</strong>
+             * @param alias alias of the asset to be retired
+             * @return updated action object
+             */
+            public Retire setAssetAlias(String alias) {
+                this.put("asset_alias", alias);
+                return this;
+            }
+
+            /**
+             * Specifies the asset to be retired using its id.<br>
+             * <strong>Either this or {@link Retire#setAssetAlias(String)} must be
+             * called.</strong>
+             * @param id id of the asset to be retired
+             * @return updated action object
+             */
+            public Retire setAssetId(String id) {
+                this.put("asset_id", id);
+                return this;
+            }
+
+            public Retire setAccountAlias(String alias) {
+                this.put("account_alias", alias);
+                return this;
+            }
+
+            public Retire setAccountId(String id) {
+                this.put("account_id", id);
+                return this;
+            }
+
+        }
+
+        /**
+         * Sets a k,v parameter pair.
+         * @param key the key on the parameter object
+         * @param value the corresponding value
+         * @return updated action object
+         */
+        public Action setParameter(String key, Object value) {
+            this.put(key, value);
+            return this;
+        }
+    }
+
+    public static class Template {
+        /**
+         * A hex-encoded representation of a transaction template.
+         */
+        @SerializedName("raw_transaction")
+        public String rawTransaction;
+
+        /**
+         * The list of signing instructions for inputs in the transaction.
+         */
+        @SerializedName("signing_instructions")
+        public List<SigningInstruction> signingInstructions;
+
+        /**
+         * For core use only.
+         */
+        @SerializedName("local")
+        private boolean local;
+
+        /**
+         * False (the default) makes the transaction "final" when signing, preventing
+         * further changes - the signature program commits to the transaction's signature
+         * hash. True makes the transaction extensible, committing only to the elements in
+         * the transaction so far, permitting the addition of new elements.
+         */
+        @SerializedName("allow_additional_actions")
+        private boolean allowAdditionalActions;
+
+        /**
+         * allowAdditionalActions causes the transaction to be signed so that it can be
+         * used as a base transaction in a multiparty trade flow. To enable this setting,
+         * call this method after building the transaction, but before sending it to the
+         * signer.
+         *
+         * All participants in a multiparty trade flow should call this method except for
+         * the last signer. Do not call this option if the transaction is complete, i.e.
+         * if it will not be used as a base transaction.
+         * @return updated transaction template
+         */
+        public Template allowAdditionalActions() {
+            this.allowAdditionalActions = true;
+            return this;
+        }
+
+        /**
+         * A single signing instruction included in a transaction template.
+         */
+        public static class SigningInstruction {
+            /**
+             * The input's position in a transaction's list of inputs.
+             */
+            public int position;
+
+            /**
+             * A list of components used to coordinate the signing of an input.
+             */
+            @SerializedName("witness_components")
+            public WitnessComponent[] witnessComponents;
+        }
+
+        /**
+         * A single witness component, holding information that will become the input
+         * witness.
+         */
+        public static class WitnessComponent {
+            /**
+             * The type of witness component.<br>
+             * Possible types are "data" and "raw_tx_signature".
+             */
+            public String type;
+
+            /**
+             * Data to be included in the input witness (null unless type is "data").
+             */
+            public String value;
+
+            /**
+             * The number of signatures required for an input (null unless type is
+             * "signature").
+             */
+            public int quorum;
+
+            /**
+             * The list of keys to sign with (null unless type is "signature").
+             */
+            public KeyID[] keys;
+
+            /**
+             * The list of signatures made with the specified keys (null unless type is
+             * "signature").
+             */
+            public String[] signatures;
+        }
+
+        /**
+         * A class representing a derived signing key.
+         */
+        public static class KeyID {
+            /**
+             * The extended public key associated with the private key used to sign.
+             */
+            public String xpub;
+
+            /**
+             * The derivation path of the extended public key.
+             */
+            @SerializedName("derivation_path")
+            public String[] derivationPath;
+        }
+
+        /**
+         * Serializes the Address into a form that is safe to transfer over the wire.
+         *
+         * @return the JSON-serialized representation of the Receiver object
+         */
+        public String toJson() {
+            return Utils.serializer.toJson(this);
+        }
+
+    }
+
+    public static class SubmitResponse {
+        /**
+         * The transaction id.
+         */
+        public String tx_id;
+
+        public String toJson() {
+            return Utils.serializer.toJson(this);
+        }
+
+    }
+
+    /**
+     * Call submit-transaction api
+     *
+     * @param client
+     * @param template
+     * @return
+     * @throws BytomException
+     */
+    public static SubmitResponse submit(Client client, Template template)
+            throws BytomException {
+        HashMap<String, Object> body = new HashMap<>();
+        body.put("raw_transaction", template.rawTransaction);
+        return client.request("submit-transaction", body, SubmitResponse.class);
+    }
+
+    public static class TransactionGas {
+        /**
+         * total consumed neu(1BTM = 10^8NEU) for execute transaction.
+         */
+        @SerializedName("total_neu")
+        public int totalNeu;
+
+        /**
+         * consumed neu for storage transaction .
+         */
+        @SerializedName("storage_neu")
+        public int storageNeu;
+        /**
+         * consumed neu for execute VM.
+         */
+        @SerializedName("vm_neu")
+        public int vmNeu;
+    }
+
+    /**
+     * call estimate-transaction-gas api
+     *
+     * @param client
+     * @param template
+     * @return
+     * @throws BytomException
+     */
+    public static TransactionGas estimateGas(Client client, Template template)
+            throws BytomException {
+        HashMap<String, Object> body = new HashMap<>();
+        body.put("transaction_template", template);
+        return client.request("estimate-transaction-gas", body, TransactionGas.class);
+    }
+}
diff --git a/src/main/java/io/bytom/api/TransactionFeed.java b/src/main/java/io/bytom/api/TransactionFeed.java
new file mode 100644 (file)
index 0000000..db7eefb
--- /dev/null
@@ -0,0 +1,159 @@
+package io.bytom.api;
+
+import io.bytom.common.Utils;
+import io.bytom.exception.BytomException;
+import io.bytom.http.Client;
+import org.apache.log4j.Logger;
+
+import java.lang.reflect.Type;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class TransactionFeed {
+    /**
+     * name of the transaction feed.
+     */
+    public String alias;
+    /**
+     * filter, filter of the transaction feed.
+     */
+    public String filter;
+
+    /**
+     * param, param of the transaction feed.
+     */
+    public TransactionFeedParam param;
+
+    private static Logger logger = Logger.getLogger(Transaction.class);
+
+    /**
+     * Serializes the TransactionFeed into a form that is safe to transfer over the wire.
+     *
+     * @return the JSON-serialized representation of the TransactionFeed object
+     */
+    public String toJson() {
+        return Utils.serializer.toJson(this);
+    }
+
+    public static class Builder {
+
+        public String alias;
+
+        public String filter;
+
+        public Builder() {
+        }
+
+        public Builder setAlias(String alias) {
+            this.alias = alias;
+            return this;
+        }
+
+        public Builder setFilter(String filter) {
+            this.filter = filter;
+            return this;
+        }
+
+        /**
+         * Call create-transaction-feed api
+         *
+         * @param client
+         * @throws BytomException
+         */
+        public void create(Client client) throws BytomException {
+            client.request("create-transaction-feed", this);
+            logger.info("create-transaction-feed");
+        }
+    }
+
+    /**
+     * Call get-transaction-feed api
+     *
+     * @param client
+     * @param alias
+     * @return
+     * @throws BytomException
+     */
+    public static TransactionFeed get(Client client, String alias) throws BytomException {
+        Map<String, Object> req = new HashMap<String, Object>();
+        req.put("alias", alias);
+
+        // the return param contains txfeed.
+        TransactionFeed transactionFeed = client.requestGet("get-transaction-feed",
+                                                req, "txfeed", TransactionFeed.class);
+        logger.info("get-transaction-feed.");
+        logger.info(transactionFeed.toJson());
+
+        return transactionFeed;
+    }
+
+    /**
+     * Call update-transaction-feed api
+     *
+     * @param client
+     * @param alias
+     * @param filter
+     * @throws BytomException
+     */
+    public static void update(Client client, String alias, String filter) throws BytomException {
+        Map<String, Object> req = new HashMap<String, Object>();
+        req.put("alias", alias);
+        req.put("filter", filter);
+        client.request("update-transaction-feed", req);
+        logger.info("update-transaction-feed");
+    }
+
+    /**
+     * Call list-transaction-feeds api
+     * @param client
+     * @return
+     * @throws BytomException
+     */
+    public static List<TransactionFeed> list(Client client) throws BytomException {
+
+        Type listType = new ParameterizedTypeImpl(List.class, new Class[]{TransactionFeed.class});
+        List<TransactionFeed> transactionFeedList = client.request("list-transaction-feeds", null, listType);
+
+        logger.info("list-transaction-feeds:");
+        logger.info("size of transactionList:" + transactionFeedList.size());
+        logger.info(transactionFeedList.get(0).toJson());
+
+        return transactionFeedList;
+    }
+
+    /**
+     * Call delete-transaction-feed api
+     *
+     * @param client
+     * @param alias
+     * @throws BytomException
+     */
+    public static void delete(Client client, String alias) throws BytomException {
+        Map<String, Object> req = new HashMap<String, Object>();
+        req.put("alias", alias);
+        client.request("delete-transaction-feed", req);
+        logger.info("delete-transaction-feed");
+    }
+
+
+
+    public class TransactionFeedParam {
+
+        /**
+         * assetid
+         */
+        public String assetid;
+
+        /**
+         * lowerlimit
+         */
+        public long lowerlimit;
+
+        /**
+         * upperlimit
+         */
+        public long upperlimit;
+
+    }
+}
diff --git a/src/main/java/io/bytom/api/UnconfirmedTransaction.java b/src/main/java/io/bytom/api/UnconfirmedTransaction.java
new file mode 100644 (file)
index 0000000..a5db29f
--- /dev/null
@@ -0,0 +1,197 @@
+package io.bytom.api;
+
+import com.google.gson.annotations.SerializedName;
+import io.bytom.common.Utils;
+import io.bytom.exception.BytomException;
+import io.bytom.http.Client;
+import org.apache.log4j.Logger;
+
+import java.lang.reflect.Type;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class UnconfirmedTransaction {
+    /**
+     * Unique identifier, or transaction hash, of a transaction.
+     */
+    private String id;
+
+    /**
+     * version
+     */
+    private Integer version;
+
+    /**
+     * size
+     */
+    private Integer size;
+    /**
+     * time_range
+     */
+    @SerializedName("time_range")
+    private Integer timeRange;
+
+    /**
+     * status
+     */
+    @SerializedName("status_fail")
+    private boolean statusFail;
+
+    /**
+     * List of specified inputs for a transaction.
+     */
+    private List<AnnotatedInput> inputs;
+
+    /**
+     * List of specified outputs for a transaction.
+     */
+    private List<AnnotatedOutput> outputs;
+
+    private static Logger logger = Logger.getLogger(UnconfirmedTransaction.class);
+
+    /**
+     * Serializes the UnconfirmedTransaction into a form that is safe to transfer over the wire.
+     *
+     * @return the JSON-serialized representation of the UnconfirmedTransaction object
+     */
+    public String toJson() {
+        return Utils.serializer.toJson(this);
+    }
+
+    /**
+     * Call get-unconfirmed-transaction api
+     *
+     * @param client
+     * @param txId
+     * @return
+     * @throws BytomException
+     */
+    public static UnconfirmedTransaction get(Client client, String txId) throws BytomException {
+        Map<String, Object> req = new HashMap<String, Object>();
+        req.put("tx_id", txId);
+        UnconfirmedTransaction UCTX = client.request("get-unconfirmed-transaction",
+                req, UnconfirmedTransaction.class);
+
+        logger.info("get-unconfirmed-transaction:");
+        logger.info(UCTX.toJson());
+
+        return UCTX;
+    }
+
+    public static UTXResponse list(Client client) throws BytomException {
+        UTXResponse utxResponse =
+                client.request("list-unconfirmed-transactions", null, UTXResponse.class);
+
+        logger.info("list-unconfirmed-transactions:");
+        logger.info(utxResponse.toJson());
+
+        return utxResponse;
+    }
+
+    public static class UTXResponse {
+
+        @SerializedName("total")
+        public Integer total;
+
+        @SerializedName("tx_ids")
+        public List<String> txIds;
+
+        public String toJson() {
+            return Utils.serializer.toJson(this);
+        }
+    }
+
+    public static class AnnotatedInput {
+
+        /**
+         * address
+         */
+        private String address;
+
+        /**
+         * The number of units of the asset being issued or spent.
+         */
+        private Integer amount;
+
+        /**
+         * The definition of the asset being issued or spent (possibly null).
+         */
+        @SerializedName("asset_definition")
+        private Map<String, Object> assetDefinition;
+
+        /**
+         * The id of the asset being issued or spent.
+         */
+        @SerializedName("asset_id")
+        private String assetId;
+
+        /**
+         * The control program which must be satisfied to transfer this output.
+         */
+        @SerializedName("control_program")
+        private String controlProgram;
+
+        /**
+         * The id of the output consumed by this input. Null if the input is an
+         * issuance.
+         */
+        @SerializedName("spent_output_id")
+        private String spentOutputId;
+
+        /**
+         * The type of the input.<br>
+         * Possible values are "issue" and "spend".
+         */
+        private String type;
+    }
+
+    public static class AnnotatedOutput {
+
+        /**
+         * address
+         */
+        private String address;
+
+        /**
+         * The number of units of the asset being controlled.
+         */
+        private Integer amount;
+
+        /**
+         * The definition of the asset being controlled (possibly null).
+         */
+        @SerializedName("asset_definition")
+        private Map<String, Object> assetDefinition;
+
+        /**
+         * The id of the asset being controlled.
+         */
+        @SerializedName("asset_id")
+        public String assetId;
+
+        /**
+         * The control program which must be satisfied to transfer this output.
+         */
+        @SerializedName("control_program")
+        private String controlProgram;
+
+        /**
+         * The id of the output.
+         */
+        @SerializedName("id")
+        private String id;
+
+        /**
+         * The output's position in a transaction's list of outputs.
+         */
+        private Integer position;
+
+        /**
+         * The type the output.<br>
+         * Possible values are "control" and "retire".
+         */
+        private String type;
+
+    }
+}
diff --git a/src/main/java/io/bytom/api/UnspentOutput.java b/src/main/java/io/bytom/api/UnspentOutput.java
new file mode 100644 (file)
index 0000000..df595c5
--- /dev/null
@@ -0,0 +1,130 @@
+package io.bytom.api;
+
+import com.google.gson.annotations.SerializedName;
+import io.bytom.common.Utils;
+import io.bytom.exception.BytomException;
+import io.bytom.http.Client;
+import org.apache.log4j.Logger;
+
+import java.lang.reflect.Type;
+import java.util.List;
+
+public class UnspentOutput {
+    /**
+     * The id of the account controlling this output (possibly null if a control program
+     * is specified).
+     */
+    @SerializedName("account_id")
+    public String accountId;
+
+    /**
+     * The alias of the account controlling this output (possibly null if a control
+     * program is specified).
+     */
+    @SerializedName("account_alias")
+    public String accountAlias;
+
+    /**
+     * The id of the asset being controlled.
+     */
+    @SerializedName("asset_id")
+    public String assetId;
+
+    /**
+     * The alias of the asset being controlled.
+     */
+    @SerializedName("asset_alias")
+    public String assetAlias;
+
+    /**
+     * The number of units of the asset being controlled.
+     */
+    public long amount;
+
+    /**
+     * address of account
+     */
+    public String address;
+
+    /**
+     * whether the account address is change
+     */
+    public boolean change;
+
+    /**
+     * The ID of the output.
+     */
+    @SerializedName("id")
+    public String id;
+
+    /**
+     * The control program which must be satisfied to transfer this output.
+     */
+    @SerializedName("program")
+    public String program;
+
+    @SerializedName("control_program_index")
+    public String controlProgramIndex;
+
+    /**
+     * source unspent output id
+     */
+    @SerializedName("source_id")
+    public String sourceId;
+
+    /**
+     * position of source unspent output id in block
+     */
+    @SerializedName("source_pos")
+    public int sourcePos;
+
+    /**
+     * The definition of the asset being controlled (possibly null).
+     */
+    @SerializedName("valid_height")
+    public int validHeight;
+
+    private static Logger logger = Logger.getLogger(UnspentOutput.class);
+
+    /**
+     * Serializes the Address into a form that is safe to transfer over the wire.
+     *
+     * @return the JSON-serialized representation of the Receiver object
+     */
+    public String toJson() {
+        return Utils.serializer.toJson(this);
+    }
+
+    public static class QueryBuilder {
+
+        /**
+         * id of unspent output.
+         */
+        public String id;
+
+        public QueryBuilder setId(String id) {
+            this.id = id;
+            return this;
+        }
+
+        /**
+         * call list-unspent-outputs api
+         *
+         * @param client client object that makes requests to the core
+         * @return
+         * @throws BytomException BytomException
+         */
+        public List<UnspentOutput> list(Client client) throws BytomException {
+
+            // TODO: 2018/5/21 need to tx and test
+            Type listType = new ParameterizedTypeImpl(List.class, new Class[]{UnspentOutput.class});
+            List<UnspentOutput> unspentOutputList = client.request("list-unspent-outputs", this, listType);
+            logger.info("list-unspent-outputs:");
+            logger.info("size of unspentOutputList:" + unspentOutputList.size());
+            logger.info(unspentOutputList.get(0).toJson());
+
+            return unspentOutputList;
+        }
+
+    }
+}
diff --git a/src/main/java/io/bytom/api/Wallet.java b/src/main/java/io/bytom/api/Wallet.java
new file mode 100644 (file)
index 0000000..5145757
--- /dev/null
@@ -0,0 +1,153 @@
+package io.bytom.api;
+
+import com.google.gson.annotations.SerializedName;
+import io.bytom.common.Utils;
+import io.bytom.exception.BytomException;
+import io.bytom.http.Client;
+import org.apache.log4j.Logger;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * <h1>Wallet Class</h1>
+ */
+public class Wallet {
+
+    @SerializedName("account_image")
+    public AccountImage accountImage;
+
+    @SerializedName("asset_image")
+    public AssetImage assetImage;
+
+    @SerializedName("key_images")
+    public KeyImages keyImages;
+
+    private static Logger logger = Logger.getLogger(Wallet.class);
+
+    /**
+     * Serializes the Address into a form that is safe to transfer over the wire.
+     *
+     * @return the JSON-serialized representation of the Receiver object
+     */
+    public String toJson() {
+        return Utils.serializer.toJson(this);
+    }
+
+    /**
+     * Call backup-wallet api
+     *
+     * @param client
+     * @return
+     * @throws BytomException
+     */
+    public static Wallet backupWallet(Client client) throws BytomException {
+        Wallet wallet = client.request("backup-wallet", null, Wallet.class);
+
+        logger.info("backup-wallet:");
+        logger.info(wallet.toJson());
+
+        return wallet;
+    }
+
+    /**
+     * Call restore-wallet api
+     *
+     * @param client
+     * @param accountImage
+     * @param assetImage
+     * @param keyImages
+     * @throws BytomException
+     */
+    public static void restoreWallet(Client client ,Object accountImage, Object assetImage , Object keyImages) throws BytomException{
+        Map<String, Object> body = new HashMap<String, Object>();
+        body.put("account_image", accountImage);
+        body.put("asset_image", assetImage);
+        body.put("key_images", keyImages);
+
+        logger.info("restore-wallet:");
+        logger.info(body.toString());
+
+        client.request("restore-wallet", body);
+    }
+
+    public static class AccountImage {
+
+        public Slices[] slices;
+
+        public static class Slices {
+
+            @SerializedName("contract_index")
+            public int contractIndex;
+
+            public Account account;
+
+            public static class Account {
+
+                public String type;
+
+                public List<String> xpubs;
+
+                public int quorum;
+
+                @SerializedName("key_index")
+                public int keyIndex;
+
+                public String id;
+
+                public String alias;
+
+            }
+
+        }
+    }
+
+    public static class AssetImage {
+
+        public Assets[] assets;
+
+        public static class Assets {
+            public String type;
+            public List<String> xpubs;
+            public int quorum;
+            public String id;
+            public String alias;
+            public Map<String, Object> definition;
+            @SerializedName("key_index")
+            public int keyIndex;
+            @SerializedName("vm_version")
+            public int vmVersion;
+            @SerializedName("asset_image")
+            public String issueProgram;
+            @SerializedName("raw_definition_byte")
+            public String rawDefinitionByte;
+        }
+    }
+
+    public static class KeyImages {
+
+        public Xkeys[] xkeys;
+
+        public static class Xkeys {
+
+            public Crypto crypto;
+            public String id;
+            public String type;
+            public int version;
+            public String alias;
+            public String xpub;
+
+            public static class Crypto {
+                public String cipher;
+                public String ciphertext;
+                public Map<String, Object> cipherparams;
+                public String kdf;
+                public Map<String, Object> kdfparams;
+                public String mac;
+            }
+
+        }
+    }
+
+}
diff --git a/src/main/java/io/bytom/common/Configuration.java b/src/main/java/io/bytom/common/Configuration.java
new file mode 100644 (file)
index 0000000..21dd949
--- /dev/null
@@ -0,0 +1,29 @@
+package io.bytom.common;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Properties;
+
+public class Configuration {
+
+    private static Properties props = new Properties();
+    static {
+        try {
+            props.load(Thread.currentThread().getContextClassLoader()
+                    .getResourceAsStream("config.properties"));
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public static String getValue(String key) {
+        return props.getProperty(key);
+    }
+
+    public static void updateProperties(String key, String value) {
+        props.setProperty(key, value);
+    }
+
+}
diff --git a/src/main/java/io/bytom/common/Utils.java b/src/main/java/io/bytom/common/Utils.java
new file mode 100644 (file)
index 0000000..7a7f69e
--- /dev/null
@@ -0,0 +1,9 @@
+package io.bytom.common;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+public class Utils {
+  public static String rfc3339DateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";
+  public static final Gson serializer = new GsonBuilder().setDateFormat(rfc3339DateFormat).create();
+}
diff --git a/src/main/java/io/bytom/exception/APIException.java b/src/main/java/io/bytom/exception/APIException.java
new file mode 100644 (file)
index 0000000..52ff64d
--- /dev/null
@@ -0,0 +1,169 @@
+package io.bytom.exception;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * APIException wraps errors returned by the API.
+ * Each error contains a brief description in addition to a unique error code.<br>
+ * The error code can be used by Chain Support to diagnose the exact cause of the error.
+ * The mapping of error codes to messages is as follows:<br><br>
+ *
+ * <h2>General errors</h2>
+ * CH001 - Request timed out
+ * CH002 - Not found
+ * CH003 - Invalid request body
+ * CH004 - Invalid request header
+ * CH006 - Not found
+ *
+ * <h2>Account/Asset errors</h2>
+ * CH200 - Quorum must be greater than 1 and less than or equal to the length of xpubs
+ * CH201 - Invalid xpub format
+ * CH202 - At least one xpub is required
+ * CH203 - Retrieved type does not match expected type
+ *
+ * <h2>Access token errors</h2>
+ * CH300 - Malformed or empty access token id
+ * CH301 - Access tokens must be type client or network
+ * CH302 - Access token id is already in use
+ * CH310 - The access token used to authenticate this request cannot be deleted
+ *
+ * <h2>Query errors</h2>
+ * CH600 - Malformed pagination parameter `after`
+ * CH601 - Incorrect number of parameters to filter
+ * CH602 - Malformed query filter
+ *
+ * <h2>Transaction errors</h2>
+ * CH700 - Reference data does not match previous transaction's reference data<br>
+ * CH701 - Invalid action type<br>
+ * CH702 - Invalid alias on action<br>
+ * CH730 - Missing raw transaction<br>
+ * CH731 - Too many signing instructions in template for transaction<br>
+ * CH732 - Invalid transaction input index<br>
+ * CH733 - Invalid signature script component<br>
+ * CH734 - Missing signature in template<br>
+ * CH735 - Transaction rejected<br>
+ * CH760 - Insufficient funds for tx<br>
+ * CH761 - Some outputs are reserved; try again<br>
+ */
+public class APIException extends BytomException {
+  /**
+   * Unique identifier for the error.
+   */
+  public String code;
+
+  /**
+   * Message describing the general nature of the error.
+   */
+  @SerializedName("message")
+  public String chainMessage;
+
+  /**
+   * Additional information about the error (possibly null).
+   */
+  public String detail;
+
+  /**
+   * Specifies whether the error is temporary, or a change to the request is necessary.
+   */
+  public boolean temporary;
+
+  /**
+   * Unique identifier of the request to the server.
+   */
+  public String requestId;
+
+  /**
+   * HTTP status code returned by the server.
+   */
+  public int statusCode;
+
+  /**
+   * Initializes exception with its message and requestId attributes.
+   * @param message error message
+   * @param requestId unique identifier of the request
+   */
+  public APIException(String message, String requestId) {
+    super(message);
+    this.requestId = requestId;
+  }
+
+  /**
+   * Intitializes exception with its code, message, detail &amp; temporary field set.
+   * @param code error code
+   * @param message error message
+   * @param detail additional error information
+   * @param temporary unique identifier of the request
+   */
+  public APIException(String code, String message, String detail, boolean temporary) {
+    super(message);
+    this.chainMessage = message;
+    this.code = code;
+    this.detail = detail;
+    this.temporary = temporary;
+  }
+
+  /**
+   * Initializes exception with its code, message, detail &amp; requestId attributes.
+   * @param code error code
+   * @param message error message
+   * @param detail additional error information
+   * @param requestId unique identifier of the request
+   */
+  public APIException(String code, String message, String detail, String requestId) {
+    super(message);
+    this.chainMessage = message;
+    this.code = code;
+    this.detail = detail;
+    this.requestId = requestId;
+  }
+
+  /**
+   * Initializes exception with all of its attributes.
+   * @param code error code
+   * @param message error message
+   * @param detail additional error information
+   * @param temporary specifies if the error is temporary
+   * @param requestId unique identifier of the request
+   * @param statusCode HTTP status code
+   */
+  public APIException(
+      String code,
+      String message,
+      String detail,
+      boolean temporary,
+      String requestId,
+      int statusCode) {
+    super(message);
+    this.chainMessage = message;
+    this.code = code;
+    this.detail = detail;
+    this.temporary = temporary;
+    this.requestId = requestId;
+    this.statusCode = statusCode;
+  }
+
+  /**
+   * Constructs a detailed message of the error.
+   * @return detailed error message
+   */
+  @Override
+  public String getMessage() {
+    String s = "";
+
+    if (this.code != null && this.code.length() > 0) {
+      s += "Code: " + this.code + " ";
+    }
+
+    s += "Message: " + this.chainMessage;
+
+    if (this.detail != null && this.detail.length() > 0) {
+      s += " Detail: " + this.detail;
+    }
+
+    if (this.requestId != null) {
+      s += " Request-ID: " + this.requestId;
+    }
+
+    return s;
+  }
+}
diff --git a/src/main/java/io/bytom/exception/BadURLException.java b/src/main/java/io/bytom/exception/BadURLException.java
new file mode 100644 (file)
index 0000000..dd16377
--- /dev/null
@@ -0,0 +1,14 @@
+package io.bytom.exception;
+
+/**
+ * BadURLException wraps errors due to malformed URLs.
+ */
+public class BadURLException extends BytomException {
+  /**
+   * Initializes exception with its message attribute.
+   * @param message error message
+   */
+  public BadURLException(String message) {
+    super(message);
+  }
+}
diff --git a/src/main/java/io/bytom/exception/BuildException.java b/src/main/java/io/bytom/exception/BuildException.java
new file mode 100644 (file)
index 0000000..19a2f89
--- /dev/null
@@ -0,0 +1,49 @@
+package io.bytom.exception;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.util.List;
+
+/**
+ * BuildException wraps errors returned by the build-transaction endpoint.
+ */
+public class BuildException extends APIException {
+
+  public BuildException(String message, String requestId) {
+    super(message, requestId);
+  }
+
+  public static class ActionError extends APIException {
+
+    public static class Data {
+      /**
+       * The index of the action that caused this error.
+       */
+      @SerializedName("index")
+      public Integer index;
+    }
+
+    public ActionError(String message, String requestId) {
+      super(message, requestId);
+    }
+
+    /**
+     * Additional data pertaining to the error.
+     */
+    public Data data;
+  }
+
+  public static class Data {
+    /**
+     * A list of errors resulting from building actions.
+     */
+    @SerializedName("actions")
+    public List<ActionError> actionErrors;
+  }
+
+  /**
+   * Extra data associated with this error, if any.
+   */
+  @SerializedName("data")
+  public Data data;
+}
diff --git a/src/main/java/io/bytom/exception/BytomException.java b/src/main/java/io/bytom/exception/BytomException.java
new file mode 100644 (file)
index 0000000..4043065
--- /dev/null
@@ -0,0 +1,32 @@
+package io.bytom.exception;
+
+/**
+ * Base exception class for the Chain Core SDK.
+ */
+public class BytomException extends Exception {
+    /**
+     * Default constructor.
+     */
+    public BytomException() {
+        super();
+    }
+
+    /**
+     * Initializes exception with its message attribute.
+     *
+     * @param message error message
+     */
+    public BytomException(String message) {
+        super(message);
+    }
+
+    /**
+     * Initializes a new exception while storing the original cause.
+     *
+     * @param message the error message
+     * @param cause   the cause of the exception
+     */
+    public BytomException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/src/main/java/io/bytom/exception/ConfigurationException.java b/src/main/java/io/bytom/exception/ConfigurationException.java
new file mode 100644 (file)
index 0000000..695e873
--- /dev/null
@@ -0,0 +1,23 @@
+package io.bytom.exception;
+
+/**
+ * ConfigurationException wraps errors during client configuration.
+ */
+public class ConfigurationException extends BytomException {
+  /**
+   * Initializes exception with its message attribute.
+   * @param message error message
+   */
+  public ConfigurationException(String message) {
+    super(message);
+  }
+
+  /**
+   * Initializes new exception while storing original cause.
+   * @param message the error message
+   * @param cause the original cause
+   */
+  public ConfigurationException(String message, Throwable cause) {
+    super(message, cause);
+  }
+}
diff --git a/src/main/java/io/bytom/exception/ConnectivityException.java b/src/main/java/io/bytom/exception/ConnectivityException.java
new file mode 100644 (file)
index 0000000..4f50197
--- /dev/null
@@ -0,0 +1,36 @@
+package io.bytom.exception;
+
+import com.squareup.okhttp.Response;
+
+import java.io.IOException;
+
+/**
+ * ConnectivityException wraps errors due to connectivity issues with the server.
+ */
+public class ConnectivityException extends BytomException {
+  /**
+   * Initializes exception with its message attribute.
+   * @param resp the server response used to create error message
+   */
+  public ConnectivityException(Response resp) {
+    super(formatMessage(resp));
+  }
+
+  /**
+   * Parses the the server response into a detailed error message.
+   * @param resp the server response
+   * @return detailed error message
+   */
+  private static String formatMessage(Response resp) {
+    String s =
+        "Response HTTP header field Chain-Request-ID is unset. There may be network issues. Please check your local network settings.";
+    // TODO(kr): include client-generated reqid here once we have that.
+    String body;
+    try {
+      body = resp.body().string();
+    } catch (IOException ex) {
+      body = "[unable to read response body: " + ex.toString() + "]";
+    }
+    return String.format("%s status=%d body=%s", s, resp.code(), body);
+  }
+}
diff --git a/src/main/java/io/bytom/exception/HTTPException.java b/src/main/java/io/bytom/exception/HTTPException.java
new file mode 100644 (file)
index 0000000..04646bd
--- /dev/null
@@ -0,0 +1,23 @@
+package io.bytom.exception;
+
+/**
+ * HTTPException wraps generic HTTP errors.
+ */
+public class HTTPException extends BytomException {
+  /**
+   * Initializes exception with its message attribute.
+   * @param message error message
+   */
+  public HTTPException(String message) {
+    super(message);
+  }
+
+  /**
+   * Initializes new exception while storing original cause.
+   * @param message the error message
+   * @param cause the original cause
+   */
+  public HTTPException(String message, Throwable cause) {
+    super(message, cause);
+  }
+}
diff --git a/src/main/java/io/bytom/exception/JSONException.java b/src/main/java/io/bytom/exception/JSONException.java
new file mode 100644 (file)
index 0000000..a81cef6
--- /dev/null
@@ -0,0 +1,39 @@
+package io.bytom.exception;
+
+/**
+ * JSONException wraps errors due to marshaling/unmarshaling json payloads.
+ */
+public class JSONException extends BytomException {
+
+    /**
+     * Unique indentifier of the request to the server.
+     */
+    public String requestId;
+
+    /**
+     * Default constructor.
+     */
+    public JSONException(String message) {
+        super(message);
+    }
+
+    /**
+     * Initializes exception with its message and requestId attributes.
+     * Use this constructor in context of an API call.
+     *
+     * @param message   error message
+     * @param requestId unique identifier of the request
+     */
+    public JSONException(String message, String requestId) {
+        super(message);
+        this.requestId = requestId;
+    }
+
+    public String getMessage() {
+        String message = "Message: " + super.getMessage();
+        if (requestId != null) {
+            message += " Request-ID: " + requestId;
+        }
+        return message;
+    }
+}
diff --git a/src/main/java/io/bytom/http/BatchResponse.java b/src/main/java/io/bytom/http/BatchResponse.java
new file mode 100644 (file)
index 0000000..da29b75
--- /dev/null
@@ -0,0 +1,140 @@
+package io.bytom.http;
+
+import io.bytom.exception.*;
+import com.google.gson.Gson;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
+import com.squareup.okhttp.Response;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.util.*;
+
+/**
+ * BatchResponse provides a convenient interface for handling the results of
+ * batched API calls. The response contains one success or error per outgoing
+ * request item in the batch. Errors are always of type APIExcpetion.
+ */
+public class BatchResponse<T> {
+  private Response response;
+  private Map<Integer, T> successesByIndex = new LinkedHashMap<>();
+  private Map<Integer, APIException> errorsByIndex = new LinkedHashMap<>();
+
+  /**
+   * This constructor is used when deserializing a response from an API call.
+   */
+  public BatchResponse(Response response, Gson serializer, Type tClass, Type eClass)
+      throws BytomException, IOException {
+    this.response = response;
+
+    try {
+      JsonArray root = new JsonParser().parse(response.body().charStream()).getAsJsonArray();
+      for (int i = 0; i < root.size(); i++) {
+        JsonElement elem = root.get(i);
+
+        // Test for interleaved errors
+        APIException err = serializer.fromJson(elem, eClass);
+        if (err.code != null) {
+          errorsByIndex.put(i, err);
+          continue;
+        }
+
+        successesByIndex.put(i, (T) serializer.fromJson(elem, tClass));
+      }
+    } catch (IllegalStateException e) {
+      throw new JSONException(
+          "Unable to read body: " + e.getMessage(), response.headers().get("Chain-Request-ID"));
+    }
+  }
+
+  /**
+   * This constructor is used for synthetically generating a batch response
+   * object from a map of successes and a map of errors. It ensures that
+   * the successes and errors are stored in an order-preserving fashion.
+   */
+  public BatchResponse(Map<Integer, T> successes, Map<Integer, APIException> errors) {
+    List<Integer> successIndexes = new ArrayList<>();
+    Iterator<Integer> successIter = successes.keySet().iterator();
+    while (successIter.hasNext()) successIndexes.add(successIter.next());
+    Collections.sort(successIndexes);
+    for (int i : successIndexes) successesByIndex.put(i, successes.get(i));
+
+    List<Integer> errorIndexes = new ArrayList<>();
+    Iterator<Integer> errorIter = errors.keySet().iterator();
+    while (errorIter.hasNext()) errorIndexes.add(errorIter.next());
+    Collections.sort(errorIndexes);
+    for (int i : errorIndexes) errorsByIndex.put(i, errors.get(i));
+  }
+
+  /**
+   * Returns the internal response object.
+   */
+  public Response response() {
+    return response;
+  }
+
+  /**
+   * Returns the total number of response objects. This should equal the number
+   * of request objects in the batch.
+   */
+  public int size() {
+    return successesByIndex.size() + errorsByIndex.size();
+  }
+
+  /**
+   * Returns whether the request object at the given index produced a success.
+   * @param index the index of the request object
+   */
+  public boolean isSuccess(int index) {
+    return successesByIndex.containsKey(index);
+  }
+
+  /**
+   * Returns whether the request object at the given index produced an error.
+   * @param index the index of the request object
+   */
+  public boolean isError(int index) {
+    return errorsByIndex.containsKey(index);
+  }
+
+  /**
+   * Returns a list of successful response objects in the batch. The order of
+   * the list corresponds to the order of the request objects that produced the
+   * successes.
+   */
+  public List<T> successes() {
+    List<T> res = new ArrayList<>();
+    res.addAll(successesByIndex.values());
+    return res;
+  }
+
+  /**
+   * Returns a list of error objects in the batch. The order of the list
+   * corresponds to the order of the request objects that produced the
+   * errors.
+   */
+  public List<APIException> errors() {
+    List<APIException> res = new ArrayList<>();
+    res.addAll(errorsByIndex.values());
+    return res;
+  }
+
+  /**
+   * Returns a map of success responses, keyed by the index of the request
+   * object that produced the success. The set of this map's keys is mutually
+   * exclusive of the keys returned by errorsByIndex.
+   */
+  public Map<Integer, T> successesByIndex() {
+    return successesByIndex;
+  }
+
+  /**
+   * Returns a map of error responses, keyed by the index of the request
+   * object that produced the error. The set of this map's keys is mutually
+   * exclusive of the keys returned by successByIndex.
+   */
+  public Map<Integer, APIException> errorsByIndex() {
+    return errorsByIndex;
+  }
+}
diff --git a/src/main/java/io/bytom/http/Client.java b/src/main/java/io/bytom/http/Client.java
new file mode 100644 (file)
index 0000000..fc84d02
--- /dev/null
@@ -0,0 +1,788 @@
+package io.bytom.http;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
+import io.bytom.common.*;
+import io.bytom.exception.*;
+import com.google.gson.Gson;
+import com.squareup.okhttp.*;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.openssl.PEMKeyPair;
+import org.bouncycastle.openssl.PEMParser;
+
+import javax.net.ssl.*;
+import java.io.*;
+import java.lang.reflect.Type;
+import java.net.*;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.KeyStore;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.KeySpec;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * The Client object contains all information necessary to
+ * perform an HTTP request against a remote API. Typically,
+ * an application will have a client that makes requests to
+ * a Chain Core, and a separate Client that makes requests
+ * to an HSM server.
+ */
+public class Client {
+    private String url;
+    private AtomicInteger urlIndex;
+    private String accessToken;
+    private OkHttpClient httpClient;
+
+    // Used to create empty, in-memory key stores.
+    private static final char[] DEFAULT_KEYSTORE_PASSWORD = "password".toCharArray();
+    private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
+    private static String version = "dev"; // updated in the static initializer
+
+    private static class BuildProperties {
+        public String version;
+    }
+
+    static {
+        InputStream in = Client.class.getClassLoader().getResourceAsStream("properties.json");
+        if (in != null) {
+            InputStreamReader inr = new InputStreamReader(in);
+            version = Utils.serializer.fromJson(inr, BuildProperties.class).version;
+        }
+    }
+
+    public Client(Builder builder) throws ConfigurationException {
+        this.urlIndex = new AtomicInteger(0);
+        this.url = builder.url;
+        this.accessToken = builder.accessToken;
+        this.httpClient = buildHttpClient(builder);
+    }
+
+    /**
+     * Create a new http Client object using the default development host URL.
+     */
+    public Client() throws BytomException {
+        this(new Builder());
+    }
+
+    /**
+     * Create a new http Client object
+     *
+     * @param url the URL of the Chain Core or HSM
+     */
+    public Client(String url) throws BytomException {
+        this(new Builder().setUrl(url));
+    }
+
+    /**
+     * Create a new http Client object
+     *
+     * @param url         the URL of the Chain Core or HSM
+     * @param accessToken a Client API access token
+     */
+    public Client(String url, String accessToken) throws BytomException {
+        this(new Builder().setUrl(url).setAccessToken(accessToken));
+    }
+
+    /**
+     * Perform a single HTTP POST request against the API for a specific action.
+     *
+     * @param action The requested API action
+     * @param body   Body payload sent to the API as JSON
+     * @param tClass Type of object to be deserialized from the response JSON
+     * @return the result of the post request
+     * @throws BytomException
+     */
+    public <T> T request(String action, Object body, final Type tClass) throws BytomException {
+        ResponseCreator<T> rc =
+                new ResponseCreator<T>() {
+                    public T create(Response response, Gson deserializer) throws IOException, BytomException {
+                        JsonElement root = new JsonParser().parse(response.body().charStream());
+                        JsonElement status = root.getAsJsonObject().get("status");
+                        JsonElement data = root.getAsJsonObject().get("data");
+                        if (status != null && status.toString().contains("fail")) {
+                            throw new BytomException(root.getAsJsonObject().get("msg").toString());
+                        } else if (data != null) {
+                            return deserializer.fromJson(data, tClass);
+                        } else {
+                            return deserializer.fromJson(response.body().charStream(), tClass);
+                        }
+                    }
+                };
+        return post(action, body, rc);
+    }
+
+    /**
+     * Perform a single HTTP POST request against the API for a specific action,
+     * ignoring the body of the response.
+     *
+     * @param action The requested API action
+     * @param body   Body payload sent to the API as JSON
+     * @throws BytomException
+     */
+    public void request(String action, Object body) throws BytomException {
+        ResponseCreator<Object> rc =
+                new ResponseCreator<Object>() {
+                    public Object create(Response response, Gson deserializer) throws IOException, BytomException {
+                        JsonElement root = new JsonParser().parse(response.body().charStream());
+                        JsonElement status = root.getAsJsonObject().get("status");
+                        JsonElement data = root.getAsJsonObject().get("data");
+                        if (status != null && status.toString().contains("fail")) {
+                            throw new BytomException(root.getAsJsonObject().get("msg").toString());
+                        }
+                        return null;
+                    }
+                };
+        post(action, body, rc);
+    }
+
+    /**
+     * return the value of named as key from json
+     *
+     * @param action
+     * @param body
+     * @param key
+     * @param tClass
+     * @param <T>
+     * @return
+     * @throws BytomException
+     */
+    public <T> T requestGet(String action, Object body, final String key, final Type tClass)
+            throws BytomException {
+        ResponseCreator<T> rc = new ResponseCreator<T>() {
+            public T create(Response response, Gson deserializer) throws IOException,
+                    BytomException {
+                JsonElement root = new JsonParser().parse(response.body().charStream());
+                JsonElement status = root.getAsJsonObject().get("status");
+                JsonElement data = root.getAsJsonObject().get("data");
+
+                if (status != null && status.toString().contains("fail"))
+                    throw new BytomException(root.getAsJsonObject().get("msg").toString());
+                else if (data != null)
+                    return deserializer.fromJson(data.getAsJsonObject().get(key), tClass);
+                else
+                    return deserializer.fromJson(response.body().charStream(), tClass);
+            }
+        };
+        return post(action, body, rc);
+    }
+
+    /**
+     * Perform a single HTTP POST request against the API for a specific action.
+     * Use this method if you want batch semantics, i.e., the endpoint response
+     * is an array of valid objects interleaved with arrays, once corresponding to
+     * each input object.
+     *
+     * @param action The requested API action
+     * @param body   Body payload sent to the API as JSON
+     * @param tClass Type of object to be deserialized from the response JSON
+     * @param eClass Type of error object to be deserialized from the response JSON
+     * @return the result of the post request
+     * @throws BytomException
+     */
+    public <T> BatchResponse<T> batchRequest(
+            String action, Object body, final Type tClass, final Type eClass) throws BytomException {
+        ResponseCreator<BatchResponse<T>> rc =
+                new ResponseCreator<BatchResponse<T>>() {
+                    public BatchResponse<T> create(Response response, Gson deserializer)
+                            throws BytomException, IOException {
+                        return new BatchResponse<>(response, deserializer, tClass, eClass);
+                    }
+                };
+        return post(action, body, rc);
+    }
+
+    /**
+     * Perform a single HTTP POST request against the API for a specific action.
+     * Use this method if you want single-item semantics (creating single assets,
+     * building single transactions) but the API endpoint is implemented as a
+     * batch call.
+     * <p>
+     * Because request bodies for batch calls do not share a consistent format,
+     * this method does not perform any automatic arrayification of outgoing
+     * parameters. Remember to arrayify your request objects where appropriate.
+     *
+     * @param action The requested API action
+     * @param body   Body payload sent to the API as JSON
+     * @param tClass Type of object to be deserialized from the response JSON
+     * @return the result of the post request
+     * @throws BytomException
+     */
+    public <T> T singletonBatchRequest(
+            String action, Object body, final Type tClass, final Type eClass) throws BytomException {
+        ResponseCreator<T> rc =
+                new ResponseCreator<T>() {
+                    public T create(Response response, Gson deserializer) throws BytomException, IOException {
+                        BatchResponse<T> batch = new BatchResponse<>(response, deserializer, tClass, eClass);
+
+                        List<APIException> errors = batch.errors();
+                        if (errors.size() == 1) {
+                            // This throw must occur within this lambda in order for APIClient's
+                            // retry logic to take effect.
+                            throw errors.get(0);
+                        }
+
+                        List<T> successes = batch.successes();
+                        if (successes.size() == 1) {
+                            return successes.get(0);
+                        }
+
+                        // We should never get here, unless there is a bug in either the SDK or
+                        // API code, causing a non-singleton response.
+                        /*
+                        throw new BytomException(
+                                "Invalid singleton response, request ID "
+                                        + batch.response().headers().get("Bytom-Request-ID"));
+                        */
+                        throw new BytomException("Invalid singleton response.");
+                    }
+                };
+        return post(action, body, rc);
+    }
+
+    /**
+     * Returns true if a client access token stored in the client.
+     *
+     * @return a boolean
+     */
+    public boolean hasAccessToken() {
+        return this.accessToken != null && !this.accessToken.isEmpty();
+    }
+
+    /**
+     * Returns the client access token (possibly null).
+     *
+     * @return the client access token
+     */
+    public String accessToken() {
+        return accessToken;
+    }
+
+    /**
+     * Pins a public key to the HTTP client.
+     *
+     * @param provider           certificate provider
+     * @param subjPubKeyInfoHash public key hash
+     */
+    public void pinCertificate(String provider, String subjPubKeyInfoHash) {
+        CertificatePinner cp =
+                new CertificatePinner.Builder().add(provider, subjPubKeyInfoHash).build();
+        this.httpClient.setCertificatePinner(cp);
+    }
+
+    /**
+     * Sets the default connect timeout for new connections. A value of 0 means no timeout.
+     *
+     * @param timeout the number of time units for the default timeout
+     * @param unit    the unit of time
+     */
+    public void setConnectTimeout(long timeout, TimeUnit unit) {
+        this.httpClient.setConnectTimeout(timeout, unit);
+    }
+
+    /**
+     * Sets the default read timeout for new connections. A value of 0 means no timeout.
+     *
+     * @param timeout the number of time units for the default timeout
+     * @param unit    the unit of time
+     */
+    public void setReadTimeout(long timeout, TimeUnit unit) {
+        this.httpClient.setReadTimeout(timeout, unit);
+    }
+
+    /**
+     * Sets the default write timeout for new connections. A value of 0 means no timeout.
+     *
+     * @param timeout the number of time units for the default timeout
+     * @param unit    the unit of time
+     */
+    public void setWriteTimeout(long timeout, TimeUnit unit) {
+        this.httpClient.setWriteTimeout(timeout, unit);
+    }
+
+    /**
+     * Sets the proxy information for the HTTP client.
+     *
+     * @param proxy proxy object
+     */
+    public void setProxy(Proxy proxy) {
+        this.httpClient.setProxy(proxy);
+    }
+
+    /**
+     * Defines an interface for deserializing HTTP responses into objects.
+     *
+     * @param <T> the type of object to return
+     */
+    public interface ResponseCreator<T> {
+        /**
+         * Deserializes an HTTP response into a Java object of type T.
+         *
+         * @param response     HTTP response object
+         * @param deserializer json deserializer
+         * @return an object of type T
+         * @throws BytomException
+         * @throws IOException
+         */
+        T create(Response response, Gson deserializer) throws BytomException, IOException;
+    }
+
+    /**
+     * Builds and executes an HTTP Post request.
+     *
+     * @param path        the path to the endpoint
+     * @param body        the request body
+     * @param respCreator object specifying the response structure
+     * @return a response deserialized into type T
+     * @throws BytomException
+     */
+    private <T> T post(String path, Object body, ResponseCreator<T> respCreator)
+            throws BytomException {
+
+        RequestBody requestBody = RequestBody.create(this.JSON, Utils.serializer.toJson(body));
+        Request req;
+
+        BytomException exception = null;
+        URL endpointURL = null;
+
+        try {
+            endpointURL = new URL(url + "/" + path);
+        } catch (MalformedURLException e) {
+            e.printStackTrace();
+        }
+
+        Request.Builder builder =
+                new Request.Builder()
+                        .header("User-Agent", "bytom-sdk-java/" + version)
+                        .url(endpointURL)
+                        .method("POST", requestBody);
+        if (hasAccessToken()) {
+            builder = builder.header("Authorization", buildCredentials());
+        }
+        req = builder.build();
+
+        Response resp = null;
+
+        T object = null;
+
+        try {
+            resp = this.checkError(this.httpClient.newCall(req).execute());
+            object = respCreator.create(resp, Utils.serializer);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+
+        return object;
+    }
+
+    private OkHttpClient buildHttpClient(Builder builder) throws ConfigurationException {
+        OkHttpClient httpClient = builder.baseHttpClient.clone();
+
+        try {
+            if (builder.trustManagers != null) {
+                SSLContext sslContext = SSLContext.getInstance("TLS");
+                sslContext.init(builder.keyManagers, builder.trustManagers, null);
+                httpClient.setSslSocketFactory(sslContext.getSocketFactory());
+            }
+        } catch (GeneralSecurityException ex) {
+            throw new ConfigurationException("Unable to configure TLS", ex);
+        }
+        if (builder.readTimeoutUnit != null) {
+            httpClient.setReadTimeout(builder.readTimeout, builder.readTimeoutUnit);
+        }
+        if (builder.writeTimeoutUnit != null) {
+            httpClient.setWriteTimeout(builder.writeTimeout, builder.writeTimeoutUnit);
+        }
+        if (builder.connectTimeoutUnit != null) {
+            httpClient.setConnectTimeout(builder.connectTimeout, builder.connectTimeoutUnit);
+        }
+        if (builder.pool != null) {
+            httpClient.setConnectionPool(builder.pool);
+        }
+        if (builder.proxy != null) {
+            httpClient.setProxy(builder.proxy);
+        }
+        if (builder.cp != null) {
+            httpClient.setCertificatePinner(builder.cp);
+        }
+
+        return httpClient;
+    }
+
+    private static final Random randomGenerator = new Random();
+    private static final int MAX_RETRIES = 10;
+    private static final int RETRY_BASE_DELAY_MILLIS = 40;
+
+    // the max amount of time cored leader election could take
+    private static final int RETRY_MAX_DELAY_MILLIS = 15000;
+
+    private static int retryDelayMillis(int retryAttempt) {
+        // Calculate the max delay as base * 2 ^ (retryAttempt - 1).
+        int max = RETRY_BASE_DELAY_MILLIS * (1 << (retryAttempt - 1));
+        max = Math.min(max, RETRY_MAX_DELAY_MILLIS);
+
+        // To incorporate jitter, use a pseudo random delay between [max/2, max] millis.
+        return randomGenerator.nextInt(max / 2) + max / 2 + 1;
+    }
+
+    private static final int[] RETRIABLE_STATUS_CODES = {
+            408, // Request Timeout
+            429, // Too Many Requests
+            500, // Internal Server Error
+            502, // Bad Gateway
+            503, // Service Unavailable
+            504, // Gateway Timeout
+            509, // Bandwidth Limit Exceeded
+    };
+
+    private static boolean isRetriableStatusCode(int statusCode) {
+        for (int i = 0; i < RETRIABLE_STATUS_CODES.length; i++) {
+            if (RETRIABLE_STATUS_CODES[i] == statusCode) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private Response checkError(Response response) throws BytomException {
+        /*
+        String rid = response.headers().get("Bytom-Request-ID");
+        if (rid == null || rid.length() == 0) {
+            // Header field Bytom-Request-ID is set by the backend
+            // API server. If this field is set, then we can expect
+            // the body to be well-formed JSON. If it's not set,
+            // then we are probably talking to a gateway or proxy.
+            throw new ConnectivityException(response);
+        } */
+
+        if ((response.code() / 100) != 2) {
+            try {
+                APIException err =
+                        Utils.serializer.fromJson(response.body().charStream(), APIException.class);
+                if (err.code != null) {
+                    //err.requestId = rid;
+                    err.statusCode = response.code();
+                    throw err;
+                }
+            } catch (IOException ex) {
+                //throw new JSONException("Unable to read body. " + ex.getMessage(), rid);
+                throw new JSONException("Unable to read body. ");
+            }
+        }
+        return response;
+    }
+
+    private String buildCredentials() {
+        String user = "";
+        String pass = "";
+        if (hasAccessToken()) {
+            String[] parts = accessToken.split(":");
+            if (parts.length >= 1) {
+                user = parts[0];
+            }
+            if (parts.length >= 2) {
+                pass = parts[1];
+            }
+        }
+        return Credentials.basic(user, pass);
+    }
+
+    /**
+     * Overrides {@link Object#hashCode()}
+     *
+     * @return the hash code
+     */
+    @Override
+    public int hashCode() {
+        int code = this.url.hashCode();
+        if (this.hasAccessToken()) {
+            code = code * 31 + this.accessToken.hashCode();
+        }
+        return code;
+    }
+
+    /**
+     * Overrides {@link Object#equals(Object)}
+     *
+     * @param o the object to compare
+     * @return a boolean specifying equality
+     */
+    @Override
+    public boolean equals(Object o) {
+        if (o == null) return false;
+        if (!(o instanceof Client)) return false;
+
+        Client other = (Client) o;
+        if (!this.url.equalsIgnoreCase(other.url)) {
+            return false;
+        }
+        return Objects.equals(this.accessToken, other.accessToken);
+    }
+
+    /**
+     * A builder class for creating client objects
+     */
+    public static class Builder {
+
+        private String url;
+
+        private OkHttpClient baseHttpClient;
+        private String accessToken;
+        private CertificatePinner cp;
+        private KeyManager[] keyManagers;
+        private TrustManager[] trustManagers;
+        private long connectTimeout;
+        private TimeUnit connectTimeoutUnit;
+        private long readTimeout;
+        private TimeUnit readTimeoutUnit;
+        private long writeTimeout;
+        private TimeUnit writeTimeoutUnit;
+        private Proxy proxy;
+        private ConnectionPool pool;
+
+        public Builder() {
+            this.baseHttpClient = new OkHttpClient();
+            this.baseHttpClient.setFollowRedirects(false);
+            this.setDefaults();
+        }
+
+        public Builder(Client client) {
+            this.baseHttpClient = client.httpClient.clone();
+            this.url = client.url;
+            this.accessToken = client.accessToken;
+        }
+
+        private void setDefaults() {
+            this.setReadTimeout(30, TimeUnit.SECONDS);
+            this.setWriteTimeout(30, TimeUnit.SECONDS);
+            this.setConnectTimeout(30, TimeUnit.SECONDS);
+            this.setConnectionPool(50, 2, TimeUnit.MINUTES);
+        }
+
+        public Builder setUrl(String url) {
+            this.url = url;
+            return this;
+        }
+
+        /**
+         * Sets the access token for the client
+         *
+         * @param accessToken The access token for the Chain Core or HSM
+         */
+        public Builder setAccessToken(String accessToken) {
+            this.accessToken = accessToken;
+            return this;
+        }
+
+        /**
+         * Sets the client's certificate and key for TLS client authentication.
+         * PEM-encoded, RSA private keys adhering to PKCS#1 or PKCS#8 are supported.
+         *
+         * @param certStream input stream of PEM-encoded X.509 certificate
+         * @param keyStream  input stream of PEM-encoded private key
+         */
+        public Builder setX509KeyPair(InputStream certStream, InputStream keyStream)
+                throws ConfigurationException {
+            try (PEMParser parser = new PEMParser(new InputStreamReader(keyStream))) {
+                // Extract certs from PEM-encoded input.
+                CertificateFactory factory = CertificateFactory.getInstance("X.509");
+                X509Certificate certificate = (X509Certificate) factory.generateCertificate(certStream);
+
+                // Parse the private key from PEM-encoded input.
+                Object obj = parser.readObject();
+                PrivateKeyInfo info;
+                if (obj instanceof PEMKeyPair) {
+                    // PKCS#1 Private Key found.
+                    PEMKeyPair kp = (PEMKeyPair) obj;
+                    info = kp.getPrivateKeyInfo();
+                } else if (obj instanceof PrivateKeyInfo) {
+                    // PKCS#8 Private Key found.
+                    info = (PrivateKeyInfo) obj;
+                } else {
+                    throw new ConfigurationException("Unsupported private key provided.");
+                }
+
+                // Create a new key store and input the pair.
+                KeySpec spec = new PKCS8EncodedKeySpec(info.getEncoded());
+                KeyFactory kf = KeyFactory.getInstance("RSA");
+                KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+                keyStore.load(null, DEFAULT_KEYSTORE_PASSWORD);
+                keyStore.setCertificateEntry("cert", certificate);
+                keyStore.setKeyEntry(
+                        "key",
+                        kf.generatePrivate(spec),
+                        DEFAULT_KEYSTORE_PASSWORD,
+                        new X509Certificate[]{certificate});
+
+                // Use key store to build a key manager.
+                KeyManagerFactory keyManagerFactory =
+                        KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+                keyManagerFactory.init(keyStore, DEFAULT_KEYSTORE_PASSWORD);
+
+                this.keyManagers = keyManagerFactory.getKeyManagers();
+                return this;
+            } catch (GeneralSecurityException | IOException ex) {
+                throw new ConfigurationException("Unable to store X.509 cert/key pair", ex);
+            }
+        }
+
+        /**
+         * Sets the client's certificate and key for TLS client authentication.
+         *
+         * @param certPath file path to PEM-encoded X.509 certificate
+         * @param keyPath  file path to PEM-encoded private key
+         */
+        public Builder setX509KeyPair(String certPath, String keyPath) throws ConfigurationException {
+            try (InputStream certStream =
+                         new ByteArrayInputStream(Files.readAllBytes(Paths.get(certPath)));
+                 InputStream keyStream =
+                         new ByteArrayInputStream(Files.readAllBytes(Paths.get(keyPath)))) {
+                return setX509KeyPair(certStream, keyStream);
+            } catch (IOException ex) {
+                throw new ConfigurationException("Unable to store X509 cert/key pair", ex);
+            }
+        }
+
+        /**
+         * Trusts the given CA certs, and no others. Use this if you are running
+         * your own CA, or are using a self-signed server certificate.
+         *
+         * @param is input stream of the certificates to trust, in PEM format.
+         */
+        public Builder setTrustedCerts(InputStream is) throws ConfigurationException {
+            try {
+                // Extract certs from PEM-encoded input.
+                CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
+                Collection<? extends Certificate> certificates =
+                        certificateFactory.generateCertificates(is);
+                if (certificates.isEmpty()) {
+                    throw new IllegalArgumentException("expected non-empty set of trusted certificates");
+                }
+
+                // Create a new key store and input the cert.
+                KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+                keyStore.load(null, DEFAULT_KEYSTORE_PASSWORD);
+                int index = 0;
+                for (Certificate certificate : certificates) {
+                    String certificateAlias = Integer.toString(index++);
+                    keyStore.setCertificateEntry(certificateAlias, certificate);
+                }
+
+                // Use key store to build an X509 trust manager.
+                KeyManagerFactory keyManagerFactory =
+                        KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+                keyManagerFactory.init(keyStore, DEFAULT_KEYSTORE_PASSWORD);
+                TrustManagerFactory trustManagerFactory =
+                        TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+                trustManagerFactory.init(keyStore);
+                TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
+                if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
+                    throw new IllegalStateException(
+                            "Unexpected default trust managers:" + Arrays.toString(trustManagers));
+                }
+
+                this.trustManagers = trustManagers;
+                return this;
+            } catch (GeneralSecurityException | IOException ex) {
+                throw new ConfigurationException("Unable to configure trusted CA certs", ex);
+            }
+        }
+
+        /**
+         * Trusts the given CA certs, and no others. Use this if you are running
+         * your own CA, or are using a self-signed server certificate.
+         *
+         * @param path The path of a file containing certificates to trust, in PEM format.
+         */
+        public Builder setTrustedCerts(String path) throws ConfigurationException {
+            try (InputStream is = new FileInputStream(path)) {
+                return setTrustedCerts(is);
+            } catch (IOException ex) {
+                throw new ConfigurationException("Unable to configure trusted CA certs", ex);
+            }
+        }
+
+        /**
+         * Sets the certificate pinner for the client
+         *
+         * @param provider           certificate provider
+         * @param subjPubKeyInfoHash public key hash
+         */
+        public Builder pinCertificate(String provider, String subjPubKeyInfoHash) {
+            this.cp = new CertificatePinner.Builder().add(provider, subjPubKeyInfoHash).build();
+            return this;
+        }
+
+        /**
+         * Sets the connect timeout for the client
+         *
+         * @param timeout the number of time units for the default timeout
+         * @param unit    the unit of time
+         */
+        public Builder setConnectTimeout(long timeout, TimeUnit unit) {
+            this.connectTimeout = timeout;
+            this.connectTimeoutUnit = unit;
+            return this;
+        }
+
+        /**
+         * Sets the read timeout for the client
+         *
+         * @param timeout the number of time units for the default timeout
+         * @param unit    the unit of time
+         */
+        public Builder setReadTimeout(long timeout, TimeUnit unit) {
+            this.readTimeout = timeout;
+            this.readTimeoutUnit = unit;
+            return this;
+        }
+
+        /**
+         * Sets the write timeout for the client
+         *
+         * @param timeout the number of time units for the default timeout
+         * @param unit    the unit of time
+         */
+        public Builder setWriteTimeout(long timeout, TimeUnit unit) {
+            this.writeTimeout = timeout;
+            this.writeTimeoutUnit = unit;
+            return this;
+        }
+
+        /**
+         * Sets the proxy for the client
+         *
+         * @param proxy
+         */
+        public Builder setProxy(Proxy proxy) {
+            this.proxy = proxy;
+            return this;
+        }
+
+        /**
+         * Sets the connection pool for the client
+         *
+         * @param maxIdle the maximum number of idle http connections in the pool
+         * @param timeout the number of time units until an idle http connection in the pool is closed
+         * @param unit    the unit of time
+         */
+        public Builder setConnectionPool(int maxIdle, long timeout, TimeUnit unit) {
+            this.pool = new ConnectionPool(maxIdle, unit.toMillis(timeout));
+            return this;
+        }
+
+        /**
+         * Builds a client with all of the provided parameters.
+         */
+        public Client build() throws ConfigurationException {
+            return new Client(this);
+        }
+    }
+}
diff --git a/src/main/java/io/bytom/http/SuccessMessage.java b/src/main/java/io/bytom/http/SuccessMessage.java
new file mode 100644 (file)
index 0000000..c069a82
--- /dev/null
@@ -0,0 +1,8 @@
+package io.bytom.http;
+
+/**
+ * This class represents RPC success responses whose content is not meaningful.
+ */
+public class SuccessMessage {
+  public String message;
+}
diff --git a/src/main/resources/config.properties b/src/main/resources/config.properties
new file mode 100644 (file)
index 0000000..5938392
--- /dev/null
@@ -0,0 +1,8 @@
+bytom.api.url=http://127.0.0.1:9888\r
+client.access.token=\r
+\r
+# bytom.api.url=http://10.100.7.47:9888/\r
+# client.access.token=wt:3d17dbb953cedd53353bf3f342bb2929e9505105ffeb21670e6bd00abeef3772\r
+\r
+#bytom.api.url=http://127.0.0.1:9888/\r
+#client.access.token=sheng:49d1623f5991c62a5094e761477ddd2838dceb49c22fbf84b492a54f1df88123\r
diff --git a/src/main/resources/log4j.properties b/src/main/resources/log4j.properties
new file mode 100644 (file)
index 0000000..d481a3b
--- /dev/null
@@ -0,0 +1,20 @@
+  log4j.rootLogger=debug, stdout, R
+  log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+  log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+  
+  log4j.logger.org.apache.commons.httpclient=info
+  log4j.logger.httpclient.wire.content=info
+  log4j.logger.httpclient.wire.header=info
+
+  # Pattern to output the caller's file name and line number.
+  log4j.appender.stdout.layout.ConversionPattern=%-4r %-5p [%d{yyyy-MM-dd HH:mm:ss}]  %m%n
+
+  log4j.appender.R=org.apache.log4j.RollingFileAppender
+  log4j.appender.R.File=bytom.log
+  log4j.appender.R.MaxFileSize= 100KB
+
+  # Keep one backup file
+  log4j.appender.R.MaxBackupIndex=1
+
+  log4j.appender.R.layout=org.apache.log4j.PatternLayout
+  log4j.appender.R.layout.ConversionPattern=%-4r %-5p [%d{yyyy-MM-dd HH:mm:ss}]  %m%n
\ No newline at end of file
diff --git a/src/test/java/io/bytom/AppTest.java b/src/test/java/io/bytom/AppTest.java
new file mode 100644 (file)
index 0000000..b5d58a9
--- /dev/null
@@ -0,0 +1,58 @@
+package io.bytom;
+
+import com.squareup.okhttp.*;
+import org.junit.Test;
+
+import java.io.IOException;
+
+/**
+ * Unit test for simple App.
+ */
+public class AppTest {
+    private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
+
+    private final OkHttpClient client = new OkHttpClient();
+
+
+    @Test
+    public void testListAccounts() throws IOException {
+
+        String postBody = "{}";
+
+        Request request = new Request.Builder()
+                .url("http://127.0.0.1:9888/list-accounts")
+                .post(RequestBody.create(JSON, postBody))
+                .build();
+
+        Response response = client.newCall(request).execute();
+
+        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
+
+        System.out.println(response.body().string());
+    }
+
+
+    @Test
+    public void testCreateAccount() throws IOException {
+
+        String postBody = "{\n" +
+                "        \"root_xpubs\": [\n" +
+                "                \"012454d25928d52d42e3ee1f2bebe0916974d958f9ec08c9a028043ffe3dd95630c1b788c947b8c07ede2a4b5e3e3bbe0e305bab4526a7bc67b21e1d051e74ef\"\n" +
+                "        ], \n" +
+                "        \"quorum\": 1, \n" +
+                "        \"alias\": \"sheng\"\n" +
+                "}";
+
+        Request request = new Request.Builder()
+                .url("http://127.0.0.1:9888/create-account")
+                .post(RequestBody.create(JSON, postBody))
+                .build();
+
+        Response response = client.newCall(request).execute();
+
+        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
+
+        System.out.println(response.body().string());
+    }
+}
+
diff --git a/src/test/java/io/bytom/TestUtils.java b/src/test/java/io/bytom/TestUtils.java
new file mode 100644 (file)
index 0000000..25f1690
--- /dev/null
@@ -0,0 +1,29 @@
+package io.bytom;
+
+import io.bytom.common.Configuration;
+import io.bytom.exception.BytomException;
+import io.bytom.http.Client;
+
+/**
+ * TestUtils provides a simplified api for testing.
+ */
+public class TestUtils {
+    public static Client generateClient_old() throws BytomException {
+
+        String coreURL = "http://127.0.0.1:9888";
+
+        return new Client(coreURL);
+    }
+
+    public static Client generateClient() throws BytomException {
+
+        String coreURL = Configuration.getValue("bytom.api.url");
+        String accessToken = Configuration.getValue("client.access.token");
+
+        if (coreURL == null || coreURL.isEmpty()) {
+            coreURL = "http://127.0.0.1:9888/";
+        }
+
+        return new Client(coreURL, accessToken);
+    }
+}
diff --git a/src/test/java/io/bytom/integration/AccessTokenTest.java b/src/test/java/io/bytom/integration/AccessTokenTest.java
new file mode 100644 (file)
index 0000000..49930b9
--- /dev/null
@@ -0,0 +1,39 @@
+package io.bytom.integration;
+
+import io.bytom.TestUtils;
+import io.bytom.api.AccessToken;
+import io.bytom.exception.BytomException;
+import io.bytom.http.Client;
+import org.junit.Test;
+
+import java.util.List;
+
+public class AccessTokenTest {
+
+    static Client client;
+
+    static {
+        try {
+            client = TestUtils.generateClient();
+        } catch (BytomException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Test
+    public void testTokenCreate() throws Exception {
+        AccessToken accessToken = new AccessToken.Builder().setId("sheng").create(client);
+    }
+
+
+    @Test
+    public void testTokenList() throws Exception {
+        List<AccessToken> tokenList = AccessToken.list(client);
+    }
+
+    @Test
+    public void testTokenCheck() throws Exception {
+        String secret = "5e37378eb59de6b10e60f2247ebf71c4955002e75e0cd31ede3bf48813a0a799";
+        AccessToken.check(client, "sheng", secret);
+    }
+}
diff --git a/src/test/java/io/bytom/integration/AccountTest.java b/src/test/java/io/bytom/integration/AccountTest.java
new file mode 100644 (file)
index 0000000..081106f
--- /dev/null
@@ -0,0 +1,98 @@
+package io.bytom.integration;
+
+import io.bytom.TestUtils;
+import io.bytom.api.Account;
+import io.bytom.api.Address;
+import io.bytom.api.Receiver;
+import io.bytom.http.Client;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+public class AccountTest {
+
+    static Client client;
+    static Account account;
+
+    @Test
+    public void testAccountCreate() throws Exception {
+        client = TestUtils.generateClient();
+
+        String alias = "AccountTest.testAccountCreate.002";
+        Integer quorum = 1;
+        List<String> root_xpubs = new ArrayList<>();
+        root_xpubs.add("c4b25825e92cd8623de4fd6a35952ad0efb2ed215fdb1b40754f0ed12eff7827d147d1e8b003601ba2f78a4a84dcc77e93ed282633f2679048c5d5ac5ea10cb5");
+
+
+//        Key.Builder builder = new Key.Builder().setAlias(alias).setPassword(password);
+//        key = Key.create(client, builder);
+
+        Account.Builder builder = new Account.Builder().setAlias(alias).setQuorum(quorum).setRootXpub(root_xpubs);
+        account = Account.create(client, builder);
+
+        assertNotNull(account.id);
+        assertEquals(alias.toLowerCase(), account.alias);
+    }
+
+    @Test
+    public void testAccountList() throws Exception {
+        client = TestUtils.generateClient();
+        List<Account> accountList = Account.list(client);
+    }
+
+    @Test
+    public void testAccountDelete() throws Exception {
+        client = TestUtils.generateClient();
+        List<Account> accountList = Account.list(client);
+        String alias = accountList.get(accountList.size()-1).alias;
+        //delete the last Account Object
+        Account.delete(client, alias);
+    }
+
+    @Test
+    public void testReceiverCreate() throws Exception {
+        client = TestUtils.generateClient();
+        List<Account> accountList = Account.list(client);
+        String alias = accountList.get(accountList.size()-1).alias;
+        String id = accountList.get(accountList.size()-1).id;
+
+        Account.ReceiverBuilder receiverBuilder = new Account.ReceiverBuilder().setAccountAlias(alias).setAccountId(id);
+        Receiver receiver = receiverBuilder.create(client);
+
+        assertNotNull(receiver.address);
+        assertNotNull(receiver.controlProgram);
+    }
+
+    @Test
+    public void testAddressList() throws Exception {
+        client = TestUtils.generateClient();
+        List<Account> accountList = Account.list(client);
+        String alias = accountList.get(accountList.size()-1).alias;
+        String id = accountList.get(accountList.size()-1).id;
+
+        Account.AddressBuilder addressBuilder = new Account.AddressBuilder().setAccountId(id).setAccountAlias(alias);
+        List<Address> addressList = addressBuilder.list(client);
+
+        assertNotNull(addressList);
+    }
+
+    @Test
+    public void testAddressValidate() throws Exception {
+        client = TestUtils.generateClient();
+
+        List<Account> accountList = Account.list(client);
+        String alias = accountList.get(accountList.size()-1).alias;
+        String id = accountList.get(accountList.size()-1).id;
+
+        Account.AddressBuilder addressBuilder = new Account.AddressBuilder().setAccountId(id).setAccountAlias(alias);
+        List<Address> addressList = addressBuilder.list(client);
+
+        Address address = addressBuilder.validate(client, addressList.get(0).address);
+        assertEquals(true, address.is_local);
+    }
+
+}
diff --git a/src/test/java/io/bytom/integration/AssetTest.java b/src/test/java/io/bytom/integration/AssetTest.java
new file mode 100644 (file)
index 0000000..a35a0a6
--- /dev/null
@@ -0,0 +1,74 @@
+package io.bytom.integration;
+
+import io.bytom.TestUtils;
+import io.bytom.api.Account;
+import io.bytom.api.Address;
+import io.bytom.api.Asset;
+import io.bytom.api.Receiver;
+import io.bytom.http.Client;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+public class AssetTest {
+
+    static Client client;
+    static Account account;
+    static Asset asset;
+
+    @Test
+    public void testAssetCreate() throws Exception {
+        client = TestUtils.generateClient();
+
+        List<Account> accountList = Account.list(client);
+        String alias = "GOLD";
+
+        List<String> xpubs = accountList.get(0).xpubs;
+
+        Asset.Builder builder = new Asset.Builder()
+                                    .setAlias(alias)
+                                    .setQuorum(1)
+                                    .setRootXpubs(xpubs);
+        asset = builder.create(client);
+        assertNotNull(asset);
+    }
+
+    @Test
+    public void testAssetGet() throws Exception {
+        client = TestUtils.generateClient();
+        Asset.QueryBuilder queryBuilder = new Asset.QueryBuilder();
+        String id = queryBuilder.list(client).get(1).id;
+        queryBuilder.setId(id);
+        Asset asset = queryBuilder.get(client);
+    }
+
+    @Test
+    public void testAssetList() throws Exception {
+        client = TestUtils.generateClient();
+        Asset.QueryBuilder queryBuilder = new Asset.QueryBuilder();
+        List<Asset> assetList = queryBuilder.list(client);
+        assertEquals(2, assetList.size());
+    }
+
+    @Test
+    public void testUpdateAssetAlias() throws Exception {
+        client = TestUtils.generateClient();
+
+        Asset.QueryBuilder queryBuilder = new Asset.QueryBuilder();
+        String id = queryBuilder.list(client).get(1).id;
+
+        String alias = "HELLOWORLD";
+
+
+        Asset.AliasUpdateBuilder aliasUpdateBuilder =
+                new Asset.AliasUpdateBuilder()
+                        .setAlias(alias)
+                        .setAssetId(id);
+        aliasUpdateBuilder.update(client);
+    }
+
+}
diff --git a/src/test/java/io/bytom/integration/KeyTest.java b/src/test/java/io/bytom/integration/KeyTest.java
new file mode 100644 (file)
index 0000000..a51ad8a
--- /dev/null
@@ -0,0 +1,58 @@
+package io.bytom.integration;
+
+import io.bytom.TestUtils;
+import io.bytom.api.Key;
+import io.bytom.exception.BytomException;
+import io.bytom.http.Client;
+import org.junit.Test;
+
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+
+public class KeyTest {
+
+    static Client client;
+    static Key key;
+
+    @Test
+    public void testClientKeyCreate() throws Exception {
+        client = TestUtils.generateClient();
+
+        String alias = "KeyTest.testKeyCreate.successli004";
+        String password = "123456";
+
+        Key.Builder builder = new Key.Builder().setAlias(alias).setPassword(password);
+        key = Key.create(client, builder);
+
+        assertNotNull(key.xpub);
+        assertEquals(alias.toLowerCase(), key.alias);
+    }
+
+    @Test
+    public void testClientKeyList() throws Exception {
+        client = TestUtils.generateClient();
+        List<Key> keyList = Key.list(client);
+    }
+
+    @Test
+    public void testClientKeyDelete() throws Exception {
+        client = TestUtils.generateClient();
+        List<Key> keyList = Key.list(client);
+        String xpub = keyList.get(keyList.size()-1).xpub;
+        //delete the last Key Object
+        Key.delete(client, xpub, "123456");
+    }
+
+    @Test
+    public void testClientKeyResetPwd() throws BytomException {
+        client = TestUtils.generateClient();
+        List<Key> keyList = Key.list(client);
+        String xpub = keyList.get(keyList.size()-1).xpub;
+        Key.resetPwd(client, xpub, "123456", "123456789");
+        Key.delete(client, xpub, "123456789");
+    }
+
+}
diff --git a/src/test/java/io/bytom/integration/TransactionTest.java b/src/test/java/io/bytom/integration/TransactionTest.java
new file mode 100644 (file)
index 0000000..b92cb84
--- /dev/null
@@ -0,0 +1,269 @@
+package io.bytom.integration;
+
+import io.bytom.TestUtils;
+import io.bytom.api.*;
+import io.bytom.exception.BytomException;
+import io.bytom.http.Client;
+import org.apache.log4j.Logger;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+public class TransactionTest {
+
+    static Client client;
+
+    static Key senderKey;
+    static Key receiverKey;
+    static Account senderAccount;
+    static Account receiverAccount;
+    static Receiver senderReceiver;
+    static Receiver receiverReceiver;
+    static Address senderAddress;
+    static Address receiverAddress;
+    static Asset senderAsset;
+    static Asset receiverAsset;
+
+    static {
+        try {
+            client = TestUtils.generateClient();
+        } catch (BytomException e) {
+            e.printStackTrace();
+        }
+    }
+
+
+    private static Logger logger = Logger.getLogger(TransactionTest.class);
+
+    @Test
+    public void testCreateAll() throws Exception {
+
+        testSenderKeyCreate();
+        testReceiverKeyCreate();
+
+        testSenderAccountCreate();
+        testReceiverAccountCreate();
+
+//        testSenderReceiverCreate();
+        testReceiverReceiverCreate();
+
+//        testSenderAssetCreate();
+//        testReceiverAssetCreate();
+
+    }
+
+    @Test
+    public void testGetAll() throws Exception {
+
+        senderKey = Key.list(client).get(0);
+        receiverKey = Key.list(client).get(1);
+        logger.info("senderKey:"+senderKey.toJson());
+        logger.info("receiverKey:"+receiverKey.toJson());
+
+        senderAccount = Account.list(client).get(0);
+        receiverAccount = Account.list(client).get(1);
+        logger.info("senderAccount:"+senderAccount.toJson());
+        logger.info("receiverAccount:"+receiverAccount.toJson());
+
+        receiverAddress = new Account.AddressBuilder()
+                .setAccountAlias(receiverAccount.alias)
+                .setAccountId(receiverAccount.id)
+                .list(client).get(0);
+        logger.info("receiver-address:"+receiverAddress.toJson());
+
+        senderAsset = new Asset.QueryBuilder().list(client).get(0);
+        receiverAsset = new Asset.QueryBuilder().list(client).get(1);
+        logger.info("senderAsset:"+senderAsset.toJson());
+        logger.info("receiverAsset:"+receiverAsset.toJson());
+    }
+
+    @Test
+    public void testTransactionAll() throws Exception {
+        testGetAll();
+
+        logger.info("before transaction:");
+
+        List<Balance> balanceList = new Balance.QueryBuilder().list(client);
+
+        logger.info("transaction:");
+
+        Transaction.Template controlAddress = new Transaction.Builder()
+                .addAction(
+                        new Transaction.Action.SpendFromAccount()
+                                .setAccountId(senderAccount.id)
+                                .setAssetId(senderAsset.id)
+                                .setAmount(300000000)
+                )
+                .addAction(
+                        new Transaction.Action.ControlWithAddress()
+                                .setAddress(receiverAddress.address)
+                                .setAssetId(senderAsset.id)
+                                .setAmount(200000000)
+                ).build(client);
+
+        Transaction.Template singer = new Transaction.SignerBuilder().sign(client,
+                controlAddress, "123456");
+
+        logger.info("singer:"+singer.toJson());
+
+        Transaction.SubmitResponse txs = Transaction.submit(client, singer);
+
+        logger.info("txs:"+txs.toJson());
+
+        logger.info("after transaction.");
+
+        balanceList = new Balance.QueryBuilder().list(client);
+
+    }
+
+    @Test
+    public void testListTransactions() throws Exception {
+        String tx_id = "b9a1baa1ae391b502fe5543a9559e6d98dc6cf77f9327daaebcb7222c3bebda6";
+        List<Transaction> transactionList =
+                new Transaction.QueryBuilder().setTxId(tx_id).list(client);
+        for (Transaction tx: transactionList
+             ) {
+            if (tx.txId.equalsIgnoreCase(tx_id)) {
+                logger.info(tx.toJson());
+            }
+        }
+    }
+
+    public void testSenderKeyCreate() throws Exception {
+        String alias = "sender-key";
+        String password = "123456";
+
+        Key.Builder builder = new Key.Builder().setAlias(alias).setPassword(password);
+        senderKey = Key.create(client, builder);
+
+        logger.info("create-sender-key:"+senderKey.toJson());
+    }
+
+    public void testReceiverKeyCreate() throws Exception {
+        String alias = "receiver-key";
+        String password = "123456";
+
+        Key.Builder builder = new Key.Builder().setAlias(alias).setPassword(password);
+        receiverKey = Key.create(client, builder);
+
+        logger.info("create-receiver-key:"+receiverKey.toJson());
+    }
+
+    public void testSenderAccountCreate() throws Exception {
+        String alias = "sender-account";
+        Integer quorum = 1;
+        List<String> root_xpubs = new ArrayList<String>();
+        root_xpubs.add(senderKey.xpub);
+
+        Account.Builder builder = new Account.Builder().setAlias(alias).setQuorum(quorum).setRootXpub(root_xpubs);
+
+        logger.info(builder.toString());
+
+        senderAccount = Account.create(client, builder);
+
+        logger.info("create-sender-account:"+senderAccount.toJson());
+    }
+
+    public void testReceiverAccountCreate() throws Exception {
+        String alias = "receiver-account";
+        Integer quorum = 1;
+        List<String> root_xpubs = new ArrayList<>();
+        root_xpubs.add(receiverKey.xpub);
+
+        Account.Builder builder = new Account.Builder().setAlias(alias).setQuorum(quorum).setRootXpub(root_xpubs);
+        receiverAccount = Account.create(client, builder);
+
+        logger.info("create-receiver-account:"+receiverAccount.toJson());
+    }
+
+    public void testSenderReceiverCreate() throws Exception {
+        String alias = senderAccount.alias;
+        String id = senderAccount.id;
+
+        Account.ReceiverBuilder receiverBuilder = new Account.ReceiverBuilder().setAccountAlias(alias).setAccountId(id);
+        senderReceiver = receiverBuilder.create(client);
+
+        logger.info("create-receiver:"+senderReceiver.toJson());
+        logger.info("receiver-address:"+senderReceiver.address);
+    }
+
+    public void testReceiverReceiverCreate() throws Exception {
+        String alias = receiverAccount.alias;
+        String id = receiverAccount.id;
+
+        Account.ReceiverBuilder receiverBuilder = new Account.ReceiverBuilder().setAccountAlias(alias).setAccountId(id);
+        receiverReceiver = receiverBuilder.create(client);
+
+        logger.info("create-receiver:"+receiverReceiver.toJson());
+        logger.info("receiver-address:"+receiverReceiver.address);
+    }
+
+    public void testSenderAssetCreate() throws Exception {
+        String alias = "sender-asset";
+
+        List<String> xpubs = senderAccount.xpubs;
+
+        Asset.Builder builder = new Asset.Builder()
+                .setAlias(alias)
+                .setQuorum(1)
+                .setRootXpubs(xpubs);
+        senderAsset = builder.create(client);
+
+        logger.info("create-sender-asset:"+senderAsset.toJson());
+    }
+
+    public void testReceiverAssetCreate() throws Exception {
+        String alias = "receiver-asset";
+
+        List<String> xpubs = receiverAccount.xpubs;
+
+        Asset.Builder builder = new Asset.Builder()
+                .setAlias(alias)
+                .setQuorum(1)
+                .setRootXpubs(xpubs);
+        receiverAsset = builder.create(client);
+
+        logger.info("create-receiver-asset:"+receiverAsset.toJson());
+    }
+
+    /**
+     * build + sign + submit transaction
+     *
+     * @throws BytomException
+     */
+    public void testBasicTransaction() throws BytomException {
+        logger.info("before transaction:");
+
+        List<Balance> balanceList = new Balance.QueryBuilder().list(client);
+
+        logger.info(balanceList.get(0).toJson());
+        logger.info(balanceList.get(1).toJson());
+
+        Transaction.Template controlAddress = new Transaction.Builder()
+                .addAction(
+                        new Transaction.Action.SpendFromAccount()
+                                .setAccountId(senderAccount.id)
+                                .setAssetId(senderAsset.id)
+                                .setAmount(300000000)
+                )
+                .addAction(
+                        new Transaction.Action.ControlWithAddress()
+                                .setAddress(receiverAddress.address)
+                                .setAssetId(receiverAsset.id)
+                                .setAmount(200000000)
+                ).build(client);
+
+        logger.info("after transaction.");
+
+        balanceList = new Balance.QueryBuilder().list(client);
+
+        logger.info(balanceList.get(0).toJson());
+        logger.info(balanceList.get(1).toJson());
+
+    }
+
+}