Skip to content

Commit

Permalink
feat: Add acp transaction example
Browse files Browse the repository at this point in the history
  • Loading branch information
duanyytop committed Jan 20, 2021
1 parent 864ade6 commit 8ad406b
Show file tree
Hide file tree
Showing 7 changed files with 241 additions and 24 deletions.
186 changes: 186 additions & 0 deletions example/src/main/java/org/nervos/ckb/ACPTransactionExample.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package org.nervos.ckb;

import static org.nervos.ckb.utils.Const.*;

import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.nervos.ckb.address.Network;
import org.nervos.ckb.crypto.secp256k1.Sign;
import org.nervos.ckb.indexer.*;
import org.nervos.ckb.service.Api;
import org.nervos.ckb.transaction.*;
import org.nervos.ckb.type.OutPoint;
import org.nervos.ckb.type.Script;
import org.nervos.ckb.type.Witness;
import org.nervos.ckb.type.cell.CellDep;
import org.nervos.ckb.type.cell.CellInput;
import org.nervos.ckb.type.cell.CellOutput;
import org.nervos.ckb.type.transaction.Transaction;
import org.nervos.ckb.utils.Numeric;
import org.nervos.ckb.utils.Utils;
import org.nervos.ckb.utils.address.AddressGenerator;
import org.nervos.ckb.utils.address.AddressParser;

/** Copyright © 2021 Nervos Foundation. All rights reserved. */
public class ACPTransactionExample {
// ACP_CKB_MINIMUM is set to 9, which means in each transaction, one must at least transfers 10^9
// shannons,
// or 10 CKBytes into the anyone-can-pay cel
private static final String ACP_CKB_MINIMUM = "09";

private static Api api;
private static CkbIndexerApi ckbIndexerApi;

private static final List<String> SendPrivateKeys =
Collections.singletonList("d00c06bfd800d27397002dca6fb0993d5ba6399b4238b2f29ee9deb97593d2bc");
private static final List<String> SendAddresses =
Collections.singletonList("ckt1qyqvsv5240xeh85wvnau2eky8pwrhh4jr8ts8vyj37");
private static final List<String> ReceiveAddresses =
Collections.singletonList("ckt1qyqtnz38fht9nvmrfdeunrhdtp29n0gagkps4duhek");
private static String receiverAcpAddress;

static {
api = new Api(NODE_URL, false);
ckbIndexerApi = new CkbIndexerApi(CKB_INDEXER_URL, true);

Script receiverScript = AddressParser.parse(ReceiveAddresses.get(0)).script;
receiverScript.codeHash = ACP_CODE_HASH;
receiverScript.args = receiverScript.args + ACP_CKB_MINIMUM;
receiverAcpAddress = AddressGenerator.generate(Network.TESTNET, receiverScript);
}

public static void main(String[] args) throws Exception {
System.out.println(
"Before transferring, first sender's balance: "
+ getBalance(SendAddresses.get(0)).divide(UnitCKB).toString(10)
+ " CKB");

String acpHash = createACPCell();
System.out.println("Create acp cell tx hash: " + acpHash);

// waiting transaction into block, sometimes you should wait more seconds
Thread.sleep(30000);
System.out.println("Transfer SUDT tx hash: " + transfer(new OutPoint(acpHash, "0x0")));
}

private static BigInteger getBalance(String address) throws IOException {
return new IndexerCollector(api, ckbIndexerApi).getCapacity(address);
}

private static String createACPCell() throws IOException {
List<ScriptGroupWithPrivateKeys> scriptGroupWithPrivateKeysList = new ArrayList<>();

TransactionBuilder txBuilder = new TransactionBuilder(api);
IndexerCollector txUtils = new IndexerCollector(api, ckbIndexerApi);

List<Receiver> receivers =
Collections.singletonList(new Receiver(receiverAcpAddress, Utils.ckbToShannon(200)));
List<CellOutput> cellOutputs = txUtils.generateOutputs(receivers, SendAddresses.get(0));
txBuilder.addOutputs(cellOutputs);

txBuilder.setOutputsData(Arrays.asList("0x", "0x"));
txBuilder.addCellDep(new CellDep(new OutPoint(ACP_TX_HASH, "0x0"), CellDep.DEP_GROUP));

// You can get fee rate by rpc or set a simple number
BigInteger feeRate = BigInteger.valueOf(1024);

// initial_length = 2 * secp256k1_signature_byte.length
// collectInputsWithIndexer method uses indexer rpc to collect cells quickly
CollectResult collectResult =
txUtils.collectInputs(SendAddresses, txBuilder.buildTx(), feeRate, Sign.SIGN_LENGTH * 2);

// update change cell output capacity after collecting cells if there is changeOutput
if (Numeric.toBigInt(collectResult.changeCapacity).compareTo(MIN_CKB) >= 0) {
cellOutputs.get(cellOutputs.size() - 1).capacity = collectResult.changeCapacity;
txBuilder.setOutputs(cellOutputs);
}

int startIndex = 0;
for (CellsWithAddress cellsWithAddress : collectResult.cellsWithAddresses) {
txBuilder.addInputs(cellsWithAddress.inputs);
for (int i = 0; i < cellsWithAddress.inputs.size(); i++) {
txBuilder.addWitness(i == 0 ? new Witness(Witness.SIGNATURE_PLACEHOLDER) : "0x");
}
if (cellsWithAddress.inputs.size() > 0) {
scriptGroupWithPrivateKeysList.add(
new ScriptGroupWithPrivateKeys(
new ScriptGroup(
NumberUtils.regionToList(startIndex, cellsWithAddress.inputs.size())),
Collections.singletonList(
SendPrivateKeys.get(SendAddresses.indexOf(cellsWithAddress.address)))));
startIndex += cellsWithAddress.inputs.size();
}
}

Secp256k1SighashAllBuilder signBuilder = new Secp256k1SighashAllBuilder(txBuilder.buildTx());

for (ScriptGroupWithPrivateKeys scriptGroupWithPrivateKeys : scriptGroupWithPrivateKeysList) {
signBuilder.sign(
scriptGroupWithPrivateKeys.scriptGroup, scriptGroupWithPrivateKeys.privateKeys.get(0));
}
Transaction tx = signBuilder.buildTx();
return api.sendTransaction(tx);
}

private static String transfer(OutPoint acpOutPoint) throws IOException {
List<ScriptGroupWithPrivateKeys> scriptGroupWithPrivateKeysList = new ArrayList<>();

TransactionBuilder txBuilder = new TransactionBuilder(api);
IndexerCollector txUtils = new IndexerCollector(api, ckbIndexerApi);

List<Receiver> receivers =
Collections.singletonList(new Receiver(receiverAcpAddress, Utils.ckbToShannon(210)));
List<CellOutput> cellOutputs = txUtils.generateOutputs(receivers, SendAddresses.get(0));
txBuilder.addOutputs(cellOutputs);

txBuilder.addCellDep(new CellDep(new OutPoint(ACP_TX_HASH, "0x0"), CellDep.DEP_GROUP));

// You can get fee rate by rpc or set a simple number
BigInteger feeRate = BigInteger.valueOf(1500);

// initial_length = 2 * secp256k1_signature_byte.length
// collectInputsWithIndexer method uses indexer rpc to collect cells quickly
CollectResult collectResult =
txUtils.collectInputs(SendAddresses, txBuilder.buildTx(), feeRate, Sign.SIGN_LENGTH * 2);

// update change cell output capacity after collecting cells if there is changeOutput
if (Numeric.toBigInt(collectResult.changeCapacity).compareTo(MIN_CKB) >= 0) {
cellOutputs.get(cellOutputs.size() - 1).capacity = collectResult.changeCapacity;
txBuilder.setOutputs(cellOutputs);
}

txBuilder.setOutputsData(Arrays.asList("0x", "0x"));

int startIndex = 0;
for (CellsWithAddress cellsWithAddress : collectResult.cellsWithAddresses) {
txBuilder.addInputs(cellsWithAddress.inputs);
for (int i = 0; i < cellsWithAddress.inputs.size(); i++) {
txBuilder.addWitness(i == 0 ? new Witness(Witness.SIGNATURE_PLACEHOLDER) : "0x");
}
if (cellsWithAddress.inputs.size() > 0) {
scriptGroupWithPrivateKeysList.add(
new ScriptGroupWithPrivateKeys(
new ScriptGroup(
NumberUtils.regionToList(startIndex, cellsWithAddress.inputs.size())),
Collections.singletonList(
SendPrivateKeys.get(SendAddresses.indexOf(cellsWithAddress.address)))));
startIndex += cellsWithAddress.inputs.size();
}
}
txBuilder.addWitness("0x");
txBuilder.addInput(new CellInput(acpOutPoint, "0x0"));

Secp256k1SighashAllBuilder signBuilder = new Secp256k1SighashAllBuilder(txBuilder.buildTx());

for (ScriptGroupWithPrivateKeys scriptGroupWithPrivateKeys : scriptGroupWithPrivateKeysList) {
signBuilder.sign(
scriptGroupWithPrivateKeys.scriptGroup, scriptGroupWithPrivateKeys.privateKeys.get(0));
}
Transaction tx = signBuilder.buildTx();
return api.sendTransaction(tx);
}
}
19 changes: 5 additions & 14 deletions example/src/main/java/org/nervos/ckb/SUDTExample.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.nervos.ckb;

import static org.nervos.ckb.utils.Const.*;

import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
Expand All @@ -23,20 +25,9 @@

/** Copyright © 2021 Nervos Foundation. All rights reserved. */
public class SUDTExample {

private static final String NODE_URL = "https://testnet.ckb.dev/rpc";
private static final String CKB_INDEXER_URL = "https://testnet.ckb.dev/indexer";
private static final BigInteger UnitCKB = new BigInteger("100000000");
private static final BigInteger MIN_CKB = new BigInteger("6100000000");

private static final BigInteger SUDT_ISSUE_SUM_AMOUNT = new BigInteger("1000000000000");
private static final BigInteger SUDT_TRANSFER_AMOUNT = new BigInteger("60000000000");

private static final String SUDT_CODE_HASH =
"0xc5e5dcf215925f7ef4dfaf5f4b4f105bc321c02776d6e7d52a1db3fcd9d011a4";
private static final String SUDT_TX_HASH =
"0xe12877ebd2c3c364dc46c5c992bcfaf4fee33fa13eebdf82c591fc9825aab769";

private static Api api;
private static CkbIndexerApi ckbIndexerApi;
private static final List<String> SendPrivateKeys =
Expand All @@ -45,7 +36,7 @@ public class SUDTExample {
Collections.singletonList("ckt1qyqxgp7za7dajm5wzjkye52asc8fxvvqy9eqlhp82g");
private static final List<String> ReceiveAddresses =
Collections.singletonList("ckt1qyqvsv5240xeh85wvnau2eky8pwrhh4jr8ts8vyj37");
private static Script sudtType;
public static Script sudtType;

static {
api = new Api(NODE_URL, false);
Expand Down Expand Up @@ -81,7 +72,7 @@ private static String issue() throws IOException {
IndexerCollector txUtils = new IndexerCollector(api, ckbIndexerApi);

List<Receiver> receivers =
Collections.singletonList(new Receiver(SendAddresses.get(0), Utils.ckbToShannon(400)));
Collections.singletonList(new Receiver(SendAddresses.get(0), Utils.ckbToShannon(2000)));
List<CellOutput> cellOutputs = txUtils.generateOutputs(receivers, SendAddresses.get(0));
cellOutputs.get(0).type = sudtType;
txBuilder.addOutputs(cellOutputs);
Expand Down Expand Up @@ -138,7 +129,7 @@ private static String transfer() throws IOException {
IndexerCollector txUtils = new IndexerCollector(api, ckbIndexerApi);

List<Receiver> receivers =
Collections.singletonList(new Receiver(ReceiveAddresses.get(0), Utils.ckbToShannon(142)));
Collections.singletonList(new Receiver(ReceiveAddresses.get(0), MIN_SUDT_CKB));
List<CellOutput> cellOutputs = txUtils.generateOutputs(receivers, SendAddresses.get(0));
cellOutputs.get(0).type = sudtType;
txBuilder.addOutputs(cellOutputs);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,6 @@ public class CellCkbIndexerIterator implements Iterator<TransactionInput> {
inputIndex = 0;
}

CellCkbIndexerIterator(CkbIndexerApi api, List<String> addresses) {
this(api, addresses, true, "asc", BigInteger.valueOf(100), "0x", null);
}

CellCkbIndexerIterator(CkbIndexerApi api, List<String> addresses, boolean skipDataAndType) {
this(api, addresses, skipDataAndType, "asc", BigInteger.valueOf(100), "0x", null);
}
Expand All @@ -73,8 +69,11 @@ public TransactionInput next() {
do {
if (type != null) {
String address = addresses.get(addressIndex);
String lockHash = AddressParser.parse(address).script.computeHash();
transactionInputs = fetchTransactionInputsByType(lockHash, new SearchKey(type, "type"));
Script lock = AddressParser.parse(address).script;
String lockHash = lock.computeHash();
transactionInputs =
fetchTransactionInputsByType(
lockHash, new SearchKey(lock, "lock", new SearchKey.Filter(type)));
} else {
transactionInputs =
fetchTransactionInputsByLock(
Expand Down Expand Up @@ -108,7 +107,8 @@ private List<TransactionInput> fetchTransactionInputsByLock(SearchKey searchKey)
|| cellOutput.type != null) {
continue;
}
} else if (liveCell.output.type != type) {
} else if (liveCell.output.type == null
|| !liveCell.output.type.computeHash().equals(type.computeHash())) {
continue;
}
CellInput cellInput = new CellInput(liveCell.outPoint, "0x0");
Expand Down Expand Up @@ -139,7 +139,8 @@ private List<TransactionInput> fetchTransactionInputsByType(
|| cellOutput.type != null) {
continue;
}
} else if (!liveCell.output.type.computeHash().equals(type.computeHash())) {
} else if (liveCell.output.type == null
|| !liveCell.output.type.computeHash().equals(type.computeHash())) {
continue;
}
CellInput cellInput = new CellInput(liveCell.outPoint, "0x0");
Expand Down
18 changes: 18 additions & 0 deletions example/src/main/java/org/nervos/ckb/indexer/SearchKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,31 @@ public class SearchKey {
@SerializedName("script_type")
public String scriptType;

public Filter filter;

public SearchKey(Script script, String scriptType, Filter filter) {
this.script = script;
this.scriptType = scriptType;
this.filter = filter;
}

public SearchKey(Script script, String scriptType) {
this.script = script;
this.scriptType = scriptType;
this.filter = null;
}

public SearchKey(Script script) {
this.script = script;
this.scriptType = "lock";
this.filter = null;
}

public static class Filter {
public Script script;

public Filter(Script script) {
this.script = script;
}
}
}
23 changes: 23 additions & 0 deletions example/src/main/java/org/nervos/ckb/utils/Const.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.nervos.ckb.utils;

import java.math.BigInteger;

/** Copyright © 2021 Nervos Foundation. All rights reserved. */
public class Const {
public static final String NODE_URL = "https://testnet.ckb.dev/rpc";
public static final String CKB_INDEXER_URL = "https://testnet.ckb.dev/indexer";

public static final BigInteger UnitCKB = new BigInteger("100000000");
public static final BigInteger MIN_CKB = new BigInteger("6100000000");
public static final BigInteger MIN_SUDT_CKB = new BigInteger("14200000000");

public static final String SUDT_CODE_HASH =
"0xc5e5dcf215925f7ef4dfaf5f4b4f105bc321c02776d6e7d52a1db3fcd9d011a4";
public static final String SUDT_TX_HASH =
"0xe12877ebd2c3c364dc46c5c992bcfaf4fee33fa13eebdf82c591fc9825aab769";

public static final String ACP_CODE_HASH =
"0x3419a1c09eb2567f6552ee7a8ecffd64155cffe0f1796e6e61ec088d740c1356";
public static final String ACP_TX_HASH =
"0xec26b0f85ed839ece5f11c4c4e837ec359f5adc4420410f6453b1f6b60fb96a6";
}
1 change: 0 additions & 1 deletion serialization/src/test/java/type/FixedTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ public void toBytesTest() {
UInt64 sinceUInt64 = new UInt64(1L);
Struct inputs = new Struct(sinceUInt64, new Struct(txHash, index));
Fixed<Struct> structFixed = new Fixed<>(Collections.singletonList(inputs));
System.out.println(Numeric.toHexString(structFixed.toBytes()));
Assertions.assertArrayEquals(
Numeric.hexStringToByteArray(
"0x010000000100000000000000000000000000000000000000000000000000000000000000000000000000000095729694"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ public static CodeHashType parseAddressType(String address) throws AddressFormat
public static String parse(String address) throws AddressFormatException {
String payload = parsePrefix(address);
String prefixCodeHash = payload.substring(TYPE.length());
System.out.println(payload);
if (prefixCodeHash.startsWith(CODE_HASH_IDX_BLAKE160)) {
return payload.substring((TYPE + CODE_HASH_IDX_BLAKE160).length());
}
Expand Down

0 comments on commit 8ad406b

Please sign in to comment.