--- /dev/null
+.idea/
+*.iml
+target/
+bytom.log
--- /dev/null
+# 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.
--- /dev/null
+<?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>
--- /dev/null
+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;
+ }
+
+}
--- /dev/null
+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 +
+ '}';
+ }
+}
--- /dev/null
+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());
+ }
+ }
+
+}
--- /dev/null
+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 + '\'' +
+ '}';
+ }
+}
--- /dev/null
+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;
+ }
+ }
+}
--- /dev/null
+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);
+ }
+
+ }
+}
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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 + '\'' +
+ '}';
+ }
+}
--- /dev/null
+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;
+ }
+
+ }
+
+
+}
--- /dev/null
+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);
+ }
+
+
+ }
+}
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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;
+
+ }
+}
--- /dev/null
+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());
+ }
+ }
+}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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;
+
+ }
+}
--- /dev/null
+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;
+
+ }
+}
--- /dev/null
+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;
+ }
+
+ }
+}
--- /dev/null
+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;
+ }
+
+ }
+ }
+
+}
--- /dev/null
+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);
+ }
+
+}
--- /dev/null
+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();
+}
--- /dev/null
+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 & 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 & 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;
+ }
+}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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;
+}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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);
+ }
+ }
+}
--- /dev/null
+package io.bytom.http;
+
+/**
+ * This class represents RPC success responses whose content is not meaningful.
+ */
+public class SuccessMessage {
+ public String message;
+}
--- /dev/null
+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
--- /dev/null
+ 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
--- /dev/null
+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());
+ }
+}
+
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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);
+ }
+
+}
--- /dev/null
+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);
+ }
+
+}
--- /dev/null
+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");
+ }
+
+}
--- /dev/null
+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());
+
+ }
+
+}