diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 455d288ae..1b35b471f 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -6,6 +6,7 @@ updates:
interval: weekly
day: wednesday
timezone: Asia/Shanghai
+ target-branch: "develop"
open-pull-requests-limit: 10
ignore:
- dependency-name: org.bouncycastle:bcprov-jdk15on
diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml
index 806be6861..6d8af6c6b 100644
--- a/.github/workflows/cd.yml
+++ b/.github/workflows/cd.yml
@@ -23,25 +23,31 @@ jobs:
tag: ${{ steps.get_tag.outputs.tag }}
id: ${{ steps.release_drafter.outputs.id }}
upload_url: ${{ steps.release_drafter.outputs.upload_url }}
-# upload_artifact:
-# needs: create_release_draft
-# runs-on: ubuntu-latest
-# steps:
-# - name: Checkout branch
-# uses: actions/checkout@v1
-# - name: Set up JDK 1.8
-# uses: actions/setup-java@v1
-# with:
-# java-version: 1.8
-# - name: Prepare signing secret key ring file
-# run: echo "${{ secrets.NEXUS_SIGNING_SECRET_KEY_BASE64 }}" | base64 --decode > ./secret_key.gpg
-# - name: Build project
-# run: |
-# chmod +x ./gradlew
-# ./gradlew shadowJar
-# - name: Upload artifact to Nexus
-# run: |
-# ./gradlew uploadArchives -PossrhUsername=${{ secrets.NEXUS_OSSRH_USERNAME }} -PossrhPassword=${{ secrets.NEXUS_OSSRH_PASSWORD }} -Psigning.keyId=${{ secrets.NEXUS_SIGNING_KEYID }} -Psigning.password=${{ secrets.NEXUS_SIGNING_PASSWORD }} -Psigning.secretKeyRingFile=./secret_key.gpg
+ upload_artifact:
+ needs: create_release_draft
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout branch
+ uses: actions/checkout@v1
+ - name: Set up JDK 1.8
+ uses: actions/setup-java@v1
+ with:
+ java-version: 1.8
+ - name: Prepare signing secret key ring file
+ run: echo "${{ secrets.NEXUS_SIGNING_SECRET_KEY_BASE64 }}" | base64 --decode > ./secret_key.gpg
+ - name: Build project
+ run: |
+ chmod +x ./gradlew
+ ./gradlew shadowJar
+ - name: Upload artifact to Nexus
+ env:
+ ORG_GRADLE_PROJECT_ossrhUsername: ${{ secrets.NEXUS_OSSRH_USERNAME }}
+ ORG_GRADLE_PROJECT_ossrhPassword: ${{ secrets.NEXUS_OSSRH_PASSWORD }}
+ ORG_GRADLE_PROJECT_signing.keyId: ${{ secrets.NEXUS_SIGNING_KEYID }}
+ ORG_GRADLE_PROJECT_signing.password: ${{ secrets.NEXUS_SIGNING_PASSWORD }}
+ ORG_GRADLE_PROJECT_signing.secretKeyRingFile: ./secret_key.gpg
+ run: |
+ ./gradlew uploadArchives
# - name: upload artifact ckb.jar to GitHub release page
# uses: actions/upload-release-asset@v1
# env:
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 000000000..de6418244
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 000000000..79ee123c2
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f1cfbd92a..a6d937703 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,17 @@
+# 2.1.0 (2022-12-26)
+
+## 🚀 Features
+
+- feat: support omnilock script (#590)
+- feat: support building transaction in dev chain (#591)
+- feat: support indexer RPC in ckb (#601)
+- feat: support light client RPC (#602)
+- feat: support offchain cell (#603, 606)
+- feat: support field extra in mercury RPC get_balance (#605)
+- feat: support RPC method estimate_cycles (#608)
+- feat:support rpc method get fee rate statics (#611)
+- feat: guitar Make ckb-indexer can configure which api url to use (#613)
+
# 2.0.3 (2022-07-13)
## 🐛 Bug Fixes
diff --git a/README.md b/README.md
index c669d7945..97cb4d422 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,7 @@ Maven
```
org.nervos.ckb
- ckb-api
+ ckb
{version}
```
@@ -27,7 +27,7 @@ Maven
Gradle
```
-implementation 'org.nervos.ckb-api:ckb:{version}'
+implementation 'org.nervos.ckb:ckb:{version}'
```
## Build
@@ -39,38 +39,30 @@ Run `gradle build` in project root directory.
Here we will give some most frequently used operations, to bring you enlightenment about how to use ckb-sdk-java to operate your asset in CKB chain.
### Setup
-ckb-java-sdk provides a convenient client to help you easily interact with [CKB](https://github.com/nervosnetwork/ckb), [CKB-indexer](https://github.com/nervosnetwork/ckb-indexer) or [Mercury](https://github.com/nervosnetwork/mercury) node.
+
+ckb-sdk-java provides a convenient client to help you easily interact with [CKB](https://github.com/nervosnetwork/ckb) node.
```java
-// Set up client. If you do not use ones of these node, just set them to null;
-String ckbUrl = "http://127.0.0.1:8114";
-String indexerUrl = "http://127.0.0.1:8114";
-String mercuryUrl = "http://127.0.0.1:8116";
-DefaultCkbApi ckbApi = new DefaultCkbApi(ckbUrl, mercuryUrl, indexerUrl, false);
+// Set up client.
+CkbRpcApi ckbAPi = new Api("http://127.0.0.1:8114");
```
-You can leverage this client to call any RPC APIs provided by CKB, CKB-indexer or Mercury in Java code.
+You can leverage this client to call any RPC APIs provided by CKB in Java code.
+
```java
byte[] blockHash = Numeric.hexStringToByteArray("0x77fdd22f6ae8a717de9ae2b128834e9b2a1424378b5fc95606ba017aab5fed75");
Block block = ckbApi.getBlock(blockHash);
```
-For more details about RPC APIs, please check:
-
-- [CKB RPC doc](https://github.com/nervosnetwork/ckb/blob/develop/rpc/README.md)
-- [CKB-indexer RPC doc](https://github.com/nervosnetwork/ckb-indexer/blob/master/README.md)
-- [Mercury RPC doc](https://github.com/nervosnetwork/mercury/blob/main/core/rpc/README.md).
+For more details about CKB RPC APIs, please refer to [CKB RPC doc](https://github.com/nervosnetwork/ckb/blob/develop/rpc/README.md).
-### Build transaction with indexer
+### Build transaction by manual
-[ckb-indexer](https://github.com/nervosnetwork/ckb-indexer) is an application to collect cells and transactions from CKB
-chain. With ckb-indexer to collect live cells, we can build transactions easily.
-
-ckb-java-sdk encapsulates the common logic into a user-friendly transaction builder. It could greatly free you from
+ckb-sdk-java encapsulates the common logic into a user-friendly transaction builder. It could greatly free you from
getting into script details and from tedious manual work of building transaction including adding celldeps, transaction
fee calculation, change output set and so on.
-Here is an example to build a CKB transfer transaction with the help of transaction builder and ckb-indexer.
+Here is an example to build a CKB transfer transaction with the help of transaction builder and ckb node.
```java
String sender = "ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq2qf8keemy2p5uu0g0gn8cd4ju23s5269qk8rg4r";
@@ -83,7 +75,7 @@ TransactionWithScriptGroups txWithGroups = new CkbTransactionBuilder(iterator, N
.build();
```
-For more use cases of building transaction with ckb-indexer, please refer
+For more use cases of building transaction with ckb node, please refer
to [these examples](./example/src/main/java/org/nervos/ckb/example).
### Build transaction with Mercury
@@ -107,7 +99,8 @@ builder.addTo(receiver, ckbAmount);
builder.assetInfo(AssetInfo.newCkbAsset());
// Get an unsigned raw transaction with the help of Mercury
-TransactionWithScriptGroups txWithScriptGroups = api.buildSimpleTransferTransaction(builder.build());
+MercuryApi mercuryApi = new DefaultMercuryApi("http://127.0.0.1:8116", false);
+TransactionWithScriptGroups txWithScriptGroups = mercuryApi.buildSimpleTransferTransaction(builder.build());
```
For more use cases of mercury, please refer to [these test cases](./ckb-mercury-sdk/src/test/java/mercury)
@@ -120,7 +113,7 @@ To send transaction you build to CKB network, you need to
1. sign transaction with your private key.
2. send signed transaction to CKB node, and wait it to be confirmed.
-Before signing and sending transaction, you need to prepare a raw transaction represented by an instance of class `TransactionWithScriptGroups`. You can get it [by Mercury](#Build-transaction-with-Mercury) or [by ckb-indexer](#Build-transaction-with-indexer)
+Before signing and sending transaction, you need to prepare a raw transaction represented by an instance of class `TransactionWithScriptGroups`. You can get it [by Mercury](#Build-transaction-with-Mercury) or [by manual](#Build-transaction-by-manual)
```java
// 0. Set your private key
@@ -133,6 +126,7 @@ System.out.println(Numeric.toHexString(txHash));
```
### Generate a new address
+
In CKB world, a lock script can be represented as an address. `secp256k1_blake160` is the most common used address and here we show how to generate it.
```java
diff --git a/build.gradle b/build.gradle
index 38efe0536..36c9fc613 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2,13 +2,12 @@ buildscript {
ext.bouncycastleVersion = '1.65'
ext.rxjavaVersion = '2.2.21'
- ext.gsonVersion = '2.9.0'
- ext.okhttpVersion = '4.9.1'
- ext.loggingOkhttpVersion = '4.9.1'
- ext.slf4jVersion = '1.7.30'
- ext.guavaVersion = '30.1.1-jre'
-
- ext.junitVersion = '5.7.1'
+ ext.gsonVersion = '2.9.1'
+ ext.okhttpVersion = '4.10.0'
+ ext.loggingOkhttpVersion = '4.10.0'
+ ext.slf4jVersion = '2.0.0'
+ ext.guavaVersion = '31.1-jre'
+ ext.junitVersion = '5.9.0'
repositories {
jcenter()
@@ -42,7 +41,7 @@ allprojects {
targetCompatibility = 1.8
group 'org.nervos.ckb'
- version '2.0.3'
+ version '2.1.0'
apply plugin: 'java'
repositories {
@@ -55,9 +54,10 @@ allprojects {
}
test {
+ ignoreFailures = true
useJUnitPlatform()
}
-
+ apply from: rootProject.file('gradle/checkstyle.gradle')
}
configure(subprojects.findAll { it.name != 'tests' }) {
@@ -70,17 +70,17 @@ configure(subprojects.findAll { it.name != 'tests' }) {
apply plugin: 'com.jfrog.bintray'
task javadocJar(type: Jar) {
- classifier = 'javadoc'
+ archiveClassifier.set('javadoc')
from javadoc
}
task sourcesJar(type: Jar) {
- classifier = 'sources'
+ archiveClassifier.set('sources')
from sourceSets.main.allSource
}
task testJar(type: Jar) {
- classifier = 'tests'
+ archiveClassifier.set('test-sources')
from sourceSets.test.output
}
@@ -97,7 +97,7 @@ configure(subprojects.findAll { it.name != 'tests' }) {
publications {
mavenJava(MavenPublication) {
groupId 'org.nervos.ckb'
- version '2.0.3'
+ version '2.1.0'
from components.java
}
}
diff --git a/ckb-api/build.gradle b/ckb-api/build.gradle
deleted file mode 100644
index 6d0394e1f..000000000
--- a/ckb-api/build.gradle
+++ /dev/null
@@ -1,14 +0,0 @@
-description 'SDK API'
-
-dependencies {
- compile project(":ckb")
- compile project(":ckb-mercury-sdk")
- compile project(":ckb-indexer")
-}
-
-apply from: rootProject.file('gradle/checkstyle.gradle')
-
-
-test {
- useJUnitPlatform()
-}
diff --git a/ckb-api/src/main/java/org/nervos/api/CkbApi.java b/ckb-api/src/main/java/org/nervos/api/CkbApi.java
deleted file mode 100644
index 4c4b9a2d2..000000000
--- a/ckb-api/src/main/java/org/nervos/api/CkbApi.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package org.nervos.api;
-
-import org.nervos.ckb.CkbRpcApi;
-import org.nervos.indexer.CkbIndexerApi;
-import org.nervos.mercury.MercuryApi;
-
-public interface CkbApi extends CkbRpcApi, MercuryApi, CkbIndexerApi {}
diff --git a/ckb-api/src/main/java/org/nervos/api/DefaultCkbApi.java b/ckb-api/src/main/java/org/nervos/api/DefaultCkbApi.java
deleted file mode 100644
index 0bb375555..000000000
--- a/ckb-api/src/main/java/org/nervos/api/DefaultCkbApi.java
+++ /dev/null
@@ -1,404 +0,0 @@
-package org.nervos.api;
-
-import org.nervos.ckb.CkbRpcApi;
-import org.nervos.ckb.service.Api;
-import org.nervos.ckb.service.RpcResponse;
-import org.nervos.ckb.service.RpcService;
-import org.nervos.ckb.sign.TransactionWithScriptGroups;
-import org.nervos.ckb.type.*;
-import org.nervos.indexer.CkbIndexerApi;
-import org.nervos.indexer.DefaultIndexerApi;
-import org.nervos.indexer.model.Order;
-import org.nervos.indexer.model.SearchKey;
-import org.nervos.indexer.model.resp.CellCapacityResponse;
-import org.nervos.indexer.model.resp.CellsResponse;
-import org.nervos.indexer.model.resp.TipResponse;
-import org.nervos.indexer.model.resp.TransactionResponse;
-import org.nervos.mercury.DefaultMercuryApi;
-import org.nervos.mercury.MercuryApi;
-import org.nervos.mercury.model.common.PaginationResponse;
-import org.nervos.mercury.model.req.payload.*;
-import org.nervos.mercury.model.resp.*;
-import org.nervos.mercury.model.resp.info.DBInfo;
-import org.nervos.mercury.model.resp.info.MercuryInfo;
-import org.nervos.mercury.model.resp.info.MercurySyncState;
-
-import java.io.IOException;
-import java.util.List;
-import java.util.Objects;
-
-public class DefaultCkbApi implements CkbApi {
- private CkbRpcApi ckbApi;
-
- private MercuryApi mercuryApi;
-
- private CkbIndexerApi ckbIndexerApi;
-
- public DefaultCkbApi(String ckbUrl, String mercuryUrl, String indexerUrl, boolean isDebug) {
- if (Objects.nonNull(ckbUrl)) {
- this.ckbApi = new Api(new RpcService(ckbUrl, isDebug));
- }
-
- if (Objects.nonNull(indexerUrl)) {
- this.ckbIndexerApi = new DefaultIndexerApi(new RpcService(indexerUrl, isDebug));
- }
-
- if (Objects.nonNull(mercuryUrl)) {
- this.mercuryApi = new DefaultMercuryApi(new RpcService(mercuryUrl, isDebug));
- }
- }
-
- @Override
- public TipResponse getTip() throws IOException {
- if (this.mercuryApi != null) {
- return this.mercuryApi.getTip();
- }
-
- if (this.ckbIndexerApi != null) {
- return this.ckbIndexerApi.getTip();
- }
-
- return null;
- }
-
- @Override
- public CellsResponse getCells(SearchKey searchKey, Order order, int limit, byte[] afterCursor)
- throws IOException {
-
- if (this.mercuryApi != null) {
- // return this.mercuryApi.getCells(searchKey, order, limit, afterCursor);
- }
-
- if (this.ckbIndexerApi != null) {
- return this.ckbIndexerApi.getCells(searchKey, order, limit, afterCursor);
- }
-
- return null;
- }
-
- @Override
- public TransactionResponse getTransactions(
- SearchKey searchKey, Order order, int limit, byte[] afterCursor) throws IOException {
-
- if (this.mercuryApi != null) {
- // return this.mercuryApi.getTransactions(searchKey, order, limit, afterCursor);
- }
-
- if (this.ckbIndexerApi != null) {
- return this.ckbIndexerApi.getTransactions(searchKey, order, limit, afterCursor);
- }
- return null;
- }
-
- @Override
- public CellCapacityResponse getCellsCapacity(SearchKey searchKey) throws IOException {
-
- if (this.mercuryApi != null) {
- return this.mercuryApi.getCellsCapacity(searchKey);
- }
- if (this.ckbIndexerApi != null) {
- return this.ckbIndexerApi.getCellsCapacity(searchKey);
- }
-
- return null;
- }
-
- @Override
- public Block getBlock(byte[] blockHash) throws IOException {
- return this.ckbApi.getBlock(blockHash);
- }
-
- @Override
- public Block getBlockByNumber(long blockNumber) throws IOException {
- return this.ckbApi.getBlockByNumber(blockNumber);
- }
-
- @Override
- public TransactionWithStatus getTransaction(byte[] transactionHash) throws IOException {
- return this.ckbApi.getTransaction(transactionHash);
- }
-
- @Override
- public byte[] getBlockHash(long blockNumber) throws IOException {
- return this.ckbApi.getBlockHash(blockNumber);
- }
-
- @Override
- public BlockEconomicState getBlockEconomicState(byte[] blockHash) throws IOException {
- return this.ckbApi.getBlockEconomicState(blockHash);
- }
-
- @Override
- public Header getTipHeader() throws IOException {
- return this.ckbApi.getTipHeader();
- }
-
- @Override
- public CellWithStatus getLiveCell(OutPoint outPoint, boolean withData) throws IOException {
- return this.ckbApi.getLiveCell(outPoint, withData);
- }
-
- @Override
- public long getTipBlockNumber() throws IOException {
- return this.ckbApi.getTipBlockNumber();
- }
-
- @Override
- public Epoch getCurrentEpoch() throws IOException {
- return this.ckbApi.getCurrentEpoch();
- }
-
- @Override
- public Epoch getEpochByNumber(long epochNumber) throws IOException {
- return this.ckbApi.getEpochByNumber(epochNumber);
- }
-
- @Override
- public Header getHeader(byte[] blockHash) throws IOException {
- return this.ckbApi.getHeader(blockHash);
- }
-
- @Override
- public Header getHeaderByNumber(long blockNumber) throws IOException {
- return this.ckbApi.getHeaderByNumber(blockNumber);
- }
-
- @Override
- public TransactionProof getTransactionProof(List txHashes) throws IOException {
- return this.ckbApi.getTransactionProof(txHashes);
- }
-
- @Override
- public TransactionProof getTransactionProof(List txHashes, byte[] blockHash)
- throws IOException {
- return this.ckbApi.getTransactionProof(txHashes, blockHash);
- }
-
- @Override
- public List verifyTransactionProof(TransactionProof transactionProof) throws IOException {
- return this.ckbApi.verifyTransactionProof(transactionProof);
- }
-
- @Override
- public Block getForkBlock(byte[] blockHash) throws IOException {
- return this.ckbApi.getForkBlock(blockHash);
- }
-
- @Override
- public Consensus getConsensus() throws IOException {
- return this.ckbApi.getConsensus();
- }
-
- @Override
- public long getBlockMedianTime(byte[] blockHash) throws IOException {
- return this.ckbApi.getBlockMedianTime(blockHash);
- }
-
- @Override
- public BlockchainInfo getBlockchainInfo() throws IOException {
- return this.ckbApi.getBlockchainInfo();
- }
-
- @Override
- public TxPoolInfo txPoolInfo() throws IOException {
- return this.ckbApi.txPoolInfo();
- }
-
- @Override
- public void clearTxPool() throws IOException {
- this.ckbApi.clearTxPool();
- }
-
- @Override
- public RawTxPool getRawTxPool() throws IOException {
- return this.ckbApi.getRawTxPool();
- }
-
- @Override
- public RawTxPoolVerbose getRawTxPoolVerbose() throws IOException {
- return this.ckbApi.getRawTxPoolVerbose();
- }
-
- @Override
- public byte[] sendTransaction(Transaction transaction) throws IOException {
- return this.ckbApi.sendTransaction(transaction);
- }
-
- @Override
- public byte[] sendTransaction(Transaction transaction, OutputsValidator outputsValidator)
- throws IOException {
- return this.ckbApi.sendTransaction(transaction, outputsValidator);
- }
-
- @Override
- public NodeInfo localNodeInfo() throws IOException {
- return this.ckbApi.localNodeInfo();
- }
-
- @Override
- public List getPeers() throws IOException {
- return this.ckbApi.getPeers();
- }
-
- @Override
- public SyncState syncState() throws IOException {
- return this.ckbApi.syncState();
- }
-
- @Override
- public void setNetworkActive(boolean state) throws IOException {
- this.ckbApi.setNetworkActive(state);
- }
-
- @Override
- public void addNode(String peerId, String address) throws IOException {
- this.ckbApi.addNode(peerId, address);
- }
-
- @Override
- public void removeNode(String peerId) throws IOException {
- this.ckbApi.removeNode(peerId);
- }
-
- @Override
- public void setBan(BannedAddress bannedAddress) throws IOException {
- this.ckbApi.setBan(bannedAddress);
- }
-
- @Override
- public List getBannedAddresses() throws IOException {
- return this.ckbApi.getBannedAddresses();
- }
-
- @Override
- public void clearBannedAddresses() throws IOException {
- this.ckbApi.clearBannedAddresses();
- }
-
- @Override
- public void pingPeers() throws IOException {
- this.ckbApi.pingPeers();
- }
-
- @Override
- public Cycles dryRunTransaction(Transaction transaction) throws IOException {
- return this.ckbApi.dryRunTransaction(transaction);
- }
-
- @Override
- public long calculateDaoMaximumWithdraw(OutPoint outPoint, byte[] withdrawBlockHash)
- throws IOException {
- return this.ckbApi.calculateDaoMaximumWithdraw(outPoint, withdrawBlockHash);
- }
-
- @Override
- public List batchRPC(List requests) throws IOException {
- return this.ckbApi.batchRPC(requests);
- }
-
- @Override
- public GetBalanceResponse getBalance(GetBalancePayload payload) throws IOException {
- return this.mercuryApi.getBalance(payload);
- }
-
- @Override
- public AccountInfo getAccountInfo(AccountInfoPayload payload) throws IOException {
- return this.mercuryApi.getAccountInfo(payload);
- }
-
- @Override
- public TransactionWithScriptGroups buildTransferTransaction(TransferPayload payload)
- throws IOException {
- return this.mercuryApi.buildTransferTransaction(payload);
- }
-
- @Override
- public TransactionWithScriptGroups buildAdjustAccountTransaction(AdjustAccountPayload payload)
- throws IOException {
- return this.mercuryApi.buildAdjustAccountTransaction(payload);
- }
-
- @Override
- public TransactionWithScriptGroups buildSimpleTransferTransaction(SimpleTransferPayload payload)
- throws IOException {
- return this.mercuryApi.buildSimpleTransferTransaction(payload);
- }
-
- @Override
- public GetTransactionInfoResponse getTransactionInfo(byte[] txHash) throws IOException {
- return this.mercuryApi.getTransactionInfo(txHash);
- }
-
- @Override
- public BlockInfoResponse getBlockInfo(GetBlockInfoPayload payload) throws IOException {
- return this.mercuryApi.getBlockInfo(payload);
- }
-
- @Override
- public List registerAddresses(List normalAddresses) throws IOException {
- return this.mercuryApi.registerAddresses(normalAddresses);
- }
-
- @Override
- public PaginationResponse queryTransactionsWithTransactionView(
- QueryTransactionsPayload payload) throws IOException {
- return this.mercuryApi.queryTransactionsWithTransactionView(payload);
- }
-
- @Override
- public PaginationResponse queryTransactionsWithTransactionInfo(
- QueryTransactionsPayload payload) throws IOException {
- return this.mercuryApi.queryTransactionsWithTransactionInfo(payload);
- }
-
- @Override
- public DBInfo getDbInfo() throws IOException {
- return this.mercuryApi.getDbInfo();
- }
-
- @Override
- public MercuryInfo getMercuryInfo() throws IOException {
- return this.mercuryApi.getMercuryInfo();
- }
-
- @Override
- public MercurySyncState getSyncState() throws IOException {
- return this.mercuryApi.getSyncState();
- }
-
- @Override
- public TxView getSpentTransactionWithTransactionView(
- GetSpentTransactionPayload payload) throws IOException {
- return this.mercuryApi.getSpentTransactionWithTransactionView(payload);
- }
-
- @Override
- public TxView getSpentTransactionWithTransactionInfo(
- GetSpentTransactionPayload payload) throws IOException {
- return this.mercuryApi.getSpentTransactionWithTransactionInfo(payload);
- }
-
- @Override
- public TransactionWithScriptGroups buildDaoDepositTransaction(DaoDepositPayload payload)
- throws IOException {
- return this.mercuryApi.buildDaoDepositTransaction(payload);
- }
-
- @Override
- public TransactionWithScriptGroups buildDaoWithdrawTransaction(DaoWithdrawPayload payload)
- throws IOException {
- return this.mercuryApi.buildDaoWithdrawTransaction(payload);
- }
-
- @Override
- public TransactionWithScriptGroups buildDaoClaimTransaction(DaoClaimPayload payload)
- throws IOException {
- return this.mercuryApi.buildDaoClaimTransaction(payload);
- }
-
- @Override
- public TransactionWithScriptGroups buildSudtIssueTransaction(SudtIssuePayload payload)
- throws IOException {
- return this.mercuryApi.buildSudtIssueTransaction(payload);
- }
-}
diff --git a/ckb-indexer/build.gradle b/ckb-indexer/build.gradle
index 9d3a041aa..c938f3d6e 100644
--- a/ckb-indexer/build.gradle
+++ b/ckb-indexer/build.gradle
@@ -2,16 +2,13 @@ description 'SDK for CKB indexer'
dependencies {
compile project(":core")
- testCompile("org.junit.jupiter:junit-jupiter-api:5.0.0-M4")
- testRuntime("org.junit.jupiter:junit-jupiter-engine:5.0.0-M4")
+ testCompile("org.junit.jupiter:junit-jupiter-api:5.9.0")
+ testRuntime("org.junit.jupiter:junit-jupiter-engine:5.9.0")
// Enable use of the JUnitPlatform Runner within the IDE
- testCompile("org.junit.platform:junit-platform-runner:1.0.0-M4")
+ testCompile("org.junit.platform:junit-platform-runner:1.9.0")
}
-apply from: rootProject.file('gradle/checkstyle.gradle')
-
-
test {
useJUnitPlatform()
}
diff --git a/ckb-indexer/src/main/java/org/nervos/indexer/CkbIndexerApi.java b/ckb-indexer/src/main/java/org/nervos/indexer/CkbIndexerApi.java
index 917df9229..a5726eef5 100644
--- a/ckb-indexer/src/main/java/org/nervos/indexer/CkbIndexerApi.java
+++ b/ckb-indexer/src/main/java/org/nervos/indexer/CkbIndexerApi.java
@@ -2,10 +2,7 @@
import org.nervos.indexer.model.Order;
import org.nervos.indexer.model.SearchKey;
-import org.nervos.indexer.model.resp.CellCapacityResponse;
-import org.nervos.indexer.model.resp.CellsResponse;
-import org.nervos.indexer.model.resp.TipResponse;
-import org.nervos.indexer.model.resp.TransactionResponse;
+import org.nervos.indexer.model.resp.*;
import java.io.IOException;
@@ -15,7 +12,10 @@ public interface CkbIndexerApi {
CellsResponse getCells(SearchKey searchKey, Order order, int limit, byte[] afterCursor)
throws IOException;
- TransactionResponse getTransactions(
+ TxsWithCell getTransactions(
+ SearchKey searchKey, Order order, int limit, byte[] afterCursor) throws IOException;
+
+ TxsWithCells getTransactionsGrouped(
SearchKey searchKey, Order order, int limit, byte[] afterCursor) throws IOException;
CellCapacityResponse getCellsCapacity(SearchKey searchKey) throws IOException;
diff --git a/ckb-indexer/src/main/java/org/nervos/indexer/CkbIndexerRpcMethods.java b/ckb-indexer/src/main/java/org/nervos/indexer/CkbIndexerRpcMethods.java
index f364ca436..75b85d521 100644
--- a/ckb-indexer/src/main/java/org/nervos/indexer/CkbIndexerRpcMethods.java
+++ b/ckb-indexer/src/main/java/org/nervos/indexer/CkbIndexerRpcMethods.java
@@ -2,6 +2,7 @@
public interface CkbIndexerRpcMethods {
String GET_TIP = "get_tip";
+ String GET_INDEXER_TIP = "get_indexer_tip";
String GET_CELLS = "get_cells";
String GET_TRANSACTIONS = "get_transactions";
String GET_CELLS_CAPACITY = "get_cells_capacity";
diff --git a/ckb-indexer/src/main/java/org/nervos/indexer/Configuration.java b/ckb-indexer/src/main/java/org/nervos/indexer/Configuration.java
new file mode 100644
index 000000000..c1180c944
--- /dev/null
+++ b/ckb-indexer/src/main/java/org/nervos/indexer/Configuration.java
@@ -0,0 +1,40 @@
+package org.nervos.indexer;
+
+import org.nervos.ckb.Network;
+
+public class Configuration {
+ private IndexerType indexerType = IndexerType.CkbModule;
+
+ public void setIndexerUrl(String indexerUrl) {
+ this.indexerUrl = indexerUrl;
+ }
+
+ private String indexerUrl;
+ private static final class InstanceHolder {
+ static final Configuration instance = new Configuration();
+ }
+
+ public static Configuration getInstance() {
+ return InstanceHolder.instance;
+ }
+
+ public void setIndexType(IndexerType type) {
+ this.indexerType = type;
+ }
+
+ public IndexerType getIndexerType() {
+ return this.indexerType;
+ }
+
+ /**
+ * Get index url, if indexerUrl is set, use the indexerUrl, or use the value in IndexerType.
+ * @param network main net or test net.
+ * @return The indexer url.
+ */
+ public String getUrl(Network network) {
+ if (this.indexerUrl != null) {
+ return indexerUrl;
+ }
+ return indexerType.getUrl(network);
+ }
+}
diff --git a/ckb-indexer/src/main/java/org/nervos/indexer/DefaultIndexerApi.java b/ckb-indexer/src/main/java/org/nervos/indexer/DefaultIndexerApi.java
index b8a0856a5..396239538 100644
--- a/ckb-indexer/src/main/java/org/nervos/indexer/DefaultIndexerApi.java
+++ b/ckb-indexer/src/main/java/org/nervos/indexer/DefaultIndexerApi.java
@@ -3,13 +3,11 @@
import org.nervos.ckb.service.RpcService;
import org.nervos.indexer.model.Order;
import org.nervos.indexer.model.SearchKey;
-import org.nervos.indexer.model.resp.CellCapacityResponse;
-import org.nervos.indexer.model.resp.CellsResponse;
-import org.nervos.indexer.model.resp.TipResponse;
-import org.nervos.indexer.model.resp.TransactionResponse;
+import org.nervos.indexer.model.resp.*;
import java.io.IOException;
import java.util.Arrays;
+import java.util.Collections;
public class DefaultIndexerApi implements CkbIndexerApi {
@@ -25,7 +23,15 @@ public DefaultIndexerApi(RpcService rpcService) {
@Override
public TipResponse getTip() throws IOException {
- return this.rpcService.post(CkbIndexerRpcMethods.GET_TIP, Arrays.asList(), TipResponse.class);
+ IndexerType type = Configuration.getInstance().getIndexerType();
+ String method;
+ switch(type) {
+ case StandAlone: method = CkbIndexerRpcMethods.GET_TIP; break;
+ case CkbModule: method =CkbIndexerRpcMethods.GET_INDEXER_TIP; break;
+ default:
+ throw new IllegalStateException("Unsupported index type:"+ type);
+ }
+ return this.rpcService.post(method, Collections.emptyList(), TipResponse.class);
}
@Override
@@ -38,12 +44,23 @@ public CellsResponse getCells(SearchKey searchKey, Order order, int limit, byte[
}
@Override
- public TransactionResponse getTransactions(
+ public TxsWithCell getTransactions(
SearchKey searchKey, Order order, int limit, byte[] afterCursor) throws IOException {
+ searchKey.groupByTransaction = false;
return this.rpcService.post(
CkbIndexerRpcMethods.GET_TRANSACTIONS,
Arrays.asList(searchKey, order, limit, afterCursor),
- TransactionResponse.class);
+ TxsWithCell.class);
+ }
+
+ @Override
+ public TxsWithCells getTransactionsGrouped(
+ SearchKey searchKey, Order order, int limit, byte[] afterCursor) throws IOException {
+ searchKey.groupByTransaction = true;
+ return this.rpcService.post(
+ CkbIndexerRpcMethods.GET_TRANSACTIONS,
+ Arrays.asList(searchKey, order, limit, afterCursor),
+ TxsWithCells.class);
}
@Override
diff --git a/ckb-indexer/src/main/java/org/nervos/indexer/IndexerType.java b/ckb-indexer/src/main/java/org/nervos/indexer/IndexerType.java
new file mode 100644
index 000000000..7a9da5b19
--- /dev/null
+++ b/ckb-indexer/src/main/java/org/nervos/indexer/IndexerType.java
@@ -0,0 +1,27 @@
+package org.nervos.indexer;
+
+import org.nervos.ckb.Network;
+
+public enum IndexerType {
+ StandAlone("https://mainnet.ckb.dev/indexer", "https://testnet.ckb.dev/indexer"),
+ CkbModule("https://mainnet.ckb.dev/", "https://testnet.ckb.dev/");
+
+ final String mainNetUrl;
+ final String testNetUrl;
+
+ IndexerType(String mainNetUrl, String testNetUrl) {
+ this.mainNetUrl = mainNetUrl;
+ this.testNetUrl = testNetUrl;
+ }
+
+ public String getUrl(Network network) {
+ switch (network) {
+ case MAINNET:
+ return this.mainNetUrl;
+ case TESTNET:
+ return this.testNetUrl;
+ default:
+ throw new IllegalStateException("Unsupported network:"+ network);
+ }
+ }
+}
diff --git a/ckb-indexer/src/main/java/org/nervos/indexer/InputIterator.java b/ckb-indexer/src/main/java/org/nervos/indexer/InputIterator.java
index 0b028f825..85b55042b 100644
--- a/ckb-indexer/src/main/java/org/nervos/indexer/InputIterator.java
+++ b/ckb-indexer/src/main/java/org/nervos/indexer/InputIterator.java
@@ -52,6 +52,21 @@ public InputIterator addSearchKey(String address) {
return addSearchKey(address, null);
}
+ public InputIterator addSearchKey(String address, Script type) {
+ Script lockScript = Address.decode(address).getScript();
+
+ SearchKey searchKey = new SearchKey();
+ searchKey.scriptType = ScriptType.LOCK;
+ searchKey.script = lockScript;
+ if (type != null) {
+ Filter filter = new Filter();
+ filter.script = type;
+ searchKey.filter = filter;
+ }
+ searchKeys.add(searchKey);
+ return this;
+ }
+
public InputIterator addSudtSearchKey(String address, byte[] sudtArgs) {
Address addr = Address.decode(address);
Network network = addr.getNetwork();
@@ -70,34 +85,8 @@ public InputIterator addSudtSearchKey(String address, byte[] sudtArgs) {
return addSearchKey(address, type);
}
-
- public InputIterator addSearchKey(String address, Script type) {
- Script lockScript = Address.decode(address).getScript();
-
- SearchKey searchKey = new SearchKey();
- searchKey.scriptType = ScriptType.LOCK;
- searchKey.script = lockScript;
- if (type != null) {
- Filter filter = new Filter();
- filter.script = type;
- searchKey.filter = filter;
- }
- searchKeys.add(searchKey);
- return this;
- }
-
private static CkbIndexerApi getDefaultIndexerApi(Network network) {
- String url;
- switch (network) {
- case MAINNET:
- url = "https://mainnet.ckb.dev/indexer";
- break;
- case TESTNET:
- url = "https://testnet.ckb.dev/indexer";
- break;
- default:
- throw new IllegalArgumentException("Unsupported network");
- }
+ String url = Configuration.getInstance().getIndexerType().getUrl(network);
return new DefaultIndexerApi(url, false);
}
diff --git a/ckb-indexer/src/main/java/org/nervos/indexer/model/SearchKey.java b/ckb-indexer/src/main/java/org/nervos/indexer/model/SearchKey.java
index b63773e46..caa7e00f8 100644
--- a/ckb-indexer/src/main/java/org/nervos/indexer/model/SearchKey.java
+++ b/ckb-indexer/src/main/java/org/nervos/indexer/model/SearchKey.java
@@ -7,4 +7,5 @@ public class SearchKey {
public Script script;
public ScriptType scriptType;
public Filter filter;
+ public boolean groupByTransaction;
}
diff --git a/ckb-indexer/src/main/java/org/nervos/indexer/model/resp/IoType.java b/ckb-indexer/src/main/java/org/nervos/indexer/model/resp/IoType.java
new file mode 100644
index 000000000..5a210c7ec
--- /dev/null
+++ b/ckb-indexer/src/main/java/org/nervos/indexer/model/resp/IoType.java
@@ -0,0 +1,10 @@
+package org.nervos.indexer.model.resp;
+
+import com.google.gson.annotations.SerializedName;
+
+public enum IoType {
+ @SerializedName("input")
+ INPUT,
+ @SerializedName("output")
+ OUTPUT
+}
diff --git a/ckb-indexer/src/main/java/org/nervos/indexer/model/resp/TransactionInfoResponse.java b/ckb-indexer/src/main/java/org/nervos/indexer/model/resp/TransactionInfoResponse.java
deleted file mode 100644
index 5b6a99435..000000000
--- a/ckb-indexer/src/main/java/org/nervos/indexer/model/resp/TransactionInfoResponse.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package org.nervos.indexer.model.resp;
-
-import com.google.gson.annotations.SerializedName;
-
-public class TransactionInfoResponse {
- public int blockNumber;
- public int ioIndex;
- public IoType ioType;
- public byte[] txHash;
- public int txIndex;
-
- public enum IoType {
- @SerializedName("input")
- INPUT,
- @SerializedName("output")
- OUTPUT;
- }
-}
diff --git a/ckb-indexer/src/main/java/org/nervos/indexer/model/resp/TxWithCell.java b/ckb-indexer/src/main/java/org/nervos/indexer/model/resp/TxWithCell.java
new file mode 100644
index 000000000..5481308d0
--- /dev/null
+++ b/ckb-indexer/src/main/java/org/nervos/indexer/model/resp/TxWithCell.java
@@ -0,0 +1,9 @@
+package org.nervos.indexer.model.resp;
+
+public class TxWithCell {
+ public int blockNumber;
+ public int ioIndex;
+ public IoType ioType;
+ public byte[] txHash;
+ public int txIndex;
+}
diff --git a/ckb-indexer/src/main/java/org/nervos/indexer/model/resp/TxWithCells.java b/ckb-indexer/src/main/java/org/nervos/indexer/model/resp/TxWithCells.java
new file mode 100644
index 000000000..5f114f893
--- /dev/null
+++ b/ckb-indexer/src/main/java/org/nervos/indexer/model/resp/TxWithCells.java
@@ -0,0 +1,32 @@
+package org.nervos.indexer.model.resp;
+
+import com.google.gson.*;
+import com.google.gson.annotations.JsonAdapter;
+
+import java.lang.reflect.Type;
+import java.util.List;
+
+public class TxWithCells {
+ public byte[] txHash;
+ public int blockNumber;
+ public int txIndex;
+ public List cells;
+
+ @JsonAdapter(Deserializer.class)
+ public static class Cell {
+ public IoType ioType;
+ public int ioIndex;
+ }
+
+ public static class Deserializer implements JsonDeserializer {
+
+ @Override
+ public Cell deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
+ JsonArray jsonArray = json.getAsJsonArray();
+ Cell cell = new Cell();
+ cell.ioType = context.deserialize(jsonArray.get(0), IoType.class);
+ cell.ioIndex = context.deserialize(jsonArray.get(1), Integer.class);
+ return cell;
+ }
+ }
+}
diff --git a/ckb-indexer/src/main/java/org/nervos/indexer/model/resp/TransactionResponse.java b/ckb-indexer/src/main/java/org/nervos/indexer/model/resp/TxsWithCell.java
similarity index 53%
rename from ckb-indexer/src/main/java/org/nervos/indexer/model/resp/TransactionResponse.java
rename to ckb-indexer/src/main/java/org/nervos/indexer/model/resp/TxsWithCell.java
index e89ae54d9..66c4c08ce 100644
--- a/ckb-indexer/src/main/java/org/nervos/indexer/model/resp/TransactionResponse.java
+++ b/ckb-indexer/src/main/java/org/nervos/indexer/model/resp/TxsWithCell.java
@@ -2,7 +2,7 @@
import java.util.List;
-public class TransactionResponse {
+public class TxsWithCell {
public byte[] lastCursor;
- public List objects;
+ public List objects;
}
diff --git a/ckb-indexer/src/main/java/org/nervos/indexer/model/resp/TxsWithCells.java b/ckb-indexer/src/main/java/org/nervos/indexer/model/resp/TxsWithCells.java
new file mode 100644
index 000000000..44faa1241
--- /dev/null
+++ b/ckb-indexer/src/main/java/org/nervos/indexer/model/resp/TxsWithCells.java
@@ -0,0 +1,8 @@
+package org.nervos.indexer.model.resp;
+
+import java.util.List;
+
+public class TxsWithCells {
+ public byte[] lastCursor;
+ public List objects;
+}
diff --git a/ckb-indexer/src/test/java/indexer/CkbIndexerFactory.java b/ckb-indexer/src/test/java/indexer/CkbIndexerFactory.java
index 76cca8dc4..150d2170b 100644
--- a/ckb-indexer/src/test/java/indexer/CkbIndexerFactory.java
+++ b/ckb-indexer/src/test/java/indexer/CkbIndexerFactory.java
@@ -1,13 +1,21 @@
package indexer;
+import org.nervos.ckb.Network;
import org.nervos.indexer.CkbIndexerApi;
+import org.nervos.indexer.Configuration;
import org.nervos.indexer.DefaultIndexerApi;
+import org.nervos.indexer.IndexerType;
+
+import java.util.HashMap;
public class CkbIndexerFactory {
- private static final String NODE_URL = "https://testnet.ckb.dev/indexer";
- private static CkbIndexerApi API = new DefaultIndexerApi(NODE_URL, false);
+ HashMap apiDict = new HashMap<>();
+ private static final class InstanceHolder {
+ static final CkbIndexerFactory instance = new CkbIndexerFactory();
+ }
public static CkbIndexerApi getApi() {
- return API;
+ String url = Configuration.getInstance().getUrl(Network.TESTNET);
+ return InstanceHolder.instance.apiDict.computeIfAbsent(url, (key) -> new DefaultIndexerApi(key, false));
}
}
diff --git a/ckb-indexer/src/test/java/indexer/TipTest.java b/ckb-indexer/src/test/java/indexer/TipTest.java
index 91150d0ac..b55882a10 100644
--- a/ckb-indexer/src/test/java/indexer/TipTest.java
+++ b/ckb-indexer/src/test/java/indexer/TipTest.java
@@ -1,7 +1,11 @@
package indexer;
+import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
+import org.nervos.indexer.Configuration;
+import org.nervos.indexer.IndexerType;
import org.nervos.indexer.model.resp.TipResponse;
import java.io.IOException;
@@ -9,8 +13,51 @@
public class TipTest {
@Test
void getTip() throws IOException {
+ Configuration.getInstance().setIndexerUrl(null);
TipResponse tip = CkbIndexerFactory.getApi().getTip();
Assertions.assertNotNull(tip.blockHash);
Assertions.assertNotEquals(0, tip.blockNumber);
+
+ Configuration.getInstance().setIndexType(IndexerType.CkbModule);
+ TipResponse tip2 = CkbIndexerFactory.getApi().getTip();
+ Assertions.assertNotNull(tip2.blockHash);
+ Assertions.assertNotEquals(0, tip2.blockNumber);
+
+ Assertions.assertTrue(tip.blockNumber <= tip2.blockNumber);
+ }
+
+ @Test
+ void getTipStandAlone() throws IOException {
+ Configuration.getInstance().setIndexType(IndexerType.StandAlone);
+ Configuration.getInstance().setIndexerUrl(null);
+ TipResponse tip = CkbIndexerFactory.getApi().getTip();
+ Assertions.assertNotNull(tip.blockHash);
+ Assertions.assertNotEquals(0, tip.blockNumber);
+ }
+
+ @Disabled
+ @Test
+ void getCkbModuleLocal() throws IOException {
+ Configuration.getInstance().setIndexType(IndexerType.CkbModule);
+ Configuration.getInstance().setIndexerUrl("http://127.0.0.1:8114");
+ TipResponse tip = CkbIndexerFactory.getApi().getTip();
+ Assertions.assertNotNull(tip.blockHash);
+ Assertions.assertNotEquals(0, tip.blockNumber);
+ }
+
+ @Disabled
+ @Test
+ void getStandAloneLocal() throws IOException {
+ Configuration.getInstance().setIndexType(IndexerType.StandAlone);
+ Configuration.getInstance().setIndexerUrl("http://127.0.0.1:8116");
+ TipResponse tip = CkbIndexerFactory.getApi().getTip();
+ Assertions.assertNotNull(tip.blockHash);
+ Assertions.assertNotEquals(0, tip.blockNumber);
+ }
+
+ @AfterAll
+ public static void cleanUp() {
+ Configuration.getInstance().setIndexType(IndexerType.CkbModule);
+ Configuration.getInstance().setIndexerUrl(null);
}
}
diff --git a/ckb-indexer/src/test/java/indexer/TransactionTest.java b/ckb-indexer/src/test/java/indexer/TransactionTest.java
index 294e06ae5..68e6bec3d 100644
--- a/ckb-indexer/src/test/java/indexer/TransactionTest.java
+++ b/ckb-indexer/src/test/java/indexer/TransactionTest.java
@@ -7,7 +7,8 @@
import org.nervos.ckb.utils.Numeric;
import org.nervos.indexer.model.Order;
import org.nervos.indexer.model.SearchKeyBuilder;
-import org.nervos.indexer.model.resp.TransactionResponse;
+import org.nervos.indexer.model.resp.TxsWithCell;
+import org.nervos.indexer.model.resp.TxsWithCells;
import java.io.IOException;
@@ -24,8 +25,26 @@ void testTransaction() throws IOException {
Script.HashType.TYPE));
key.scriptType(ScriptType.LOCK);
- TransactionResponse txs =
+ TxsWithCell txs =
CkbIndexerFactory.getApi().getTransactions(key.build(), Order.ASC, 10, null);
Assertions.assertTrue(txs.objects.size() > 0);
}
+
+ @Test
+ void testTransactionsGrouped() throws IOException {
+ SearchKeyBuilder key = new SearchKeyBuilder();
+ key.script(
+ new Script(
+ Numeric.hexStringToByteArray(
+ "0x58c5f491aba6d61678b7cf7edf4910b1f5e00ec0cde2f42e0abb4fd9aff25a63"),
+ Numeric.hexStringToByteArray("0xe53f35ccf63bb37a3bb0ac3b7f89808077a78eae"),
+ Script.HashType.TYPE));
+ key.scriptType(ScriptType.LOCK);
+
+ TxsWithCells txs =
+ CkbIndexerFactory.getApi().getTransactionsGrouped(key.build(), Order.ASC, 10, null);
+ Assertions.assertTrue(txs.objects.size() > 0);
+ Assertions.assertNotNull(txs.objects.get(0));
+ Assertions.assertNotNull(txs.objects.get(0).cells.get(0));
+ }
}
diff --git a/ckb-mercury-sdk/build.gradle b/ckb-mercury-sdk/build.gradle
index ef56180d7..733532cc2 100644
--- a/ckb-mercury-sdk/build.gradle
+++ b/ckb-mercury-sdk/build.gradle
@@ -4,15 +4,12 @@ dependencies {
compile project(":core")
compile project(":ckb-indexer")
testCompile project(":ckb")
- testCompile("org.junit.jupiter:junit-jupiter-api:5.0.0-M4")
- testRuntime("org.junit.jupiter:junit-jupiter-engine:5.0.0-M4")
+ testCompile("org.junit.jupiter:junit-jupiter-api:5.9.0")
+ testRuntime("org.junit.jupiter:junit-jupiter-engine:5.9.0")
// Enable use of the JUnitPlatform Runner within the IDE
- testCompile("org.junit.platform:junit-platform-runner:1.0.0-M4")
+ testCompile("org.junit.platform:junit-platform-runner:1.9.0")
}
-apply from: rootProject.file('gradle/checkstyle.gradle')
-
-
test {
useJUnitPlatform()
}
diff --git a/ckb-mercury-sdk/src/main/java/org/nervos/mercury/model/req/payload/GetBalancePayload.java b/ckb-mercury-sdk/src/main/java/org/nervos/mercury/model/req/payload/GetBalancePayload.java
index 5c522abcf..2626f42d9 100644
--- a/ckb-mercury-sdk/src/main/java/org/nervos/mercury/model/req/payload/GetBalancePayload.java
+++ b/ckb-mercury-sdk/src/main/java/org/nervos/mercury/model/req/payload/GetBalancePayload.java
@@ -1,6 +1,7 @@
package org.nervos.mercury.model.req.payload;
import org.nervos.mercury.model.common.AssetInfo;
+import org.nervos.mercury.model.common.ExtraFilter;
import org.nervos.mercury.model.req.item.Item;
import java.util.Set;
@@ -9,4 +10,5 @@ public class GetBalancePayload {
public Item item;
public Set assetInfos;
public Long tipBlockNumber;
+ public ExtraFilter.Type extra;
}
diff --git a/ckb-mercury-sdk/src/test/java/mercury/BuildSimpleTransferTransactionTest.java b/ckb-mercury-sdk/src/test/java/mercury/BuildSimpleTransferTransactionTest.java
index 456c94df8..6c7ffcc66 100644
--- a/ckb-mercury-sdk/src/test/java/mercury/BuildSimpleTransferTransactionTest.java
+++ b/ckb-mercury-sdk/src/test/java/mercury/BuildSimpleTransferTransactionTest.java
@@ -17,8 +17,8 @@ void testBuildTransferTransaction() throws IOException {
SimpleTransferPayloadBuilder builder = new SimpleTransferPayloadBuilder();
builder.assetInfo(AssetInfo.newCkbAsset());
builder.addFrom("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqfqyerlanzmnkxtmd9ww9n7gr66k8jt4tclm9jnk");
- builder.addTo("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqg958atl2zdh8jn3ch8lc72nt0cf864ecqdxm9zf"
- , BigInteger.valueOf(10000000000L));
+ builder.addTo("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqg958atl2zdh8jn3ch8lc72nt0cf864ecqdxm9zf",
+ BigInteger.valueOf(10000000000L));
builder.feeRate(500L);
TransactionWithScriptGroups s =
diff --git a/ckb-mercury-sdk/src/test/java/mercury/BuildTransferTransactionTest.java b/ckb-mercury-sdk/src/test/java/mercury/BuildTransferTransactionTest.java
index 504088fb0..6f2fe3e87 100644
--- a/ckb-mercury-sdk/src/test/java/mercury/BuildTransferTransactionTest.java
+++ b/ckb-mercury-sdk/src/test/java/mercury/BuildTransferTransactionTest.java
@@ -19,8 +19,8 @@ void testBuildTransferTransaction() throws IOException {
TransferPayloadBuilder builder = new TransferPayloadBuilder();
builder.assetInfo(AssetInfo.newCkbAsset());
builder.addFrom(ItemFactory.newAddressItem("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqfqyerlanzmnkxtmd9ww9n7gr66k8jt4tclm9jnk"));
- builder.addTo("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqg958atl2zdh8jn3ch8lc72nt0cf864ecqdxm9zf"
- , BigInteger.valueOf(100));
+ builder.addTo("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqg958atl2zdh8jn3ch8lc72nt0cf864ecqdxm9zf",
+ BigInteger.valueOf(100));
builder.payFee(TransferPayload.PayFee.FROM);
builder.feeRate(1100L);
diff --git a/ckb-mercury-sdk/src/test/java/mercury/RegisterAddressTest.java b/ckb-mercury-sdk/src/test/java/mercury/RegisterAddressTest.java
index 01ccc3cea..f12857690 100644
--- a/ckb-mercury-sdk/src/test/java/mercury/RegisterAddressTest.java
+++ b/ckb-mercury-sdk/src/test/java/mercury/RegisterAddressTest.java
@@ -13,6 +13,7 @@
public class RegisterAddressTest {
@Test
+ @SuppressWarnings("deprecation")
void testRegisterAddress() throws IOException {
List scriptHashes =
ApiFactory.getApi().registerAddresses(
diff --git a/ckb/build.gradle b/ckb/build.gradle
index 4544618da..9d89bb61d 100644
--- a/ckb/build.gradle
+++ b/ckb/build.gradle
@@ -2,9 +2,10 @@ description 'ckb-sdk-java is a lightweight Java library for integration with ner
dependencies {
compile project(":core")
+ compile project(":ckb-indexer")
+ compile project(":light-client")
compile project(":utils")
compile project(":serialization")
}
-apply from: rootProject.file('gradle/checkstyle.gradle')
apply plugin: 'com.github.johnrengelman.shadow'
diff --git a/ckb/src/main/java/org/nervos/ckb/CkbRpcApi.java b/ckb/src/main/java/org/nervos/ckb/CkbRpcApi.java
index f61f13d23..ee724e354 100644
--- a/ckb/src/main/java/org/nervos/ckb/CkbRpcApi.java
+++ b/ckb/src/main/java/org/nervos/ckb/CkbRpcApi.java
@@ -2,6 +2,9 @@
import org.nervos.ckb.service.RpcResponse;
import org.nervos.ckb.type.*;
+import org.nervos.indexer.model.Order;
+import org.nervos.indexer.model.SearchKey;
+import org.nervos.indexer.model.resp.*;
import java.io.IOException;
import java.util.List;
@@ -78,10 +81,36 @@ byte[] sendTransaction(Transaction transaction, OutputsValidator outputsValidato
void pingPeers() throws IOException;
+ @Deprecated
Cycles dryRunTransaction(Transaction transaction) throws IOException;
+ Cycles estimateCycles(Transaction transaction) throws IOException;
+
+ TipResponse getIndexerTip() throws IOException;
+
+ CellsResponse getCells(SearchKey searchKey, Order order, int limit, byte[] afterCursor)
+ throws IOException;
+
+ TxsWithCell getTransactions(
+ SearchKey searchKey, Order order, int limit, byte[] afterCursor) throws IOException;
+
+ TxsWithCells getTransactionsGrouped(
+ SearchKey searchKey, Order order, int limit, byte[] afterCursor) throws IOException;
+
+ CellCapacityResponse getCellsCapacity(SearchKey searchKey) throws IOException;
+
long calculateDaoMaximumWithdraw(OutPoint outPoint, byte[] withdrawBlockHash)
throws IOException;
List batchRPC(List requests) throws IOException;
+
+ /**
+ * Get the fee_rate statistics of confirmed blocks on the chain
+ *
+ * @param target Specify the number (1 - 101) of confirmed blocks to be counted.
+ * If the number is even, automatically add one. If not specified(null), defaults to 21.
+ * @return Returns the fee_rate statistics of confirmed blocks on the chain.
+ * @throws IOException if error there is an error
+ */
+ FeeRateStatics getFeeRateStatics(Integer target) throws IOException;
}
diff --git a/ckb/src/main/java/org/nervos/ckb/service/Api.java b/ckb/src/main/java/org/nervos/ckb/service/Api.java
index 91dd4ada5..774fa44ef 100644
--- a/ckb/src/main/java/org/nervos/ckb/service/Api.java
+++ b/ckb/src/main/java/org/nervos/ckb/service/Api.java
@@ -4,6 +4,9 @@
import org.nervos.ckb.CkbRpcApi;
import org.nervos.ckb.type.*;
import org.nervos.ckb.utils.Convert;
+import org.nervos.indexer.model.Order;
+import org.nervos.indexer.model.SearchKey;
+import org.nervos.indexer.model.resp.*;
import java.io.IOException;
import java.util.Arrays;
@@ -248,6 +251,52 @@ public Cycles dryRunTransaction(Transaction transaction) throws IOException {
Cycles.class);
}
+ @Override
+ public Cycles estimateCycles(Transaction transaction) throws IOException {
+ return rpcService.post(
+ "estimate_cycles",
+ Collections.singletonList(Convert.parseTransaction(transaction)),
+ Cycles.class);
+ }
+
+ @Override
+ public TipResponse getIndexerTip() throws IOException {
+ return this.rpcService.post("get_indexer_tip", Arrays.asList(), TipResponse.class);
+ }
+
+ @Override
+ public CellsResponse getCells(SearchKey searchKey, Order order, int limit, byte[] afterCursor)
+ throws IOException {
+ return this.rpcService.post("get_cells",
+ Arrays.asList(searchKey, order, limit, afterCursor),
+ CellsResponse.class);
+ }
+
+ @Override
+ public TxsWithCell getTransactions(
+ SearchKey searchKey, Order order, int limit, byte[] afterCursor) throws IOException {
+ searchKey.groupByTransaction = false;
+ return this.rpcService.post("get_transactions",
+ Arrays.asList(searchKey, order, limit, afterCursor),
+ TxsWithCell.class);
+ }
+
+ @Override
+ public TxsWithCells getTransactionsGrouped(
+ SearchKey searchKey, Order order, int limit, byte[] afterCursor) throws IOException {
+ searchKey.groupByTransaction = true;
+ return this.rpcService.post("get_transactions",
+ Arrays.asList(searchKey, order, limit, afterCursor),
+ TxsWithCells.class);
+ }
+
+ @Override
+ public CellCapacityResponse getCellsCapacity(SearchKey searchKey) throws IOException {
+ return this.rpcService.post("get_cells_capacity",
+ Arrays.asList(searchKey),
+ CellCapacityResponse.class);
+ }
+
@Override
public long calculateDaoMaximumWithdraw(OutPoint outPoint, byte[] withdrawBlockHash)
throws IOException {
@@ -269,4 +318,12 @@ public long calculateDaoMaximumWithdraw(OutPoint outPoint, byte[] withdrawBlockH
public List batchRPC(List requests) throws IOException {
return rpcService.batchPost(requests);
}
+
+ @Override
+ public FeeRateStatics getFeeRateStatics(Integer target) throws IOException {
+ return rpcService.post(
+ "get_fee_rate_statics",
+ target == null ? Collections.emptyList() : Collections.singletonList(target),
+ FeeRateStatics.class);
+ }
}
diff --git a/ckb/src/main/java/org/nervos/ckb/transaction/AbstractInputIterator.java b/ckb/src/main/java/org/nervos/ckb/transaction/AbstractInputIterator.java
new file mode 100644
index 000000000..9a19bc758
--- /dev/null
+++ b/ckb/src/main/java/org/nervos/ckb/transaction/AbstractInputIterator.java
@@ -0,0 +1,186 @@
+package org.nervos.ckb.transaction;
+
+import org.nervos.ckb.Network;
+import org.nervos.ckb.type.CellInput;
+import org.nervos.ckb.type.Script;
+import org.nervos.ckb.type.ScriptType;
+import org.nervos.ckb.type.TransactionInput;
+import org.nervos.ckb.utils.address.Address;
+import org.nervos.indexer.model.Filter;
+import org.nervos.indexer.model.Order;
+import org.nervos.indexer.model.SearchKey;
+import org.nervos.indexer.model.resp.CellResponse;
+import org.nervos.indexer.model.resp.CellsResponse;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+public abstract class AbstractInputIterator implements Iterator {
+ protected List transactionInputs = new ArrayList<>();
+ protected TransactionInput current;
+ protected byte[] afterCursor;
+
+ protected int inputIndex = 0;
+ protected int searchKeysIndex = 0;
+ protected List searchKeys = new ArrayList<>();
+ protected Order order = Order.ASC;
+ protected Integer limit = 100;
+
+ public List getTransactionInputs() {
+ return transactionInputs;
+ }
+
+ public void setTransactionInputs(List transactionInputs) {
+ this.transactionInputs = transactionInputs;
+ }
+
+ public TransactionInput getCurrent() {
+ return current;
+ }
+
+ public void setCurrent(TransactionInput current) {
+ this.current = current;
+ }
+
+ public byte[] getAfterCursor() {
+ return afterCursor;
+ }
+
+ public void setAfterCursor(byte[] afterCursor) {
+ this.afterCursor = afterCursor;
+ }
+
+ public int getInputIndex() {
+ return inputIndex;
+ }
+
+ public void setInputIndex(int inputIndex) {
+ this.inputIndex = inputIndex;
+ }
+
+ public int getSearchKeysIndex() {
+ return searchKeysIndex;
+ }
+
+ public void setSearchKeysIndex(int searchKeysIndex) {
+ this.searchKeysIndex = searchKeysIndex;
+ }
+
+ public List getSearchKeys() {
+ return searchKeys;
+ }
+
+ public void setSearchKeys(List searchKeys) {
+ this.searchKeys = searchKeys;
+ }
+
+ public Order getOrder() {
+ return order;
+ }
+
+ public void setOrder(Order order) {
+ this.order = order;
+ }
+
+ public Integer getLimit() {
+ return limit;
+ }
+
+ public void setLimit(Integer limit) {
+ this.limit = limit;
+ }
+
+ public AbstractInputIterator addSearchKey(String address) {
+ return addSearchKey(address, null);
+ }
+
+ public AbstractInputIterator addSearchKey(String address, Script type) {
+ Script lockScript = Address.decode(address).getScript();
+
+ SearchKey searchKey = new SearchKey();
+ searchKey.scriptType = ScriptType.LOCK;
+ searchKey.script = lockScript;
+ if (type != null) {
+ Filter filter = new Filter();
+ filter.script = type;
+ searchKey.filter = filter;
+ }
+ searchKeys.add(searchKey);
+ return this;
+ }
+
+ public AbstractInputIterator addSudtSearchKey(String address, byte[] sudtArgs) {
+ Address addr = Address.decode(address);
+ Network network = addr.getNetwork();
+ byte[] codeHash;
+ if (network == Network.TESTNET) {
+ codeHash = Script.SUDT_CODE_HASH_TESTNET;
+ } else if (network == Network.MAINNET) {
+ codeHash = Script.SUDT_CODE_HASH_MAINNET;
+ } else {
+ throw new IllegalArgumentException("Unsupported network");
+ }
+ Script type = new Script(
+ codeHash,
+ sudtArgs,
+ Script.HashType.TYPE);
+ return addSearchKey(address, type);
+ }
+
+ @Override
+ public boolean hasNext() {
+ updateCurrent();
+ return current != null;
+ }
+
+ @Override
+ public TransactionInput next() {
+ updateCurrent();
+ if (current != null) {
+ inputIndex += 1;
+ return current;
+ } else {
+ throw new NoSuchElementException();
+ }
+ }
+
+ protected void updateCurrent() {
+ if (inputIndex < transactionInputs.size()) {
+ current = transactionInputs.get(inputIndex);
+ } else {
+ current = null;
+ }
+
+ while (current == null && searchKeysIndex < searchKeys.size()) {
+ SearchKey searchKey = searchKeys.get(searchKeysIndex);
+ try {
+ fetchTransactionInputs(searchKey);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ if (transactionInputs.isEmpty()) {
+ afterCursor = null;
+ searchKeysIndex++;
+ } else {
+ inputIndex = 0;
+ current = transactionInputs.get(0);
+ }
+ }
+ }
+
+ protected void fetchTransactionInputs(SearchKey searchKey) throws IOException {
+ CellsResponse response = getLiveCells(searchKey, order, limit, afterCursor);
+ List newTransactionInputs = new ArrayList<>();
+ for (CellResponse liveCell: response.objects) {
+ CellInput cellInput = new CellInput(liveCell.outPoint);
+ newTransactionInputs.add(new TransactionInput(cellInput, liveCell.output, liveCell.outputData));
+ }
+ transactionInputs = newTransactionInputs;
+ afterCursor = response.lastCursor;
+ }
+
+ public abstract CellsResponse getLiveCells(SearchKey searchKey, Order order, int limit, byte[] afterCursor) throws IOException;
+}
diff --git a/ckb/src/main/java/org/nervos/ckb/transaction/AbstractTransactionBuilder.java b/ckb/src/main/java/org/nervos/ckb/transaction/AbstractTransactionBuilder.java
index 85f32916e..a21c65e2c 100644
--- a/ckb/src/main/java/org/nervos/ckb/transaction/AbstractTransactionBuilder.java
+++ b/ckb/src/main/java/org/nervos/ckb/transaction/AbstractTransactionBuilder.java
@@ -1,8 +1,6 @@
package org.nervos.ckb.transaction;
-import org.nervos.ckb.Network;
import org.nervos.ckb.sign.TransactionWithScriptGroups;
-import org.nervos.ckb.transaction.scriptHandler.*;
import org.nervos.ckb.type.CellDep;
import org.nervos.ckb.type.Transaction;
import org.nervos.ckb.type.TransactionInput;
@@ -16,46 +14,22 @@
public abstract class AbstractTransactionBuilder {
protected int changeOutputIndex = -1;
- protected long feeRate = 1000;
- protected Network network;
-
- protected List scriptHandlers = new ArrayList<>();
+ protected TransactionBuilderConfiguration configuration;
+ protected Iterator availableInputs;
protected List inputsDetail = new ArrayList<>();
protected Transaction tx = new Transaction();
- protected Iterator availableInputs;
-
- private static List TESTNET_SCRIPT_HANDLERS = new ArrayList<>();
- private static List MAINNET_SCRIPT_HANDLERS = new ArrayList<>();
-
- static {
- TESTNET_SCRIPT_HANDLERS.add(new Secp256k1Blake160SighashAllScriptHandler(Network.TESTNET));
- TESTNET_SCRIPT_HANDLERS.add(new Secp256k1Blake160MultisigAllScriptHandler(Network.TESTNET));
- TESTNET_SCRIPT_HANDLERS.add(new SudtScriptHandler(Network.TESTNET));
- TESTNET_SCRIPT_HANDLERS.add(new DaoScriptHandler(Network.TESTNET));
- MAINNET_SCRIPT_HANDLERS.add(new Secp256k1Blake160SighashAllScriptHandler(Network.MAINNET));
- MAINNET_SCRIPT_HANDLERS.add(new Secp256k1Blake160MultisigAllScriptHandler(Network.MAINNET));
- MAINNET_SCRIPT_HANDLERS.add(new SudtScriptHandler(Network.MAINNET));
- MAINNET_SCRIPT_HANDLERS.add(new DaoScriptHandler(Network.MAINNET));
- }
-
- public AbstractTransactionBuilder(Iterator availableInputs, Network network) {
+ public AbstractTransactionBuilder(TransactionBuilderConfiguration configuration, Iterator availableInputs) {
+ this.configuration = configuration;
this.availableInputs = availableInputs;
- this.network = network;
- if (network == Network.TESTNET) {
- scriptHandlers.addAll(TESTNET_SCRIPT_HANDLERS);
- } else {
- scriptHandlers.addAll(MAINNET_SCRIPT_HANDLERS);
- }
}
- protected AbstractTransactionBuilder registerScriptHandler(ScriptHandler scriptHandler) {
- scriptHandlers.add(scriptHandler);
- return this;
+ public TransactionBuilderConfiguration getConfiguration() {
+ return configuration;
}
- public long getFeeRate() {
- return feeRate;
+ public void setConfiguration(TransactionBuilderConfiguration configuration) {
+ this.configuration = configuration;
}
public void setInputSince(int index, long since) {
@@ -73,7 +47,7 @@ public int setHeaderDep(byte[] headerDep) {
}
public void addCellDeps(List cellDeps) {
- for (CellDep cellDep : cellDeps) {
+ for (CellDep cellDep: cellDeps) {
addCellDep(cellDep);
}
}
@@ -99,6 +73,8 @@ public void setWitness(int i, WitnessArgs.Type type, byte[] data) {
case OUTPUT_TYPE:
witnessArgs.setOutputType(data);
break;
+ default:
+ throw new IllegalArgumentException("Unsupported witness type");
}
tx.witnesses.set(i, witnessArgs.pack().toByteArray());
}
diff --git a/ckb/src/main/java/org/nervos/ckb/transaction/CkbTransactionBuilder.java b/ckb/src/main/java/org/nervos/ckb/transaction/CkbTransactionBuilder.java
index 4211791f6..8aa085ae3 100644
--- a/ckb/src/main/java/org/nervos/ckb/transaction/CkbTransactionBuilder.java
+++ b/ckb/src/main/java/org/nervos/ckb/transaction/CkbTransactionBuilder.java
@@ -1,10 +1,9 @@
package org.nervos.ckb.transaction;
-import org.nervos.ckb.Network;
import org.nervos.ckb.sign.ScriptGroup;
import org.nervos.ckb.sign.TransactionWithScriptGroups;
-import org.nervos.ckb.transaction.scriptHandler.DaoScriptHandler;
-import org.nervos.ckb.transaction.scriptHandler.ScriptHandler;
+import org.nervos.ckb.transaction.handler.DaoScriptHandler;
+import org.nervos.ckb.transaction.handler.ScriptHandler;
import org.nervos.ckb.type.*;
import org.nervos.ckb.utils.Numeric;
import org.nervos.ckb.utils.address.Address;
@@ -15,19 +14,8 @@ public class CkbTransactionBuilder extends AbstractTransactionBuilder {
protected List transactionInputs = new ArrayList<>();
protected long reward = 0;
- public CkbTransactionBuilder(Iterator availableInputs, Network network) {
- super(availableInputs, network);
- }
-
- @Override
- public CkbTransactionBuilder registerScriptHandler(ScriptHandler scriptHandler) {
- super.registerScriptHandler(scriptHandler);
- return this;
- }
-
- public CkbTransactionBuilder setFeeRate(long feeRate) {
- this.feeRate = feeRate;
- return this;
+ public CkbTransactionBuilder(TransactionBuilderConfiguration configuration, Iterator availableInputs) {
+ super(configuration, availableInputs);
}
public CkbTransactionBuilder addInput(TransactionInput transactionInput) {
@@ -99,8 +87,8 @@ public TransactionWithScriptGroups build(Object... contexts) {
scriptGroupMap.put(type, scriptGroup);
}
scriptGroup.getOutputIndices().add(i);
- for (ScriptHandler handler : scriptHandlers) {
- for (Object context : contexts) {
+ for (ScriptHandler handler: configuration.getScriptHandlers()) {
+ for (Object context: contexts) {
handler.buildTransaction(this, scriptGroup, context);
}
}
@@ -128,8 +116,8 @@ public TransactionWithScriptGroups build(Object... contexts) {
}
scriptGroup.getInputIndices().add(inputIndex);
// add cellDeps and set witness placeholder
- for (ScriptHandler handler : scriptHandlers) {
- for (Object context : contexts) {
+ for (ScriptHandler handler: configuration.getScriptHandlers()) {
+ for (Object context: contexts) {
handler.buildTransaction(this, scriptGroup, context);
}
}
@@ -144,8 +132,8 @@ public TransactionWithScriptGroups build(Object... contexts) {
scriptGroupMap.put(type, scriptGroup);
}
scriptGroup.getInputIndices().add(inputIndex);
- for (ScriptHandler handler : scriptHandlers) {
- for (Object context : contexts) {
+ for (ScriptHandler handler: configuration.getScriptHandlers()) {
+ for (Object context: contexts) {
handler.buildTransaction(this, scriptGroup, context);
}
}
@@ -153,7 +141,7 @@ public TransactionWithScriptGroups build(Object... contexts) {
inputsCapacity += input.output.capacity;
// check if there is enough capacity for output capacity and change
- long fee = calculateTxFee(tx, feeRate);
+ long fee = calculateTxFee(tx, configuration.getFeeRate());
long changeCapacity = inputsCapacity - outputsCapacity - fee + reward;
CellOutput changeOutput = tx.outputs.get(changeOutputIndex);
byte[] changeOutputData = tx.outputsData.get(changeOutputIndex);
@@ -182,9 +170,7 @@ public TransactionInput next() {
if (availableInputs != null) {
while (availableInputs.hasNext()) {
TransactionInput input = availableInputs.next();
- if (toFilter(input)) {
- continue;
- } else {
+ if (!shouldFilterOut(input)) {
return input;
}
}
@@ -192,7 +178,7 @@ public TransactionInput next() {
return null;
}
- private boolean toFilter(TransactionInput input) {
+ private boolean shouldFilterOut(TransactionInput input) {
OutPoint outPoint = input.input.previousOutput;
// Filter duplicate found inputs same with customized input
for (int i = 0; i < transactionInputs.size(); i++) {
diff --git a/ckb/src/main/java/org/nervos/ckb/transaction/DaoTransactionBuilder.java b/ckb/src/main/java/org/nervos/ckb/transaction/DaoTransactionBuilder.java
index 42da9cef7..8c9fc9fcc 100644
--- a/ckb/src/main/java/org/nervos/ckb/transaction/DaoTransactionBuilder.java
+++ b/ckb/src/main/java/org/nervos/ckb/transaction/DaoTransactionBuilder.java
@@ -1,9 +1,7 @@
package org.nervos.ckb.transaction;
-import org.nervos.ckb.Network;
import org.nervos.ckb.service.Api;
import org.nervos.ckb.sign.TransactionWithScriptGroups;
-import org.nervos.ckb.transaction.scriptHandler.ScriptHandler;
import org.nervos.ckb.type.*;
import org.nervos.ckb.utils.MoleculeConverter;
import org.nervos.ckb.utils.Numeric;
@@ -14,7 +12,7 @@
import java.util.Arrays;
import java.util.Iterator;
-import static org.nervos.ckb.transaction.scriptHandler.DaoScriptHandler.*;
+import static org.nervos.ckb.transaction.handler.DaoScriptHandler.*;
public class DaoTransactionBuilder extends AbstractTransactionBuilder {
CkbTransactionBuilder builder;
@@ -28,12 +26,12 @@ private enum TransactionType {
CLAIM,
}
- public DaoTransactionBuilder(Iterator availableInputs, Network network, OutPoint daoOutpoint, Api api) throws IOException {
- super(availableInputs, network);
- builder = new CkbTransactionBuilder(availableInputs, network);
+ public DaoTransactionBuilder(TransactionBuilderConfiguration configuration, Iterator availableInputs, OutPoint daoOutPoint, Api api) throws IOException {
+ super(configuration, availableInputs);
+ builder = new CkbTransactionBuilder(configuration, availableInputs);
this.api = api;
- CellInput cellInput = new CellInput(daoOutpoint, 0);
- CellWithStatus cellWithStatus = api.getLiveCell(daoOutpoint, true);
+ CellInput cellInput = new CellInput(daoOutPoint, 0);
+ CellWithStatus cellWithStatus = api.getLiveCell(daoOutPoint, true);
TransactionInput input = new TransactionInput(
cellInput,
cellWithStatus.cell.output,
@@ -41,20 +39,22 @@ public DaoTransactionBuilder(Iterator availableInputs, Network
transactionType = getTransactionType(cellWithStatus.cell.data.content);
switch (transactionType) {
case WITHDRAW:
- TransactionWithStatus txWithStatus = api.getTransaction(daoOutpoint.txHash);
+ TransactionWithStatus txWithStatus = api.getTransaction(daoOutPoint.txHash);
depositBlockNumber = api.getHeader(txWithStatus.txStatus.blockHash).number;
- depositCellCapacity = txWithStatus.transaction.outputs.get(daoOutpoint.index).capacity;
+ depositCellCapacity = txWithStatus.transaction.outputs.get(daoOutPoint.index).capacity;
break;
case CLAIM:
- builder.reward += getDaoReward(daoOutpoint);
+ builder.reward += getDaoReward(daoOutPoint);
break;
+ default:
+ throw new IllegalArgumentException("Unsupported transaction type");
}
builder.transactionInputs.add(input);
}
private static TransactionType getTransactionType(byte[] outputData) {
if (outputData.length != 8) {
- throw new IllegalArgumentException("Dao cell's length should be 8 bytes");
+ throw new IllegalArgumentException("Dao cell's output data length should be 8 bytes");
}
if (Arrays.equals(outputData, DEPOSIT_CELL_DATA)) {
return TransactionType.WITHDRAW;
@@ -98,10 +98,7 @@ private long getDaoReward(OutPoint withdrawOutpoint) throws IOException {
return daoReward;
}
- private static long calculateDaoMaximumWithdraw(Header depositBlockHeader,
- Header withdrawBlockHeader,
- CellOutput output,
- long occupiedCapacity) {
+ private static long calculateDaoMaximumWithdraw(Header depositBlockHeader, Header withdrawBlockHeader, CellOutput output, long occupiedCapacity) {
BigInteger depositAr = BigInteger.valueOf(extractAr(depositBlockHeader.dao));
BigInteger withdrawAr = BigInteger.valueOf(extractAr(withdrawBlockHeader.dao));
@@ -117,24 +114,8 @@ public static long extractAr(byte[] dao) {
return Numeric.littleEndianBytesToBigInteger(slice).longValue();
}
- public DaoTransactionBuilder(Iterator availableInputs, Network network) {
- super(availableInputs, network);
- }
-
- @Override
- public DaoTransactionBuilder registerScriptHandler(ScriptHandler scriptHandler) {
- builder.registerScriptHandler(scriptHandler);
- return this;
- }
-
- @Override
- public long getFeeRate() {
- return builder.getFeeRate();
- }
-
- public DaoTransactionBuilder setFeeRate(long feeRate) {
- builder.setFeeRate(feeRate);
- return this;
+ public DaoTransactionBuilder(TransactionBuilderConfiguration configuration, Iterator availableInputs) {
+ super(configuration, availableInputs);
}
public DaoTransactionBuilder addOutput(String address, long capacity) {
@@ -153,8 +134,8 @@ public DaoTransactionBuilder addWithdrawOutput(String address) {
}
CellOutput output = new CellOutput(
depositCellCapacity,
- Address.decode(address).getScript()
- , DAO_SCRIPT);
+ Address.decode(address).getScript(),
+ DAO_SCRIPT);
byte[] data = MoleculeConverter.packUint64(depositBlockNumber).toByteArray();
builder.addOutput(output, data);
return this;
diff --git a/ckb/src/main/java/org/nervos/ckb/transaction/InputIterator.java b/ckb/src/main/java/org/nervos/ckb/transaction/InputIterator.java
new file mode 100644
index 000000000..3638a3b73
--- /dev/null
+++ b/ckb/src/main/java/org/nervos/ckb/transaction/InputIterator.java
@@ -0,0 +1,53 @@
+package org.nervos.ckb.transaction;
+
+import org.nervos.ckb.CkbRpcApi;
+import org.nervos.ckb.Network;
+import org.nervos.ckb.service.Api;
+import org.nervos.ckb.utils.address.Address;
+import org.nervos.indexer.model.Order;
+import org.nervos.indexer.model.SearchKey;
+import org.nervos.indexer.model.resp.CellsResponse;
+
+import java.io.IOException;
+
+public class InputIterator extends AbstractInputIterator {
+ private CkbRpcApi ckbRpcApi;
+
+ public InputIterator(
+ CkbRpcApi ckbRpcApi,
+ Order order,
+ Integer limit) {
+ this.ckbRpcApi = ckbRpcApi;
+ this.order = order;
+ this.limit = limit;
+ }
+
+ public InputIterator(CkbRpcApi api) {
+ this.ckbRpcApi = api;
+ }
+
+ public InputIterator(String address) {
+ this(getDefaultCkbRpcApi(Address.decode(address).getNetwork()));
+ addSearchKey(address);
+ }
+
+ private static CkbRpcApi getDefaultCkbRpcApi(Network network) {
+ String url;
+ switch (network) {
+ case MAINNET:
+ url = "https://mainnet.ckb.dev";
+ break;
+ case TESTNET:
+ url = "https://testnet.ckb.dev";
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported network");
+ }
+ return new Api(url, false);
+ }
+
+ @Override
+ public CellsResponse getLiveCells(SearchKey searchKey, Order order, int limit, byte[] afterCursor) throws IOException {
+ return ckbRpcApi.getCells(searchKey, order, limit, afterCursor);
+ }
+}
diff --git a/ckb/src/main/java/org/nervos/ckb/transaction/LightClientInputIterator.java b/ckb/src/main/java/org/nervos/ckb/transaction/LightClientInputIterator.java
new file mode 100644
index 000000000..51d9106db
--- /dev/null
+++ b/ckb/src/main/java/org/nervos/ckb/transaction/LightClientInputIterator.java
@@ -0,0 +1,30 @@
+package org.nervos.ckb.transaction;
+
+import com.nervos.lightclient.LightClientApi;
+import org.nervos.indexer.model.Order;
+import org.nervos.indexer.model.SearchKey;
+import org.nervos.indexer.model.resp.CellsResponse;
+
+import java.io.IOException;
+
+public class LightClientInputIterator extends AbstractInputIterator {
+ private LightClientApi lightClientApi;
+
+ public LightClientInputIterator(
+ LightClientApi lightClientApi,
+ Order order,
+ Integer limit) {
+ this.lightClientApi = lightClientApi;
+ this.order = order;
+ this.limit = limit;
+ }
+
+ public LightClientInputIterator(LightClientApi api) {
+ this.lightClientApi = api;
+ }
+
+ @Override
+ public CellsResponse getLiveCells(SearchKey searchKey, Order order, int limit, byte[] afterCursor) throws IOException {
+ return lightClientApi.getCells(searchKey, order, limit, afterCursor);
+ }
+}
diff --git a/ckb/src/main/java/org/nervos/ckb/transaction/OffChainInputCollector.java b/ckb/src/main/java/org/nervos/ckb/transaction/OffChainInputCollector.java
new file mode 100644
index 000000000..1c0fa00b9
--- /dev/null
+++ b/ckb/src/main/java/org/nervos/ckb/transaction/OffChainInputCollector.java
@@ -0,0 +1,107 @@
+package org.nervos.ckb.transaction;
+
+import org.nervos.ckb.type.*;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+public class OffChainInputCollector {
+ private static OffChainInputCollector INSTANCE;
+ // store used cells for avoiding double spending.
+ private List usedLiveCells = new ArrayList<>();
+ // store newly created outpoints for offchain live cells supply
+ private List offChainLiveCells = new ArrayList<>();
+ private long blockNumberOffset = 13;
+
+ public static OffChainInputCollector getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new OffChainInputCollector();
+ }
+ return INSTANCE;
+ }
+
+ public List getUsedLiveCells() {
+ return usedLiveCells;
+ }
+
+ public void setUsedLiveCells(List usedLiveCells) {
+ this.usedLiveCells = usedLiveCells;
+ }
+
+ public List getOffChainLiveCells() {
+ return offChainLiveCells;
+ }
+
+ public void setOffChainLiveCells(List offChainLiveCells) {
+ this.offChainLiveCells = offChainLiveCells;
+ }
+
+ public long getBlockNumberOffset() {
+ return blockNumberOffset;
+ }
+
+ public void setBlockNumberOffset(long blockNumberOffset) {
+ this.blockNumberOffset = blockNumberOffset;
+ }
+
+ public void applyOffChainTransaction(long tipBlockNumber, Transaction transaction) throws IOException {
+ byte[] transactionHash = transaction.computeHash();
+ usedLiveCells = usedLiveCells.stream()
+ .filter(o -> tipBlockNumber >= o.blockNumber && tipBlockNumber - o.blockNumber <= blockNumberOffset)
+ .collect(Collectors.toList());
+
+ offChainLiveCells = offChainLiveCells.stream()
+ .filter(o -> tipBlockNumber >= o.blockNumber && tipBlockNumber - o.blockNumber <= blockNumberOffset)
+ .collect(Collectors.toList());
+
+ for (int i = 0; i < transaction.inputs.size(); i++) {
+ OutPoint consumedOutPoint = transaction.inputs.get(i).previousOutput;
+ // Add input to usedLiveCells
+ usedLiveCells.add(new OutPointWithBlockNumber(consumedOutPoint, tipBlockNumber));
+ // Remove input from offChainLiveCells
+ Iterator it = offChainLiveCells.iterator();
+ while (it.hasNext()) {
+ TransactionInputWithBlockNumber offChainLiveCell = it.next();
+ if (Objects.equals(consumedOutPoint, offChainLiveCell.input.previousOutput)) {
+ it.remove();
+ }
+ }
+ }
+ for (int i = 0; i < transaction.outputs.size(); i++) {
+ TransactionInputWithBlockNumber transactionInputWithBlockNumber = new TransactionInputWithBlockNumber(
+ new CellInput(new OutPoint(transactionHash, i)),
+ transaction.outputs.get(i),
+ transaction.outputsData.get(i),
+ tipBlockNumber);
+ offChainLiveCells.add(transactionInputWithBlockNumber);
+ }
+ }
+
+ public static class OutPointWithBlockNumber extends OutPoint {
+ public long blockNumber;
+
+ public OutPointWithBlockNumber(byte[] txHash, int index, long blockNumber) {
+ super(txHash, index);
+ this.blockNumber = blockNumber;
+ }
+
+ public OutPointWithBlockNumber(OutPoint outPoint, long blockNumber) {
+ this.index = outPoint.index;
+ this.txHash = outPoint.txHash;
+ this.blockNumber = blockNumber;
+ }
+ }
+
+ public static class TransactionInputWithBlockNumber extends TransactionInput {
+ public long blockNumber;
+
+ public TransactionInputWithBlockNumber(CellInput input, CellOutput output, byte[] outputData, long blockNumber) {
+ super(input, output, outputData);
+ this.blockNumber = blockNumber;
+ }
+ }
+}
diff --git a/ckb/src/main/java/org/nervos/ckb/transaction/OffChainInputIterator.java b/ckb/src/main/java/org/nervos/ckb/transaction/OffChainInputIterator.java
new file mode 100644
index 000000000..cd09ea21b
--- /dev/null
+++ b/ckb/src/main/java/org/nervos/ckb/transaction/OffChainInputIterator.java
@@ -0,0 +1,173 @@
+package org.nervos.ckb.transaction;
+
+import org.nervos.ckb.type.CellOutput;
+import org.nervos.ckb.type.TransactionInput;
+import org.nervos.indexer.model.Filter;
+import org.nervos.indexer.model.Order;
+import org.nervos.indexer.model.SearchKey;
+import org.nervos.indexer.model.resp.CellsResponse;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+
+public class OffChainInputIterator extends AbstractInputIterator {
+ private AbstractInputIterator iterator;
+ private OffChainInputCollector offChainInputCollector;
+ private boolean consumeOffChainCellsFirstly;
+ private boolean isCurrentFromOffChain = false;
+
+ public OffChainInputIterator(AbstractInputIterator iterator, OffChainInputCollector offChainInputCollector, boolean consumeOffChainCellsFirstly) {
+ this.offChainInputCollector = offChainInputCollector;
+ this.consumeOffChainCellsFirstly = consumeOffChainCellsFirstly;
+ this.iterator = iterator;
+ this.transactionInputs = iterator.transactionInputs;
+ this.current = iterator.current;
+ this.afterCursor = iterator.afterCursor;
+ this.inputIndex = iterator.inputIndex;
+ this.searchKeysIndex = iterator.searchKeysIndex;
+ this.searchKeys = iterator.searchKeys;
+ this.order = iterator.order;
+ this.limit = iterator.limit;
+ }
+
+ public OffChainInputIterator(AbstractInputIterator iterator, OffChainInputCollector offChainInputCollector) {
+ this(iterator, offChainInputCollector, false);
+ }
+
+ public OffChainInputIterator(AbstractInputIterator iterator) {
+ this(iterator, OffChainInputCollector.getInstance());
+ }
+
+ public OffChainInputCollector getOffChainInputCollector() {
+ return offChainInputCollector;
+ }
+
+ public void setOffChainInputCollector(OffChainInputCollector offChainInputCollector) {
+ this.offChainInputCollector = offChainInputCollector;
+ }
+
+ public boolean isConsumeOffChainCellsFirstly() {
+ return consumeOffChainCellsFirstly;
+ }
+
+ public void setConsumeOffChainCellsFirstly(boolean consumeOffChainCellsFirstly) {
+ this.consumeOffChainCellsFirstly = consumeOffChainCellsFirstly;
+ }
+
+ @Override
+ public TransactionInput next() {
+ updateCurrent();
+ if (current != null) {
+ if (!isCurrentFromOffChain) {
+ inputIndex += 1;
+ }
+ TransactionInput input = current;
+ current = null;
+ return input;
+ } else {
+ throw new NoSuchElementException();
+ }
+ }
+
+ @Override
+ protected void updateCurrent() {
+ if (isCurrentFromOffChain && current != null) {
+ return;
+ }
+
+ if (consumeOffChainCellsFirstly) {
+ current = consumeNextOffChainCell();
+ if (current != null) {
+ isCurrentFromOffChain = true;
+ return;
+ }
+ }
+
+ // Update from RPC client
+ isCurrentFromOffChain = false;
+ super.updateCurrent();
+
+ if (current == null && !consumeOffChainCellsFirstly) {
+ current = consumeNextOffChainCell();
+ if (current != null) {
+ isCurrentFromOffChain = true;
+ }
+ }
+ }
+
+ private TransactionInput consumeNextOffChainCell() {
+ Iterator it = offChainInputCollector.getOffChainLiveCells().iterator();
+ while (it.hasNext()) {
+ OffChainInputCollector.TransactionInputWithBlockNumber input = it.next();
+ if (isTransactionInputForSearchKey(input, searchKeys)) {
+ it.remove();
+ return input;
+ }
+ }
+ return null;
+ }
+
+ private static boolean isTransactionInputForSearchKey(OffChainInputCollector.TransactionInputWithBlockNumber transactionInputWithBlockNumber, List searchKeys) {
+ CellOutput cellOutput = transactionInputWithBlockNumber.output;
+ byte[] cellOutputData = transactionInputWithBlockNumber.outputData;
+ for (SearchKey searchKey: searchKeys) {
+ switch (searchKey.scriptType) {
+ case LOCK:
+ if (!Objects.equals(cellOutput.lock, searchKey.script)) {
+ continue;
+ }
+ break;
+ case TYPE:
+ if (!Objects.equals(cellOutput.type, searchKey.script)) {
+ continue;
+ }
+ break;
+ }
+ Filter filter = searchKey.filter;
+ if (filter != null) {
+ if (filter.script != null) {
+ switch (searchKey.scriptType) {
+ case LOCK:
+ if (!Objects.equals(cellOutput.type, filter.script)) {
+ continue;
+ }
+ break;
+ case TYPE:
+ if (!Objects.equals(cellOutput.lock, filter.script)) {
+ continue;
+ }
+ break;
+ }
+ if (filter.outputCapacityRange != null) {
+ if (cellOutput.capacity < filter.outputCapacityRange.get(0) ||
+ cellOutput.capacity >= filter.outputCapacityRange.get(1)) {
+ continue;
+ }
+ }
+ if (filter.blockRange != null) {
+ if (transactionInputWithBlockNumber.blockNumber < filter.blockRange.get(0) ||
+ transactionInputWithBlockNumber.blockNumber >= filter.blockRange.get(1)) {
+ continue;
+ }
+ }
+ if (filter.outputDataLenRange != null) {
+ if (cellOutputData.length < filter.outputDataLenRange.get(0) ||
+ cellOutputData.length >= filter.outputDataLenRange.get(1)) {
+ continue;
+ }
+ }
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public CellsResponse getLiveCells(SearchKey searchKey, Order order, int limit, byte[] afterCursor) throws IOException {
+ return iterator.getLiveCells(searchKey, order, limit, afterCursor);
+ }
+}
diff --git a/ckb/src/main/java/org/nervos/ckb/transaction/SudtTransactionBuilder.java b/ckb/src/main/java/org/nervos/ckb/transaction/SudtTransactionBuilder.java
index de9f637c6..86e28b681 100644
--- a/ckb/src/main/java/org/nervos/ckb/transaction/SudtTransactionBuilder.java
+++ b/ckb/src/main/java/org/nervos/ckb/transaction/SudtTransactionBuilder.java
@@ -3,7 +3,7 @@
import org.nervos.ckb.Network;
import org.nervos.ckb.sign.ScriptGroup;
import org.nervos.ckb.sign.TransactionWithScriptGroups;
-import org.nervos.ckb.transaction.scriptHandler.ScriptHandler;
+import org.nervos.ckb.transaction.handler.ScriptHandler;
import org.nervos.ckb.type.CellOutput;
import org.nervos.ckb.type.Script;
import org.nervos.ckb.type.ScriptType;
@@ -18,29 +18,34 @@
public class SudtTransactionBuilder extends AbstractTransactionBuilder {
private TransactionType transactionType;
- private Script sudtType;
+ private Script sudtTypeScript;
public enum TransactionType {
ISSUE,
TRANSFER
}
- public SudtTransactionBuilder(Iterator availableInputs, Network network,
+ public SudtTransactionBuilder(TransactionBuilderConfiguration configuration, Iterator availableInputs,
TransactionType transactionType, byte[] sudtArgs) {
- super(availableInputs, network);
+ super(configuration, availableInputs);
this.transactionType = transactionType;
- setSudtArgs(sudtArgs);
+ setSudtTypeScript(sudtArgs);
}
- public SudtTransactionBuilder(Iterator availableInputs, Network network,
+ public SudtTransactionBuilder(TransactionBuilderConfiguration configuration, Iterator availableInputs,
TransactionType transactionType, String sudtOwnerAddress) {
- super(availableInputs, network);
+ super(configuration, availableInputs);
this.transactionType = transactionType;
- setSudtArgs(sudtOwnerAddress);
+ setSudtTypeScript(sudtOwnerAddress);
}
- public SudtTransactionBuilder setSudtArgs(byte[] sudtArgs) {
+ public void setSudtTypeScript(Script sudtTypeScript) {
+ this.sudtTypeScript = sudtTypeScript;
+ }
+
+ public SudtTransactionBuilder setSudtTypeScript(byte[] sudtArgs) {
byte[] codeHash;
+ Network network = configuration.getNetwork();
if (network == Network.TESTNET) {
codeHash = Script.SUDT_CODE_HASH_TESTNET;
} else if (network == Network.MAINNET) {
@@ -48,27 +53,17 @@ public SudtTransactionBuilder setSudtArgs(byte[] sudtArgs) {
} else {
throw new IllegalArgumentException("Unsupported network");
}
- sudtType = new Script(
+ sudtTypeScript = new Script(
codeHash,
sudtArgs,
Script.HashType.TYPE);
return this;
}
- public SudtTransactionBuilder setSudtArgs(String sudtOwnerAddress) {
- byte[] sudtArgs = Address.decode(sudtOwnerAddress).getScript().computeHash();
- return setSudtArgs(sudtArgs);
- }
-
- @Override
- public SudtTransactionBuilder registerScriptHandler(ScriptHandler scriptHandler) {
- super.registerScriptHandler(scriptHandler);
- return this;
- }
-
- public SudtTransactionBuilder setFeeRate(long feeRate) {
- this.feeRate = feeRate;
- return this;
+ public SudtTransactionBuilder setSudtTypeScript(String sudtOwnerAddress) {
+ Address address = Address.decode(sudtOwnerAddress);
+ byte[] sudtArgs = address.getScript().computeHash();
+ return setSudtTypeScript(sudtArgs);
}
public SudtTransactionBuilder addOutput(CellOutput output, byte[] data) {
@@ -85,7 +80,7 @@ public SudtTransactionBuilder addSudtOutput(String address, BigInteger udtAmount
CellOutput output = new CellOutput(
0,
Address.decode(address).getScript(),
- sudtType);
+ sudtTypeScript);
byte[] data = sudtAmountToData(udtAmount);
output.capacity = output.occupiedCapacity(data);
return addOutput(output, data);
@@ -99,7 +94,7 @@ public SudtTransactionBuilder addSudtOutput(String address, BigInteger udtAmount
CellOutput output = new CellOutput(
capacity,
Address.decode(address).getScript(),
- sudtType);
+ sudtTypeScript);
byte[] data = sudtAmountToData(udtAmount);
return addOutput(output, data);
}
@@ -113,13 +108,13 @@ public SudtTransactionBuilder setChangeOutput(String address) {
CellOutput output = new CellOutput(
0,
Address.decode(address).getScript(),
- sudtType);
+ sudtTypeScript);
return addOutput(output, data);
}
@Override
public TransactionWithScriptGroups build(Object... contexts) {
- if (sudtType == null) {
+ if (sudtTypeScript == null) {
throw new IllegalStateException("Sudt type script is not initialized");
}
if (transactionType == null) {
@@ -151,8 +146,8 @@ public TransactionWithScriptGroups build(Object... contexts) {
scriptGroupMap.put(type, scriptGroup);
}
scriptGroup.getOutputIndices().add(i);
- for (ScriptHandler handler : scriptHandlers) {
- for (Object context : contexts) {
+ for (ScriptHandler handler: configuration.getScriptHandlers()) {
+ for (Object context: contexts) {
handler.buildTransaction(this, scriptGroup, context);
}
}
@@ -176,7 +171,7 @@ public TransactionWithScriptGroups build(Object... contexts) {
Script lock = input.output.lock;
if (transactionType == TransactionType.ISSUE) {
- if (!Arrays.equals(lock.computeHash(), sudtType.args)) {
+ if (!Arrays.equals(lock.computeHash(), sudtTypeScript.args)) {
throw new IllegalStateException("input lock hash should be the same as SUDT args in the SUDT-issue transaction");
}
}
@@ -189,8 +184,8 @@ public TransactionWithScriptGroups build(Object... contexts) {
}
scriptGroup.getInputIndices().add(inputIndex);
// add cellDeps and set witness placeholder
- for (ScriptHandler handler : scriptHandlers) {
- for (Object context : contexts) {
+ for (ScriptHandler handler: configuration.getScriptHandlers()) {
+ for (Object context: contexts) {
handler.buildTransaction(this, scriptGroup, context);
}
}
@@ -205,8 +200,8 @@ public TransactionWithScriptGroups build(Object... contexts) {
scriptGroupMap.put(type, scriptGroup);
}
scriptGroup.getInputIndices().add(inputIndex);
- for (ScriptHandler handler : scriptHandlers) {
- for (Object context : contexts) {
+ for (ScriptHandler handler: configuration.getScriptHandlers()) {
+ for (Object context: contexts) {
handler.buildTransaction(this, scriptGroup, context);
}
}
@@ -221,7 +216,7 @@ public TransactionWithScriptGroups build(Object... contexts) {
}
// check if there is enough capacity for output capacity and change
- long fee = calculateTxFee(tx, feeRate);
+ long fee = calculateTxFee(tx, configuration.getFeeRate());
long changeCapacity = inputsCapacity - outputsCapacity - fee;
CellOutput changeOutput = tx.outputs.get(changeOutputIndex);
byte[] changeOutputData = tx.outputsData.get(changeOutputIndex);
diff --git a/ckb/src/main/java/org/nervos/ckb/transaction/TransactionBuilderConfiguration.java b/ckb/src/main/java/org/nervos/ckb/transaction/TransactionBuilderConfiguration.java
new file mode 100644
index 000000000..6ecbcce42
--- /dev/null
+++ b/ckb/src/main/java/org/nervos/ckb/transaction/TransactionBuilderConfiguration.java
@@ -0,0 +1,65 @@
+package org.nervos.ckb.transaction;
+
+import org.nervos.ckb.Network;
+import org.nervos.ckb.transaction.handler.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+public class TransactionBuilderConfiguration {
+ private Network network;
+ private List scriptHandlers = new ArrayList<>();
+ private long feeRate = 1000;
+
+ public TransactionBuilderConfiguration() {
+ }
+
+ public TransactionBuilderConfiguration(Network network) {
+ Objects.requireNonNull(network);
+ this.network = network;
+ registerScriptHandler(Secp256k1Blake160SighashAllScriptHandler.class);
+ registerScriptHandler(Secp256k1Blake160MultisigAllScriptHandler.class);
+ registerScriptHandler(SudtScriptHandler.class);
+ registerScriptHandler(DaoScriptHandler.class);
+ registerScriptHandler(OmnilockScriptHandler.class);
+ }
+
+ public Network getNetwork() {
+ return network;
+ }
+
+ public void setNetwork(Network network) {
+ this.network = network;
+ }
+
+ public List getScriptHandlers() {
+ return scriptHandlers;
+ }
+
+ public void setScriptHandlers(List scriptHandlers) {
+ this.scriptHandlers = scriptHandlers;
+ }
+
+ public void registerScriptHandler(ScriptHandler scriptHandler) {
+ this.scriptHandlers.add(scriptHandler);
+ }
+
+ public void registerScriptHandler(Class extends ScriptHandler> clazz) {
+ try {
+ ScriptHandler instance = clazz.newInstance();
+ instance.init(network);
+ registerScriptHandler(instance);
+ } catch (InstantiationException | IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public long getFeeRate() {
+ return feeRate;
+ }
+
+ public void setFeeRate(long feeRate) {
+ this.feeRate = feeRate;
+ }
+}
diff --git a/ckb/src/main/java/org/nervos/ckb/transaction/scriptHandler/DaoScriptHandler.java b/ckb/src/main/java/org/nervos/ckb/transaction/handler/DaoScriptHandler.java
similarity index 81%
rename from ckb/src/main/java/org/nervos/ckb/transaction/scriptHandler/DaoScriptHandler.java
rename to ckb/src/main/java/org/nervos/ckb/transaction/handler/DaoScriptHandler.java
index f8fb61cfd..f5fc22132 100644
--- a/ckb/src/main/java/org/nervos/ckb/transaction/scriptHandler/DaoScriptHandler.java
+++ b/ckb/src/main/java/org/nervos/ckb/transaction/handler/DaoScriptHandler.java
@@ -1,4 +1,4 @@
-package org.nervos.ckb.transaction.scriptHandler;
+package org.nervos.ckb.transaction.handler;
import org.nervos.ckb.Network;
import org.nervos.ckb.service.Api;
@@ -20,11 +20,30 @@ public class DaoScriptHandler implements ScriptHandler {
public static byte[] DEPOSIT_CELL_DATA = Numeric.hexStringToByteArray("0x0000000000000000");
public static int DAO_LOCK_PERIOD_EPOCHS = 180;
-
private List cellDeps;
private byte[] codeHash;
- public DaoScriptHandler(Network network) {
+ public DaoScriptHandler() {
+ }
+
+ public List getCellDeps() {
+ return cellDeps;
+ }
+
+ public void setCellDeps(List cellDeps) {
+ this.cellDeps = cellDeps;
+ }
+
+ public byte[] getCodeHash() {
+ return codeHash;
+ }
+
+ public void setCodeHash(byte[] codeHash) {
+ this.codeHash = codeHash;
+ }
+
+ @Override
+ public void init(Network network) {
OutPoint outPoint = new OutPoint();
if (network == Network.MAINNET) {
outPoint.txHash = Numeric.hexStringToByteArray("0xe2fb199810d49a4d8beec56718ba2593b665db9d52299a0f9e6e75416d73ff5c");
@@ -72,7 +91,7 @@ public boolean buildTransaction(AbstractTransactionBuilder txBuilder, ScriptGrou
byte[] inputType = MoleculeConverter.packUint64(depositHeaderDepIndex).toByteArray();
txBuilder.setWitness(index, WitnessArgs.Type.INPUT_TYPE, inputType);
// update input since
- txBuilder.setInputSince(index, info.calculateDaoMinimalSince());
+ txBuilder.setInputSince(index, info.calculateDaoMinimumSince());
} else if (context instanceof WithdrawInfo) {
WithdrawInfo info = (WithdrawInfo) context;
txBuilder.setHeaderDep(info.depositBlockHash);
@@ -90,7 +109,6 @@ public ClaimInfo(Api api, OutPoint withdrawOutpoint) {
try {
TransactionWithStatus txWithStatus = api.getTransaction(withdrawOutpoint.txHash);
Transaction withdrawTx = txWithStatus.transaction;
- byte[] withdrawBlockHash = txWithStatus.txStatus.blockHash;
byte[] depositBlockHash = null;
for (int i = 0; i < withdrawTx.inputs.size(); i++) {
OutPoint outPoint = withdrawTx.inputs.get(i).previousOutput;
@@ -105,6 +123,7 @@ public ClaimInfo(Api api, OutPoint withdrawOutpoint) {
if (depositBlockHash == null) {
throw new RuntimeException("Can find deposit cell");
}
+ byte[] withdrawBlockHash = txWithStatus.txStatus.blockHash;
depositBlockHeader = api.getHeader(depositBlockHash);
withdrawBlockHeader = api.getHeader(withdrawBlockHash);
} catch (IOException e) {
@@ -112,12 +131,11 @@ public ClaimInfo(Api api, OutPoint withdrawOutpoint) {
}
}
- public long calculateDaoMinimalSince() {
- return calculateDaoMinimalSince(depositBlockHeader, withdrawBlockHeader);
+ public long calculateDaoMinimumSince() {
+ return calculateDaoMinimumSince(depositBlockHeader, withdrawBlockHeader);
}
- private static long calculateDaoMinimalSince(Header depositBlockHeader,
- Header withdrawBlockHeader) {
+ private static long calculateDaoMinimumSince(Header depositBlockHeader, Header withdrawBlockHeader) {
EpochUtils.EpochInfo depositEpoch = EpochUtils.parse(depositBlockHeader.epoch);
EpochUtils.EpochInfo withdrawEpoch = EpochUtils.parse(withdrawBlockHeader.epoch);
@@ -131,25 +149,25 @@ private static long calculateDaoMinimalSince(Header depositBlockHeader,
long lockEpochs = (depositedEpochs + (DAO_LOCK_PERIOD_EPOCHS - 1))
/ DAO_LOCK_PERIOD_EPOCHS
* DAO_LOCK_PERIOD_EPOCHS;
- long minimalSinceEpochNumber = depositEpoch.number + lockEpochs;
- long minimalSinceEpochIndex = depositEpoch.index;
- long minimalSinceEpochLength = depositEpoch.length;
- long minimalSince =
+ long minimumSinceEpochNumber = depositEpoch.number + lockEpochs;
+ long minimumSinceEpochIndex = depositEpoch.index;
+ long minimumSinceEpochLength = depositEpoch.length;
+ long minimumSince =
EpochUtils.generateSince(
- minimalSinceEpochLength, minimalSinceEpochIndex, minimalSinceEpochNumber);
- return minimalSince;
+ minimumSinceEpochLength, minimumSinceEpochIndex, minimumSinceEpochNumber);
+ return minimumSince;
}
}
public static class WithdrawInfo {
- OutPoint depositOutpoint;
+ OutPoint depositOutPoint;
long depositBlockNumber;
byte[] depositBlockHash;
- public WithdrawInfo(Api api, OutPoint depositOutpoint) {
- this.depositOutpoint = depositOutpoint;
+ public WithdrawInfo(Api api, OutPoint depositOutPoint) {
+ this.depositOutPoint = depositOutPoint;
try {
- TransactionWithStatus txWithStatus = api.getTransaction(depositOutpoint.txHash);
+ TransactionWithStatus txWithStatus = api.getTransaction(depositOutPoint.txHash);
depositBlockHash = txWithStatus.txStatus.blockHash;
depositBlockNumber = api.getHeader(depositBlockHash).number;
} catch (IOException e) {
diff --git a/ckb/src/main/java/org/nervos/ckb/transaction/handler/OmnilockScriptHandler.java b/ckb/src/main/java/org/nervos/ckb/transaction/handler/OmnilockScriptHandler.java
new file mode 100644
index 000000000..8d27ff308
--- /dev/null
+++ b/ckb/src/main/java/org/nervos/ckb/transaction/handler/OmnilockScriptHandler.java
@@ -0,0 +1,144 @@
+package org.nervos.ckb.transaction.handler;
+
+import org.nervos.ckb.Network;
+import org.nervos.ckb.sign.ScriptGroup;
+import org.nervos.ckb.sign.omnilock.OmnilockWitnessLock;
+import org.nervos.ckb.sign.signer.OmnilockSigner;
+import org.nervos.ckb.sign.signer.Secp256k1Blake160MultisigAllSigner;
+import org.nervos.ckb.transaction.AbstractTransactionBuilder;
+import org.nervos.ckb.type.CellDep;
+import org.nervos.ckb.type.Script;
+import org.nervos.ckb.type.WitnessArgs;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+public class OmnilockScriptHandler implements ScriptHandler {
+ private CellDep cellDep;
+ private CellDep singleSignCellDep;
+ private CellDep multiSignCellDep;
+ private byte[] codeHash;
+
+ public OmnilockScriptHandler() {
+ }
+
+ @Override
+ public void init(Network network) {
+ if (network == Network.MAINNET) {
+ codeHash = Script.OMNILOCK_CODE_HASH_MAINNET;
+ cellDep = new CellDep("0xdfdb40f5d229536915f2d5403c66047e162e25dedd70a79ef5164356e1facdc8", 0, CellDep.DepType.CODE);
+ singleSignCellDep = new CellDep("0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c", 0, CellDep.DepType.DEP_GROUP);
+ multiSignCellDep = new CellDep("0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c", 1, CellDep.DepType.DEP_GROUP);
+ } else if (network == Network.TESTNET) {
+ codeHash = Script.OMNILOCK_CODE_HASH_TESTNET;
+ cellDep = new CellDep("0x27b62d8be8ed80b9f56ee0fe41355becdb6f6a40aeba82d3900434f43b1c8b60", 0, CellDep.DepType.CODE);
+ singleSignCellDep = new CellDep("0xf8de3bb47d055cdf460d93a2a6e1b05f7432f9777c8c474abf4eec1d4aee5d37", 0, CellDep.DepType.DEP_GROUP);
+ multiSignCellDep = new CellDep("0xf8de3bb47d055cdf460d93a2a6e1b05f7432f9777c8c474abf4eec1d4aee5d37", 1, CellDep.DepType.DEP_GROUP);
+ } else {
+ throw new IllegalArgumentException("Unsupported network: " + network);
+ }
+ }
+
+ public void setCellDep(CellDep cellDep) {
+ this.cellDep = cellDep;
+ }
+
+ public void setSingleSignCellDep(CellDep singleSignCellDep) {
+ this.singleSignCellDep = singleSignCellDep;
+ }
+
+ public void setMultiSignCellDep(CellDep multiSignCellDep) {
+ this.multiSignCellDep = multiSignCellDep;
+ }
+
+ public void setCodeHash(byte[] codeHash) {
+ this.codeHash = codeHash;
+ }
+
+ public CellDep getCellDep() {
+ return cellDep;
+ }
+
+ public CellDep getSingleSignCellDep() {
+ return singleSignCellDep;
+ }
+
+ public CellDep getMultiSignCellDep() {
+ return multiSignCellDep;
+ }
+
+ public byte[] getCodeHash() {
+ return codeHash;
+ }
+
+ @Override
+ public boolean buildTransaction(AbstractTransactionBuilder txBuilder, ScriptGroup scriptGroup, Object context) {
+ if (scriptGroup == null || !isMatched(scriptGroup.getScript())) {
+ return false;
+ }
+ OmnilockSigner.Configuration configuration;
+ if (context instanceof OmnilockSigner.Configuration) {
+ configuration = (OmnilockSigner.Configuration) context;
+ } else {
+ return false;
+ }
+ txBuilder.addCellDep(cellDep);
+ OmnilockSigner.Configuration.Mode mode = configuration.getMode();
+ switch (mode) {
+ case AUTH:
+ return buildTransactionForAuthMode(txBuilder, scriptGroup, configuration);
+ case ADMINISTRATOR:
+ return buildTransactionForAdministratorMode(txBuilder, scriptGroup, configuration);
+ default:
+ throw new IllegalArgumentException("Omnilock mode is null");
+ }
+ }
+
+ private boolean buildTransactionForAuthMode(AbstractTransactionBuilder txBuilder, ScriptGroup scriptGroup, OmnilockSigner.Configuration configuration) {
+ OmnilockWitnessLock omnilockWitnessLock = new OmnilockWitnessLock();
+ switch (configuration.getOmnilockArgs().getAuthenticationArgs().getFlag()) {
+ case CKB_SECP256K1_BLAKE160:
+ txBuilder.addCellDep(singleSignCellDep);
+ omnilockWitnessLock.setSignature(new byte[65]);
+ break;
+ case ETHEREUM:
+ throw new UnsupportedOperationException("Ethereum");
+ case EOS:
+ throw new UnsupportedOperationException("EOS");
+ case TRON:
+ throw new UnsupportedOperationException("TRON");
+ case BITCOIN:
+ throw new UnsupportedOperationException("Bitcoin");
+ case DOGECOIN:
+ throw new UnsupportedOperationException("Dogecoin");
+ case CKB_MULTI_SIG:
+ txBuilder.addCellDep(multiSignCellDep);
+ Secp256k1Blake160MultisigAllSigner.MultisigScript multisigScript = configuration.getMultisigScript();
+ Objects.requireNonNull(multisigScript);
+ omnilockWitnessLock.setSignature(multisigScript.witnessEmptyPlaceholderInLock());
+ break;
+ case LOCK_SCRIPT_HASH:
+ break;
+ case EXEC:
+ throw new UnsupportedOperationException("Exec");
+ case DYNAMIC_LINKING:
+ throw new UnsupportedOperationException("Dynamic linking");
+ default:
+ throw new IllegalArgumentException("Unknown auth flag " + configuration.getOmnilockArgs().getOmniArgs().getFlag());
+ }
+ int index = scriptGroup.getInputIndices().get(0);
+ txBuilder.setWitness(index, WitnessArgs.Type.LOCK, omnilockWitnessLock.packAsEmptyPlaceholder());
+ return true;
+ }
+
+ private boolean buildTransactionForAdministratorMode(AbstractTransactionBuilder txBuilder, ScriptGroup scriptGroup, OmnilockSigner.Configuration configuration) {
+ throw new UnsupportedOperationException();
+ }
+
+ private boolean isMatched(Script script) {
+ if (script == null) {
+ return false;
+ }
+ return Arrays.equals(script.codeHash, codeHash);
+ }
+}
diff --git a/ckb/src/main/java/org/nervos/ckb/transaction/scriptHandler/ScriptHandler.java b/ckb/src/main/java/org/nervos/ckb/transaction/handler/ScriptHandler.java
similarity index 69%
rename from ckb/src/main/java/org/nervos/ckb/transaction/scriptHandler/ScriptHandler.java
rename to ckb/src/main/java/org/nervos/ckb/transaction/handler/ScriptHandler.java
index 40af4053d..994e26c8d 100644
--- a/ckb/src/main/java/org/nervos/ckb/transaction/scriptHandler/ScriptHandler.java
+++ b/ckb/src/main/java/org/nervos/ckb/transaction/handler/ScriptHandler.java
@@ -1,8 +1,11 @@
-package org.nervos.ckb.transaction.scriptHandler;
+package org.nervos.ckb.transaction.handler;
+import org.nervos.ckb.Network;
import org.nervos.ckb.sign.ScriptGroup;
import org.nervos.ckb.transaction.AbstractTransactionBuilder;
public interface ScriptHandler {
boolean buildTransaction(AbstractTransactionBuilder txBuilder, ScriptGroup scriptGroup, Object context);
+
+ void init(Network network);
}
diff --git a/ckb/src/main/java/org/nervos/ckb/transaction/scriptHandler/Secp256k1Blake160MultisigAllScriptHandler.java b/ckb/src/main/java/org/nervos/ckb/transaction/handler/Secp256k1Blake160MultisigAllScriptHandler.java
similarity index 80%
rename from ckb/src/main/java/org/nervos/ckb/transaction/scriptHandler/Secp256k1Blake160MultisigAllScriptHandler.java
rename to ckb/src/main/java/org/nervos/ckb/transaction/handler/Secp256k1Blake160MultisigAllScriptHandler.java
index 51b6bb353..3e73dbb71 100644
--- a/ckb/src/main/java/org/nervos/ckb/transaction/scriptHandler/Secp256k1Blake160MultisigAllScriptHandler.java
+++ b/ckb/src/main/java/org/nervos/ckb/transaction/handler/Secp256k1Blake160MultisigAllScriptHandler.java
@@ -1,4 +1,4 @@
-package org.nervos.ckb.transaction.scriptHandler;
+package org.nervos.ckb.transaction.handler;
import org.nervos.ckb.Network;
import org.nervos.ckb.sign.ScriptGroup;
@@ -15,8 +15,29 @@
public class Secp256k1Blake160MultisigAllScriptHandler implements ScriptHandler {
private List cellDeps;
+ private byte[] codeHash;
- public Secp256k1Blake160MultisigAllScriptHandler(Network network) {
+ public Secp256k1Blake160MultisigAllScriptHandler() {
+ }
+
+ public List getCellDeps() {
+ return cellDeps;
+ }
+
+ public void setCellDeps(List cellDeps) {
+ this.cellDeps = cellDeps;
+ }
+
+ public byte[] getCodeHash() {
+ return codeHash;
+ }
+
+ public void setCodeHash(byte[] codeHash) {
+ this.codeHash = codeHash;
+ }
+
+ @Override
+ public void init(Network network) {
OutPoint outPoint = new OutPoint();
if (network == Network.MAINNET) {
outPoint.txHash = Numeric.hexStringToByteArray("0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c");
@@ -31,13 +52,13 @@ public Secp256k1Blake160MultisigAllScriptHandler(Network network) {
cellDep.outPoint = outPoint;
cellDep.depType = CellDep.DepType.DEP_GROUP;
cellDeps = Arrays.asList(cellDep);
+ this.codeHash = Script.SECP256K1_BLAKE160_MULTISIG_ALL_CODE_HASH;
}
private boolean isMatched(Script script) {
if (script == null) {
return false;
}
- byte[] codeHash = Script.SECP256K1_BLAKE160_MULTISIG_ALL_CODE_HASH;
return Arrays.equals(script.codeHash, codeHash);
}
diff --git a/ckb/src/main/java/org/nervos/ckb/transaction/scriptHandler/Secp256k1Blake160SighashAllScriptHandler.java b/ckb/src/main/java/org/nervos/ckb/transaction/handler/Secp256k1Blake160SighashAllScriptHandler.java
similarity index 76%
rename from ckb/src/main/java/org/nervos/ckb/transaction/scriptHandler/Secp256k1Blake160SighashAllScriptHandler.java
rename to ckb/src/main/java/org/nervos/ckb/transaction/handler/Secp256k1Blake160SighashAllScriptHandler.java
index 8ffaaf299..eb9fde345 100644
--- a/ckb/src/main/java/org/nervos/ckb/transaction/scriptHandler/Secp256k1Blake160SighashAllScriptHandler.java
+++ b/ckb/src/main/java/org/nervos/ckb/transaction/handler/Secp256k1Blake160SighashAllScriptHandler.java
@@ -1,4 +1,4 @@
-package org.nervos.ckb.transaction.scriptHandler;
+package org.nervos.ckb.transaction.handler;
import org.nervos.ckb.Network;
import org.nervos.ckb.sign.ScriptGroup;
@@ -14,8 +14,29 @@
public class Secp256k1Blake160SighashAllScriptHandler implements ScriptHandler {
private List cellDeps;
+ private byte[] codeHash;
- public Secp256k1Blake160SighashAllScriptHandler(Network network) {
+ public Secp256k1Blake160SighashAllScriptHandler() {
+ }
+
+ public List getCellDeps() {
+ return cellDeps;
+ }
+
+ public void setCellDeps(List cellDeps) {
+ this.cellDeps = cellDeps;
+ }
+
+ public byte[] getCodeHash() {
+ return codeHash;
+ }
+
+ public void setCodeHash(byte[] codeHash) {
+ this.codeHash = codeHash;
+ }
+
+ @Override
+ public void init(Network network) {
OutPoint outPoint = new OutPoint();
if (network == Network.MAINNET) {
outPoint.txHash = Numeric.hexStringToByteArray("0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c");
@@ -30,13 +51,13 @@ public Secp256k1Blake160SighashAllScriptHandler(Network network) {
cellDep.outPoint = outPoint;
cellDep.depType = CellDep.DepType.DEP_GROUP;
cellDeps = Arrays.asList(cellDep);
+ codeHash = Script.SECP256K1_BLAKE160_SIGNHASH_ALL_CODE_HASH;
}
private boolean isMatched(Script script) {
if (script == null) {
return false;
}
- byte[] codeHash = Script.SECP256K1_BLAKE160_SIGNHASH_ALL_CODE_HASH;
return Arrays.equals(script.codeHash, codeHash);
}
@@ -53,4 +74,5 @@ public boolean buildTransaction(AbstractTransactionBuilder txBuilder, ScriptGrou
txBuilder.addCellDeps(cellDeps);
return true;
}
+
}
diff --git a/ckb/src/main/java/org/nervos/ckb/transaction/scriptHandler/SudtScriptHandler.java b/ckb/src/main/java/org/nervos/ckb/transaction/handler/SudtScriptHandler.java
similarity index 79%
rename from ckb/src/main/java/org/nervos/ckb/transaction/scriptHandler/SudtScriptHandler.java
rename to ckb/src/main/java/org/nervos/ckb/transaction/handler/SudtScriptHandler.java
index 6ac914aa1..7f1ffb08b 100644
--- a/ckb/src/main/java/org/nervos/ckb/transaction/scriptHandler/SudtScriptHandler.java
+++ b/ckb/src/main/java/org/nervos/ckb/transaction/handler/SudtScriptHandler.java
@@ -1,4 +1,4 @@
-package org.nervos.ckb.transaction.scriptHandler;
+package org.nervos.ckb.transaction.handler;
import org.nervos.ckb.Network;
import org.nervos.ckb.sign.ScriptGroup;
@@ -15,7 +15,27 @@ public class SudtScriptHandler implements ScriptHandler {
private List cellDeps;
private byte[] codeHash;
- public SudtScriptHandler(Network network) {
+ public SudtScriptHandler() {
+ }
+
+ public List getCellDeps() {
+ return cellDeps;
+ }
+
+ public void setCellDeps(List cellDeps) {
+ this.cellDeps = cellDeps;
+ }
+
+ public byte[] getCodeHash() {
+ return codeHash;
+ }
+
+ public void setCodeHash(byte[] codeHash) {
+ this.codeHash = codeHash;
+ }
+
+ @Override
+ public void init(Network network) {
OutPoint outPoint = new OutPoint();
if (network == Network.MAINNET) {
outPoint.txHash = Numeric.hexStringToByteArray("0xc7813f6a415144643970c2e88e0bb6ca6a8edc5dd7c1022746f628284a9936d5");
@@ -40,7 +60,7 @@ private boolean isMatched(Script script) {
}
return Arrays.equals(script.codeHash, codeHash);
}
-
+
@Override
public boolean buildTransaction(AbstractTransactionBuilder txBuilder, ScriptGroup scriptGroup, Object context) {
if (scriptGroup == null || !isMatched(scriptGroup.getScript())) {
diff --git a/ckb/src/test/java/service/ApiTest.java b/ckb/src/test/java/service/ApiTest.java
index d1664003f..843aff2d1 100644
--- a/ckb/src/test/java/service/ApiTest.java
+++ b/ckb/src/test/java/service/ApiTest.java
@@ -1,15 +1,15 @@
package service;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.*;
import org.junit.jupiter.api.function.Executable;
import org.nervos.ckb.service.Api;
import org.nervos.ckb.service.GsonFactory;
import org.nervos.ckb.service.RpcResponse;
import org.nervos.ckb.type.*;
import org.nervos.ckb.utils.Numeric;
+import org.nervos.indexer.model.Order;
+import org.nervos.indexer.model.SearchKeyBuilder;
+import org.nervos.indexer.model.resp.*;
import java.io.IOException;
import java.util.Arrays;
@@ -70,6 +70,15 @@ public void testTransaction() throws IOException {
Assertions.assertEquals(1, transaction.inputs.size());
Assertions.assertEquals(3, transaction.outputs.size());
Assertions.assertEquals(30000000000L, transaction.outputs.get(0).capacity);
+
+ transactionHash =
+ Numeric.hexStringToByteArray(
+ "0x3dca00e45e2f3a39d707d5559ba49d27d21038624b0402039898d3a8830525be");
+ TransactionWithStatus transactionWithStatus = api.getTransaction(transactionHash);
+
+ Assertions.assertNotNull(transactionWithStatus.txStatus);
+ Assertions.assertNotNull(transactionWithStatus.cycles);
+ Assertions.assertTrue(transactionWithStatus.cycles > 0);
}
@Test
@@ -177,21 +186,25 @@ public void testSyncState() throws IOException {
Assertions.assertNotEquals(0, state.bestKnownBlockNumber);
}
+ @Disabled
@Test
public void testSetNetworkActive() throws IOException {
api.setNetworkActive(true);
}
+ @Disabled
@Test
public void testAddNode() throws IOException {
api.addNode("QmUsZHPbjjzU627UZFt4k8j6ycEcNvXRnVGxCPKqwbAfQS", "/ip4/192.168.2.100/tcp/8114");
}
+ @Disabled
@Test
public void testRemoveNode() throws IOException {
api.removeNode("QmUsZHPbjjzU627UZFt4k8j6ycEcNvXRnVGxCPKqwbAfQS");
}
+ @Disabled
@Test
public void testSetBan() throws IOException {
BannedAddress bannedAddress =
@@ -206,11 +219,13 @@ public void testGetBannedAddresses() throws IOException {
Assertions.assertNotNull(bannedAddresses);
}
+ @Disabled
@Test
public void testClearBannedAddresses() throws IOException {
api.clearBannedAddresses();
}
+ @Disabled
@Test
public void testPingPeers() throws IOException {
api.clearBannedAddresses();
@@ -224,6 +239,7 @@ public void testTxPoolInfo() throws IOException {
Assertions.assertNotNull(txPoolInfo.tipHash);
}
+ @Disabled
@Test
public void testClearTxPool() throws IOException {
api.clearTxPool();
@@ -240,12 +256,12 @@ public void testGetRawTxPoolVerbose() throws IOException {
RawTxPoolVerbose rawTxPoolVerbose = api.getRawTxPoolVerbose();
Assertions.assertNotNull(rawTxPoolVerbose);
- for (Map.Entry entry :
+ for (Map.Entry entry:
rawTxPoolVerbose.pending.entrySet()) {
Assertions.assertNotNull((entry.getValue()));
}
- for (Map.Entry entry :
+ for (Map.Entry entry:
rawTxPoolVerbose.proposed.entrySet()) {
Assertions.assertNotNull((entry.getValue()));
}
@@ -355,6 +371,70 @@ public void execute() throws Throwable {
"Transaction Empty");
}
+ @Test
+ void testGetIndexerTip() throws IOException {
+ TipResponse tip = api.getIndexerTip();
+ Assertions.assertNotNull(tip.blockHash);
+ Assertions.assertNotEquals(0, tip.blockNumber);
+ }
+
+ @Test
+ void testGetTransactions() throws IOException {
+ SearchKeyBuilder key = new SearchKeyBuilder();
+ key.script(
+ new Script(
+ Numeric.hexStringToByteArray(
+ "0x58c5f491aba6d61678b7cf7edf4910b1f5e00ec0cde2f42e0abb4fd9aff25a63"),
+ Numeric.hexStringToByteArray("0xe53f35ccf63bb37a3bb0ac3b7f89808077a78eae"),
+ Script.HashType.TYPE));
+ key.scriptType(ScriptType.LOCK);
+ TxsWithCell txs = api.getTransactions(key.build(), Order.ASC, 10, null);
+ Assertions.assertTrue(txs.objects.size() > 0);
+ }
+
+ @Test
+ void testTransactionsGrouped() throws IOException {
+ SearchKeyBuilder key = new SearchKeyBuilder();
+ key.script(
+ new Script(Numeric.hexStringToByteArray(
+ "0x58c5f491aba6d61678b7cf7edf4910b1f5e00ec0cde2f42e0abb4fd9aff25a63"),
+ Numeric.hexStringToByteArray("0xe53f35ccf63bb37a3bb0ac3b7f89808077a78eae"),
+ Script.HashType.TYPE));
+ key.scriptType(ScriptType.LOCK);
+
+ TxsWithCells txs = api.getTransactionsGrouped(key.build(), Order.ASC, 10, null);
+ Assertions.assertTrue(txs.objects.size() > 0);
+ Assertions.assertNotNull(txs.objects.get(0));
+ Assertions.assertNotNull(txs.objects.get(0).cells.get(0));
+ }
+
+ @Test
+ void testGetCells() throws IOException {
+ SearchKeyBuilder key = new SearchKeyBuilder();
+ key.script(
+ new Script(Numeric.hexStringToByteArray(
+ "0x58c5f491aba6d61678b7cf7edf4910b1f5e00ec0cde2f42e0abb4fd9aff25a63"),
+ Numeric.hexStringToByteArray("0xe53f35ccf63bb37a3bb0ac3b7f89808077a78eae"),
+ Script.HashType.TYPE));
+ key.scriptType(ScriptType.LOCK);
+
+ CellsResponse cells = api.getCells(key.build(), Order.ASC, 10, null);
+ Assertions.assertTrue(cells.objects.size() > 0);
+ }
+
+ @Test
+ void testGetCellCapacity() throws IOException {
+ SearchKeyBuilder key = new SearchKeyBuilder();
+ key.script(
+ new Script(Numeric.hexStringToByteArray(
+ "0x58c5f491aba6d61678b7cf7edf4910b1f5e00ec0cde2f42e0abb4fd9aff25a63"),
+ Numeric.hexStringToByteArray("0xe53f35ccf63bb37a3bb0ac3b7f89808077a78eae"),
+ Script.HashType.TYPE));
+ key.scriptType(ScriptType.LOCK);
+ CellCapacityResponse capacity = api.getCellsCapacity(key.build());
+ Assertions.assertEquals(1388355000000L, capacity.capacity);
+ }
+
@Test
public void testDryRunTransaction() throws IOException {
Cycles cycles =
@@ -370,6 +450,21 @@ public void testDryRunTransaction() throws IOException {
Assertions.assertNotNull(cycles);
}
+ @Test
+ public void testEstimateCycles() throws IOException {
+ Cycles cycles =
+ api.estimateCycles(
+ new Transaction(
+ 0,
+ Collections.emptyList(),
+ Collections.emptyList(),
+ Collections.emptyList(),
+ Collections.emptyList(),
+ Collections.emptyList(),
+ Collections.emptyList()));
+ Assertions.assertNotNull(cycles);
+ }
+
@Test
public void testBatchRpc() throws IOException {
List rpcResponses =
@@ -410,4 +505,29 @@ public void execute() throws Throwable {
},
"RPC method name must be a non-null string");
}
+
+ @Test
+ public void testGetFeeRateStatics() throws IOException {
+ FeeRateStatics statics = api.getFeeRateStatics(null);
+ Assertions.assertNotNull(statics);
+ Assertions.assertTrue(statics.mean > 0 && statics.median > 0);
+
+ statics = api.getFeeRateStatics(1);
+ Assertions.assertNotNull(statics);
+ Assertions.assertTrue(statics.mean > 0 && statics.median > 0);
+
+ statics = api.getFeeRateStatics(101);
+ Assertions.assertNotNull(statics);
+ Assertions.assertTrue(statics.mean > 0 && statics.median > 0);
+
+ statics = api.getFeeRateStatics(0);
+ Assertions.assertNotNull(statics);
+ Assertions.assertTrue(statics.mean > 0 && statics.median > 0);
+
+ statics = api.getFeeRateStatics(102);
+ Assertions.assertNotNull(statics);
+ Assertions.assertTrue(statics.mean > 0 && statics.median > 0);
+
+ Assertions.assertThrows(IOException.class, () -> api.getFeeRateStatics(-1));
+ }
}
diff --git a/ckb/src/test/java/transaction/CkbTransactionBuilderTest.java b/ckb/src/test/java/transaction/CkbTransactionBuilderTest.java
index 4a88ce650..b69044350 100644
--- a/ckb/src/test/java/transaction/CkbTransactionBuilderTest.java
+++ b/ckb/src/test/java/transaction/CkbTransactionBuilderTest.java
@@ -5,6 +5,7 @@
import org.nervos.ckb.Network;
import org.nervos.ckb.sign.TransactionWithScriptGroups;
import org.nervos.ckb.transaction.CkbTransactionBuilder;
+import org.nervos.ckb.transaction.TransactionBuilderConfiguration;
import org.nervos.ckb.type.*;
import org.nervos.ckb.utils.Numeric;
import org.nervos.ckb.utils.address.Address;
@@ -21,10 +22,11 @@ class CkbTransactionBuilderTest {
@Test
void testSingleInput() {
Iterator iterator = newTransactionInputs();
- TransactionWithScriptGroups txWithGroups = new CkbTransactionBuilder(iterator, Network.TESTNET)
+ TransactionBuilderConfiguration configuration = new TransactionBuilderConfiguration(Network.TESTNET);
+ configuration.setFeeRate(1000);
+ TransactionWithScriptGroups txWithGroups = new CkbTransactionBuilder(configuration, iterator)
.addOutput("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq2qf8keemy2p5uu0g0gn8cd4ju23s5269qk8rg4r",
50100000000L)
- .setFeeRate(1000)
.setChangeOutput(sender.encode())
.build();
@@ -41,11 +43,11 @@ void testSingleInput() {
void testMultipleInputs() {
String sender = "ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq2qf8keemy2p5uu0g0gn8cd4ju23s5269qk8rg4r";
- Iterator iterator = newTransactionInputs();
- TransactionWithScriptGroups txWithGroups = new CkbTransactionBuilder(iterator, Network.TESTNET)
+ TransactionBuilderConfiguration configuration = new TransactionBuilderConfiguration(Network.TESTNET);
+ configuration.setFeeRate(1000);
+ TransactionWithScriptGroups txWithGroups = new CkbTransactionBuilder(configuration, newTransactionInputs())
.addOutput("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq2qf8keemy2p5uu0g0gn8cd4ju23s5269qk8rg4r",
100000000000L)
- .setFeeRate(1000)
.setChangeOutput(sender)
.build();
@@ -71,4 +73,4 @@ private Iterator newTransactionInputs() {
return inputs.iterator();
}
-}
\ No newline at end of file
+}
diff --git a/ckb/src/test/java/transaction/OffChainInputCollectorTest.java b/ckb/src/test/java/transaction/OffChainInputCollectorTest.java
new file mode 100644
index 000000000..676f21f8c
--- /dev/null
+++ b/ckb/src/test/java/transaction/OffChainInputCollectorTest.java
@@ -0,0 +1,83 @@
+package transaction;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.nervos.ckb.transaction.OffChainInputCollector;
+import org.nervos.ckb.type.*;
+import org.nervos.ckb.utils.address.Address;
+
+import java.io.IOException;
+import java.util.*;
+
+class OffChainInputCollectorTest {
+ private String sender = "ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq03ewkvsva4cchhntydu648l7lyvn9w2cctnpask";
+ private Address senderAddress = Address.decode(sender);
+
+ @Test
+ void applyOffChainTransaction() throws IOException {
+ OffChainInputCollector.TransactionInputWithBlockNumber input = getRandomTransactionInput(300);
+ OffChainInputCollector cells = new OffChainInputCollector();
+ cells.getOffChainLiveCells().add(input);
+
+ Transaction tx = new Transaction();
+ tx.inputs.add(input.input);
+ tx.outputs.add(getRandomOutput());
+ tx.outputsData.add(new byte[0]);
+ tx.outputs.add(getRandomOutput());
+ tx.outputsData.add(new byte[0]);
+
+ Assertions.assertEquals(0, cells.getUsedLiveCells().size());
+ Assertions.assertEquals(1, cells.getOffChainLiveCells().size());
+ cells.applyOffChainTransaction(500, tx);
+ // test correct cell management
+ Assertions.assertEquals(1, cells.getUsedLiveCells().size()); // + 1
+ CellInput txInput1 = tx.inputs.get(0);
+ Assertions.assertTrue(cells.getUsedLiveCells().stream().anyMatch(
+ i -> i.index == txInput1.previousOutput.index && Arrays.equals(i.txHash, txInput1.previousOutput.txHash)));
+ CellOutput output1 = tx.outputs.get(0);
+ Assertions.assertEquals(2, cells.getOffChainLiveCells().size());
+ Assertions.assertTrue(cells.getOffChainLiveCells().stream().anyMatch(
+ i -> Objects.equals(i.output.lock, output1.lock) &&
+ i.output.capacity == output1.capacity));
+
+ tx = new Transaction();
+ tx.inputs.add(getRandomTransactionInput(600).input);
+ tx.outputs.add(getRandomOutput());
+ tx.outputsData.add(new byte[0]);
+ cells.applyOffChainTransaction(999, tx);
+ // Because 999 > 500, so clear all usedLiveCells and offChainLiveCells at first.
+ Assertions.assertEquals(1, cells.getUsedLiveCells().size());
+ Assertions.assertEquals(1, cells.getOffChainLiveCells().size());
+
+
+ tx = new Transaction();
+ // Consume 1 cell from offChainLiveCells. offChainLiveCells - 1.
+ // usedLiveCells + 1
+ tx.inputs.add(cells.getOffChainLiveCells().get(0).input);
+ // Add 2 cells from offChainLiveCells. offChainLiveCells + 2.
+ tx.outputs.add(getRandomOutput());
+ tx.outputsData.add(new byte[0]);
+ tx.outputs.add(new CellOutput(50000000000L, Address.decode("ckt1qrl2cyw7ulrxu48ysexpwus46r9md670h5h73cxjh3zmxsf4gt3d5qg2d5amjwfzgtqr2l72ulxw4k8c0dpga55qjzdlm749f9ffhpwl8zc422t2hvxmtlkk299l30k6xlgccjps9pe2sfhx5y3flvtlu56lu9u6pcqqqqqqqqvykxmu").getScript()));
+ tx.outputsData.add(new byte[0]);
+ cells.applyOffChainTransaction(1000, tx);
+ Assertions.assertEquals(2, cells.getUsedLiveCells().size());
+ Assertions.assertEquals(2, cells.getOffChainLiveCells().size());
+ }
+
+ private OutPoint getRandomOutPoint() {
+ byte[] hash = new byte[32];
+ new Random().nextBytes(hash);
+ return new OutPoint(hash, new Random().nextInt(10));
+ }
+
+ private OffChainInputCollector.TransactionInputWithBlockNumber getRandomTransactionInput(int blockNumber) {
+ return new OffChainInputCollector.TransactionInputWithBlockNumber(
+ new CellInput(getRandomOutPoint()), getRandomOutput(),
+ new byte[0], blockNumber);
+ }
+
+ private CellOutput getRandomOutput() {
+ return new CellOutput(new Random().nextLong(), senderAddress.getScript());
+ }
+
+}
diff --git a/ckb/src/test/java/transaction/SudtTransactionBuilderTest.java b/ckb/src/test/java/transaction/SudtTransactionBuilderTest.java
index f49e5ca86..d6611187a 100644
--- a/ckb/src/test/java/transaction/SudtTransactionBuilderTest.java
+++ b/ckb/src/test/java/transaction/SudtTransactionBuilderTest.java
@@ -5,6 +5,7 @@
import org.nervos.ckb.Network;
import org.nervos.ckb.sign.TransactionWithScriptGroups;
import org.nervos.ckb.transaction.SudtTransactionBuilder;
+import org.nervos.ckb.transaction.TransactionBuilderConfiguration;
import org.nervos.ckb.type.*;
import org.nervos.ckb.utils.Numeric;
import org.nervos.ckb.utils.address.Address;
@@ -26,17 +27,17 @@ class SudtTransactionBuilderTest {
Numeric.hexStringToByteArray("0xc5e5dcf215925f7ef4dfaf5f4b4f105bc321c02776d6e7d52a1db3fcd9d011a4"),
sudtArgs,
Script.HashType.TYPE);
-
+
@Test
void testBalance() {
+ TransactionBuilderConfiguration configuration = new TransactionBuilderConfiguration(Network.TESTNET);
+ configuration.setFeeRate(1000);
Iterator iterator = newTransactionInputs();
- TransactionWithScriptGroups txWithGroups = new SudtTransactionBuilder(iterator,
- Network.TESTNET,
+ TransactionWithScriptGroups txWithGroups = new SudtTransactionBuilder(configuration, iterator,
SudtTransactionBuilder.TransactionType.TRANSFER,
sudtArgs)
.addSudtOutput("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdamwzrffgc54ef48493nfd2sd0h4cjnxg4850up",
1L)
- .setFeeRate(1000)
.setChangeOutput("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdamwzrffgc54ef48493nfd2sd0h4cjnxg4850up")
.build();
@@ -63,4 +64,4 @@ private Iterator newTransactionInputs() {
return inputs.iterator();
}
-}
\ No newline at end of file
+}
diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
index 29655ad6c..d4f1b9f09 100644
--- a/config/checkstyle/checkstyle.xml
+++ b/config/checkstyle/checkstyle.xml
@@ -15,7 +15,8 @@
Authors: Max Vetrenko, Ruslan Diachenko, Roman Ivanov.
-->
-
+
+
@@ -212,5 +213,6 @@
+
diff --git a/core/build.gradle b/core/build.gradle
index 10635025d..c44afe8e1 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -8,5 +8,3 @@ dependencies {
compile "com.squareup.okhttp3:okhttp:$okhttpVersion"
compile "com.squareup.okhttp3:logging-interceptor:$loggingOkhttpVersion"
}
-
-apply from: rootProject.file('gradle/checkstyle.gradle')
diff --git a/core/src/main/java/org/nervos/ckb/service/RpcService.java b/core/src/main/java/org/nervos/ckb/service/RpcService.java
index 4f5ada7be..445ff6e8d 100644
--- a/core/src/main/java/org/nervos/ckb/service/RpcService.java
+++ b/core/src/main/java/org/nervos/ckb/service/RpcService.java
@@ -65,7 +65,7 @@ public T post(@NotNull String method, List params, Type cls, Gson gson) thro
}
JsonElement jsonElement =
- new JsonParser().parse(responseBody).getAsJsonObject().get("result");
+ JsonParser.parseString(responseBody).getAsJsonObject().get("result");
if (jsonElement.isJsonObject()) {
return gson.fromJson(jsonElement.getAsJsonObject(), cls);
}
@@ -101,7 +101,7 @@ public void onResponse(@NotNull Call call, @NotNull Response response)
"RpcService method " + method + " error " + gson.toJson(rpcResponse.error));
}
JsonElement jsonElement =
- new JsonParser().parse(responseBody).getAsJsonObject().get("result");
+ JsonParser.parseString(responseBody).getAsJsonObject().get("result");
if (jsonElement.isJsonObject()) {
callback.onResponse(gson.fromJson(jsonElement.getAsJsonObject(), cls));
}
diff --git a/core/src/main/java/org/nervos/ckb/service/adapter/CheckedEnumTypeAdapterFactory.java b/core/src/main/java/org/nervos/ckb/service/adapter/CheckedEnumTypeAdapterFactory.java
index e6863391d..85e321532 100644
--- a/core/src/main/java/org/nervos/ckb/service/adapter/CheckedEnumTypeAdapterFactory.java
+++ b/core/src/main/java/org/nervos/ckb/service/adapter/CheckedEnumTypeAdapterFactory.java
@@ -10,36 +10,36 @@
public class CheckedEnumTypeAdapterFactory implements TypeAdapterFactory {
- @Override
- public TypeAdapter create(Gson gson, TypeToken type) {
- if (!type.getRawType().isEnum()) {
- return null;
+ @Override
+ public TypeAdapter create(Gson gson, TypeToken type) {
+ if (!type.getRawType().isEnum()) {
+ return null;
+ }
+
+ TypeAdapter delegate = gson.getDelegateAdapter(this, type);
+
+ return new TypeAdapter() {
+ @Override
+ public void write(JsonWriter out, T value) throws IOException {
+ delegate.write(out, value);
+ }
+
+ @Override
+ public T read(JsonReader in) throws IOException {
+ if (in.peek() == JsonToken.NULL) {
+ in.nextNull();
+ return null;
}
- TypeAdapter delegate = gson.getDelegateAdapter(this, type);
-
- return new TypeAdapter() {
- @Override
- public void write(JsonWriter out, T value) throws IOException {
- delegate.write(out, value);
- }
-
- @Override
- public T read(JsonReader in) throws IOException {
- if (in.peek() == JsonToken.NULL) {
- in.nextNull();
- return null;
- }
-
- String rawValue = in.nextString();
- T value = delegate.fromJsonTree(new JsonPrimitive(rawValue));
- // By default, Gson return null for enum from unknown string.
- // Here we only return null when json is null.
- if (value == null) {
- throw new JsonParseException(String.format("Undefined value '%s' for enum '%s'", rawValue, type));
- }
- return value;
- }
- };
- }
+ String rawValue = in.nextString();
+ T value = delegate.fromJsonTree(new JsonPrimitive(rawValue));
+ // By default, Gson return null for enum from unknown string.
+ // Here we only return null when json is null.
+ if (value == null) {
+ throw new JsonParseException(String.format("Undefined value '%s' for enum '%s'", rawValue, type));
+ }
+ return value;
+ }
+ };
+ }
}
diff --git a/core/src/main/java/org/nervos/ckb/sign/ScriptGroup.java b/core/src/main/java/org/nervos/ckb/sign/ScriptGroup.java
index 311e7114b..e2afac5d2 100644
--- a/core/src/main/java/org/nervos/ckb/sign/ScriptGroup.java
+++ b/core/src/main/java/org/nervos/ckb/sign/ScriptGroup.java
@@ -59,10 +59,6 @@ private Builder() {
this.outputIndices = new ArrayList<>();
}
- public static Builder aScriptGroup() {
- return new Builder();
- }
-
public Builder setScript(Script script) {
this.script = script;
return this;
@@ -89,7 +85,7 @@ public Builder setInputIndices(List inputIndices) {
}
public Builder addInputIndices(int... indices) {
- for (int index : indices) {
+ for (int index: indices) {
this.inputIndices.add(index);
}
return this;
@@ -101,7 +97,7 @@ public Builder setOutputIndices(List outputIndices) {
}
public Builder addOutputIndices(int... indices) {
- for (int index : indices) {
+ for (int index: indices) {
this.outputIndices.add(index);
}
return this;
diff --git a/core/src/main/java/org/nervos/ckb/sign/TransactionSigner.java b/core/src/main/java/org/nervos/ckb/sign/TransactionSigner.java
index 156d8ef59..c06243012 100644
--- a/core/src/main/java/org/nervos/ckb/sign/TransactionSigner.java
+++ b/core/src/main/java/org/nervos/ckb/sign/TransactionSigner.java
@@ -1,10 +1,7 @@
package org.nervos.ckb.sign;
import org.nervos.ckb.Network;
-import org.nervos.ckb.sign.signer.AcpSigner;
-import org.nervos.ckb.sign.signer.PwSigner;
-import org.nervos.ckb.sign.signer.Secp256k1Blake160MultisigAllSigner;
-import org.nervos.ckb.sign.signer.Secp256k1Blake160SighashAllSigner;
+import org.nervos.ckb.sign.signer.*;
import org.nervos.ckb.type.Script;
import org.nervos.ckb.type.ScriptType;
import org.nervos.ckb.type.Transaction;
@@ -20,23 +17,27 @@ public class TransactionSigner {
static {
TESTNET_TRANSACTION_SIGNER = new TransactionSigner()
.registerLockScriptSigner(
- Script.SECP256K1_BLAKE160_SIGNHASH_ALL_CODE_HASH, Secp256k1Blake160SighashAllSigner.getInstance())
+ Script.SECP256K1_BLAKE160_SIGNHASH_ALL_CODE_HASH, new Secp256k1Blake160SighashAllSigner())
.registerLockScriptSigner(
- Script.SECP256K1_BLAKE160_MULTISIG_ALL_CODE_HASH, Secp256k1Blake160MultisigAllSigner.getInstance())
+ Script.SECP256K1_BLAKE160_MULTISIG_ALL_CODE_HASH, new Secp256k1Blake160MultisigAllSigner())
.registerLockScriptSigner(
- Script.ANY_CAN_PAY_CODE_HASH_TESTNET, AcpSigner.getInstance())
+ Script.ANY_CAN_PAY_CODE_HASH_TESTNET, new AcpSigner())
.registerLockScriptSigner(
- Script.PW_LOCK_CODE_HASH_TESTNET, PwSigner.getInstance());
+ Script.PW_LOCK_CODE_HASH_TESTNET, new PwSigner())
+ .registerLockScriptSigner(
+ Script.OMNILOCK_CODE_HASH_TESTNET, new OmnilockSigner());
MAINNET_TRANSACTION_SIGNER = new TransactionSigner()
.registerLockScriptSigner(
- Script.SECP256K1_BLAKE160_SIGNHASH_ALL_CODE_HASH, Secp256k1Blake160SighashAllSigner.getInstance())
+ Script.SECP256K1_BLAKE160_SIGNHASH_ALL_CODE_HASH, new Secp256k1Blake160SighashAllSigner())
+ .registerLockScriptSigner(
+ Script.SECP256K1_BLAKE160_MULTISIG_ALL_CODE_HASH, new Secp256k1Blake160MultisigAllSigner())
.registerLockScriptSigner(
- Script.SECP256K1_BLAKE160_MULTISIG_ALL_CODE_HASH, Secp256k1Blake160MultisigAllSigner.getInstance())
+ Script.ANY_CAN_PAY_CODE_HASH_MAINNET, new AcpSigner())
.registerLockScriptSigner(
- Script.ANY_CAN_PAY_CODE_HASH_MAINNET, AcpSigner.getInstance())
+ Script.PW_LOCK_CODE_HASH_MAINNET, new PwSigner())
.registerLockScriptSigner(
- Script.PW_LOCK_CODE_HASH_MAINNET, PwSigner.getInstance());
+ Script.OMNILOCK_CODE_HASH_MAINNET, new OmnilockSigner());
}
public TransactionSigner() {
@@ -49,7 +50,7 @@ public TransactionSigner(Map scriptSignerMap) {
public TransactionSigner(TransactionSigner s) {
scriptSignerMap = new HashMap<>();
- for (Map.Entry entry : s.scriptSignerMap.entrySet()) {
+ for (Map.Entry entry: s.scriptSignerMap.entrySet()) {
scriptSignerMap.put(entry.getKey(), entry.getValue());
}
}
@@ -86,6 +87,12 @@ public TransactionSigner registerLockScriptSigner(String codeHash, ScriptSigner
return registerLockScriptSigner(Numeric.hexStringToByteArray(codeHash), scriptSigner);
}
+
+ public Set signTransaction(
+ TransactionWithScriptGroups transaction, Context... contexts) {
+ return signTransaction(transaction, new HashSet<>(Arrays.asList(contexts)));
+ }
+
public Set signTransaction(
TransactionWithScriptGroups transaction, Set contexts) {
Set signedGroupsIndices = new HashSet<>();
@@ -104,7 +111,7 @@ public Set signTransaction(
ScriptSigner signer =
scriptSignerMap.get(new Key(script.codeHash, script.hashType, group.getGroupType()));
if (signer != null) {
- for (Context context : contexts) {
+ for (Context context: contexts) {
if (signer.signTransaction(tx, group, context)) {
signedGroupsIndices.add(i);
break;
@@ -174,4 +181,4 @@ public int hashCode() {
return result;
}
}
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/org/nervos/ckb/sign/omnilock/AuthFlag.java b/core/src/main/java/org/nervos/ckb/sign/omnilock/AuthFlag.java
new file mode 100644
index 000000000..e128fdcfc
--- /dev/null
+++ b/core/src/main/java/org/nervos/ckb/sign/omnilock/AuthFlag.java
@@ -0,0 +1,32 @@
+package org.nervos.ckb.sign.omnilock;
+
+public enum AuthFlag {
+ CKB_SECP256K1_BLAKE160((byte) 0x0),
+ ETHEREUM((byte) 0x1),
+ EOS((byte) 0x2),
+ TRON((byte) 0x3),
+ BITCOIN((byte) 0x4),
+ DOGECOIN((byte) 0x5),
+ CKB_MULTI_SIG((byte) 0x6),
+ LOCK_SCRIPT_HASH((byte) 0xFC),
+ EXEC((byte) 0xFD),
+ DYNAMIC_LINKING((byte) 0xFE);
+ private byte value;
+
+ AuthFlag(byte value) {
+ this.value = value;
+ }
+
+ public byte getValue() {
+ return value;
+ }
+
+ public static AuthFlag valueOf(byte flag) {
+ for (AuthFlag authFlag: AuthFlag.values()) {
+ if (authFlag.value == flag) {
+ return authFlag;
+ }
+ }
+ throw new IllegalArgumentException("unknown value");
+ }
+}
diff --git a/core/src/main/java/org/nervos/ckb/sign/omnilock/OmnilockArgs.java b/core/src/main/java/org/nervos/ckb/sign/omnilock/OmnilockArgs.java
new file mode 100644
index 000000000..ab6cce09d
--- /dev/null
+++ b/core/src/main/java/org/nervos/ckb/sign/omnilock/OmnilockArgs.java
@@ -0,0 +1,216 @@
+package org.nervos.ckb.sign.omnilock;
+
+import org.nervos.ckb.utils.Numeric;
+import org.nervos.ckb.utils.address.Address;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+public class OmnilockArgs {
+ private Authentication authentication;
+ private OmniConfig omniConfig;
+
+ public OmnilockArgs(String address) {
+ this(Address.decode(address).getScript().args);
+ }
+
+ public OmnilockArgs(byte[] args) {
+ authentication = Authentication.decode(args);
+ omniConfig = OmniConfig.decode(Arrays.copyOfRange(args, 21, args.length));
+ }
+
+ public OmnilockArgs(Authentication authentication, OmniConfig omniConfig) {
+ this.authentication = authentication;
+ this.omniConfig = omniConfig;
+ }
+
+ public byte[] encode() {
+ byte[] args1 = authentication.encode();
+ byte[] args2 = omniConfig.encode();
+ return Numeric.concatBytes(args1, args2);
+ }
+
+ public Authentication getAuthenticationArgs() {
+ return authentication;
+ }
+
+ public void setAuthenticationArgs(Authentication authentication) {
+ this.authentication = authentication;
+ }
+
+ public OmniConfig getOmniArgs() {
+ return omniConfig;
+ }
+
+ public void setOmniArgs(OmniConfig omniConfig) {
+ this.omniConfig = omniConfig;
+ }
+
+
+ public static class Authentication {
+ private static int AUTH_CONTENT_LENGTH = 20;
+ private AuthFlag flag;
+ private byte[] authContent;
+
+ public AuthFlag getFlag() {
+ return flag;
+ }
+
+ public void setFlag(AuthFlag flag) {
+ this.flag = flag;
+ }
+
+ public byte[] getAuthContent() {
+ return authContent;
+ }
+
+ public void setAuthContent(byte[] authContent) {
+ this.authContent = authContent;
+ }
+
+ public static Authentication decode(byte[] args) {
+ Authentication authentication = new Authentication();
+ authentication.flag = AuthFlag.valueOf(args[0]);
+ authentication.authContent = new byte[AUTH_CONTENT_LENGTH];
+ System.arraycopy(args, 1, authentication.authContent, 0, AUTH_CONTENT_LENGTH);
+ return authentication;
+ }
+
+ public byte[] encode() {
+ byte[] encoded = new byte[21];
+ encoded[0] = flag.getValue();
+ System.arraycopy(authContent, 0, encoded, 1, AUTH_CONTENT_LENGTH);
+ return encoded;
+ }
+ }
+
+
+ public static class OmniConfig {
+ private int flag;
+ private byte[] adminListCellTypeId;
+ private Integer minimumCKBExponentInAcp;
+ private Integer minimumSUDTExponentInAcp;
+ private Long sinceForTimeLock;
+ private byte[] typeScriptHashForSupply;
+
+ public static OmniConfig decode(byte[] raw) {
+ OmniConfig args = new OmniConfig();
+ args.flag = raw[0];
+ if (args.isAdminModeEnabled()) {
+ args.adminListCellTypeId = Arrays.copyOfRange(raw, 1, 33);
+ }
+ if (args.isAnyoneCanPayModeEnabled()) {
+ args.minimumCKBExponentInAcp = Byte.toUnsignedInt(raw[33]);
+ args.minimumSUDTExponentInAcp = Byte.toUnsignedInt(raw[34]);
+ }
+ if (args.isTimeLockModeEnabled()) {
+ ByteBuffer buffer = ByteBuffer.wrap(raw, 35, 8);
+ args.sinceForTimeLock = buffer.getLong();
+ }
+ if (args.isSupplyModeEnabled()) {
+ args.typeScriptHashForSupply = Arrays.copyOfRange(raw, 43, 75);
+ }
+ return args;
+ }
+
+ private void writeBytes(ByteArrayOutputStream out, byte[] bytes) {
+ out.write(bytes, 0, bytes.length);
+ }
+
+ public byte[] encode() {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ writeBytes(out, new byte[]{(byte) flag});
+ if (isAdminModeEnabled()) {
+ writeBytes(out, adminListCellTypeId);
+ } else {
+ return out.toByteArray();
+ }
+ if (isAnyoneCanPayModeEnabled()) {
+ writeBytes(out, new byte[]{minimumCKBExponentInAcp.byteValue()});
+ if (minimumSUDTExponentInAcp != null) {
+ writeBytes(out, new byte[]{minimumSUDTExponentInAcp.byteValue()});
+ } else {
+ return out.toByteArray();
+ }
+ } else {
+ return out.toByteArray();
+ }
+ if (isTimeLockModeEnabled()) {
+ ByteBuffer buffer = ByteBuffer.allocate(8);
+ buffer.putLong(sinceForTimeLock);
+ writeBytes(out, buffer.array());
+ } else {
+ return out.toByteArray();
+ }
+ if (isSupplyModeEnabled()) {
+ writeBytes(out, typeScriptHashForSupply);
+ }
+ return out.toByteArray();
+ }
+
+ public boolean isAdminModeEnabled() {
+ return (flag & 0b1) != 0;
+ }
+
+ public boolean isAnyoneCanPayModeEnabled() {
+ return (flag & 0b10) != 0;
+ }
+
+ public boolean isTimeLockModeEnabled() {
+ return (flag & 0b100) != 0;
+ }
+
+ public boolean isSupplyModeEnabled() {
+ return (flag & 0b1000) != 0;
+ }
+
+ public int getFlag() {
+ return flag;
+ }
+
+ public void setFlag(int flag) {
+ this.flag = flag;
+ }
+
+ public byte[] getAdminListCellTypeId() {
+ return adminListCellTypeId;
+ }
+
+ public void setAdminListCellTypeId(byte[] adminListCellTypeId) {
+ this.adminListCellTypeId = adminListCellTypeId;
+ }
+
+ public Integer getMinimumCKBExponentInAcp() {
+ return minimumCKBExponentInAcp;
+ }
+
+ public void setMinimumCKBExponentInAcp(Integer minimumCKBExponentInAcp) {
+ this.minimumCKBExponentInAcp = minimumCKBExponentInAcp;
+ }
+
+ public Integer getMinimumSUDTExponentInAcp() {
+ return minimumSUDTExponentInAcp;
+ }
+
+ public void setMinimumSUDTExponentInAcp(Integer minimumSUDTExponentInAcp) {
+ this.minimumSUDTExponentInAcp = minimumSUDTExponentInAcp;
+ }
+
+ public Long getSinceForTimeLock() {
+ return sinceForTimeLock;
+ }
+
+ public void setSinceForTimeLock(Long sinceForTimeLock) {
+ this.sinceForTimeLock = sinceForTimeLock;
+ }
+
+ public byte[] getTypeScriptHashForSupply() {
+ return typeScriptHashForSupply;
+ }
+
+ public void setTypeScriptHashForSupply(byte[] typeScriptHashForSupply) {
+ this.typeScriptHashForSupply = typeScriptHashForSupply;
+ }
+ }
+}
diff --git a/core/src/main/java/org/nervos/ckb/sign/omnilock/OmnilockIdentity.java b/core/src/main/java/org/nervos/ckb/sign/omnilock/OmnilockIdentity.java
new file mode 100644
index 000000000..7dd5be042
--- /dev/null
+++ b/core/src/main/java/org/nervos/ckb/sign/omnilock/OmnilockIdentity.java
@@ -0,0 +1,97 @@
+package org.nervos.ckb.sign.omnilock;
+
+import java.util.List;
+
+public class OmnilockIdentity {
+ private Auth identity;
+ private List proofs;
+
+ public Auth getIdentity() {
+ return identity;
+ }
+
+ public void setIdentity(Auth identity) {
+ this.identity = identity;
+ }
+
+ public List getProofs() {
+ return proofs;
+ }
+
+ public void setProofs(List proofs) {
+ this.proofs = proofs;
+ }
+
+ public static class SmtProofEntry {
+ private byte mask;
+ private byte[] smtProof;
+
+ public byte getMask() {
+ return mask;
+ }
+
+ public void setMask(byte mask) {
+ this.mask = mask;
+ }
+
+ public byte[] getSmtProof() {
+ return smtProof;
+ }
+
+ public void setSmtProof(byte[] smtProof) {
+ this.smtProof = smtProof;
+ }
+ }
+
+ public enum OmnilockFlag {
+ CKB_SECP256K1_BLAKE160((byte) 0x0),
+ LOCK_SCRIPT_HASH((byte) 0xFC);
+ private byte value;
+
+ OmnilockFlag(byte value) {
+ this.value = value;
+ }
+
+ public byte getValue() {
+ return value;
+ }
+
+ public static OmnilockFlag valueOf(byte flag) {
+ for (OmnilockFlag omnilockFlag: OmnilockFlag.values()) {
+ if (omnilockFlag.value == flag) {
+ return omnilockFlag;
+ }
+ }
+ throw new IllegalArgumentException("unknown value");
+ }
+ }
+
+ public static class Auth {
+ private OmnilockFlag flag;
+ private byte[] authContent;
+
+ public OmnilockFlag getFlag() {
+ return flag;
+ }
+
+ public void setFlag(OmnilockFlag flag) {
+ this.flag = flag;
+ }
+
+ public byte[] getAuthContent() {
+ return authContent;
+ }
+
+ public void setAuthContent(byte[] authContent) {
+ this.authContent = authContent;
+ }
+
+ public byte[] encode() {
+ byte[] encoded = new byte[21];
+ encoded[0] = flag.getValue();
+ System.arraycopy(authContent, 0, encoded, 1, 20);
+ return encoded;
+ }
+ }
+
+}
diff --git a/core/src/main/java/org/nervos/ckb/sign/omnilock/OmnilockWitnessLock.java b/core/src/main/java/org/nervos/ckb/sign/omnilock/OmnilockWitnessLock.java
new file mode 100644
index 000000000..cfc9b2248
--- /dev/null
+++ b/core/src/main/java/org/nervos/ckb/sign/omnilock/OmnilockWitnessLock.java
@@ -0,0 +1,109 @@
+package org.nervos.ckb.sign.omnilock;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.nervos.ckb.utils.MoleculeConverter.packBytes;
+
+public class OmnilockWitnessLock {
+ private byte[] signature;
+ private OmnilockIdentity omnilockIdentity;
+ private byte[] preimage;
+
+ public byte[] getSignature() {
+ return signature;
+ }
+
+ public void setSignature(byte[] signature) {
+ this.signature = signature;
+ }
+
+ public OmnilockIdentity getOmnilockIdentity() {
+ return omnilockIdentity;
+ }
+
+ public void setOmnilockIdentity(OmnilockIdentity omnilockIdentity) {
+ this.omnilockIdentity = omnilockIdentity;
+ }
+
+ public byte[] getPreimage() {
+ return preimage;
+ }
+
+ public void setPreimage(byte[] preimage) {
+ this.preimage = preimage;
+ }
+
+ public org.nervos.ckb.sign.omnilock.molecule.OmniLockWitnessLock pack() {
+ org.nervos.ckb.sign.omnilock.molecule.OmniLockWitnessLock.Builder moleculeLock = org.nervos.ckb.sign.omnilock.molecule.OmniLockWitnessLock.builder();
+ if (signature != null) {
+ moleculeLock.setSignature(packBytes(signature));
+ }
+ if (preimage != null) {
+ moleculeLock.setPreimage(packBytes(preimage));
+ }
+ if (omnilockIdentity != null) {
+ org.nervos.ckb.sign.omnilock.molecule.Identity.Builder identityBuilder = org.nervos.ckb.sign.omnilock.molecule.Identity.builder();
+ org.nervos.ckb.sign.omnilock.molecule.Auth.Builder authBuilder = org.nervos.ckb.sign.omnilock.molecule.Auth.builder(omnilockIdentity.getIdentity().encode());
+ identityBuilder.setIdentity(authBuilder.build());
+ org.nervos.ckb.sign.omnilock.molecule.SmtProofEntryVec.Builder smtProofEntryVec = org.nervos.ckb.sign.omnilock.molecule.SmtProofEntryVec.builder();
+ for (OmnilockIdentity.SmtProofEntry s: omnilockIdentity.getProofs()) {
+ smtProofEntryVec.add(packSmtProofEntry(s));
+ }
+ identityBuilder.setProofs(smtProofEntryVec.build());
+ moleculeLock.setOmniIdentity(identityBuilder.build());
+ }
+ return moleculeLock.build();
+ }
+
+ public byte[] packAsEmptyPlaceholder() {
+ return new byte[pack().toByteArray().length];
+ }
+
+ public static OmnilockWitnessLock unpack(byte[] in) {
+ if (in == null) {
+ return null;
+ }
+ org.nervos.ckb.sign.omnilock.molecule.OmniLockWitnessLock moleculeOmniLockWitnessLock = org.nervos.ckb.sign.omnilock.molecule.OmniLockWitnessLock.builder(in).build();
+ OmnilockWitnessLock omnilockWitnessLock = new OmnilockWitnessLock();
+ if (moleculeOmniLockWitnessLock.getPreimage() != null) {
+ omnilockWitnessLock.setPreimage(moleculeOmniLockWitnessLock.getPreimage().toByteArray());
+ }
+ if (moleculeOmniLockWitnessLock.getSignature() != null) {
+ omnilockWitnessLock.setSignature(moleculeOmniLockWitnessLock.getSignature().getItems());
+ }
+
+ if (moleculeOmniLockWitnessLock.getOmniIdentity() != null) {
+ OmnilockIdentity identity = new OmnilockIdentity();
+ List proofs = new ArrayList<>();
+ org.nervos.ckb.sign.omnilock.molecule.Identity moleculeIdentity = moleculeOmniLockWitnessLock.getOmniIdentity();
+ for (int i = 0; i < moleculeIdentity.getProofs().getSize(); i++) {
+ org.nervos.ckb.sign.omnilock.molecule.SmtProofEntry moleculeSmtProofEntry = moleculeIdentity.getProofs().get(i);
+ OmnilockIdentity.SmtProofEntry smtProofEntry = new OmnilockIdentity.SmtProofEntry();
+ smtProofEntry.setMask(moleculeSmtProofEntry.getMask());
+ smtProofEntry.setSmtProof(moleculeSmtProofEntry.getProof().toByteArray());
+ proofs.add(smtProofEntry);
+ }
+
+ byte[] bytes = moleculeIdentity.getIdentity().toByteArray();
+ omnilockWitnessLock.setOmnilockIdentity(identity);
+ OmnilockIdentity.Auth auth = new OmnilockIdentity.Auth();
+ auth.setFlag(OmnilockIdentity.OmnilockFlag.valueOf(bytes[0]));
+ auth.setAuthContent(Arrays.copyOfRange(bytes, 1, 20));
+
+ identity.setIdentity(auth);
+ identity.setProofs(proofs);
+ omnilockWitnessLock.setOmnilockIdentity(identity);
+ }
+
+ return omnilockWitnessLock;
+ }
+
+ public org.nervos.ckb.sign.omnilock.molecule.SmtProofEntry packSmtProofEntry(OmnilockIdentity.SmtProofEntry smtProofEntry) {
+ org.nervos.ckb.sign.omnilock.molecule.SmtProofEntry.Builder builder = org.nervos.ckb.sign.omnilock.molecule.SmtProofEntry.builder();
+ builder.setProof(org.nervos.ckb.sign.omnilock.molecule.SmtProof.builder(smtProofEntry.getSmtProof()).build());
+ builder.setMask(smtProofEntry.getMask());
+ return builder.build();
+ }
+}
diff --git a/core/src/main/java/org/nervos/ckb/sign/omnilock/molecule/Auth.java b/core/src/main/java/org/nervos/ckb/sign/omnilock/molecule/Auth.java
new file mode 100644
index 000000000..7e84e43d5
--- /dev/null
+++ b/core/src/main/java/org/nervos/ckb/sign/omnilock/molecule/Auth.java
@@ -0,0 +1,94 @@
+package org.nervos.ckb.sign.omnilock.molecule;
+
+import org.nervos.ckb.type.base.Array;
+import org.nervos.ckb.type.base.MoleculeException;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.Objects;
+
+public final class Auth extends Array {
+ public static Class ITEM_TYPE = byte.class;
+
+ public static int ITEM_SIZE = 1;
+
+ public static int ITEM_COUNT = 21;
+
+ public static int SIZE = ITEM_SIZE * ITEM_COUNT;
+
+ private byte[] items;
+
+ private Auth() {
+ }
+
+ @Nonnull
+ public byte get(int i) {
+ return items[i];
+ }
+
+ @Nullable
+ public byte[] getItems() {
+ return items;
+ }
+
+ @Override
+ public int getItemCount() {
+ return ITEM_COUNT;
+ }
+
+ @Override
+ public int getItemSize() {
+ return ITEM_SIZE;
+ }
+
+ @Override
+ public Class getItemType() {
+ return ITEM_TYPE;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static Builder builder(@Nonnull byte[] buf) {
+ return new Builder(buf);
+ }
+
+ public static final class Builder {
+ private byte[] items;
+
+ private Builder() {
+ items = new byte[ITEM_COUNT];
+ }
+
+ private Builder(@Nonnull byte[] buf) {
+ Objects.requireNonNull(buf);
+ if (buf.length != SIZE) {
+ throw MoleculeException.invalidByteSize(SIZE, buf.length, Auth.class);
+ }
+ items = buf;
+ }
+
+ public Builder set(int i, @Nonnull byte item) {
+ Objects.requireNonNull(item);
+ items[i] = item;
+ return this;
+ }
+
+ public Builder set(@Nonnull byte[] items) {
+ Objects.requireNonNull(items);
+ if (items.length != ITEM_COUNT) {
+ throw MoleculeException.invalidItemCount(ITEM_COUNT, items.length, Auth.class);
+ }
+ this.items = items;
+ return this;
+ }
+
+ public Auth build() {
+ Auth a = new Auth();
+ a.buf = items;
+ a.items = items;
+ return a;
+ }
+ }
+}
diff --git a/core/src/main/java/org/nervos/ckb/sign/omnilock/molecule/Identity.java b/core/src/main/java/org/nervos/ckb/sign/omnilock/molecule/Identity.java
new file mode 100644
index 000000000..29d2b8a81
--- /dev/null
+++ b/core/src/main/java/org/nervos/ckb/sign/omnilock/molecule/Identity.java
@@ -0,0 +1,109 @@
+package org.nervos.ckb.sign.omnilock.molecule;
+
+import org.nervos.ckb.type.base.MoleculeException;
+import org.nervos.ckb.type.base.MoleculeUtils;
+import org.nervos.ckb.type.base.Table;
+
+import javax.annotation.Nonnull;
+import java.util.Arrays;
+import java.util.Objects;
+
+public final class Identity extends Table {
+ public static int FIELD_COUNT = 2;
+
+ private Auth identity;
+
+ private SmtProofEntryVec proofs;
+
+ private Identity() {
+ }
+
+ @Nonnull
+ public Auth getIdentity() {
+ return identity;
+ }
+
+ @Nonnull
+ public SmtProofEntryVec getProofs() {
+ return proofs;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static Builder builder(@Nonnull byte[] buf) {
+ return new Builder(buf);
+ }
+
+ public static final class Builder {
+ private Auth identity;
+
+ private SmtProofEntryVec proofs;
+
+ private Builder() {
+ identity = Auth.builder().build();
+ proofs = SmtProofEntryVec.builder().build();
+ }
+
+ private Builder(@Nonnull byte[] buf) {
+ Objects.requireNonNull(buf);
+ int size = MoleculeUtils.littleEndianBytes4ToInt(buf, 0);
+ if (buf.length != size) {
+ throw MoleculeException.invalidByteSize(size, buf.length, Identity.class);
+ }
+ int[] offsets = MoleculeUtils.getOffsets(buf);
+ if (offsets.length - 1 != FIELD_COUNT) {
+ throw MoleculeException.invalidFieldCount(FIELD_COUNT, offsets.length - 1, Identity.class);
+ }
+ byte[] itemBuf;
+ itemBuf = Arrays.copyOfRange(buf, offsets[0], offsets[1]);
+ identity = Auth.builder(itemBuf).build();
+ itemBuf = Arrays.copyOfRange(buf, offsets[1], offsets[2]);
+ proofs = SmtProofEntryVec.builder(itemBuf).build();
+ }
+
+ public Builder setIdentity(@Nonnull Auth identity) {
+ Objects.requireNonNull(identity);
+ this.identity = identity;
+ return this;
+ }
+
+ public Builder setProofs(@Nonnull SmtProofEntryVec proofs) {
+ Objects.requireNonNull(proofs);
+ this.proofs = proofs;
+ return this;
+ }
+
+ public Identity build() {
+ int[] offsets = new int[FIELD_COUNT];
+ offsets[0] = 4 + 4 * FIELD_COUNT;
+ offsets[1] = offsets[0] + identity.getSize();
+ int[] fieldsSize = new int[FIELD_COUNT];
+ fieldsSize[0] = identity.getSize();
+ fieldsSize[1] = proofs.getSize();
+ byte[][] fieldsBuf = new byte[FIELD_COUNT][];
+ fieldsBuf[0] = identity.toByteArray();
+ fieldsBuf[1] = proofs.toByteArray();
+ int size = 4 + 4 * FIELD_COUNT;
+ for (int i = 0; i < FIELD_COUNT; i++) {
+ size += fieldsSize[i];
+ }
+ byte[] buf = new byte[size];
+ MoleculeUtils.setInt(size, buf, 0);
+ int start = 4;
+ for (int i = 0; i < FIELD_COUNT; i++) {
+ MoleculeUtils.setInt(offsets[i], buf, start);
+ start += 4;
+ }
+ for (int i = 0; i < FIELD_COUNT; i++) {
+ MoleculeUtils.setBytes(fieldsBuf[i], buf, offsets[i]);
+ }
+ Identity t = new Identity();
+ t.buf = buf;
+ t.identity = identity;
+ t.proofs = proofs;
+ return t;
+ }
+ }
+}
diff --git a/core/src/main/java/org/nervos/ckb/sign/omnilock/molecule/OmniLockWitnessLock.java b/core/src/main/java/org/nervos/ckb/sign/omnilock/molecule/OmniLockWitnessLock.java
new file mode 100644
index 000000000..0a3986346
--- /dev/null
+++ b/core/src/main/java/org/nervos/ckb/sign/omnilock/molecule/OmniLockWitnessLock.java
@@ -0,0 +1,136 @@
+package org.nervos.ckb.sign.omnilock.molecule;
+
+import org.nervos.ckb.type.base.MoleculeException;
+import org.nervos.ckb.type.base.MoleculeUtils;
+import org.nervos.ckb.type.base.Table;
+import org.nervos.ckb.type.concrete.Bytes;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.Arrays;
+import java.util.Objects;
+
+public final class OmniLockWitnessLock extends Table {
+ public static int FIELD_COUNT = 3;
+
+ private Bytes signature;
+
+ private Identity omniIdentity;
+
+ private Bytes preimage;
+
+ private OmniLockWitnessLock() {
+ }
+
+ @Nullable
+ public Bytes getSignature() {
+ return signature;
+ }
+
+ @Nullable
+ public Identity getOmniIdentity() {
+ return omniIdentity;
+ }
+
+ @Nullable
+ public Bytes getPreimage() {
+ return preimage;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static Builder builder(@Nonnull byte[] buf) {
+ return new Builder(buf);
+ }
+
+ public static final class Builder {
+ private Bytes signature;
+
+ private Identity omniIdentity;
+
+ private Bytes preimage;
+
+ private Builder() {
+ signature = null;
+ omniIdentity = null;
+ preimage = null;
+ }
+
+ private Builder(@Nonnull byte[] buf) {
+ Objects.requireNonNull(buf);
+ int size = MoleculeUtils.littleEndianBytes4ToInt(buf, 0);
+ if (buf.length != size) {
+ throw MoleculeException.invalidByteSize(size, buf.length, OmniLockWitnessLock.class);
+ }
+ int[] offsets = MoleculeUtils.getOffsets(buf);
+ if (offsets.length - 1 != FIELD_COUNT) {
+ throw MoleculeException.invalidFieldCount(FIELD_COUNT, offsets.length - 1, OmniLockWitnessLock.class);
+ }
+ byte[] itemBuf;
+ if (offsets[0] != offsets[1]) {
+ itemBuf = Arrays.copyOfRange(buf, offsets[0], offsets[1]);
+ signature = Bytes.builder(itemBuf).build();
+ }
+ if (offsets[1] != offsets[2]) {
+ itemBuf = Arrays.copyOfRange(buf, offsets[1], offsets[2]);
+ omniIdentity = Identity.builder(itemBuf).build();
+ }
+ if (offsets[2] != offsets[3]) {
+ itemBuf = Arrays.copyOfRange(buf, offsets[2], offsets[3]);
+ preimage = Bytes.builder(itemBuf).build();
+ }
+ }
+
+ public Builder setSignature(@Nullable Bytes signature) {
+ this.signature = signature;
+ return this;
+ }
+
+ public Builder setOmniIdentity(@Nullable Identity omniIdentity) {
+ this.omniIdentity = omniIdentity;
+ return this;
+ }
+
+ public Builder setPreimage(@Nullable Bytes preimage) {
+ this.preimage = preimage;
+ return this;
+ }
+
+ public OmniLockWitnessLock build() {
+ int[] offsets = new int[FIELD_COUNT];
+ offsets[0] = 4 + 4 * FIELD_COUNT;
+ offsets[1] = offsets[0] + (signature == null ? 0 : signature.getSize());
+ offsets[2] = offsets[1] + (omniIdentity == null ? 0 : omniIdentity.getSize());
+ int[] fieldsSize = new int[FIELD_COUNT];
+ fieldsSize[0] = (signature == null ? 0 : signature.getSize());
+ fieldsSize[1] = (omniIdentity == null ? 0 : omniIdentity.getSize());
+ fieldsSize[2] = (preimage == null ? 0 : preimage.getSize());
+ byte[][] fieldsBuf = new byte[FIELD_COUNT][];
+ fieldsBuf[0] = (signature == null ? new byte[]{} : signature.toByteArray());
+ fieldsBuf[1] = (omniIdentity == null ? new byte[]{} : omniIdentity.toByteArray());
+ fieldsBuf[2] = (preimage == null ? new byte[]{} : preimage.toByteArray());
+ int size = 4 + 4 * FIELD_COUNT;
+ for (int i = 0; i < FIELD_COUNT; i++) {
+ size += fieldsSize[i];
+ }
+ byte[] buf = new byte[size];
+ MoleculeUtils.setInt(size, buf, 0);
+ int start = 4;
+ for (int i = 0; i < FIELD_COUNT; i++) {
+ MoleculeUtils.setInt(offsets[i], buf, start);
+ start += 4;
+ }
+ for (int i = 0; i < FIELD_COUNT; i++) {
+ MoleculeUtils.setBytes(fieldsBuf[i], buf, offsets[i]);
+ }
+ OmniLockWitnessLock t = new OmniLockWitnessLock();
+ t.buf = buf;
+ t.signature = signature;
+ t.omniIdentity = omniIdentity;
+ t.preimage = preimage;
+ return t;
+ }
+ }
+}
diff --git a/core/src/main/java/org/nervos/ckb/sign/omnilock/molecule/SmtProof.java b/core/src/main/java/org/nervos/ckb/sign/omnilock/molecule/SmtProof.java
new file mode 100644
index 000000000..405400d80
--- /dev/null
+++ b/core/src/main/java/org/nervos/ckb/sign/omnilock/molecule/SmtProof.java
@@ -0,0 +1,123 @@
+package org.nervos.ckb.sign.omnilock.molecule;
+
+import org.nervos.ckb.type.base.FixedVector;
+import org.nervos.ckb.type.base.MoleculeException;
+import org.nervos.ckb.type.base.MoleculeUtils;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.Arrays;
+import java.util.Objects;
+
+public final class SmtProof extends FixedVector {
+ public static int ITEM_SIZE = 1;
+
+ public static Class ITEM_TYPE = byte.class;
+
+ private byte[] items;
+
+ private SmtProof() {
+ }
+
+ @Override
+ public int getItemSize() {
+ return ITEM_SIZE;
+ }
+
+ @Nonnull
+ public byte get(int i) {
+ return items[i];
+ }
+
+ @Nullable
+ public byte[] getItems() {
+ return items;
+ }
+
+ @Override
+ public int getItemCount() {
+ return items.length;
+ }
+
+ @Override
+ public Class getItemType() {
+ return ITEM_TYPE;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static Builder builder(@Nonnull byte[] buf) {
+ return new Builder(buf);
+ }
+
+ public static final class Builder {
+ private byte[] items;
+
+ private Builder() {
+ this.items = new byte[0];
+ }
+
+ private Builder(@Nonnull byte[] buf) {
+ Objects.requireNonNull(buf);
+ int itemCount = MoleculeUtils.littleEndianBytes4ToInt(buf, 0);
+ int size = 4 + itemCount * ITEM_SIZE;
+ if (buf.length != size) {
+ throw MoleculeException.invalidByteSize(size, buf.length, SmtProof.class);
+ }
+ items = Arrays.copyOfRange(buf, 4, buf.length);
+ }
+
+ public Builder add(@Nonnull byte item) {
+ Objects.requireNonNull(item);
+ byte[] originalItems = items;
+ items = new byte[originalItems.length + 1];
+ System.arraycopy(originalItems, 0, items, 0, originalItems.length);
+ items[items.length - 1] = item;
+ return this;
+ }
+
+ public Builder add(@Nonnull byte[] items) {
+ Objects.requireNonNull(items);
+ byte[] originalItems = this.items;
+ this.items = new byte[originalItems.length + items.length];
+ System.arraycopy(originalItems, 0, this.items, 0, originalItems.length);
+ System.arraycopy(items, 0, this.items, originalItems.length, items.length);
+ return this;
+ }
+
+ public Builder set(int i, @Nonnull byte item) {
+ Objects.requireNonNull(item);
+ items[i] = item;
+ return this;
+ }
+
+ public Builder set(@Nonnull byte[] items) {
+ Objects.requireNonNull(items);
+ this.items = items;
+ return this;
+ }
+
+ public Builder remove(int i) {
+ if (i < 0 || i >= items.length) {
+ throw new ArrayIndexOutOfBoundsException(i);
+ }
+ byte[] originalItems = items;
+ items = new byte[originalItems.length - 1];
+ System.arraycopy(originalItems, 0, items, 0, i);
+ System.arraycopy(originalItems, i + 1, items, i, originalItems.length - i - 1);
+ return this;
+ }
+
+ public SmtProof build() {
+ byte[] buf = new byte[4 + items.length * ITEM_SIZE];
+ MoleculeUtils.setInt(items.length, buf, 0);
+ MoleculeUtils.setBytes(items, buf, 4);
+ SmtProof v = new SmtProof();
+ v.buf = buf;
+ v.items = items;
+ return v;
+ }
+ }
+}
diff --git a/core/src/main/java/org/nervos/ckb/sign/omnilock/molecule/SmtProofEntry.java b/core/src/main/java/org/nervos/ckb/sign/omnilock/molecule/SmtProofEntry.java
new file mode 100644
index 000000000..4c4d8085f
--- /dev/null
+++ b/core/src/main/java/org/nervos/ckb/sign/omnilock/molecule/SmtProofEntry.java
@@ -0,0 +1,107 @@
+package org.nervos.ckb.sign.omnilock.molecule;
+
+import org.nervos.ckb.type.base.MoleculeException;
+import org.nervos.ckb.type.base.MoleculeUtils;
+import org.nervos.ckb.type.base.Table;
+
+import javax.annotation.Nonnull;
+import java.util.Arrays;
+import java.util.Objects;
+
+public final class SmtProofEntry extends Table {
+ public static int FIELD_COUNT = 2;
+
+ private byte mask;
+
+ private SmtProof proof;
+
+ private SmtProofEntry() {
+ }
+
+ @Nonnull
+ public byte getMask() {
+ return mask;
+ }
+
+ @Nonnull
+ public SmtProof getProof() {
+ return proof;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static Builder builder(@Nonnull byte[] buf) {
+ return new Builder(buf);
+ }
+
+ public static final class Builder {
+ private byte mask;
+
+ private SmtProof proof;
+
+ private Builder() {
+ proof = SmtProof.builder().build();
+ }
+
+ private Builder(@Nonnull byte[] buf) {
+ Objects.requireNonNull(buf);
+ int size = MoleculeUtils.littleEndianBytes4ToInt(buf, 0);
+ if (buf.length != size) {
+ throw MoleculeException.invalidByteSize(size, buf.length, SmtProofEntry.class);
+ }
+ int[] offsets = MoleculeUtils.getOffsets(buf);
+ if (offsets.length - 1 != FIELD_COUNT) {
+ throw MoleculeException.invalidFieldCount(FIELD_COUNT, offsets.length - 1, SmtProofEntry.class);
+ }
+ byte[] itemBuf;
+ mask = buf[offsets[0]];
+ itemBuf = Arrays.copyOfRange(buf, offsets[1], offsets[2]);
+ proof = SmtProof.builder(itemBuf).build();
+ }
+
+ public Builder setMask(@Nonnull byte mask) {
+ Objects.requireNonNull(mask);
+ this.mask = mask;
+ return this;
+ }
+
+ public Builder setProof(@Nonnull SmtProof proof) {
+ Objects.requireNonNull(proof);
+ this.proof = proof;
+ return this;
+ }
+
+ public SmtProofEntry build() {
+ int[] offsets = new int[FIELD_COUNT];
+ offsets[0] = 4 + 4 * FIELD_COUNT;
+ offsets[1] = offsets[0] + 1;
+ int[] fieldsSize = new int[FIELD_COUNT];
+ fieldsSize[0] = 1;
+ fieldsSize[1] = proof.getSize();
+ byte[][] fieldsBuf = new byte[FIELD_COUNT][];
+ fieldsBuf[0] = new byte[]{mask};
+ fieldsBuf[1] = proof.toByteArray();
+ int size = 4 + 4 * FIELD_COUNT;
+ for (int i = 0; i < FIELD_COUNT; i++) {
+ size += fieldsSize[i];
+ }
+ byte[] buf = new byte[size];
+ MoleculeUtils.setInt(size, buf, 0);
+ int start = 4;
+ for (int i = 0; i < FIELD_COUNT; i++) {
+ MoleculeUtils.setInt(offsets[i], buf, start);
+ start += 4;
+ }
+ for (int i = 0; i < FIELD_COUNT; i++) {
+ MoleculeUtils.setBytes(fieldsBuf[i], buf, offsets[i]);
+ }
+ SmtProofEntry t = new SmtProofEntry();
+ t.buf = buf;
+ t.mask = mask;
+ t.proof = proof;
+ return t;
+ }
+ }
+}
diff --git a/core/src/main/java/org/nervos/ckb/sign/omnilock/molecule/SmtProofEntryVec.java b/core/src/main/java/org/nervos/ckb/sign/omnilock/molecule/SmtProofEntryVec.java
new file mode 100644
index 000000000..5c138ada8
--- /dev/null
+++ b/core/src/main/java/org/nervos/ckb/sign/omnilock/molecule/SmtProofEntryVec.java
@@ -0,0 +1,134 @@
+package org.nervos.ckb.sign.omnilock.molecule;
+
+import org.nervos.ckb.type.base.DynamicVector;
+import org.nervos.ckb.type.base.MoleculeException;
+import org.nervos.ckb.type.base.MoleculeUtils;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.Arrays;
+import java.util.Objects;
+
+public final class SmtProofEntryVec extends DynamicVector {
+ public static Class ITEM_TYPE = SmtProofEntry.class;
+
+ private SmtProofEntry[] items;
+
+ private SmtProofEntryVec() {
+ }
+
+ @Nonnull
+ public SmtProofEntry get(int i) {
+ return items[i];
+ }
+
+ @Nullable
+ public SmtProofEntry[] getItems() {
+ return items;
+ }
+
+ @Override
+ public int getItemCount() {
+ return items.length;
+ }
+
+ @Override
+ public Class getItemType() {
+ return ITEM_TYPE;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static Builder builder(@Nonnull byte[] buf) {
+ return new Builder(buf);
+ }
+
+ public static final class Builder {
+ private SmtProofEntry[] items;
+
+ private Builder() {
+ items = new SmtProofEntry[0];
+ }
+
+ private Builder(@Nonnull byte[] buf) {
+ Objects.requireNonNull(buf);
+ int size = MoleculeUtils.littleEndianBytes4ToInt(buf, 0);
+ if (buf.length != size) {
+ throw MoleculeException.invalidByteSize(size, buf.length, SmtProofEntryVec.class);
+ }
+ int[] offsets = MoleculeUtils.getOffsets(buf);
+ items = new SmtProofEntry[offsets.length - 1];
+ for (int i = 0; i < items.length; i++) {
+ byte[] itemBuf = Arrays.copyOfRange(buf, offsets[i], offsets[i + 1]);
+ items[i] = SmtProofEntry.builder(itemBuf).build();
+ }
+ }
+
+ public Builder add(@Nonnull SmtProofEntry item) {
+ Objects.requireNonNull(item);
+ SmtProofEntry[] originalItems = items;
+ items = new SmtProofEntry[originalItems.length + 1];
+ System.arraycopy(originalItems, 0, items, 0, originalItems.length);
+ items[items.length - 1] = item;
+ return this;
+ }
+
+ public Builder add(@Nonnull SmtProofEntry[] items) {
+ Objects.requireNonNull(items);
+ SmtProofEntry[] originalItems = this.items;
+ this.items = new SmtProofEntry[originalItems.length + items.length];
+ System.arraycopy(originalItems, 0, this.items, 0, originalItems.length);
+ System.arraycopy(items, 0, this.items, originalItems.length, items.length);
+ return this;
+ }
+
+ public Builder set(int i, @Nonnull SmtProofEntry item) {
+ Objects.requireNonNull(item);
+ items[i] = item;
+ return this;
+ }
+
+ public Builder set(@Nonnull SmtProofEntry[] items) {
+ Objects.requireNonNull(items);
+ this.items = items;
+ return this;
+ }
+
+ public Builder remove(int i) {
+ if (i < 0 || i >= items.length) {
+ throw new ArrayIndexOutOfBoundsException(i);
+ }
+ SmtProofEntry[] originalItems = items;
+ items = new SmtProofEntry[originalItems.length - 1];
+ System.arraycopy(originalItems, 0, items, 0, i);
+ System.arraycopy(originalItems, i + 1, items, i, originalItems.length - i - 1);
+ return this;
+ }
+
+ public SmtProofEntryVec build() {
+ int size = 4 + 4 * items.length;
+ for (int i = 0; i < items.length; i++) {
+ size += items[i].getSize();
+ }
+ byte[] buf = new byte[size];
+ MoleculeUtils.setInt(size, buf, 0);
+ int offset = 4 + 4 * items.length;
+ int start = 4;
+ for (int i = 0; i < items.length; i++) {
+ MoleculeUtils.setInt(offset, buf, start);
+ offset += items[i].getSize();
+ start += 4;
+ }
+ for (int i = 0; i < items.length; i++) {
+ MoleculeUtils.setBytes(items[i].toByteArray(), buf, start);
+ start += items[i].getSize();
+ }
+ SmtProofEntryVec v = new SmtProofEntryVec();
+ v.buf = buf;
+ v.items = items;
+ return v;
+ }
+ }
+}
diff --git a/core/src/main/java/org/nervos/ckb/sign/signer/AcpSigner.java b/core/src/main/java/org/nervos/ckb/sign/signer/AcpSigner.java
index 0ba518aa2..3108309b7 100644
--- a/core/src/main/java/org/nervos/ckb/sign/signer/AcpSigner.java
+++ b/core/src/main/java/org/nervos/ckb/sign/signer/AcpSigner.java
@@ -8,34 +8,20 @@
import org.nervos.ckb.type.Transaction;
public class AcpSigner implements ScriptSigner {
- private Secp256k1Blake160SighashAllSigner secp256K1Blake160SighashAllSigner =
- Secp256k1Blake160SighashAllSigner.getInstance();
- private static AcpSigner INSTANCE;
-
- private AcpSigner() {
- }
-
- public static AcpSigner getInstance() {
- if (INSTANCE == null) {
- INSTANCE = new AcpSigner();
- }
- return INSTANCE;
- }
-
@Override
public boolean signTransaction(
Transaction transaction, ScriptGroup scriptGroup, Context context) {
Script script = scriptGroup.getScript();
ECKeyPair keyPair = context.getKeyPair();
if (isMatched(keyPair, script.args)) {
- return secp256K1Blake160SighashAllSigner.signScriptGroup(
+ return Secp256k1Blake160SighashAllSigner.signTransactionInPlace(
transaction, scriptGroup, keyPair);
} else {
return false;
}
}
- public boolean isMatched(ECKeyPair keyPair, byte[] scriptArgs) {
- return secp256K1Blake160SighashAllSigner.isMatched(keyPair, scriptArgs);
+ public static boolean isMatched(ECKeyPair keyPair, byte[] scriptArgs) {
+ return Secp256k1Blake160SighashAllSigner.isMatched(keyPair, scriptArgs);
}
}
diff --git a/core/src/main/java/org/nervos/ckb/sign/signer/OmnilockSigner.java b/core/src/main/java/org/nervos/ckb/sign/signer/OmnilockSigner.java
new file mode 100644
index 000000000..c733412f8
--- /dev/null
+++ b/core/src/main/java/org/nervos/ckb/sign/signer/OmnilockSigner.java
@@ -0,0 +1,239 @@
+package org.nervos.ckb.sign.signer;
+
+import org.nervos.ckb.crypto.Blake2b;
+import org.nervos.ckb.crypto.secp256k1.ECKeyPair;
+import org.nervos.ckb.sign.Context;
+import org.nervos.ckb.sign.ScriptGroup;
+import org.nervos.ckb.sign.ScriptSigner;
+import org.nervos.ckb.sign.omnilock.OmnilockArgs;
+import org.nervos.ckb.sign.omnilock.OmnilockIdentity;
+import org.nervos.ckb.sign.omnilock.OmnilockWitnessLock;
+import org.nervos.ckb.type.CellDep;
+import org.nervos.ckb.type.Transaction;
+import org.nervos.ckb.type.WitnessArgs;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class OmnilockSigner implements ScriptSigner {
+
+ @Override
+ public boolean signTransaction(Transaction transaction, ScriptGroup scriptGroup, Context context) {
+ if (!(context.getPayload() instanceof Configuration)) {
+ return false;
+ }
+ Configuration config = (Configuration) context.getPayload();
+ byte[] args = scriptGroup.getScript().args;
+ // Check if script args is matched with given omnilockConfig
+ if (!Arrays.equals(args, config.getOmnilockArgs().encode())) {
+ return false;
+ }
+
+ List witnesses = transaction.witnesses;
+ int index = scriptGroup.getInputIndices().get(0);
+ WitnessArgs witnessArgs = WitnessArgs.unpack(witnesses.get(index));
+ OmnilockWitnessLock omnilockWitnessLock;
+ switch (config.getMode()) {
+ case AUTH:
+ omnilockWitnessLock = signForAuthMode(transaction, scriptGroup, context.getKeyPair(), config);
+ break;
+ case ADMINISTRATOR:
+ omnilockWitnessLock = signForAdministratorMode(transaction, scriptGroup, context.getKeyPair(), config);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown Omnilock mode " + config.getMode());
+ }
+ if (omnilockWitnessLock != null) {
+ witnessArgs.setLock(omnilockWitnessLock.pack().toByteArray());
+ witnesses.set(index, witnessArgs.pack().toByteArray());
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private static OmnilockWitnessLock signForAuthMode(Transaction transaction, ScriptGroup scriptGroup, ECKeyPair keyPair, Configuration configuration) {
+ byte[] authArgs = Arrays.copyOfRange(scriptGroup.getScript().args, 1, 21);
+ int firstIndex = scriptGroup.getInputIndices().get(0);
+ byte[] firstWitness = transaction.witnesses.get(firstIndex);
+
+ // witness placeholder should be set before signing
+ WitnessArgs witnessArgs = WitnessArgs.unpack(firstWitness);
+ OmnilockWitnessLock omnilockWitnessLock = new OmnilockWitnessLock();
+ switch (configuration.getOmnilockArgs().getAuthenticationArgs().getFlag()) {
+ case CKB_SECP256K1_BLAKE160:
+ byte[] hash = Blake2b.digest(keyPair.getEncodedPublicKey(true));
+ hash = Arrays.copyOfRange(hash, 0, 20);
+ if (!Arrays.equals(authArgs, hash)) {
+ return null;
+ }
+ // Prepare witness placeholder
+ omnilockWitnessLock.setSignature(new byte[65]);
+ witnessArgs.setLock(new byte[omnilockWitnessLock.pack().toByteArray().length]);
+ byte[] witnessPlaceholder = witnessArgs.pack().toByteArray();
+ // Sign
+ byte[] signature = Secp256k1Blake160SighashAllSigner.signTransaction(transaction, scriptGroup, witnessPlaceholder, keyPair);
+ omnilockWitnessLock.setSignature(signature);
+ break;
+ case ETHEREUM:
+ throw new UnsupportedOperationException("Ethereum");
+ case EOS:
+ throw new UnsupportedOperationException("EOS");
+ case TRON:
+ throw new UnsupportedOperationException("TRON");
+ case BITCOIN:
+ throw new UnsupportedOperationException("Bitcoin");
+ case DOGECOIN:
+ throw new UnsupportedOperationException("Dogecoin");
+ case CKB_MULTI_SIG:
+ Secp256k1Blake160MultisigAllSigner.MultisigScript multisigScript = configuration.getMultisigScript();
+ if (multisigScript == null || !Arrays.equals(authArgs, multisigScript.computeHash())) {
+ return null;
+ }
+ boolean inMultisigScript = false;
+ for (byte[] v: multisigScript.getKeysHashes()) {
+ hash = Blake2b.digest(keyPair.getEncodedPublicKey(true));
+ hash = Arrays.copyOfRange(hash, 0, 20);
+ if (Arrays.equals(v, hash)) {
+ inMultisigScript = true;
+ break;
+ }
+ }
+ if (!inMultisigScript) {
+ return null;
+ }
+
+ // Prepare witness placeholder
+ WitnessArgs witnessArgsPlaceholder = WitnessArgs.unpack(firstWitness);
+ omnilockWitnessLock.setSignature(multisigScript.witnessEmptyPlaceholderInLock());
+ witnessArgsPlaceholder.setLock(omnilockWitnessLock.packAsEmptyPlaceholder());
+ witnessPlaceholder = witnessArgsPlaceholder.pack().toByteArray();
+ // Sign
+ signature = Secp256k1Blake160SighashAllSigner.signTransaction(transaction, scriptGroup, witnessPlaceholder, keyPair);
+
+ // get existent signature
+ byte[] oldSignature;
+ byte[] lockBytes = witnessArgs.getLock();
+ if (isEmpty(lockBytes, 0, lockBytes.length)) {
+ oldSignature = multisigScript.witnessPlaceholderInLock();
+ } else {
+ oldSignature = OmnilockWitnessLock.unpack(lockBytes).getSignature();
+ }
+
+ // set segment signature to signature
+ signature = setSignatureToWitness(oldSignature, signature, multisigScript);
+ omnilockWitnessLock.setSignature(signature);
+ break;
+ case LOCK_SCRIPT_HASH:
+ break;
+ case EXEC:
+ throw new UnsupportedOperationException("Exec");
+ case DYNAMIC_LINKING:
+ throw new UnsupportedOperationException("Dynamic linking");
+ default:
+ throw new IllegalArgumentException("Unknown auth flag " + configuration.getOmnilockArgs().getOmniArgs().getFlag());
+ }
+ return omnilockWitnessLock;
+ }
+
+ private static byte[] setSignatureToWitness(byte[] signatures, byte[] signature, Secp256k1Blake160MultisigAllSigner.MultisigScript multisigScript) {
+ int offset = multisigScript.encode().length;
+ for (int i = 0; i < multisigScript.getThreshold(); i++) {
+ if (isEmpty(signatures, offset, 65)) {
+ System.arraycopy(signature, 0, signatures, offset, 65);
+ break;
+ }
+ offset += 65;
+ }
+ return signatures;
+ }
+
+ private static boolean isEmpty(byte[] lock, int offset, int length) {
+ for (int i = offset; i < offset + length; i++) {
+ if (lock[i] != 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static OmnilockWitnessLock signForAdministratorMode(Transaction transaction, ScriptGroup scriptGroup, ECKeyPair keyPair, Configuration configuration) {
+ byte[] signature = null;
+ switch (configuration.getOmnilockIdentity().getIdentity().getFlag()) {
+ case CKB_SECP256K1_BLAKE160:
+ throw new UnsupportedOperationException("CKB_SECP256K1_BLAKE160");
+ case LOCK_SCRIPT_HASH:
+ break;
+ default:
+ }
+ OmnilockWitnessLock omnilockWitnessLock = new OmnilockWitnessLock();
+ omnilockWitnessLock.setOmnilockIdentity(configuration.getOmnilockIdentity());
+ omnilockWitnessLock.setSignature(signature);
+ return omnilockWitnessLock;
+ }
+
+ public static class Configuration {
+ private OmnilockArgs omnilockArgs;
+ private Mode mode;
+
+ // For Auth mode with flag 0x06 (multisig)
+ private Secp256k1Blake160MultisigAllSigner.MultisigScript multisigScript;
+
+ // For Administrator mode
+ private CellDep adminListCell;
+ private OmnilockIdentity omnilockIdentity;
+
+ public Configuration() {
+ }
+
+ public Configuration(OmnilockArgs omnilockArgs, Mode mode) {
+ this.omnilockArgs = omnilockArgs;
+ this.mode = mode;
+ }
+
+ public OmnilockArgs getOmnilockArgs() {
+ return omnilockArgs;
+ }
+
+ public void setOmnilockArgs(OmnilockArgs omnilockArgs) {
+ this.omnilockArgs = omnilockArgs;
+ }
+
+ public Mode getMode() {
+ return mode;
+ }
+
+ public void setMode(Mode mode) {
+ this.mode = mode;
+ }
+
+ public Secp256k1Blake160MultisigAllSigner.MultisigScript getMultisigScript() {
+ return multisigScript;
+ }
+
+ public void setMultisigScript(Secp256k1Blake160MultisigAllSigner.MultisigScript multisigScript) {
+ this.multisigScript = multisigScript;
+ }
+
+ public CellDep getAdminListCell() {
+ return adminListCell;
+ }
+
+ public void setAdminListCell(CellDep adminListCell) {
+ this.adminListCell = adminListCell;
+ }
+
+ public OmnilockIdentity getOmnilockIdentity() {
+ return omnilockIdentity;
+ }
+
+ public void setOmnilockIdentity(OmnilockIdentity omnilockIdentity) {
+ this.omnilockIdentity = omnilockIdentity;
+ }
+
+ public enum Mode {
+ AUTH,
+ ADMINISTRATOR
+ }
+ }
+}
diff --git a/core/src/main/java/org/nervos/ckb/sign/signer/PwSigner.java b/core/src/main/java/org/nervos/ckb/sign/signer/PwSigner.java
index 45db2548e..e18bcc3fb 100644
--- a/core/src/main/java/org/nervos/ckb/sign/signer/PwSigner.java
+++ b/core/src/main/java/org/nervos/ckb/sign/signer/PwSigner.java
@@ -16,17 +16,6 @@
import java.util.List;
public class PwSigner implements ScriptSigner {
- private static PwSigner INSTANCE;
-
- private PwSigner() {
- }
-
- public static PwSigner getInstance() {
- if (INSTANCE == null) {
- INSTANCE = new PwSigner();
- }
- return INSTANCE;
- }
@Override
public boolean signTransaction(
@@ -34,33 +23,38 @@ public boolean signTransaction(
Script script = scriptGroup.getScript();
ECKeyPair keyPair = context.getKeyPair();
if (isMatched(keyPair, script.args)) {
- return signScriptGroup(transaction, scriptGroup, keyPair);
+ return signTransactionInPlace(transaction, scriptGroup, keyPair);
} else {
return false;
}
}
- private boolean signScriptGroup(
+ private boolean signTransactionInPlace(
Transaction transaction, ScriptGroup scriptGroup, ECKeyPair keyPair) {
+ byte[] signature = signTransactionETH(transaction, scriptGroup, keyPair);
+
+ int index = scriptGroup.getInputIndices().get(0);
+ List witnesses = transaction.witnesses;
+ WitnessArgs witnessArgs = WitnessArgs.unpack(witnesses.get(index));
+ witnessArgs.setLock(signature);
+ witnesses.set(index, witnessArgs.pack().toByteArray());
+ return true;
+ }
+
+ public static byte[] signTransactionETH(Transaction transaction, ScriptGroup scriptGroup, ECKeyPair keyPair) {
Keccak256 keccak256 = new Keccak256();
byte[] txHash = transaction.computeHash();
keccak256.update(txHash);
List witnesses = transaction.witnesses;
- for (int i : scriptGroup.getInputIndices()) {
+ for (int i: scriptGroup.getInputIndices()) {
byte[] witness = witnesses.get(i);
keccak256.update(MoleculeConverter.packUint64(witness.length).toByteArray());
keccak256.update(witness);
}
byte[] digest = keccak256.doFinal();
- byte[] signature = ethereumPersonalSign(digest, keyPair);
-
- int index = scriptGroup.getInputIndices().get(0);
- WitnessArgs witnessArgs = WitnessArgs.unpack(witnesses.get(index));
- witnessArgs.setLock(signature);
- witnesses.set(index, witnessArgs.pack().toByteArray());
- return true;
+ return ethereumPersonalSign(digest, keyPair);
}
private static byte[] ethereumPersonalSign(byte[] message, ECKeyPair keyPair) {
@@ -89,7 +83,6 @@ public boolean isMatched(ECKeyPair keyPair, byte[] scriptArgs) {
byte[] ethereumAddress =
Arrays.copyOfRange(publicKeyHash, publicKeyHash.length - 20, publicKeyHash.length);
-
return Arrays.equals(scriptArgs, ethereumAddress);
}
}
diff --git a/core/src/main/java/org/nervos/ckb/sign/signer/Secp256k1Blake160MultisigAllSigner.java b/core/src/main/java/org/nervos/ckb/sign/signer/Secp256k1Blake160MultisigAllSigner.java
index 487281d6a..06a53eb75 100644
--- a/core/src/main/java/org/nervos/ckb/sign/signer/Secp256k1Blake160MultisigAllSigner.java
+++ b/core/src/main/java/org/nervos/ckb/sign/signer/Secp256k1Blake160MultisigAllSigner.java
@@ -2,14 +2,13 @@
import org.nervos.ckb.crypto.Blake2b;
import org.nervos.ckb.crypto.secp256k1.ECKeyPair;
-import org.nervos.ckb.crypto.secp256k1.Sign;
import org.nervos.ckb.sign.Context;
import org.nervos.ckb.sign.ScriptGroup;
import org.nervos.ckb.sign.ScriptSigner;
import org.nervos.ckb.type.Script;
import org.nervos.ckb.type.Transaction;
import org.nervos.ckb.type.WitnessArgs;
-import org.nervos.ckb.utils.MoleculeConverter;
+import org.nervos.ckb.utils.Numeric;
import java.util.ArrayList;
import java.util.Arrays;
@@ -18,16 +17,17 @@
public class Secp256k1Blake160MultisigAllSigner implements ScriptSigner {
private static final int SIGNATURE_LENGTH_IN_BYTE = 65;
- private static Secp256k1Blake160MultisigAllSigner INSTANCE;
+ public boolean signTransactionInPlace(
+ Transaction transaction, ScriptGroup scriptGroup, ECKeyPair keyPair, MultisigScript multisigScript) {
+ int firstIndex = scriptGroup.getInputIndices().get(0);
+ byte[] firstWitness = transaction.witnesses.get(firstIndex);
- private Secp256k1Blake160MultisigAllSigner() {
- }
+ byte[] witnessPlaceholder = multisigScript.witnessPlaceholder(firstWitness);
+ byte[] signature = Secp256k1Blake160SighashAllSigner.signTransaction(transaction, scriptGroup, witnessPlaceholder, keyPair);
- public static Secp256k1Blake160MultisigAllSigner getInstance() {
- if (INSTANCE == null) {
- INSTANCE = new Secp256k1Blake160MultisigAllSigner();
- }
- return INSTANCE;
+ firstWitness = setSignatureToWitness(firstWitness, signature, multisigScript);
+ transaction.witnesses.set(firstIndex, firstWitness);
+ return true;
}
@Override
@@ -39,43 +39,12 @@ public boolean signTransaction(
if (payload instanceof MultisigScript) {
MultisigScript multisigScript = (MultisigScript) payload;
if (isMatched(keyPair, script.args, multisigScript)) {
- return signScriptGroup(transaction, scriptGroup, keyPair, multisigScript);
+ return signTransactionInPlace(transaction, scriptGroup, keyPair, multisigScript);
}
}
return false;
}
- public boolean signScriptGroup(
- Transaction transaction, ScriptGroup scriptGroup, ECKeyPair keyPair, MultisigScript multisigScript) {
- byte[] txHash = transaction.computeHash();
- List witnesses = transaction.witnesses;
- Blake2b blake2b = new Blake2b();
- blake2b.update(txHash);
-
- int firstIndex = scriptGroup.getInputIndices().get(0);
- byte[] firstWitness = witnesses.get(firstIndex);
- byte[] firstWitnessPlaceholder = multisigScript.witnessPlaceholder(firstWitness);
- witnesses.set(firstIndex, firstWitnessPlaceholder);
-
- for (int i : scriptGroup.getInputIndices()) {
- byte[] witness = witnesses.get(i);
- blake2b.update(MoleculeConverter.packUint64(witness.length).toByteArray());
- blake2b.update(witness);
- }
- for (int i = transaction.inputs.size(); i < transaction.witnesses.size(); i++) {
- byte[] witness = witnesses.get(i);
- blake2b.update(MoleculeConverter.packUint64(witness.length).toByteArray());
- blake2b.update(witness);
- }
-
- byte[] message = blake2b.doFinal();
- byte[] signature = Sign.signMessage(message, keyPair).getSignature();
-
- firstWitness = setSignatureToWitness(firstWitness, signature, multisigScript);
- witnesses.set(firstIndex, firstWitness);
- return true;
- }
-
private static byte[] setSignatureToWitness(byte[] witness, byte[] signature, MultisigScript multisigScript) {
WitnessArgs witnessArgs = WitnessArgs.unpack(witness);
byte[] lock = witnessArgs.getLock();
@@ -100,7 +69,7 @@ private static boolean isEmptySignature(byte[] lock, int start) {
return true;
}
- private static boolean isMatched(ECKeyPair keyPair, byte[] scriptArgs, MultisigScript multisigScript) {
+ public static boolean isMatched(ECKeyPair keyPair, byte[] scriptArgs, MultisigScript multisigScript) {
if (scriptArgs == null || keyPair == null) {
return false;
}
@@ -135,12 +104,35 @@ public MultisigScript(int version, int firstN, int threshold, List keysH
this.keysHashes = keysHashes;
}
+ public MultisigScript(int version, int firstN, int threshold, byte[]... keysHashes) {
+ this(version, firstN, threshold, Arrays.asList(keysHashes));
+ }
+
+ public MultisigScript(int version, int firstN, int threshold, String... keysHashes) {
+ this(version, firstN, threshold, toBytesArray(keysHashes));
+ }
+
public MultisigScript(int firstN, int threshold, List keysHashes) {
- this(0, 0, threshold, keysHashes);
+ this(0, firstN, threshold, keysHashes);
+ }
+
+ public MultisigScript(int firstN, int threshold, byte[]... keysHashes) {
+ this(0, firstN, threshold, keysHashes);
}
- public MultisigScript(int threshold, List keysHashes) {
- this(0, 0, threshold, keysHashes);
+ public MultisigScript(int firstN, int threshold, String... keysHashes) {
+ this(0, firstN, threshold, keysHashes);
+ }
+
+ private static byte[][] toBytesArray(String[] in) {
+ if (in == null) {
+ return null;
+ }
+ byte[][] out = new byte[in.length][];
+ for (int i = 0; i < in.length; i++) {
+ out[i] = Numeric.hexStringToByteArray(in[i]);
+ }
+ return out;
}
public int getVersion() {
@@ -166,7 +158,7 @@ public byte[] encode() {
out[2] = (byte) this.threshold;
out[3] = (byte) this.keysHashes.size();
int pos = 4;
- for (byte[] publicKeyHash : this.keysHashes) {
+ for (byte[] publicKeyHash: this.keysHashes) {
System.arraycopy(publicKeyHash, 0, out, pos, 20);
pos += 20;
}
@@ -220,6 +212,10 @@ public byte[] witnessPlaceholderInLock() {
return placeholder;
}
+ public byte[] witnessEmptyPlaceholderInLock() {
+ return new byte[witnessPlaceholderInLock().length];
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
diff --git a/core/src/main/java/org/nervos/ckb/sign/signer/Secp256k1Blake160SighashAllSigner.java b/core/src/main/java/org/nervos/ckb/sign/signer/Secp256k1Blake160SighashAllSigner.java
index f80191177..1a1d5a480 100644
--- a/core/src/main/java/org/nervos/ckb/sign/signer/Secp256k1Blake160SighashAllSigner.java
+++ b/core/src/main/java/org/nervos/ckb/sign/signer/Secp256k1Blake160SighashAllSigner.java
@@ -11,64 +11,69 @@
import org.nervos.ckb.type.WitnessArgs;
import org.nervos.ckb.utils.MoleculeConverter;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Secp256k1Blake160SighashAllSigner implements ScriptSigner {
- private static Secp256k1Blake160SighashAllSigner INSTANCE;
-
- private Secp256k1Blake160SighashAllSigner() {
- }
-
- public static Secp256k1Blake160SighashAllSigner getInstance() {
- if (INSTANCE == null) {
- INSTANCE = new Secp256k1Blake160SighashAllSigner();
- }
- return INSTANCE;
- }
-
- @Override
- public boolean signTransaction(
- Transaction transaction, ScriptGroup scriptGroup, Context context) {
- Script script = scriptGroup.getScript();
- ECKeyPair keyPair = context.getKeyPair();
- if (isMatched(keyPair, script.args)) {
- return signScriptGroup(transaction, scriptGroup, keyPair);
- } else {
- return false;
- }
+ public static boolean signTransactionInPlace(
+ Transaction transaction, ScriptGroup scriptGroup, ECKeyPair keyPair) {
+ byte[] signature = signTransaction(transaction, scriptGroup, keyPair);
+ List witnesses = transaction.witnesses;
+ int index = scriptGroup.getInputIndices().get(0);
+ WitnessArgs witnessArgs = WitnessArgs.unpack(witnesses.get(index));
+ witnessArgs.setLock(signature);
+ witnesses.set(index, witnessArgs.pack().toByteArray());
+ return true;
}
- public boolean signScriptGroup(
- Transaction transaction, ScriptGroup scriptGroup, ECKeyPair keyPair) {
+ public static byte[] signTransaction(
+ Transaction transaction, ScriptGroup scriptGroup, byte[] witnessPlaceholder, ECKeyPair keyPair) {
byte[] txHash = transaction.computeHash();
- List witnesses = transaction.witnesses;
Blake2b blake2b = new Blake2b();
blake2b.update(txHash);
- for (int i : scriptGroup.getInputIndices()) {
- byte[] witness = witnesses.get(i);
- blake2b.update(MoleculeConverter.packUint64(witness.length).toByteArray());
- blake2b.update(witness);
+ blake2b.update(MoleculeConverter.packUint64(witnessPlaceholder.length).toByteArray());
+ blake2b.update(witnessPlaceholder);
+ List includedWitnessIndex = new ArrayList<>();
+
+ for (int i = 1; i < scriptGroup.getInputIndices().size(); i++) {
+ includedWitnessIndex.add(scriptGroup.getInputIndices().get(i));
}
for (int i = transaction.inputs.size(); i < transaction.witnesses.size(); i++) {
- byte[] witness = witnesses.get(i);
+ includedWitnessIndex.add(i);
+ }
+ for (int i: includedWitnessIndex) {
+ byte[] witness = transaction.witnesses.get(i);
blake2b.update(MoleculeConverter.packUint64(witness.length).toByteArray());
blake2b.update(witness);
}
byte[] message = blake2b.doFinal();
byte[] signature = Sign.signMessage(message, keyPair).getSignature();
+ return signature;
+ }
- int index = scriptGroup.getInputIndices().get(0);
- WitnessArgs witnessArgs = WitnessArgs.unpack(witnesses.get(index));
- witnessArgs.setLock(signature);
- witnesses.set(index, witnessArgs.pack().toByteArray());
- return true;
+ public static byte[] signTransaction(
+ Transaction transaction, ScriptGroup scriptGroup, ECKeyPair keyPair) {
+ byte[] witnessPlaceholder = transaction.witnesses.get(scriptGroup.getInputIndices().get(0));
+ return signTransaction(transaction, scriptGroup, witnessPlaceholder, keyPair);
+ }
+
+ @Override
+ public boolean signTransaction(
+ Transaction transaction, ScriptGroup scriptGroup, Context context) {
+ Script script = scriptGroup.getScript();
+ ECKeyPair keyPair = context.getKeyPair();
+ if (isMatched(keyPair, script.args)) {
+ return signTransactionInPlace(transaction, scriptGroup, keyPair);
+ } else {
+ return false;
+ }
}
// Check if the script with `scriptArgs` is generated by and can be unlocked by `privateKey`
- public boolean isMatched(ECKeyPair keyPair, byte[] scriptArgs) {
+ public static boolean isMatched(ECKeyPair keyPair, byte[] scriptArgs) {
if (scriptArgs == null || keyPair == null) {
return false;
}
diff --git a/core/src/main/java/org/nervos/ckb/type/CellDep.java b/core/src/main/java/org/nervos/ckb/type/CellDep.java
index cfdc23269..85d9d5820 100644
--- a/core/src/main/java/org/nervos/ckb/type/CellDep.java
+++ b/core/src/main/java/org/nervos/ckb/type/CellDep.java
@@ -9,6 +9,16 @@ public class CellDep {
public CellDep() {
}
+ public CellDep(String txHash, int index, DepType depType) {
+ this.outPoint = new OutPoint(txHash, index);
+ this.depType = depType;
+ }
+
+ public CellDep(byte[] txHash, int index, DepType depType) {
+ this.outPoint = new OutPoint(txHash, index);
+ this.depType = depType;
+ }
+
public CellDep(OutPoint outPoint, DepType depType) {
this.outPoint = outPoint;
this.depType = depType;
diff --git a/core/src/main/java/org/nervos/ckb/type/FeeRateStatics.java b/core/src/main/java/org/nervos/ckb/type/FeeRateStatics.java
new file mode 100644
index 000000000..a47bdc995
--- /dev/null
+++ b/core/src/main/java/org/nervos/ckb/type/FeeRateStatics.java
@@ -0,0 +1,9 @@
+package org.nervos.ckb.type;
+
+/**
+ * The fee_rate statistics information, includes mean and median
+ */
+public class FeeRateStatics {
+ public long mean;
+ public long median;
+}
diff --git a/core/src/main/java/org/nervos/ckb/type/OutPoint.java b/core/src/main/java/org/nervos/ckb/type/OutPoint.java
index a14e8ead8..86d3fd8c8 100644
--- a/core/src/main/java/org/nervos/ckb/type/OutPoint.java
+++ b/core/src/main/java/org/nervos/ckb/type/OutPoint.java
@@ -1,5 +1,7 @@
package org.nervos.ckb.type;
+import org.nervos.ckb.utils.Numeric;
+
import java.util.Arrays;
import static org.nervos.ckb.utils.MoleculeConverter.packByte32;
@@ -13,6 +15,10 @@ public class OutPoint {
public OutPoint() {
}
+ public OutPoint(String txHash, int index) {
+ this(Numeric.hexStringToByteArray(txHash), index);
+ }
+
public OutPoint(byte[] txHash, int index) {
this.txHash = txHash;
this.index = index;
diff --git a/core/src/main/java/org/nervos/ckb/type/Script.java b/core/src/main/java/org/nervos/ckb/type/Script.java
index ef6005c14..6f65b7cfd 100644
--- a/core/src/main/java/org/nervos/ckb/type/Script.java
+++ b/core/src/main/java/org/nervos/ckb/type/Script.java
@@ -32,6 +32,10 @@ public class Script {
Numeric.hexStringToByteArray("0x5e7a36a77e68eecc013dfa2fe6a23f3b6c344b04005808694ae6dd45eea4cfd5");
public static final byte[] SUDT_CODE_HASH_TESTNET =
Numeric.hexStringToByteArray("0xc5e5dcf215925f7ef4dfaf5f4b4f105bc321c02776d6e7d52a1db3fcd9d011a4");
+ public static final byte[] OMNILOCK_CODE_HASH_MAINNET =
+ Numeric.hexStringToByteArray("0x9b819793a64463aed77c615d6cb226eea5487ccfc0783043a587254cda2b6f26");
+ public static final byte[] OMNILOCK_CODE_HASH_TESTNET =
+ Numeric.hexStringToByteArray("0xf329effd1c475a2978453c8600e1eaf0bc2087ee093c3ee64cc96ec6847752cb");
public static final byte[] DAO_CODE_HASH =
Numeric.hexStringToByteArray("0x82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e");
diff --git a/core/src/main/java/org/nervos/ckb/type/TransactionWithStatus.java b/core/src/main/java/org/nervos/ckb/type/TransactionWithStatus.java
index 3a3fcd5ad..23fb50afd 100644
--- a/core/src/main/java/org/nervos/ckb/type/TransactionWithStatus.java
+++ b/core/src/main/java/org/nervos/ckb/type/TransactionWithStatus.java
@@ -5,6 +5,7 @@
public class TransactionWithStatus {
public TxStatus txStatus;
public Transaction transaction;
+ public Long cycles;
public static class TxStatus {
public Status status;
diff --git a/core/src/main/java/org/nervos/ckb/utils/Calculator.java b/core/src/main/java/org/nervos/ckb/utils/Calculator.java
index e00eb2c93..db6386970 100644
--- a/core/src/main/java/org/nervos/ckb/utils/Calculator.java
+++ b/core/src/main/java/org/nervos/ckb/utils/Calculator.java
@@ -27,4 +27,11 @@ public static long calculateTransactionFee(Transaction transaction, long feeRate
long txSize = calculateTransactionSize(transaction);
return calculateTransactionFee(txSize, feeRate);
}
+
+ private static final double DEFAULT_BYTES_PER_CYCLE = 0.000_170_571_4;
+
+ public static long calculateTransactionFee(Transaction transaction, long cycles, long feeRate) {
+ long txSize = Math.max(calculateTransactionSize(transaction), (long) (cycles * DEFAULT_BYTES_PER_CYCLE));
+ return calculateTransactionFee(txSize, feeRate);
+ }
}
diff --git a/core/src/main/java/org/nervos/ckb/utils/address/Bech32.java b/core/src/main/java/org/nervos/ckb/utils/address/Bech32.java
index 44c9d2664..b31524f6c 100644
--- a/core/src/main/java/org/nervos/ckb/utils/address/Bech32.java
+++ b/core/src/main/java/org/nervos/ckb/utils/address/Bech32.java
@@ -44,7 +44,9 @@ public class Bech32 {
private static final int BECH32_CONST = 1;
private static final int BECH32M_CONST = 0x2bc830a3;
- public enum Encoding {BECH32, BECH32M}
+ public enum Encoding {
+ BECH32, BECH32M
+ }
public static class Bech32Data {
public final Encoding encoding;
@@ -115,12 +117,24 @@ private static byte[] createChecksum(final Encoding encoding, final String hrp,
return ret;
}
- /** Encode a Bech32 string. */
+ /**
+ * Encode a Bech32 string.
+ *
+ * @param bech32 the Bech32 to encode
+ * @return the encoded Bech32 string
+ */
public static String encode(final Bech32Data bech32) {
return encode(bech32.encoding, bech32.hrp, bech32.data);
}
- /** Encode a Bech32 string. */
+ /**
+ * Encode a Bech32 string.
+ *
+ * @param encoding the encoding to use
+ * @param hrp the human-readable part of the Bech32 string
+ * @param values the data part of the Bech32 string
+ * @return the encoded Bech32 string
+ */
public static String encode(Encoding encoding, String hrp, final byte[] values) {
if (hrp.length() < 1) {
throw new AddressFormatException.InvalidDataLength("Human-readable part is too short");
@@ -136,13 +150,19 @@ public static String encode(Encoding encoding, String hrp, final byte[] values)
StringBuilder sb = new StringBuilder(hrp.length() + 1 + combined.length);
sb.append(hrp);
sb.append('1');
- for (byte b : combined) {
+ for (byte b: combined) {
sb.append(CHARSET.charAt(b));
}
return sb.toString();
}
- /** Decode a Bech32 string. */
+ /**
+ * Decode a Bech32 string.
+ *
+ * @param str the Bech32 string to decode
+ * @return the decoded Bech32 data
+ * @throws AddressFormatException if the string is not a valid Bech32 string
+ */
public static Bech32Data decode(final String str) throws AddressFormatException {
boolean lower = false, upper = false;
if (str.length() < 8)
@@ -178,4 +198,4 @@ public static Bech32Data decode(final String str) throws AddressFormatException
if (encoding == null) throw new AddressFormatException.InvalidChecksum();
return new Bech32Data(encoding, hrp, Arrays.copyOfRange(values, 0, values.length - 6));
}
-}
\ No newline at end of file
+}
diff --git a/core/src/test/java/org/nervos/ckb/sign/omnilock/OmnilockArgsTest.java b/core/src/test/java/org/nervos/ckb/sign/omnilock/OmnilockArgsTest.java
new file mode 100644
index 000000000..adcb53d8d
--- /dev/null
+++ b/core/src/test/java/org/nervos/ckb/sign/omnilock/OmnilockArgsTest.java
@@ -0,0 +1,31 @@
+package org.nervos.ckb.sign.omnilock;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.nervos.ckb.utils.Numeric;
+
+class OmnilockArgsTest {
+ @Test
+ public void testEncode() {
+ OmnilockArgs.OmniConfig config = new OmnilockArgs.OmniConfig();
+ config.setFlag(15);
+ config.setAdminListCellTypeId(Numeric.hexStringToByteArray("0xeb760e1ad345001c6a31ae4ef531c38f94f64f2f6b6ac862a4822248adcb421c"));
+ config.setMinimumCKBExponentInAcp(2);
+ config.setMinimumSUDTExponentInAcp(1);
+ config.setSinceForTimeLock(Long.parseUnsignedLong("129d5", 16));
+ config.setTypeScriptHashForSupply(Numeric.hexStringToByteArray("0x1386a372b6b103f1de175dfb16c36f9358385d67239253131100fdd9624d699b"));
+ byte[] expected = Numeric.hexStringToByteArray("0feb760e1ad345001c6a31ae4ef531c38f94f64f2f6b6ac862a4822248adcb421c020100000000000129d51386a372b6b103f1de175dfb16c36f9358385d67239253131100fdd9624d699b");
+ Assertions.assertArrayEquals(expected, config.encode());
+ }
+
+ @Test
+ public void testDecode() {
+ byte[] bytes = Numeric.hexStringToByteArray("0feb760e1ad345001c6a31ae4ef531c38f94f64f2f6b6ac862a4822248adcb421c020100000000000129d51386a372b6b103f1de175dfb16c36f9358385d67239253131100fdd9624d699b");
+ OmnilockArgs.OmniConfig config = OmnilockArgs.OmniConfig.decode(bytes);
+ Assertions.assertArrayEquals(Numeric.hexStringToByteArray("0xeb760e1ad345001c6a31ae4ef531c38f94f64f2f6b6ac862a4822248adcb421c"), config.getAdminListCellTypeId());
+ Assertions.assertEquals(2, config.getMinimumCKBExponentInAcp());
+ Assertions.assertEquals(1, config.getMinimumSUDTExponentInAcp());
+ Assertions.assertEquals(0x129d5, config.getSinceForTimeLock());
+ Assertions.assertArrayEquals(Numeric.hexStringToByteArray("0x1386a372b6b103f1de175dfb16c36f9358385d67239253131100fdd9624d699b"), config.getTypeScriptHashForSupply());
+ }
+}
diff --git a/core/src/test/java/sign/OmnilockSignerTest.java b/core/src/test/java/sign/OmnilockSignerTest.java
new file mode 100644
index 000000000..5696fb68d
--- /dev/null
+++ b/core/src/test/java/sign/OmnilockSignerTest.java
@@ -0,0 +1,17 @@
+package sign;
+
+import org.junit.jupiter.api.Test;
+
+public class OmnilockSignerTest {
+
+ @Test
+ void testCkbSecp256k1Blake160SighashAll() {
+ SignerChecker.signAndCheck("omnilock_secp256k1_blake160_sighash_all");
+ }
+
+ @Test
+ void testCkbSecp256k1Blake160MultisigAll() {
+ SignerChecker.signAndCheck("omnilock_secp256k1_blake160_multisig_all_first");
+ SignerChecker.signAndCheck("omnilock_secp256k1_blake160_multisig_all_second");
+ }
+}
diff --git a/core/src/test/java/sign/PwSignerTest.java b/core/src/test/java/sign/PwSignerTest.java
index 2888167bf..740b07173 100644
--- a/core/src/test/java/sign/PwSignerTest.java
+++ b/core/src/test/java/sign/PwSignerTest.java
@@ -10,7 +10,7 @@ public class PwSignerTest {
@Test
void testIsMatched() {
- PwSigner signer = PwSigner.getInstance();
+ PwSigner signer = new PwSigner();
Assertions.assertTrue(
signer.isMatched(
diff --git a/core/src/test/java/sign/Secp256k1Blake160SighashAllSignerTest.java b/core/src/test/java/sign/Secp256k1Blake160SighashAllSignerTest.java
index 18d620e41..37aae3bab 100644
--- a/core/src/test/java/sign/Secp256k1Blake160SighashAllSignerTest.java
+++ b/core/src/test/java/sign/Secp256k1Blake160SighashAllSignerTest.java
@@ -12,7 +12,7 @@ public class Secp256k1Blake160SighashAllSignerTest {
@Test
void testIsMatched() {
- Secp256k1Blake160SighashAllSigner signer = Secp256k1Blake160SighashAllSigner.getInstance();
+ Secp256k1Blake160SighashAllSigner signer = new Secp256k1Blake160SighashAllSigner();
assertTrue(
signer.isMatched(
diff --git a/core/src/test/java/sign/SignerChecker.java b/core/src/test/java/sign/SignerChecker.java
index 66d8d5cbb..d0415258f 100644
--- a/core/src/test/java/sign/SignerChecker.java
+++ b/core/src/test/java/sign/SignerChecker.java
@@ -11,6 +11,8 @@
import org.nervos.ckb.sign.Context;
import org.nervos.ckb.sign.TransactionSigner;
import org.nervos.ckb.sign.TransactionWithScriptGroups;
+import org.nervos.ckb.sign.omnilock.OmnilockArgs;
+import org.nervos.ckb.sign.signer.OmnilockSigner;
import org.nervos.ckb.sign.signer.Secp256k1Blake160MultisigAllSigner;
import org.nervos.ckb.utils.Numeric;
@@ -43,12 +45,31 @@ private static Gson getGson() {
int threshold = obj2.get("threshold").getAsInt();
int firstN = obj2.get("first_n").getAsInt();
List keyHashes = new ArrayList<>();
- for (JsonElement e : obj2.get("key_hashes").getAsJsonArray()) {
+ for (JsonElement e: obj2.get("key_hashes").getAsJsonArray()) {
keyHashes.add(Numeric.hexStringToByteArray(e.getAsString()));
}
Secp256k1Blake160MultisigAllSigner.MultisigScript multisigScript =
new Secp256k1Blake160MultisigAllSigner.MultisigScript(firstN, threshold, keyHashes);
c.setPayload(multisigScript);
+ } else if (obj.get("omnilock_config") != null) {
+ JsonObject obj2 = obj.get("omnilock_config").getAsJsonObject();
+ byte[] args = Numeric.hexStringToByteArray(obj2.get("args").getAsString());
+ OmnilockArgs config = new OmnilockArgs(args);
+ OmnilockSigner.Configuration.Mode mode = OmnilockSigner.Configuration.Mode.valueOf(obj2.get("mode").getAsString());
+ OmnilockSigner.Configuration configuration = new OmnilockSigner.Configuration(config, mode);
+ if (obj2.get("multisig_script") != null) {
+ JsonObject obj3 = obj2.get("multisig_script").getAsJsonObject();
+ int threshold = obj3.get("threshold").getAsInt();
+ int firstN = obj3.get("first_n").getAsInt();
+ List keyHashes = new ArrayList<>();
+ for (JsonElement e: obj3.get("key_hashes").getAsJsonArray()) {
+ keyHashes.add(Numeric.hexStringToByteArray(e.getAsString()));
+ }
+ Secp256k1Blake160MultisigAllSigner.MultisigScript multisigScript =
+ new Secp256k1Blake160MultisigAllSigner.MultisigScript(firstN, threshold, keyHashes);
+ configuration.setMultisigScript(multisigScript);
+ }
+ c.setPayload(configuration);
}
return c;
};
diff --git a/core/src/test/java/sign/omnilock/OmnilockWitnessLockTest.java b/core/src/test/java/sign/omnilock/OmnilockWitnessLockTest.java
new file mode 100644
index 000000000..069f95057
--- /dev/null
+++ b/core/src/test/java/sign/omnilock/OmnilockWitnessLockTest.java
@@ -0,0 +1,21 @@
+package sign.omnilock;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.nervos.ckb.sign.omnilock.OmnilockWitnessLock;
+import org.nervos.ckb.type.WitnessArgs;
+import org.nervos.ckb.utils.Numeric;
+
+class OmnilockWitnessLockTest {
+
+ @Test
+ public void testPackUnpack() {
+ byte[] witness = Numeric.hexStringToByteArray("0x690000001000000069000000690000005500000055000000100000005500000055000000410000003434ca813dc378de0146aac8e60431fb52114acb3cb639f2fb2a479e1f219223532540413a154f440e939ee888c29221c0e8d6fef39402cbeedb6155317b356200");
+
+ WitnessArgs witnessArgs = WitnessArgs.unpack(witness);
+ OmnilockWitnessLock lock = OmnilockWitnessLock.unpack(witnessArgs.getLock());
+
+ Assertions.assertEquals("0x55000000100000005500000055000000410000003434ca813dc378de0146aac8e60431fb52114acb3cb639f2fb2a479e1f219223532540413a154f440e939ee888c29221c0e8d6fef39402cbeedb6155317b356200",
+ Numeric.toHexString(lock.pack().toByteArray()));
+ }
+}
diff --git a/core/src/test/java/type/ScriptTest.java b/core/src/test/java/type/ScriptTest.java
index 3a06ecf5d..92f7465c2 100644
--- a/core/src/test/java/type/ScriptTest.java
+++ b/core/src/test/java/type/ScriptTest.java
@@ -14,6 +14,7 @@
public class ScriptTest {
@Test
+ @SuppressWarnings("checkstyle:linelength")
public void testScriptHashWithCodeHash() throws IOException {
byte[] code = Numeric.hexStringToByteArray("0x1400000000000e00100000000c000800000004000e0000000c00000014000000740100000000000000000600080004000600000004000000580100007f454c460201010000000000000000000200f3000100000078000100000000004000000000000000980000000000000005000000400038000100400003000200010000000500000000000000000000000000010000000000000001000000000082000000000000008200000000000000001000000000000001459308d00573000000002e7368737472746162002e74657874000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b000000010000000600000000000000780001000000000078000000000000000a000000000000000000000000000000020000000000000000000000000000000100000003000000000000000000000000000000000000008200000000000000110000000000000000000000000000000100000000000000000000000000000000000000");
byte[] codeHash = Blake2b.digest(code);
diff --git a/core/src/test/java/utils/MoleculeSerializationTest.java b/core/src/test/java/utils/MoleculeSerializationTest.java
index b1e1cb40f..34ac049b0 100644
--- a/core/src/test/java/utils/MoleculeSerializationTest.java
+++ b/core/src/test/java/utils/MoleculeSerializationTest.java
@@ -16,6 +16,7 @@
public class MoleculeSerializationTest {
@Test
+ @SuppressWarnings("checkstyle:linelength")
public void testTransaction() {
Transaction transaction = readData("transaction.json", Transaction.class);
@@ -47,6 +48,7 @@ public void testTransaction() {
}
@Test
+ @SuppressWarnings("checkstyle:linelength")
public void testHeader() {
Header header = readData("header.json", Header.class);
assertByteArray(
diff --git a/core/src/test/resources/transaction/omnilock_secp256k1_blake160_multisig_all_first.json b/core/src/test/resources/transaction/omnilock_secp256k1_blake160_multisig_all_first.json
new file mode 100644
index 000000000..768a677ff
--- /dev/null
+++ b/core/src/test/resources/transaction/omnilock_secp256k1_blake160_multisig_all_first.json
@@ -0,0 +1,103 @@
+{
+ "contexts": [
+ {
+ "private_key": "0x7438f7b35c355e3d2fb9305167a31a72d22ddeafb80a21cc99ff6329d92e8087",
+ "omnilock_config": {
+ "args": "0x06bc9818d8a149cfc0cd0323386c46ba07920a037f00",
+ "mode": "AUTH",
+ "multisig_script": {
+ "threshold": 2,
+ "first_n": 0,
+ "key_hashes": [
+ "0x7336b0ba900684cb3cb00f0d46d4f64c0994a562",
+ "0x5724c1e3925a5206944d753a6f3edaedf977d77f"
+ ]
+ }
+ }
+ }
+ ],
+ "raw_transaction": {
+ "tx_view": {
+ "version": "0x0",
+ "cell_deps": [
+ {
+ "out_point": {
+ "tx_hash": "0x27b62d8be8ed80b9f56ee0fe41355becdb6f6a40aeba82d3900434f43b1c8b60",
+ "index": "0x0"
+ },
+ "dep_type": "code"
+ },
+ {
+ "out_point": {
+ "tx_hash": "0xf8de3bb47d055cdf460d93a2a6e1b05f7432f9777c8c474abf4eec1d4aee5d37",
+ "index": "0x1"
+ },
+ "dep_type": "dep_group"
+ }
+ ],
+ "hash": "0xb2c1b732fb0b68e2da2102b3f29508ffc4da1a6499f284094c4990bfc8c4f56c",
+ "header_deps": [],
+ "inputs": [
+ {
+ "previous_output": {
+ "tx_hash": "0x0cbdea8a600e2405eeaa5863a9e3561b16d2b702fe85f74bfddb3bc87545b83e",
+ "index": "0x0"
+ },
+ "since": "0x0"
+ },
+ {
+ "previous_output": {
+ "tx_hash": "0x0cbdea8a600e2405eeaa5863a9e3561b16d2b702fe85f74bfddb3bc87545b83e",
+ "index": "0x1"
+ },
+ "since": "0x0"
+ }
+ ],
+ "outputs": [
+ {
+ "capacity": "0xbaa315500",
+ "lock": {
+ "code_hash": "0xf329effd1c475a2978453c8600e1eaf0bc2087ee093c3ee64cc96ec6847752cb",
+ "args": "0x06bc9818d8a149cfc0cd0323386c46ba07920a037f00",
+ "hash_type": "type"
+ }
+ },
+ {
+ "capacity": "0xdd2a73a872",
+ "lock": {
+ "code_hash": "0xf329effd1c475a2978453c8600e1eaf0bc2087ee093c3ee64cc96ec6847752cb",
+ "args": "0x06bc9818d8a149cfc0cd0323386c46ba07920a037f00",
+ "hash_type": "type"
+ }
+ }
+ ],
+ "outputs_data": [
+ "0x",
+ "0x"
+ ],
+ "witnesses": [
+ "0xd600000010000000d6000000d6000000c20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ "0x"
+ ]
+ },
+ "script_groups": [
+ {
+ "script": {
+ "code_hash": "0xf329effd1c475a2978453c8600e1eaf0bc2087ee093c3ee64cc96ec6847752cb",
+ "args": "0x06bc9818d8a149cfc0cd0323386c46ba07920a037f00",
+ "hash_type": "type"
+ },
+ "group_type": "lock",
+ "input_indices": [
+ "0x0",
+ "0x1"
+ ],
+ "output_indices": []
+ }
+ ]
+ },
+ "expected_witnesses": [
+ "0xd600000010000000d6000000d6000000c2000000c200000010000000c2000000c2000000ae000000000002027336b0ba900684cb3cb00f0d46d4f64c0994a5625724c1e3925a5206944d753a6f3edaedf977d77f1155a40d725a497c7cd9c766c9e214a059d280944783e897136a49cec3e70c7b26630928354f3321f763088eae7ebd60846d004698d0e28f762b5b7a08329de1010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ "0x"
+ ]
+}
diff --git a/core/src/test/resources/transaction/omnilock_secp256k1_blake160_multisig_all_second.json b/core/src/test/resources/transaction/omnilock_secp256k1_blake160_multisig_all_second.json
new file mode 100644
index 000000000..21c278861
--- /dev/null
+++ b/core/src/test/resources/transaction/omnilock_secp256k1_blake160_multisig_all_second.json
@@ -0,0 +1,103 @@
+{
+ "contexts": [
+ {
+ "private_key": "0x4fd809631a6aa6e3bb378dd65eae5d71df895a82c91a615a1e8264741515c79c",
+ "omnilock_config": {
+ "args": "0x06bc9818d8a149cfc0cd0323386c46ba07920a037f00",
+ "mode": "AUTH",
+ "multisig_script": {
+ "threshold": 2,
+ "first_n": 0,
+ "key_hashes": [
+ "0x7336b0ba900684cb3cb00f0d46d4f64c0994a562",
+ "0x5724c1e3925a5206944d753a6f3edaedf977d77f"
+ ]
+ }
+ }
+ }
+ ],
+ "raw_transaction": {
+ "tx_view": {
+ "version": "0x0",
+ "cell_deps": [
+ {
+ "out_point": {
+ "tx_hash": "0x27b62d8be8ed80b9f56ee0fe41355becdb6f6a40aeba82d3900434f43b1c8b60",
+ "index": "0x0"
+ },
+ "dep_type": "code"
+ },
+ {
+ "out_point": {
+ "tx_hash": "0xf8de3bb47d055cdf460d93a2a6e1b05f7432f9777c8c474abf4eec1d4aee5d37",
+ "index": "0x1"
+ },
+ "dep_type": "dep_group"
+ }
+ ],
+ "hash": "0xb2c1b732fb0b68e2da2102b3f29508ffc4da1a6499f284094c4990bfc8c4f56c",
+ "header_deps": [],
+ "inputs": [
+ {
+ "previous_output": {
+ "tx_hash": "0x0cbdea8a600e2405eeaa5863a9e3561b16d2b702fe85f74bfddb3bc87545b83e",
+ "index": "0x0"
+ },
+ "since": "0x0"
+ },
+ {
+ "previous_output": {
+ "tx_hash": "0x0cbdea8a600e2405eeaa5863a9e3561b16d2b702fe85f74bfddb3bc87545b83e",
+ "index": "0x1"
+ },
+ "since": "0x0"
+ }
+ ],
+ "outputs": [
+ {
+ "capacity": "0xbaa315500",
+ "lock": {
+ "code_hash": "0xf329effd1c475a2978453c8600e1eaf0bc2087ee093c3ee64cc96ec6847752cb",
+ "args": "0x06bc9818d8a149cfc0cd0323386c46ba07920a037f00",
+ "hash_type": "type"
+ }
+ },
+ {
+ "capacity": "0xdd2a73a872",
+ "lock": {
+ "code_hash": "0xf329effd1c475a2978453c8600e1eaf0bc2087ee093c3ee64cc96ec6847752cb",
+ "args": "0x06bc9818d8a149cfc0cd0323386c46ba07920a037f00",
+ "hash_type": "type"
+ }
+ }
+ ],
+ "outputs_data": [
+ "0x",
+ "0x"
+ ],
+ "witnesses": [
+ "0xd600000010000000d6000000d6000000c2000000c200000010000000c2000000c2000000ae000000000002027336b0ba900684cb3cb00f0d46d4f64c0994a5625724c1e3925a5206944d753a6f3edaedf977d77f1155a40d725a497c7cd9c766c9e214a059d280944783e897136a49cec3e70c7b26630928354f3321f763088eae7ebd60846d004698d0e28f762b5b7a08329de1010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ "0x"
+ ]
+ },
+ "script_groups": [
+ {
+ "script": {
+ "code_hash": "0xf329effd1c475a2978453c8600e1eaf0bc2087ee093c3ee64cc96ec6847752cb",
+ "args": "0x06bc9818d8a149cfc0cd0323386c46ba07920a037f00",
+ "hash_type": "type"
+ },
+ "group_type": "lock",
+ "input_indices": [
+ "0x0",
+ "0x1"
+ ],
+ "output_indices": []
+ }
+ ]
+ },
+ "expected_witnesses": [
+ "0xd600000010000000d6000000d6000000c2000000c200000010000000c2000000c2000000ae000000000002027336b0ba900684cb3cb00f0d46d4f64c0994a5625724c1e3925a5206944d753a6f3edaedf977d77f1155a40d725a497c7cd9c766c9e214a059d280944783e897136a49cec3e70c7b26630928354f3321f763088eae7ebd60846d004698d0e28f762b5b7a08329de1017052dd31a789b87de036de042aa620683a9ceaf450ef348cf6ef8746350df5d91c2bb4ca4f65605fae4512cedf290f9b1838d09620d5adb5c2b09201699caa5600",
+ "0x"
+ ]
+}
diff --git a/core/src/test/resources/transaction/omnilock_secp256k1_blake160_sighash_all.json b/core/src/test/resources/transaction/omnilock_secp256k1_blake160_sighash_all.json
new file mode 100644
index 000000000..84a5d044c
--- /dev/null
+++ b/core/src/test/resources/transaction/omnilock_secp256k1_blake160_sighash_all.json
@@ -0,0 +1,97 @@
+{
+ "contexts": [
+ {
+ "private_key": "0x6c9ed03816e3111e49384b8d180174ad08e29feb1393ea1b51cef1c505d4e36a",
+ "omnilock_config": {
+ "args": "0x004049ed9cec8a0d39c7a1e899f0dacb8a8c28ad1400",
+ "mode": "AUTH"
+ }
+ }
+ ],
+ "raw_transaction": {
+ "tx_view": {
+ "cell_deps": [
+ {
+ "dep_type": "code",
+ "out_point": {
+ "index": "0x0",
+ "tx_hash": "0x27b62d8be8ed80b9f56ee0fe41355becdb6f6a40aeba82d3900434f43b1c8b60"
+ }
+ },
+ {
+ "dep_type": "dep_group",
+ "out_point": {
+ "index": "0x0",
+ "tx_hash": "0xf8de3bb47d055cdf460d93a2a6e1b05f7432f9777c8c474abf4eec1d4aee5d37"
+ }
+ }
+ ],
+ "hash": "0xa4d4c83175de71e638727919a3339ca36462cfd522778ae69ec5278bcb7369ce",
+ "header_deps": [],
+ "inputs": [
+ {
+ "previous_output": {
+ "index": "0x0",
+ "tx_hash": "0xa4894901899eff12bacb0e6d4bfe96e54c0461e8afe7388e8ab2ebb1626cf816"
+ },
+ "since": "0x0"
+ },
+ {
+ "previous_output": {
+ "index": "0x1",
+ "tx_hash": "0xa4894901899eff12bacb0e6d4bfe96e54c0461e8afe7388e8ab2ebb1626cf816"
+ },
+ "since": "0x0"
+ }
+ ],
+ "outputs": [
+ {
+ "capacity": "0xbaa315500",
+ "lock": {
+ "args": "0x004049ed9cec8a0d39c7a1e899f0dacb8a8c28ad1400",
+ "code_hash": "0xf329effd1c475a2978453c8600e1eaf0bc2087ee093c3ee64cc96ec6847752cb",
+ "hash_type": "type"
+ },
+ "type": null
+ },
+ {
+ "capacity": "0xdd2a73b230",
+ "lock": {
+ "args": "0x004049ed9cec8a0d39c7a1e899f0dacb8a8c28ad1400",
+ "code_hash": "0xf329effd1c475a2978453c8600e1eaf0bc2087ee093c3ee64cc96ec6847752cb",
+ "hash_type": "type"
+ },
+ "type": null
+ }
+ ],
+ "outputs_data": [
+ "0x",
+ "0x"
+ ],
+ "version": "0x0",
+ "witnesses": [
+ "690000001000000069000000690000005500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ "0x"
+ ]
+ },
+ "script_groups": [
+ {
+ "script": {
+ "code_hash": "0xf329effd1c475a2978453c8600e1eaf0bc2087ee093c3ee64cc96ec6847752cb",
+ "args": "0x004049ed9cec8a0d39c7a1e899f0dacb8a8c28ad1400",
+ "hash_type": "type"
+ },
+ "group_type": "lock",
+ "input_indices": [
+ "0x0",
+ "0x1"
+ ],
+ "output_indices": []
+ }
+ ]
+ },
+ "expected_witnesses": [
+ "0x690000001000000069000000690000005500000055000000100000005500000055000000410000003434ca813dc378de0146aac8e60431fb52114acb3cb639f2fb2a479e1f219223532540413a154f440e939ee888c29221c0e8d6fef39402cbeedb6155317b356200",
+ "0x"
+ ]
+}
diff --git a/example/build.gradle b/example/build.gradle
index 8e9e0d7cb..a346116b6 100644
--- a/example/build.gradle
+++ b/example/build.gradle
@@ -3,9 +3,8 @@ description 'ckb-sdk-java is a lightweight Java library for integration with ner
dependencies {
compile project(":ckb")
compile project(":ckb-indexer")
+ compile project(":light-client")
compile project(":utils")
compile "com.google.code.gson:gson:$gsonVersion"
compile "org.slf4j:slf4j-api:$slf4jVersion"
}
-
-apply from: rootProject.file('gradle/checkstyle.gradle')
diff --git a/example/src/main/java/org/nervos/ckb/example/DaoClaimExample.java b/example/src/main/java/org/nervos/ckb/example/DaoClaimExample.java
index 932275e3b..a315c09d1 100644
--- a/example/src/main/java/org/nervos/ckb/example/DaoClaimExample.java
+++ b/example/src/main/java/org/nervos/ckb/example/DaoClaimExample.java
@@ -5,11 +5,12 @@
import org.nervos.ckb.sign.TransactionSigner;
import org.nervos.ckb.sign.TransactionWithScriptGroups;
import org.nervos.ckb.transaction.DaoTransactionBuilder;
-import org.nervos.ckb.transaction.scriptHandler.DaoScriptHandler;
+import org.nervos.ckb.transaction.TransactionBuilderConfiguration;
+import org.nervos.ckb.transaction.handler.DaoScriptHandler;
import org.nervos.ckb.type.OutPoint;
import org.nervos.ckb.type.TransactionInput;
import org.nervos.ckb.utils.Numeric;
-import org.nervos.indexer.InputIterator;
+import org.nervos.ckb.transaction.InputIterator;
import java.io.IOException;
import java.util.Iterator;
@@ -18,13 +19,12 @@ public class DaoClaimExample {
public static void main(String[] args) throws IOException {
Network network = Network.TESTNET;
String sender = "ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq2qf8keemy2p5uu0g0gn8cd4ju23s5269qk8rg4r";
- OutPoint withdrawOutpoint = new OutPoint(
- Numeric.hexStringToByteArray("0x287554d155a9b9e30a1a6fd9e5d9e41afee612b0c8996f0073afb7f2894025f9"), 0);
+ OutPoint withdrawOutpoint = new OutPoint("0x287554d155a9b9e30a1a6fd9e5d9e41afee612b0c8996f0073afb7f2894025f9", 0);
Api api = new Api("https://testnet.ckb.dev", false);
+ TransactionBuilderConfiguration configuration = new TransactionBuilderConfiguration(network);
Iterator iterator = new InputIterator(sender);
- TransactionWithScriptGroups txWithGroups = new DaoTransactionBuilder(iterator, network, withdrawOutpoint, api)
- .setFeeRate(1000)
+ TransactionWithScriptGroups txWithGroups = new DaoTransactionBuilder(configuration, iterator, withdrawOutpoint, api)
.setChangeOutput(sender)
.build(new DaoScriptHandler.ClaimInfo(api, withdrawOutpoint));
diff --git a/example/src/main/java/org/nervos/ckb/example/DaoDepositExample.java b/example/src/main/java/org/nervos/ckb/example/DaoDepositExample.java
index 806ee729d..62729ffcd 100644
--- a/example/src/main/java/org/nervos/ckb/example/DaoDepositExample.java
+++ b/example/src/main/java/org/nervos/ckb/example/DaoDepositExample.java
@@ -5,9 +5,10 @@
import org.nervos.ckb.sign.TransactionSigner;
import org.nervos.ckb.sign.TransactionWithScriptGroups;
import org.nervos.ckb.transaction.CkbTransactionBuilder;
+import org.nervos.ckb.transaction.TransactionBuilderConfiguration;
import org.nervos.ckb.type.TransactionInput;
import org.nervos.ckb.utils.Numeric;
-import org.nervos.indexer.InputIterator;
+import org.nervos.ckb.transaction.InputIterator;
import java.io.IOException;
import java.util.Iterator;
@@ -18,10 +19,10 @@ public static void main(String[] args) throws IOException {
String sender = "ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq2qf8keemy2p5uu0g0gn8cd4ju23s5269qk8rg4r";
// Construct transaction to deposit 510 CKB to DAO
+ TransactionBuilderConfiguration configuration = new TransactionBuilderConfiguration(network);
Iterator iterator = new InputIterator(sender);
- TransactionWithScriptGroups txWithGroups = new CkbTransactionBuilder(iterator, network)
+ TransactionWithScriptGroups txWithGroups = new CkbTransactionBuilder(configuration, iterator)
.addDaoDepositOutput(sender, 51000000000L)
- .setFeeRate(1000)
.setChangeOutput(sender)
.build();
diff --git a/example/src/main/java/org/nervos/ckb/example/DaoWithdrawExample.java b/example/src/main/java/org/nervos/ckb/example/DaoWithdrawExample.java
index d4bd92da0..4020a8076 100644
--- a/example/src/main/java/org/nervos/ckb/example/DaoWithdrawExample.java
+++ b/example/src/main/java/org/nervos/ckb/example/DaoWithdrawExample.java
@@ -5,35 +5,30 @@
import org.nervos.ckb.sign.TransactionSigner;
import org.nervos.ckb.sign.TransactionWithScriptGroups;
import org.nervos.ckb.transaction.DaoTransactionBuilder;
-import org.nervos.ckb.transaction.scriptHandler.DaoScriptHandler;
+import org.nervos.ckb.transaction.TransactionBuilderConfiguration;
+import org.nervos.ckb.transaction.handler.DaoScriptHandler;
import org.nervos.ckb.type.OutPoint;
-import org.nervos.ckb.type.Script;
import org.nervos.ckb.type.TransactionInput;
import org.nervos.ckb.utils.Numeric;
-import org.nervos.indexer.InputIterator;
+import org.nervos.ckb.transaction.InputIterator;
import java.io.IOException;
import java.util.Iterator;
public class DaoWithdrawExample {
- private static Script daoScript = new Script(Script.DAO_CODE_HASH,
- new byte[0],
- Script.HashType.TYPE);
-
public static void main(String[] args) throws IOException {
Network network = Network.TESTNET;
Api api = new Api("https://testnet.ckb.dev", false);
// the block number where the deposit dao transaction is
- OutPoint depositOutpoint = new OutPoint(
- Numeric.hexStringToByteArray("0xc4662aa4a0c9087aa299121fef06dcc2dbf30271441a85fdf9d62fb312b259e6"), 0);
+ OutPoint depositOutPoint = new OutPoint("0xc4662aa4a0c9087aa299121fef06dcc2dbf30271441a85fdf9d62fb312b259e6", 0);
String sender = "ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq2qf8keemy2p5uu0g0gn8cd4ju23s5269qk8rg4r";
+ TransactionBuilderConfiguration configuration = new TransactionBuilderConfiguration(network);
Iterator iterator = new InputIterator(sender);
- TransactionWithScriptGroups txWithGroups = new DaoTransactionBuilder(iterator, network, depositOutpoint, api)
+ TransactionWithScriptGroups txWithGroups = new DaoTransactionBuilder(configuration, iterator, depositOutPoint, api)
.addWithdrawOutput(sender)
- .setFeeRate(1000)
.setChangeOutput(sender)
- .build(new DaoScriptHandler.WithdrawInfo(api, depositOutpoint));
+ .build(new DaoScriptHandler.WithdrawInfo(api, depositOutPoint));
// Sign transaction
TransactionSigner.getInstance(network)
diff --git a/example/src/main/java/org/nervos/ckb/example/IssueSudtExample.java b/example/src/main/java/org/nervos/ckb/example/IssueSudtExample.java
index 526cad24b..5a5115b76 100644
--- a/example/src/main/java/org/nervos/ckb/example/IssueSudtExample.java
+++ b/example/src/main/java/org/nervos/ckb/example/IssueSudtExample.java
@@ -5,9 +5,10 @@
import org.nervos.ckb.sign.TransactionSigner;
import org.nervos.ckb.sign.TransactionWithScriptGroups;
import org.nervos.ckb.transaction.SudtTransactionBuilder;
+import org.nervos.ckb.transaction.TransactionBuilderConfiguration;
import org.nervos.ckb.type.TransactionInput;
import org.nervos.ckb.utils.Numeric;
-import org.nervos.indexer.InputIterator;
+import org.nervos.ckb.transaction.InputIterator;
import java.io.IOException;
import java.util.Iterator;
@@ -17,13 +18,13 @@ public static void main(String[] args) throws IOException {
Network network = Network.TESTNET;
String sender = "ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdamwzrffgc54ef48493nfd2sd0h4cjnxg4850up";
+ TransactionBuilderConfiguration configuration = new TransactionBuilderConfiguration(network);
+
Iterator iterator = new InputIterator(sender);
- TransactionWithScriptGroups txWithGroups = new SudtTransactionBuilder(iterator,
- network,
+ TransactionWithScriptGroups txWithGroups = new SudtTransactionBuilder(configuration, iterator,
SudtTransactionBuilder.TransactionType.ISSUE,
sender)
.addSudtOutput(sender, 10L)
- .setFeeRate(1000)
.setChangeOutput(sender)
.build();
diff --git a/example/src/main/java/org/nervos/ckb/example/OmnilockExample.java b/example/src/main/java/org/nervos/ckb/example/OmnilockExample.java
new file mode 100644
index 000000000..d88f9c733
--- /dev/null
+++ b/example/src/main/java/org/nervos/ckb/example/OmnilockExample.java
@@ -0,0 +1,43 @@
+package org.nervos.ckb.example;
+
+import org.nervos.ckb.Network;
+import org.nervos.ckb.service.Api;
+import org.nervos.ckb.sign.Context;
+import org.nervos.ckb.sign.TransactionSigner;
+import org.nervos.ckb.sign.TransactionWithScriptGroups;
+import org.nervos.ckb.sign.omnilock.OmnilockArgs;
+import org.nervos.ckb.sign.signer.OmnilockSigner;
+import org.nervos.ckb.transaction.CkbTransactionBuilder;
+import org.nervos.ckb.transaction.TransactionBuilderConfiguration;
+import org.nervos.ckb.type.Script;
+import org.nervos.ckb.type.TransactionInput;
+import org.nervos.ckb.utils.Numeric;
+import org.nervos.ckb.transaction.InputIterator;
+
+import java.io.IOException;
+import java.util.Iterator;
+
+public class OmnilockExample {
+ public static void main(String[] args) throws IOException {
+ Network network = Network.TESTNET;
+ String sender = "ckt1qrejnmlar3r452tcg57gvq8patctcgy8acync0hxfnyka35ywafvkqgqgpy7m88v3gxnn3apazvlpkkt32xz3tg5qq3kzjf3";
+ OmnilockSigner.Configuration config = new OmnilockSigner.Configuration();
+ config.setOmnilockArgs(new OmnilockArgs(sender));
+ config.setMode(OmnilockSigner.Configuration.Mode.AUTH);
+
+ TransactionBuilderConfiguration configuration = new TransactionBuilderConfiguration(network);
+ Iterator iterator = new InputIterator(sender);
+ TransactionWithScriptGroups txWithGroups = new CkbTransactionBuilder(configuration, iterator)
+ .addOutput(sender, 50100000000L)
+ .setChangeOutput(sender)
+ .build(config);
+
+ TransactionSigner.getInstance(network)
+ .registerLockScriptSigner(Script.OMNILOCK_CODE_HASH_TESTNET, new OmnilockSigner())
+ .signTransaction(txWithGroups, new Context("0x6c9ed03816e3111e49384b8d180174ad08e29feb1393ea1b51cef1c505d4e36a", config));
+
+ Api api = new Api("https://testnet.ckb.dev", false);
+ byte[] txHash = api.sendTransaction(txWithGroups.getTxView());
+ System.out.println("Transaction hash: " + Numeric.toHexString(txHash));
+ }
+}
diff --git a/example/src/main/java/org/nervos/ckb/example/OmnilockMultisigExample.java b/example/src/main/java/org/nervos/ckb/example/OmnilockMultisigExample.java
new file mode 100644
index 000000000..ee2fa6720
--- /dev/null
+++ b/example/src/main/java/org/nervos/ckb/example/OmnilockMultisigExample.java
@@ -0,0 +1,50 @@
+package org.nervos.ckb.example;
+
+import org.nervos.ckb.Network;
+import org.nervos.ckb.service.Api;
+import org.nervos.ckb.sign.Context;
+import org.nervos.ckb.sign.TransactionSigner;
+import org.nervos.ckb.sign.TransactionWithScriptGroups;
+import org.nervos.ckb.sign.omnilock.OmnilockArgs;
+import org.nervos.ckb.sign.signer.OmnilockSigner;
+import org.nervos.ckb.sign.signer.Secp256k1Blake160MultisigAllSigner;
+import org.nervos.ckb.transaction.CkbTransactionBuilder;
+import org.nervos.ckb.transaction.TransactionBuilderConfiguration;
+import org.nervos.ckb.type.Script;
+import org.nervos.ckb.type.TransactionInput;
+import org.nervos.ckb.utils.Numeric;
+import org.nervos.ckb.transaction.InputIterator;
+
+import java.io.IOException;
+import java.util.Iterator;
+
+public class OmnilockMultisigExample {
+ public static void main(String[] args) throws IOException {
+ Network network = Network.TESTNET;
+ String sender = "ckt1qrejnmlar3r452tcg57gvq8patctcgy8acync0hxfnyka35ywafvkqgxhjvp3k9pf88upngryvuxc346q7fq5qmlqqlrhr0p";
+ Secp256k1Blake160MultisigAllSigner.MultisigScript multisigScript =
+ new Secp256k1Blake160MultisigAllSigner.MultisigScript(0, 2,
+ "0x7336b0ba900684cb3cb00f0d46d4f64c0994a562",
+ "0x5724c1e3925a5206944d753a6f3edaedf977d77f");
+ OmnilockSigner.Configuration config = new OmnilockSigner.Configuration();
+ config.setOmnilockArgs(new OmnilockArgs(sender));
+ config.setMode(OmnilockSigner.Configuration.Mode.AUTH);
+ config.setMultisigScript(multisigScript);
+
+ TransactionBuilderConfiguration configuration = new TransactionBuilderConfiguration(network);
+ Iterator iterator = new InputIterator(sender);
+ TransactionWithScriptGroups txWithGroups = new CkbTransactionBuilder(configuration, iterator)
+ .addOutput(sender, 50100000000L)
+ .setChangeOutput(sender)
+ .build(config);
+
+ TransactionSigner signer = TransactionSigner.getInstance(network)
+ .registerLockScriptSigner(Script.OMNILOCK_CODE_HASH_TESTNET, new OmnilockSigner());
+ signer.signTransaction(txWithGroups, new Context("0x7438f7b35c355e3d2fb9305167a31a72d22ddeafb80a21cc99ff6329d92e8087", config));
+ signer.signTransaction(txWithGroups, new Context("0x4fd809631a6aa6e3bb378dd65eae5d71df895a82c91a615a1e8264741515c79c", config));
+
+ Api api = new Api("https://testnet.ckb.dev", false);
+ byte[] txHash = api.sendTransaction(txWithGroups.getTxView());
+ System.out.println("Transaction hash: " + Numeric.toHexString(txHash));
+ }
+}
diff --git a/example/src/main/java/org/nervos/ckb/example/SendChainedTransactionExample.java b/example/src/main/java/org/nervos/ckb/example/SendChainedTransactionExample.java
new file mode 100644
index 000000000..6ead1efc5
--- /dev/null
+++ b/example/src/main/java/org/nervos/ckb/example/SendChainedTransactionExample.java
@@ -0,0 +1,55 @@
+package org.nervos.ckb.example;
+
+import org.nervos.ckb.Network;
+import org.nervos.ckb.service.Api;
+import org.nervos.ckb.service.GsonFactory;
+import org.nervos.ckb.sign.TransactionSigner;
+import org.nervos.ckb.sign.TransactionWithScriptGroups;
+import org.nervos.ckb.transaction.*;
+import org.nervos.ckb.type.TransactionInput;
+import org.nervos.ckb.utils.Numeric;
+
+import java.io.IOException;
+import java.util.Iterator;
+
+public class SendChainedTransactionExample {
+ public static void main(String[] args) throws IOException {
+ Network network = Network.TESTNET;
+ String address1 = "ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq2qf8keemy2p5uu0g0gn8cd4ju23s5269qk8rg4r";
+ String address2 = "ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdamwzrffgc54ef48493nfd2sd0h4cjnxg4850up";
+ String address3 = "ckt1qrejnmlar3r452tcg57gvq8patctcgy8acync0hxfnyka35ywafvkqgxhjvp3k9pf88upngryvuxc346q7fq5qmlqqlrhr0p";
+
+ OffChainInputCollector offChainInputCollector = OffChainInputCollector.getInstance();
+ TransactionBuilderConfiguration configuration = new TransactionBuilderConfiguration(network);
+ AbstractInputIterator iterator1 = new OffChainInputIterator(
+ new InputIterator(address1), offChainInputCollector, true);
+
+ // The first tx
+ TransactionWithScriptGroups txWithGroups = new CkbTransactionBuilder(configuration, iterator1)
+ .addOutput(address2, 50100000000L)
+ .setChangeOutput(address1)
+ .build();
+ TransactionSigner.getInstance(network)
+ .signTransaction(txWithGroups, "0x6c9ed03816e3111e49384b8d180174ad08e29feb1393ea1b51cef1c505d4e36a");
+
+ Api api = new Api("https://testnet.ckb.dev", false);
+ byte[] txHash = api.sendTransaction(txWithGroups.getTxView());
+ offChainInputCollector.applyOffChainTransaction(api.getTipBlockNumber(), txWithGroups.getTxView());
+ System.out.println("Transaction hash: " + Numeric.toHexString(txHash));
+
+ AbstractInputIterator iterator2 = new OffChainInputIterator(
+ new InputIterator(address2), offChainInputCollector, true);
+
+ // The second tx will consume outpoint created by the first tx.
+ txWithGroups = new CkbTransactionBuilder(configuration, iterator2)
+ .addOutput(address3,100000000000L)
+ .setChangeOutput(address2)
+ .build();
+ TransactionSigner.getInstance(network)
+ .signTransaction(txWithGroups, "0x0c982052ffd4af5f3bbf232301dcddf468009161fc48ba1426e3ce0929fb59f8");
+
+ txHash = api.sendTransaction(txWithGroups.getTxView());
+ offChainInputCollector.applyOffChainTransaction(api.getTipBlockNumber(), txWithGroups.getTxView());
+ System.out.println("Transaction hash: " + Numeric.toHexString(txHash));
+ }
+}
diff --git a/example/src/main/java/org/nervos/ckb/example/SendCkbByLightClientExample.java b/example/src/main/java/org/nervos/ckb/example/SendCkbByLightClientExample.java
new file mode 100644
index 000000000..18c11f8cf
--- /dev/null
+++ b/example/src/main/java/org/nervos/ckb/example/SendCkbByLightClientExample.java
@@ -0,0 +1,49 @@
+package org.nervos.ckb.example;
+
+import com.nervos.lightclient.DefaultLightClientApi;
+import com.nervos.lightclient.LightClientApi;
+import com.nervos.lightclient.type.ScriptDetail;
+import org.nervos.ckb.Network;
+import org.nervos.ckb.sign.TransactionSigner;
+import org.nervos.ckb.sign.TransactionWithScriptGroups;
+import org.nervos.ckb.transaction.CkbTransactionBuilder;
+import org.nervos.ckb.transaction.LightClientInputIterator;
+import org.nervos.ckb.transaction.TransactionBuilderConfiguration;
+import org.nervos.ckb.type.ScriptType;
+import org.nervos.ckb.utils.Numeric;
+import org.nervos.ckb.utils.address.Address;
+
+import java.io.IOException;
+import java.util.Collections;
+
+public class SendCkbByLightClientExample {
+ public static void main(String[] args) throws IOException {
+ Network network = Network.TESTNET;
+ String sender = "ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq2qf8keemy2p5uu0g0gn8cd4ju23s5269qk8rg4r";
+
+ TransactionBuilderConfiguration configuration = new TransactionBuilderConfiguration(network);
+
+ LightClientApi lightClientApi = new DefaultLightClientApi("http://localhost:9000");
+
+ ScriptDetail senderScriptDetail = new ScriptDetail();
+ senderScriptDetail.script = Address.decode(sender).getScript();
+ senderScriptDetail.scriptType = ScriptType.LOCK;
+ senderScriptDetail.blockNumber = 0;
+ // Set script to let light client sync information about this script on chain.
+ lightClientApi.setScripts(Collections.singletonList(senderScriptDetail));
+
+ LightClientInputIterator iterator = new LightClientInputIterator(lightClientApi);
+ iterator.addSearchKey(sender);
+ TransactionWithScriptGroups txWithGroups = new CkbTransactionBuilder(configuration, iterator)
+ .addOutput("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq2qf8keemy2p5uu0g0gn8cd4ju23s5269qk8rg4r",
+ 50100000000L)
+ .setChangeOutput(sender)
+ .build();
+
+ TransactionSigner.getInstance(network)
+ .signTransaction(txWithGroups, "0x6c9ed03816e3111e49384b8d180174ad08e29feb1393ea1b51cef1c505d4e36a");
+
+ byte[] txHash = lightClientApi.sendTransaction(txWithGroups.getTxView());
+ System.out.println("Transaction hash: " + Numeric.toHexString(txHash));
+ }
+}
diff --git a/example/src/main/java/org/nervos/ckb/example/SendCkbExample.java b/example/src/main/java/org/nervos/ckb/example/SendCkbExample.java
index f67b6d354..5f43d8136 100644
--- a/example/src/main/java/org/nervos/ckb/example/SendCkbExample.java
+++ b/example/src/main/java/org/nervos/ckb/example/SendCkbExample.java
@@ -5,26 +5,28 @@
import org.nervos.ckb.sign.TransactionSigner;
import org.nervos.ckb.sign.TransactionWithScriptGroups;
import org.nervos.ckb.transaction.CkbTransactionBuilder;
+import org.nervos.ckb.transaction.InputIterator;
+import org.nervos.ckb.transaction.TransactionBuilderConfiguration;
import org.nervos.ckb.type.TransactionInput;
import org.nervos.ckb.utils.Numeric;
-import org.nervos.indexer.InputIterator;
import java.io.IOException;
import java.util.Iterator;
public class SendCkbExample {
public static void main(String[] args) throws IOException {
+ Network network = Network.TESTNET;
String sender = "ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq2qf8keemy2p5uu0g0gn8cd4ju23s5269qk8rg4r";
+ TransactionBuilderConfiguration configuration = new TransactionBuilderConfiguration(network);
Iterator iterator = new InputIterator(sender);
- TransactionWithScriptGroups txWithGroups = new CkbTransactionBuilder(iterator, Network.TESTNET)
+ TransactionWithScriptGroups txWithGroups = new CkbTransactionBuilder(configuration, iterator)
.addOutput("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq2qf8keemy2p5uu0g0gn8cd4ju23s5269qk8rg4r",
50100000000L)
- .setFeeRate(1000)
.setChangeOutput(sender)
.build();
- TransactionSigner.getInstance(Network.TESTNET)
+ TransactionSigner.getInstance(network)
.signTransaction(txWithGroups, "0x6c9ed03816e3111e49384b8d180174ad08e29feb1393ea1b51cef1c505d4e36a");
Api api = new Api("https://testnet.ckb.dev", false);
diff --git a/example/src/main/java/org/nervos/ckb/example/SendCkbFromMultisigAddressExample.java b/example/src/main/java/org/nervos/ckb/example/SendCkbMultisigExample.java
similarity index 65%
rename from example/src/main/java/org/nervos/ckb/example/SendCkbFromMultisigAddressExample.java
rename to example/src/main/java/org/nervos/ckb/example/SendCkbMultisigExample.java
index 307f52530..bb82e96d7 100644
--- a/example/src/main/java/org/nervos/ckb/example/SendCkbFromMultisigAddressExample.java
+++ b/example/src/main/java/org/nervos/ckb/example/SendCkbMultisigExample.java
@@ -7,23 +7,23 @@
import org.nervos.ckb.sign.TransactionWithScriptGroups;
import org.nervos.ckb.sign.signer.Secp256k1Blake160MultisigAllSigner;
import org.nervos.ckb.transaction.CkbTransactionBuilder;
+import org.nervos.ckb.transaction.TransactionBuilderConfiguration;
import org.nervos.ckb.type.Script;
import org.nervos.ckb.type.TransactionInput;
import org.nervos.ckb.utils.Numeric;
import org.nervos.ckb.utils.address.Address;
-import org.nervos.indexer.InputIterator;
+import org.nervos.ckb.transaction.InputIterator;
import java.io.IOException;
-import java.util.*;
+import java.util.Iterator;
-public class SendCkbFromMultisigAddressExample {
+public class SendCkbMultisigExample {
public static void main(String[] args) throws IOException {
Network network = Network.TESTNET;
- List keyHashes = new ArrayList<>();
- keyHashes.add(Numeric.hexStringToByteArray("0x7336b0ba900684cb3cb00f0d46d4f64c0994a562"));
- keyHashes.add(Numeric.hexStringToByteArray("0x5724c1e3925a5206944d753a6f3edaedf977d77f"));
Secp256k1Blake160MultisigAllSigner.MultisigScript multisigScript =
- new Secp256k1Blake160MultisigAllSigner.MultisigScript(0, 2, keyHashes);
+ new Secp256k1Blake160MultisigAllSigner.MultisigScript(0, 2,
+ "0x7336b0ba900684cb3cb00f0d46d4f64c0994a562",
+ "0x5724c1e3925a5206944d753a6f3edaedf977d77f");
Script lock = new Script(
Script.SECP256K1_BLAKE160_MULTISIG_ALL_CODE_HASH,
multisigScript.computeHash(),
@@ -31,18 +31,17 @@ public static void main(String[] args) throws IOException {
// ckt1qpw9q60tppt7l3j7r09qcp7lxnp3vcanvgha8pmvsa3jplykxn32sqdunqvd3g2felqv6qer8pkydws8jg9qxlca0st5v
String sender = new Address(lock, network).encode();
+ TransactionBuilderConfiguration configuration = new TransactionBuilderConfiguration(network);
Iterator iterator = new InputIterator(sender);
- TransactionWithScriptGroups txWithGroups = new CkbTransactionBuilder(iterator, network)
+ TransactionWithScriptGroups txWithGroups = new CkbTransactionBuilder(configuration, iterator)
.addOutput("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq2qf8keemy2p5uu0g0gn8cd4ju23s5269qk8rg4r",
50100000000L)
- .setFeeRate(1000)
.setChangeOutput(sender)
.build(multisigScript);
- TransactionSigner.getInstance(network)
- .signTransaction(txWithGroups, new HashSet<>(Arrays.asList(new Context("0x4fd809631a6aa6e3bb378dd65eae5d71df895a82c91a615a1e8264741515c79c", multisigScript))));
- TransactionSigner.getInstance(network)
- .signTransaction(txWithGroups, new HashSet<>(Arrays.asList(new Context("0x7438f7b35c355e3d2fb9305167a31a72d22ddeafb80a21cc99ff6329d92e8087", multisigScript))));
+ TransactionSigner signer = TransactionSigner.getInstance(network);
+ signer.signTransaction(txWithGroups, new Context("0x4fd809631a6aa6e3bb378dd65eae5d71df895a82c91a615a1e8264741515c79c", multisigScript));
+ signer.signTransaction(txWithGroups, new Context("0x7438f7b35c355e3d2fb9305167a31a72d22ddeafb80a21cc99ff6329d92e8087", multisigScript));
Api api = new Api("https://testnet.ckb.dev", false);
byte[] txHash = api.sendTransaction(txWithGroups.getTxView());
diff --git a/example/src/main/java/org/nervos/ckb/example/SendSudtExample.java b/example/src/main/java/org/nervos/ckb/example/SendSudtExample.java
index 5b335595c..56757f5ba 100644
--- a/example/src/main/java/org/nervos/ckb/example/SendSudtExample.java
+++ b/example/src/main/java/org/nervos/ckb/example/SendSudtExample.java
@@ -4,38 +4,36 @@
import org.nervos.ckb.service.Api;
import org.nervos.ckb.sign.TransactionSigner;
import org.nervos.ckb.sign.TransactionWithScriptGroups;
+import org.nervos.ckb.transaction.InputIterator;
import org.nervos.ckb.transaction.SudtTransactionBuilder;
+import org.nervos.ckb.transaction.TransactionBuilderConfiguration;
import org.nervos.ckb.type.TransactionInput;
import org.nervos.ckb.utils.Numeric;
-import org.nervos.indexer.DefaultIndexerApi;
-import org.nervos.indexer.InputIterator;
import java.io.IOException;
import java.util.Iterator;
public class SendSudtExample {
public static void main(String[] args) throws IOException {
+ Network network = Network.TESTNET;
+ Api api = new Api("https://testnet.ckb.dev", false);
String sender = "ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdamwzrffgc54ef48493nfd2sd0h4cjnxg4850up";
byte[] sudtArgs = Numeric.hexStringToByteArray("0x9d2dab815b9158b2344827749d769fd66e2d3ebdfca32e5628ba0454651851f5");
String receiver = "ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqd0pdquvfuq077aemn447shf4d8u5f4a0glzz2g4";
+ TransactionBuilderConfiguration configuration = new TransactionBuilderConfiguration(network);
Iterator iterator =
- new InputIterator(new DefaultIndexerApi("https://testnet.ckb.dev/indexer", false))
- .addSudtSearchKey(sender, sudtArgs);
-
- TransactionWithScriptGroups txWithGroups = new SudtTransactionBuilder(iterator,
- Network.TESTNET,
+ new InputIterator(api).addSudtSearchKey(sender, sudtArgs);
+ TransactionWithScriptGroups txWithGroups = new SudtTransactionBuilder(configuration, iterator,
SudtTransactionBuilder.TransactionType.TRANSFER,
sudtArgs)
.addSudtOutput(receiver, 1L)
- .setFeeRate(1000)
.setChangeOutput(sender)
.build();
- TransactionSigner.getInstance(Network.TESTNET)
+ TransactionSigner.getInstance(network)
.signTransaction(txWithGroups, "0x0c982052ffd4af5f3bbf232301dcddf468009161fc48ba1426e3ce0929fb59f8");
- Api api = new Api("https://testnet.ckb.dev", false);
byte[] txHash = api.sendTransaction(txWithGroups.getTxView());
System.out.println("Transaction hash: " + Numeric.toHexString(txHash));
}
diff --git a/light-client/build.gradle b/light-client/build.gradle
new file mode 100644
index 000000000..3d7449f8b
--- /dev/null
+++ b/light-client/build.gradle
@@ -0,0 +1,20 @@
+plugins {
+ id 'java'
+}
+
+group 'org.nervos.ckb'
+version '2.1.0'
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ implementation project(":ckb-indexer")
+ testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
+ testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
+}
+
+test {
+ useJUnitPlatform()
+}
diff --git a/light-client/src/main/java/com/nervos/lightclient/DefaultLightClientApi.java b/light-client/src/main/java/com/nervos/lightclient/DefaultLightClientApi.java
new file mode 100644
index 000000000..f276417c3
--- /dev/null
+++ b/light-client/src/main/java/com/nervos/lightclient/DefaultLightClientApi.java
@@ -0,0 +1,132 @@
+package com.nervos.lightclient;
+
+import com.google.gson.reflect.TypeToken;
+import com.nervos.lightclient.type.*;
+import org.nervos.ckb.service.GsonFactory;
+import org.nervos.ckb.service.RpcService;
+import org.nervos.ckb.type.Block;
+import org.nervos.ckb.type.Header;
+import org.nervos.ckb.type.Transaction;
+import org.nervos.ckb.utils.Convert;
+import org.nervos.indexer.model.Order;
+import org.nervos.indexer.model.SearchKey;
+import org.nervos.indexer.model.resp.CellCapacityResponse;
+import org.nervos.indexer.model.resp.CellsResponse;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+public class DefaultLightClientApi implements LightClientApi {
+
+ private RpcService rpcService;
+
+
+ public DefaultLightClientApi(String nodeUrl) {
+ this(nodeUrl, false);
+ }
+
+ public DefaultLightClientApi(String nodeUrl, boolean isDebug) {
+ rpcService = new RpcService(nodeUrl, isDebug);
+ }
+
+ public DefaultLightClientApi(RpcService rpcService) {
+ this.rpcService = rpcService;
+ }
+
+ @Override
+ public void setScripts(List scriptDetails) throws IOException {
+ rpcService.post(
+ "set_scripts",
+ Collections.singletonList(scriptDetails), Object.class);
+ }
+
+ @Override
+ public List getScripts() throws IOException {
+ return rpcService.post(
+ "get_scripts",
+ Collections.emptyList(),
+ new TypeToken>() {}.getType());
+ }
+
+ @Override
+ public byte[] sendTransaction(Transaction transaction) throws IOException {
+ return rpcService.post(
+ "send_transaction",
+ Arrays.asList(Convert.parseTransaction(transaction)),
+ byte[].class);
+ }
+
+ @Override
+ public Header getTipHeader() throws IOException {
+ return rpcService.post("get_tip_header", Collections.emptyList(), Header.class);
+ }
+
+ @Override
+ public Block getGenesisBlock() throws IOException {
+ return rpcService.post("get_genesis_block", Collections.emptyList(), Block.class);
+ }
+
+ @Override
+ public Header getHeader(byte[] blockHash) throws IOException {
+ return rpcService.post(
+ "get_header",
+ Collections.singletonList(blockHash), Header.class);
+ }
+
+ @Override
+ public TransactionWithHeader getTransaction(byte[] transactionHash) throws IOException {
+ return rpcService.post(
+ "get_transaction",
+ Collections.singletonList(transactionHash), TransactionWithHeader.class);
+ }
+
+ @Override
+ public FetchedHeader fetchHeader(byte[] blockHash) throws IOException {
+ return rpcService.post(
+ "fetch_header",
+ Collections.singletonList(blockHash), FetchedHeader.class);
+ }
+
+ @Override
+ public FetchedTransaction fetchTransaction(byte[] transactionHash) throws IOException {
+ return rpcService.post(
+ "fetch_transaction",
+ Collections.singletonList(transactionHash), FetchedTransaction.class);
+ }
+
+ @Override
+ public CellsResponse getCells(SearchKey searchKey, Order order, int limit, byte[] afterCursor) throws IOException {
+ return this.rpcService.post(
+ "get_cells",
+ Arrays.asList(searchKey, order, limit, afterCursor),
+ CellsResponse.class);
+ }
+
+ @Override
+ public TxsWithCell getTransactions(SearchKey searchKey, Order order, int limit, byte[] afterCursor) throws IOException {
+ searchKey.groupByTransaction = false;
+ return this.rpcService.post(
+ "get_transactions",
+ Arrays.asList(searchKey, order, limit, afterCursor),
+ TxsWithCell.class, GsonFactory.create());
+ }
+
+ @Override
+ public TxsWithCells getTransactionsGrouped(SearchKey searchKey, Order order, int limit, byte[] afterCursor) throws IOException {
+ searchKey.groupByTransaction = true;
+ return this.rpcService.post(
+ "get_transactions",
+ Arrays.asList(searchKey, order, limit, afterCursor),
+ TxsWithCells.class);
+ }
+
+ @Override
+ public CellCapacityResponse getCellsCapacity(SearchKey searchKey) throws IOException {
+ return this.rpcService.post(
+ "get_cells_capacity",
+ Arrays.asList(searchKey),
+ CellCapacityResponse.class);
+ }
+}
diff --git a/light-client/src/main/java/com/nervos/lightclient/LightClientApi.java b/light-client/src/main/java/com/nervos/lightclient/LightClientApi.java
new file mode 100644
index 000000000..c87945f28
--- /dev/null
+++ b/light-client/src/main/java/com/nervos/lightclient/LightClientApi.java
@@ -0,0 +1,42 @@
+package com.nervos.lightclient;
+
+import com.nervos.lightclient.type.*;
+import org.nervos.ckb.type.Block;
+import org.nervos.ckb.type.Header;
+import org.nervos.ckb.type.Transaction;
+import org.nervos.indexer.model.Order;
+import org.nervos.indexer.model.SearchKey;
+import org.nervos.indexer.model.resp.CellCapacityResponse;
+import org.nervos.indexer.model.resp.CellsResponse;
+
+import java.io.IOException;
+import java.util.List;
+
+public interface LightClientApi {
+
+ void setScripts(List scriptDetails) throws IOException;
+
+ List getScripts() throws IOException;
+
+ byte[] sendTransaction(Transaction transaction) throws IOException;
+
+ Header getTipHeader() throws IOException;
+
+ Block getGenesisBlock() throws IOException;
+
+ Header getHeader(byte[] blockHash) throws IOException;
+
+ TransactionWithHeader getTransaction(byte[] transactionHash) throws IOException;
+
+ FetchedHeader fetchHeader(byte[] blockHash) throws IOException;
+
+ FetchedTransaction fetchTransaction(byte[] transactionHash) throws IOException;
+
+ CellsResponse getCells(SearchKey searchKey, Order order, int limit, byte[] afterCursor) throws IOException;
+
+ TxsWithCell getTransactions(SearchKey searchKey, Order order, int limit, byte[] afterCursor) throws IOException;
+
+ TxsWithCells getTransactionsGrouped(SearchKey searchKey, Order order, int limit, byte[] afterCursor) throws IOException;
+
+ CellCapacityResponse getCellsCapacity(SearchKey searchKey) throws IOException;
+}
diff --git a/light-client/src/main/java/com/nervos/lightclient/type/FetchStatus.java b/light-client/src/main/java/com/nervos/lightclient/type/FetchStatus.java
new file mode 100644
index 000000000..f20d95f66
--- /dev/null
+++ b/light-client/src/main/java/com/nervos/lightclient/type/FetchStatus.java
@@ -0,0 +1,14 @@
+package com.nervos.lightclient.type;
+
+import com.google.gson.annotations.SerializedName;
+
+public enum FetchStatus {
+ @SerializedName("fetched")
+ FETCHED,
+ @SerializedName("fetching")
+ FETCHING,
+ @SerializedName("added")
+ ADDED,
+ @SerializedName("not_found")
+ NOT_FOUND
+}
diff --git a/light-client/src/main/java/com/nervos/lightclient/type/FetchedHeader.java b/light-client/src/main/java/com/nervos/lightclient/type/FetchedHeader.java
new file mode 100644
index 000000000..46a45ebee
--- /dev/null
+++ b/light-client/src/main/java/com/nervos/lightclient/type/FetchedHeader.java
@@ -0,0 +1,10 @@
+package com.nervos.lightclient.type;
+
+import org.nervos.ckb.type.Header;
+
+public class FetchedHeader {
+ public FetchStatus status;
+ public Header data;
+ public long firstSent;
+ public long timestamp;
+}
diff --git a/light-client/src/main/java/com/nervos/lightclient/type/FetchedTransaction.java b/light-client/src/main/java/com/nervos/lightclient/type/FetchedTransaction.java
new file mode 100644
index 000000000..633822bc3
--- /dev/null
+++ b/light-client/src/main/java/com/nervos/lightclient/type/FetchedTransaction.java
@@ -0,0 +1,11 @@
+package com.nervos.lightclient.type;
+
+import org.nervos.ckb.type.Transaction;
+
+public class FetchedTransaction {
+ public FetchStatus status;
+ public Transaction data;
+ public long firstSent;
+ public long timestamp;
+
+}
diff --git a/light-client/src/main/java/com/nervos/lightclient/type/ScriptDetail.java b/light-client/src/main/java/com/nervos/lightclient/type/ScriptDetail.java
new file mode 100644
index 000000000..ad1e3bfc6
--- /dev/null
+++ b/light-client/src/main/java/com/nervos/lightclient/type/ScriptDetail.java
@@ -0,0 +1,10 @@
+package com.nervos.lightclient.type;
+
+import org.nervos.ckb.type.Script;
+import org.nervos.ckb.type.ScriptType;
+
+public class ScriptDetail {
+ public Script script;
+ public ScriptType scriptType;
+ public long blockNumber;
+}
diff --git a/light-client/src/main/java/com/nervos/lightclient/type/TransactionWithHeader.java b/light-client/src/main/java/com/nervos/lightclient/type/TransactionWithHeader.java
new file mode 100644
index 000000000..cafa68460
--- /dev/null
+++ b/light-client/src/main/java/com/nervos/lightclient/type/TransactionWithHeader.java
@@ -0,0 +1,9 @@
+package com.nervos.lightclient.type;
+
+import org.nervos.ckb.type.Header;
+import org.nervos.ckb.type.Transaction;
+
+public class TransactionWithHeader {
+ public Transaction transaction;
+ public Header header;
+}
diff --git a/light-client/src/main/java/com/nervos/lightclient/type/TxsWithCell.java b/light-client/src/main/java/com/nervos/lightclient/type/TxsWithCell.java
new file mode 100644
index 000000000..fe06e7acf
--- /dev/null
+++ b/light-client/src/main/java/com/nervos/lightclient/type/TxsWithCell.java
@@ -0,0 +1,19 @@
+package com.nervos.lightclient.type;
+
+import org.nervos.ckb.type.Transaction;
+import org.nervos.indexer.model.resp.IoType;
+
+import java.util.List;
+
+public class TxsWithCell {
+ public byte[] lastCursor;
+ public List objects;
+
+ public static class TxWithCell {
+ public int blockNumber;
+ public int ioIndex;
+ public IoType ioType;
+ public Transaction transaction;
+ public int txIndex;
+ }
+}
diff --git a/light-client/src/main/java/com/nervos/lightclient/type/TxsWithCells.java b/light-client/src/main/java/com/nervos/lightclient/type/TxsWithCells.java
new file mode 100644
index 000000000..496832fd8
--- /dev/null
+++ b/light-client/src/main/java/com/nervos/lightclient/type/TxsWithCells.java
@@ -0,0 +1,38 @@
+package com.nervos.lightclient.type;
+
+import com.google.gson.*;
+import com.google.gson.annotations.JsonAdapter;
+import org.nervos.ckb.type.Transaction;
+import org.nervos.indexer.model.resp.IoType;
+
+import java.lang.reflect.Type;
+import java.util.List;
+
+public class TxsWithCells {
+ public byte[] lastCursor;
+ public List objects;
+
+ public class TxWithCells {
+ public Transaction transaction;
+ public int blockNumber;
+ public int txIndex;
+ public List cells;
+ }
+
+ @JsonAdapter(Deserializer.class)
+ public static class Cell {
+ public IoType ioType;
+ public int ioIndex;
+ }
+
+ public static class Deserializer implements JsonDeserializer {
+ @Override
+ public Cell deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
+ JsonArray jsonArray = json.getAsJsonArray();
+ Cell cell = new Cell();
+ cell.ioType = context.deserialize(jsonArray.get(0), IoType.class);
+ cell.ioIndex = context.deserialize(jsonArray.get(1), Integer.class);
+ return cell;
+ }
+ }
+}
diff --git a/light-client/src/test/java/com/nervos/lightclient/DefaultLightClientApiTest.java b/light-client/src/test/java/com/nervos/lightclient/DefaultLightClientApiTest.java
new file mode 100644
index 000000000..86d681ea2
--- /dev/null
+++ b/light-client/src/test/java/com/nervos/lightclient/DefaultLightClientApiTest.java
@@ -0,0 +1,144 @@
+package com.nervos.lightclient;
+
+import com.nervos.lightclient.type.*;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable;
+import org.nervos.ckb.type.Block;
+import org.nervos.ckb.type.Header;
+import org.nervos.ckb.type.Script;
+import org.nervos.ckb.type.ScriptType;
+import org.nervos.ckb.utils.Numeric;
+import org.nervos.indexer.model.Order;
+import org.nervos.indexer.model.SearchKeyBuilder;
+import org.nervos.indexer.model.resp.CellCapacityResponse;
+import org.nervos.indexer.model.resp.CellsResponse;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+@DisabledIfEnvironmentVariable(named = "CI", matches = "true")
+class DefaultLightClientApiTest {
+ public LightClientApi api = new DefaultLightClientApi("http://localhost:9000");
+ // ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq2qf8keemy2p5uu0g0gn8cd4ju23s5269qk8rg4r
+ public Script script = new Script(
+ Numeric.hexStringToByteArray("0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8"),
+ Numeric.hexStringToByteArray("0x4049ed9cec8a0d39c7a1e899f0dacb8a8c28ad14"),
+ Script.HashType.TYPE);
+
+ @Test
+ void setScripts() throws IOException {
+ ScriptDetail scriptDetail = new ScriptDetail();
+ scriptDetail.script = script;
+ scriptDetail.scriptType = ScriptType.LOCK;
+ scriptDetail.blockNumber = 7033100;
+ List scriptDetails = new ArrayList<>();
+ scriptDetails.add(scriptDetail);
+ api.setScripts(scriptDetails);
+ }
+
+ @Test
+ void getScripts() throws IOException {
+ List scriptDetails = api.getScripts();
+ Assertions.assertTrue(scriptDetails.size() > 0);
+ Assertions.assertTrue(scriptDetails.stream().anyMatch(scriptDetail -> scriptDetail.script != null));
+ Assertions.assertTrue(scriptDetails.stream().anyMatch(scriptDetail -> scriptDetail.blockNumber > 0));
+ Assertions.assertTrue(scriptDetails.stream().anyMatch(scriptDetail -> scriptDetail.scriptType != null));
+ }
+
+ @Test
+ void getTipHeader() throws IOException {
+ Header header = api.getTipHeader();
+ Assertions.assertNotNull(header);
+ }
+
+ @Test
+ void getGenesisBlock() throws IOException {
+ Block block = api.getGenesisBlock();
+ Assertions.assertNotNull(block);
+ }
+
+ @Test
+ void getHeader() throws IOException {
+ Header header = api.getHeader(Numeric.hexStringToByteArray("0xc78c65185c14e1b02d6457a06b4678bab7e15f194f49a840319b57c67d20053c"));
+ Assertions.assertNotNull(header);
+ }
+
+ @Test
+ void getTransaction() throws IOException {
+ TransactionWithHeader tx = api.getTransaction(Numeric.hexStringToByteArray("0x151d4d450c9e3bccf4b47d1ba6942d4e9c8c0eeeb7b9f708df827c164f035aa8"));
+ Assertions.assertNotNull(tx.header);
+ Assertions.assertNotNull(tx.transaction);
+ }
+
+ @Test
+ void fetchHeader() throws IOException {
+ FetchedHeader header = api.fetchHeader(Numeric.hexStringToByteArray("0xcb5eae958e3ea24b0486a393133aa33d51224ffaab3c4819350095b3446e4f70"));
+ Assertions.assertNotNull(header.status);
+ Assertions.assertNotNull(header.data);
+ }
+
+ @Test
+ void fetchTransaction() throws IOException {
+ FetchedTransaction tx = api.fetchTransaction(Numeric.hexStringToByteArray("0x716e211698d3d9499aae7903867c744b67b539beeceddad330e73d1b6b617aef"));
+ Assertions.assertNotNull(tx.status);
+ Assertions.assertNotNull(tx.data);
+ }
+
+ @Test
+ void getCells() throws IOException {
+ SearchKeyBuilder key = new SearchKeyBuilder();
+ key.script(script);
+ key.scriptType(ScriptType.LOCK);
+
+ CellsResponse cells = api.getCells(key.build(), Order.ASC, 10, null);
+ Assertions.assertTrue(cells.objects.size() > 0);
+ Assertions.assertTrue(cells.objects.stream().anyMatch(obj -> obj.blockNumber != 0));
+ Assertions.assertTrue(cells.objects.stream().anyMatch(obj -> obj.txIndex != 0));
+ Assertions.assertTrue(cells.objects.stream().anyMatch(obj -> obj.outPoint != null));
+ Assertions.assertTrue(cells.objects.stream().anyMatch(obj -> obj.output != null));
+ }
+
+ @Test
+ void getTransactions() throws IOException {
+ SearchKeyBuilder key = new SearchKeyBuilder();
+ key.script(script);
+ key.scriptType(ScriptType.LOCK);
+
+ TxsWithCell txs = api.getTransactions(key.build(), Order.ASC, 10, null);
+ Assertions.assertTrue(txs.objects.size() > 0);
+ Assertions.assertTrue(txs.objects.stream().anyMatch(obj -> obj.transaction != null));
+ Assertions.assertTrue(txs.objects.stream().anyMatch(obj -> obj.blockNumber != 0));
+ Assertions.assertTrue(txs.objects.stream().anyMatch(obj -> obj.ioIndex != 0));
+ Assertions.assertTrue(txs.objects.stream().anyMatch(obj -> obj.ioType != null));
+ Assertions.assertTrue(txs.objects.stream().anyMatch(obj -> obj.txIndex != 0));
+ }
+
+ @Test
+ void getTransactionsGrouped() throws IOException {
+ SearchKeyBuilder key = new SearchKeyBuilder();
+ key.script(script);
+ key.scriptType(ScriptType.LOCK);
+
+ TxsWithCells txs = api.getTransactionsGrouped(key.build(), Order.ASC, 10, null);
+ Assertions.assertTrue(txs.objects.size() > 0);
+ Assertions.assertTrue(txs.objects.stream().anyMatch(obj -> obj.blockNumber != 0));
+ Assertions.assertTrue(txs.objects.stream().anyMatch(obj -> obj.cells.size() > 0));
+ Assertions.assertTrue(txs.objects.stream().anyMatch(obj -> obj.cells.get(0) != null));
+ Assertions.assertTrue(txs.objects.stream().anyMatch(obj -> obj.cells.size() >= 2 && obj.cells.get(1).ioIndex != 0));
+ Assertions.assertTrue(txs.objects.stream().anyMatch(obj -> obj.txIndex != 0));
+ Assertions.assertTrue(txs.objects.stream().anyMatch(obj -> obj.transaction != null));
+ }
+
+ @Test
+ void getCellsCapacity() throws IOException {
+ SearchKeyBuilder key = new SearchKeyBuilder();
+ key.script(script);
+ key.scriptType(ScriptType.LOCK);
+ CellCapacityResponse capacity = api.getCellsCapacity(key.build());
+ Assertions.assertNotEquals(0L, capacity.capacity);
+ Assertions.assertNotEquals(0L, capacity.blockNumber);
+ Assertions.assertNotNull(capacity.blockHash);
+ }
+}
diff --git a/serialization/build.gradle b/serialization/build.gradle
index 62e7e4593..e7c9da7ec 100644
--- a/serialization/build.gradle
+++ b/serialization/build.gradle
@@ -3,5 +3,3 @@ description 'Minimal set of ckb serialization classes'
dependencies {
compile project(":utils")
}
-
-apply from: rootProject.file('gradle/checkstyle.gradle')
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index 4fcc04cca..152dcb7f4 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -6,6 +6,5 @@ include 'serialization'
include 'example'
include 'ckb-mercury-sdk'
include 'ckb-indexer'
-include 'ckb-api'
include 'core'
-
+include 'light-client'
diff --git a/utils/build.gradle b/utils/build.gradle
index bcdc22110..bae5c46cb 100644
--- a/utils/build.gradle
+++ b/utils/build.gradle
@@ -4,5 +4,3 @@ dependencies {
compile "org.bouncycastle:bcprov-jdk15on:$bouncycastleVersion"
compile "com.google.guava:guava:$guavaVersion"
}
-
-apply from: rootProject.file('gradle/checkstyle.gradle')
diff --git a/utils/src/main/java/org/nervos/ckb/crypto/secp256k1/ECKeyPair.java b/utils/src/main/java/org/nervos/ckb/crypto/secp256k1/ECKeyPair.java
index 5dbeba127..acf0f90c0 100644
--- a/utils/src/main/java/org/nervos/ckb/crypto/secp256k1/ECKeyPair.java
+++ b/utils/src/main/java/org/nervos/ckb/crypto/secp256k1/ECKeyPair.java
@@ -74,6 +74,7 @@ public static ECKeyPair create(byte[] privateKey) {
* Returns public key from the given private key.
*
* @param privateKey the private key to derive the public key from
+ * @param compressed whether the public key should be compressed
* @return byte array encoded public key
*/
public static byte[] publicKeyFromPrivate(BigInteger privateKey, boolean compressed) {
| | | |