Compare commits
27 commits
606f7c183e
...
4631ec94a9
Author | SHA1 | Date | |
---|---|---|---|
4631ec94a9 | |||
|
1129a92aa3 | ||
|
9b915b3d36 | ||
|
6e17a55deb | ||
|
9b27bced03 | ||
|
e3b030b3fb | ||
|
dc348b2578 | ||
|
71896ad65b | ||
|
1e8a1e0346 | ||
|
ff59f38ba7 | ||
|
823fc27c28 | ||
|
51f98e12a4 | ||
|
91275f3cc0 | ||
|
2cd989eade | ||
|
5350cfb739 | ||
|
6ba8379b92 | ||
|
39bcf78add | ||
|
d82bc6add1 | ||
|
9057121146 | ||
|
9312280897 | ||
|
ca30338af6 | ||
|
bb4fd5ce5d | ||
|
bf346f25bf | ||
|
d953dae81b | ||
|
12ce0ef276 | ||
|
5440286448 | ||
|
e872f480cd |
37 changed files with 1345 additions and 44 deletions
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
# These are supported funding model platforms
|
||||
|
||||
custom: https://www.blockchain.com/btc/payment_request?address=1AFGT5gVj7xhfjgHTuwEoaV56WTCh7Gjf1#BITCOIN_ONLY
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2017 AGORISE, LTD.
|
||||
Copyright (c) 2019 AGORISE, LTD.
|
||||
An International Business Company, Cyprus Reg# ΗΕ375959
|
||||
|
||||
Contains works from BitShares Munich IVS
|
||||
|
|
|
@ -13,7 +13,7 @@ allprojects {
|
|||
}
|
||||
```
|
||||
|
||||
In yout app module, add the following dependency:
|
||||
In your app module, add the following dependency:
|
||||
|
||||
```Groovy
|
||||
dependencies {
|
||||
|
@ -43,7 +43,7 @@ operationList.add(transferOperation);
|
|||
Transaction transaction = new Transaction(sourcePrivateKey, null, operationList);
|
||||
```
|
||||
|
||||
From here on, it is just a matter of creating a websocket connection and using a custom handler called
|
||||
From here on out, it is just a matter of creating a websocket connection and using a custom handler called
|
||||
```TransactionBroadcastSequence``` in order to broadcast it to the witness node.
|
||||
|
||||
```java
|
||||
|
|
|
@ -20,8 +20,10 @@ buildscript {
|
|||
name 'Google'
|
||||
}
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.4.0'
|
||||
classpath 'com.android.tools.build:gradle:3.4.2'
|
||||
classpath 'com.novoda:bintray-release:0.9.1'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,15 @@
|
|||
group 'cy.agorise'
|
||||
version '0.4.7-alpha2'
|
||||
|
||||
apply plugin: 'com.novoda.bintray-release'
|
||||
apply plugin: 'com.android.library'
|
||||
//apply from: 'maven-push.gradle'
|
||||
|
||||
publish {
|
||||
userOrg = 'bilthon'
|
||||
groupId = 'cy.agorise.graphenej'
|
||||
artifactId = 'graphenej'
|
||||
publishVersion = '0.6.0'
|
||||
repoName = 'Graphenej'
|
||||
desc = 'A Java library for mobile app Developers; Graphene/Bitshares blockchain.'
|
||||
website = 'https://github.com/Agorise/graphenej'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
|
@ -11,7 +18,7 @@ android {
|
|||
minSdkVersion 14
|
||||
targetSdkVersion 28
|
||||
versionCode 12
|
||||
versionName "0.4.7-alpha3"
|
||||
versionName "0.6.0"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
}
|
||||
buildTypes {
|
||||
|
@ -32,16 +39,16 @@ dependencies {
|
|||
testImplementation group: 'junit', name: 'junit', version: '4.12'
|
||||
implementation 'com.neovisionaries:nv-websocket-client:1.30'
|
||||
implementation 'org.bitcoinj:bitcoinj-core:0.14.3'
|
||||
implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.0'
|
||||
implementation group: "org.tukaani", name: "xz", version: "1.6"
|
||||
implementation 'com.google.code.gson:gson:2.8.5'
|
||||
implementation 'org.tukaani:xz:1.6'
|
||||
|
||||
androidTestImplementation 'com.android.support:support-annotations:27.1.1'
|
||||
androidTestImplementation 'com.android.support:support-annotations:28.0.0'
|
||||
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
||||
androidTestImplementation 'com.android.support.test:rules:1.0.2'
|
||||
|
||||
// Rx dependencies
|
||||
api 'io.reactivex.rxjava2:rxandroid:2.0.2'
|
||||
api 'io.reactivex.rxjava2:rxjava:2.1.16'
|
||||
api 'com.jakewharton.rxrelay2:rxrelay:2.0.0'
|
||||
api 'com.squareup.okhttp3:okhttp:3.5.0'
|
||||
api 'io.reactivex.rxjava2:rxandroid:2.1.0'
|
||||
api 'io.reactivex.rxjava2:rxjava:2.2.2'
|
||||
api 'com.jakewharton.rxrelay2:rxrelay:2.1.0'
|
||||
api 'com.squareup.okhttp3:okhttp:3.12.2'
|
||||
}
|
|
@ -9,7 +9,6 @@ import org.junit.runner.RunWith;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import cy.agorise.graphenej.api.bitshares.Nodes;
|
||||
import cy.agorise.graphenej.network.FullNode;
|
||||
import cy.agorise.graphenej.network.LatencyNodeProvider;
|
||||
import cy.agorise.graphenej.network.NodeLatencyVerifier;
|
||||
|
@ -22,12 +21,20 @@ import io.reactivex.subjects.PublishSubject;
|
|||
public class NodeLatencyVerifierTest {
|
||||
private final String TAG = this.getClass().getName();
|
||||
|
||||
String[] nodeURLs = new String[]{
|
||||
"wss://bitshares.openledger.info/ws",
|
||||
"wss://us.nodes.bitshares.ws",
|
||||
"wss://eu.nodes.bitshares.ws",
|
||||
"wss://citadel.li/node",
|
||||
"wss://api.bts.mobi/ws"
|
||||
};
|
||||
|
||||
@Test
|
||||
public void testNodeLatencyTest() throws Exception {
|
||||
ArrayList<FullNode> nodeList = new ArrayList<>();
|
||||
nodeList.add(new FullNode(Nodes.NODE_URLS[0]));
|
||||
nodeList.add(new FullNode(Nodes.NODE_URLS[1]));
|
||||
nodeList.add(new FullNode(Nodes.NODE_URLS[2]));
|
||||
nodeList.add(new FullNode(nodeURLs[0]));
|
||||
nodeList.add(new FullNode(nodeURLs[1]));
|
||||
nodeList.add(new FullNode(nodeURLs[2]));
|
||||
final NodeLatencyVerifier nodeLatencyVerifier = new NodeLatencyVerifier(nodeList);
|
||||
PublishSubject subject = nodeLatencyVerifier.start();
|
||||
final NodeProvider nodeProvider = new LatencyNodeProvider();
|
||||
|
|
|
@ -80,6 +80,8 @@ public class GrapheneObject {
|
|||
return ObjectType.WORKER_OBJECT;
|
||||
case 15:
|
||||
return ObjectType.BALANCE_OBJECT;
|
||||
case 16:
|
||||
return ObjectType.HTLC_OBJECT;
|
||||
}
|
||||
case IMPLEMENTATION_SPACE:
|
||||
switch(type){
|
||||
|
|
43
graphenej/src/main/java/cy/agorise/graphenej/Htlc.java
Normal file
43
graphenej/src/main/java/cy/agorise/graphenej/Htlc.java
Normal file
|
@ -0,0 +1,43 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataOutput;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import cy.agorise.graphenej.interfaces.ByteSerializable;
|
||||
import cy.agorise.graphenej.interfaces.JsonSerializable;
|
||||
|
||||
/**
|
||||
* Class used to represent an existing HTLC contract.
|
||||
*/
|
||||
public class Htlc extends GrapheneObject implements ByteSerializable, JsonSerializable {
|
||||
|
||||
public Htlc(String id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toBytes() {
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
DataOutput out = new DataOutputStream(byteArrayOutputStream);
|
||||
try {
|
||||
Varint.writeUnsignedVarLong(this.instance, out);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return byteArrayOutputStream.toByteArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toJsonString() {
|
||||
return this.getObjectId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement toJsonObject() {
|
||||
return null;
|
||||
}
|
||||
}
|
45
graphenej/src/main/java/cy/agorise/graphenej/HtlcHash.java
Normal file
45
graphenej/src/main/java/cy/agorise/graphenej/HtlcHash.java
Normal file
|
@ -0,0 +1,45 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
import cy.agorise.graphenej.interfaces.ByteSerializable;
|
||||
import cy.agorise.graphenej.interfaces.JsonSerializable;
|
||||
|
||||
/**
|
||||
* Class used to represent a HTLC hash.
|
||||
*/
|
||||
public class HtlcHash implements ByteSerializable, JsonSerializable {
|
||||
private HtlcHashType hashType;
|
||||
private byte[] hash;
|
||||
|
||||
public HtlcHash(HtlcHashType hashType, byte[] hash) {
|
||||
this.hashType = hashType;
|
||||
this.hash = hash;
|
||||
}
|
||||
|
||||
public HtlcHashType getType(){
|
||||
return this.hashType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toBytes() {
|
||||
byte[] hashTypeBytes = new byte[] { Util.revertInteger(hashType.ordinal())[3] };
|
||||
return Bytes.concat(hashTypeBytes, hash);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toJsonString() {
|
||||
JsonElement element = toJsonObject();
|
||||
return element.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement toJsonObject() {
|
||||
JsonArray array = new JsonArray();
|
||||
array.add(hashType.ordinal());
|
||||
array.add(Util.byteToString(hash));
|
||||
return array;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
/**
|
||||
* Used to enumerate the possible hash algorithms used in HTLCs.
|
||||
* @see <a href="https://github.com/bitshares/bitshares-core/blob/623aea265f2711adade982fc3248e6528dc8ac51/libraries/chain/include/graphene/chain/protocol/htlc.hpp">htlc.hpp</a>
|
||||
*/
|
||||
public enum HtlcHashType {
|
||||
RIPEMD160,
|
||||
SHA1,
|
||||
SHA256
|
||||
}
|
|
@ -20,6 +20,7 @@ public enum ObjectType {
|
|||
VESTING_BALANCE_OBJECT,
|
||||
WORKER_OBJECT,
|
||||
BALANCE_OBJECT,
|
||||
HTLC_OBJECT,
|
||||
GLOBAL_PROPERTY_OBJECT,
|
||||
DYNAMIC_GLOBAL_PROPERTY_OBJECT,
|
||||
ASSET_DYNAMIC_DATA,
|
||||
|
@ -53,6 +54,7 @@ public enum ObjectType {
|
|||
case VESTING_BALANCE_OBJECT:
|
||||
case WORKER_OBJECT:
|
||||
case BALANCE_OBJECT:
|
||||
case HTLC_OBJECT:
|
||||
space = 1;
|
||||
break;
|
||||
case GLOBAL_PROPERTY_OBJECT:
|
||||
|
@ -123,6 +125,8 @@ public enum ObjectType {
|
|||
case BALANCE_OBJECT:
|
||||
type = 15;
|
||||
break;
|
||||
case HTLC_OBJECT:
|
||||
type = 16;
|
||||
case GLOBAL_PROPERTY_OBJECT:
|
||||
type = 0;
|
||||
break;
|
||||
|
|
|
@ -51,5 +51,15 @@ public enum OperationType {
|
|||
BLIND_TRANSFER_OPERATION,
|
||||
TRANSFER_FROM_BLIND_OPERATION,
|
||||
ASSET_SETTLE_CANCEL_OPERATION, // VIRTUAL
|
||||
ASSET_CLAIM_FEES_OPERATION
|
||||
ASSET_CLAIM_FEES_OPERATION,
|
||||
FBA_DISTRIBUTE_OPERATION,
|
||||
BID_COLLATERAL_OPERATION,
|
||||
EXECUTE_BID_OPERATION, // VIRTUAL
|
||||
ASSET_CLAIM_POOL_OPERATION,
|
||||
ASSET_UPDATE_ISSUER_OPERATION,
|
||||
HTLC_CREATE_OPERATION,
|
||||
HTLC_REDEEM_OPERATION,
|
||||
HTLC_REDEEMED_OPERATION, // VIRTUAL
|
||||
HTLC_EXTEND_OPERATION,
|
||||
HTLC_REFUND_OPERATION // VIRTUAL
|
||||
}
|
||||
|
|
|
@ -417,6 +417,26 @@ public class Transaction implements ByteSerializable, JsonSerializable {
|
|||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.ASSET_CLAIM_FEES_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.FBA_DISTRIBUTE_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.BID_COLLATERAL_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.EXECUTE_BID_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.ASSET_CLAIM_POOL_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.ASSET_UPDATE_ISSUER_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.HTLC_CREATE_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.HTLC_REDEEM_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.HTLC_REDEEMED_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.HTLC_EXTEND_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.HTLC_REFUND_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
}
|
||||
if (operation != null) operationList.add(operation);
|
||||
operation = null;
|
||||
|
|
|
@ -2,14 +2,25 @@ package cy.agorise.graphenej;
|
|||
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
|
||||
import org.spongycastle.crypto.DataLengthException;
|
||||
import org.spongycastle.crypto.InvalidCipherTextException;
|
||||
import org.spongycastle.crypto.digests.GeneralDigest;
|
||||
import org.spongycastle.crypto.digests.RIPEMD160Digest;
|
||||
import org.spongycastle.crypto.digests.SHA1Digest;
|
||||
import org.spongycastle.crypto.digests.SHA256Digest;
|
||||
import org.spongycastle.crypto.engines.AESFastEngine;
|
||||
import org.spongycastle.crypto.modes.CBCBlockCipher;
|
||||
import org.spongycastle.crypto.paddings.PaddedBufferedBlockCipher;
|
||||
import org.spongycastle.crypto.params.KeyParameter;
|
||||
import org.spongycastle.crypto.params.ParametersWithIV;
|
||||
import org.tukaani.xz.*;
|
||||
import org.tukaani.xz.CorruptedInputException;
|
||||
import org.tukaani.xz.FinishableOutputStream;
|
||||
import org.tukaani.xz.LZMA2Options;
|
||||
import org.tukaani.xz.LZMAInputStream;
|
||||
import org.tukaani.xz.LZMAOutputStream;
|
||||
import org.tukaani.xz.XZInputStream;
|
||||
import org.tukaani.xz.XZOutputStream;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
@ -378,4 +389,36 @@ public class Util {
|
|||
public static long toBase(double value, int precision){
|
||||
return (long) (value * Math.pow(10, precision));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a hash for HTLC operations.
|
||||
*
|
||||
* @param preimage The data we want to operate on.
|
||||
* @param hashType The type of hash.
|
||||
* @return The hash.
|
||||
* @throws NoSuchAlgorithmException
|
||||
*/
|
||||
public static byte[] htlcHash(byte[] preimage, HtlcHashType hashType) throws NoSuchAlgorithmException {
|
||||
byte[] out = null;
|
||||
GeneralDigest digest = null;
|
||||
switch(hashType){
|
||||
case RIPEMD160:
|
||||
digest = new RIPEMD160Digest();
|
||||
out = new byte[20];
|
||||
break;
|
||||
case SHA1:
|
||||
digest = new SHA1Digest();
|
||||
out = new byte[20];
|
||||
break;
|
||||
case SHA256:
|
||||
digest = new SHA256Digest();
|
||||
out = new byte[32];
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Not supported hash function!");
|
||||
}
|
||||
digest.update(preimage, 0, preimage.length);
|
||||
digest.doFinal(out, 0);
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,11 +25,8 @@ public class LatencyNodeProvider implements NodeProvider {
|
|||
|
||||
@Override
|
||||
public boolean updateNode(FullNode fullNode) {
|
||||
boolean existed = mFullNodeHeap.remove(fullNode);
|
||||
if(existed){
|
||||
return mFullNodeHeap.offer(fullNode);
|
||||
}
|
||||
return false;
|
||||
mFullNodeHeap.remove(fullNode);
|
||||
return mFullNodeHeap.offer(fullNode);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
package cy.agorise.graphenej.operations;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import cy.agorise.graphenej.AssetAmount;
|
||||
import cy.agorise.graphenej.BaseOperation;
|
||||
import cy.agorise.graphenej.HtlcHash;
|
||||
import cy.agorise.graphenej.OperationType;
|
||||
import cy.agorise.graphenej.UserAccount;
|
||||
import cy.agorise.graphenej.Util;
|
||||
|
||||
public class CreateHtlcOperation extends BaseOperation {
|
||||
static final String KEY_FROM = "from";
|
||||
static final String KEY_TO = "to";
|
||||
static final String KEY_AMOUNT = "amount";
|
||||
static final String KEY_PREIMAGE_HASH = "preimage_hash";
|
||||
static final String KEY_PREIMAGE_SIZE = "preimage_size";
|
||||
static final String KEY_CLAIM_PERIOD_SECONDS = "claim_period_seconds";
|
||||
|
||||
private AssetAmount fee;
|
||||
private UserAccount from;
|
||||
private UserAccount to;
|
||||
private AssetAmount amount;
|
||||
private HtlcHash preimageHash;
|
||||
private short preimageSize;
|
||||
private int claimPeriodSeconds;
|
||||
|
||||
/**
|
||||
* Public constructor
|
||||
*
|
||||
* @param fee The operation fee.
|
||||
* @param from The source account.
|
||||
* @param to The destination account.
|
||||
* @param amount The amount to be traded.
|
||||
* @param hash The pre-image hash.
|
||||
* @param preimageSize The pre-image size.
|
||||
* @param claimPeriodSeconds The claim period, in seconds.
|
||||
*/
|
||||
public CreateHtlcOperation(AssetAmount fee, UserAccount from, UserAccount to, AssetAmount amount, HtlcHash hash, short preimageSize, int claimPeriodSeconds) {
|
||||
super(OperationType.HTLC_CREATE_OPERATION);
|
||||
this.fee = fee;
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
this.amount = amount;
|
||||
this.preimageHash = hash;
|
||||
this.preimageSize = preimageSize;
|
||||
this.claimPeriodSeconds = claimPeriodSeconds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFee(AssetAmount newFee){
|
||||
this.fee = newFee;
|
||||
}
|
||||
|
||||
public AssetAmount getFee() {
|
||||
return fee;
|
||||
}
|
||||
|
||||
public UserAccount getFrom() {
|
||||
return from;
|
||||
}
|
||||
|
||||
public void setFrom(UserAccount from) {
|
||||
this.from = from;
|
||||
}
|
||||
|
||||
public UserAccount getTo() {
|
||||
return to;
|
||||
}
|
||||
|
||||
public void setTo(UserAccount to) {
|
||||
this.to = to;
|
||||
}
|
||||
|
||||
public AssetAmount getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public void setAmount(AssetAmount amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public HtlcHash getPreimageHash() {
|
||||
return preimageHash;
|
||||
}
|
||||
|
||||
public void setPreimageHash(HtlcHash preimageHash) {
|
||||
this.preimageHash = preimageHash;
|
||||
}
|
||||
|
||||
public short getPreimageSize() {
|
||||
return preimageSize;
|
||||
}
|
||||
|
||||
public void setPreimageSize(short preimageSize) {
|
||||
this.preimageSize = preimageSize;
|
||||
}
|
||||
|
||||
public int getClaimPeriodSeconds() {
|
||||
return claimPeriodSeconds;
|
||||
}
|
||||
|
||||
public void setClaimPeriodSeconds(int claimPeriodSeconds) {
|
||||
this.claimPeriodSeconds = claimPeriodSeconds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toBytes() {
|
||||
byte[] feeBytes = fee.toBytes();
|
||||
byte[] fromBytes = from.toBytes();
|
||||
byte[] toBytes = to.toBytes();
|
||||
byte[] amountBytes = amount.toBytes();
|
||||
byte[] htlcHashBytes = preimageHash.toBytes();
|
||||
byte[] preimageSizeBytes = Util.revertShort(preimageSize);
|
||||
byte[] claimPeriodBytes = Util.revertInteger(claimPeriodSeconds);
|
||||
byte[] extensionsBytes = extensions.toBytes();
|
||||
return Bytes.concat(feeBytes, fromBytes, toBytes, amountBytes, htlcHashBytes, preimageSizeBytes, claimPeriodBytes, extensionsBytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement toJsonObject() {
|
||||
JsonArray array = new JsonArray();
|
||||
array.add(this.getId());
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
jsonObject.add(KEY_FEE, fee.toJsonObject());
|
||||
jsonObject.addProperty(KEY_FROM, from.getObjectId());
|
||||
jsonObject.addProperty(KEY_TO, to.getObjectId());
|
||||
jsonObject.add(KEY_AMOUNT, amount.toJsonObject());
|
||||
jsonObject.add(KEY_PREIMAGE_HASH, preimageHash.toJsonObject());
|
||||
jsonObject.addProperty(KEY_PREIMAGE_SIZE, preimageSize);
|
||||
jsonObject.addProperty(KEY_CLAIM_PERIOD_SECONDS, claimPeriodSeconds);
|
||||
jsonObject.add(KEY_EXTENSIONS, new JsonArray());
|
||||
array.add(jsonObject);
|
||||
return array;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toJsonString() {
|
||||
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||
return gsonBuilder.create().toJson(this);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
package cy.agorise.graphenej.operations;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataOutput;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import cy.agorise.graphenej.AssetAmount;
|
||||
import cy.agorise.graphenej.BaseOperation;
|
||||
import cy.agorise.graphenej.Htlc;
|
||||
import cy.agorise.graphenej.OperationType;
|
||||
import cy.agorise.graphenej.UserAccount;
|
||||
import cy.agorise.graphenej.Util;
|
||||
import cy.agorise.graphenej.Varint;
|
||||
|
||||
/**
|
||||
* Class used to encapsulate the redeem_htlc operation.
|
||||
*/
|
||||
public class RedeemHtlcOperation extends BaseOperation {
|
||||
static final String KEY_REDEEMER = "redeemer";
|
||||
static final String KEY_PREIMAGE = "preimage";
|
||||
static final String KEY_HTLC_ID = "htlc_id";
|
||||
|
||||
private AssetAmount fee;
|
||||
private UserAccount redeemer;
|
||||
private Htlc htlc;
|
||||
private byte[] preimage;
|
||||
|
||||
/**
|
||||
* Public constructor
|
||||
*
|
||||
* @param fee The fee associated with this operation.
|
||||
* @param redeemer The user account that will redeem the HTLC.
|
||||
* @param htlc The existing HTLC operation.
|
||||
*/
|
||||
public RedeemHtlcOperation(AssetAmount fee, UserAccount redeemer, Htlc htlc, byte[] preimage) {
|
||||
super(OperationType.HTLC_REDEEM_OPERATION);
|
||||
this.fee = fee;
|
||||
this.redeemer = redeemer;
|
||||
this.htlc = htlc;
|
||||
this.preimage = preimage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFee(AssetAmount fee) {
|
||||
this.fee = fee;
|
||||
}
|
||||
|
||||
public AssetAmount getFee(){
|
||||
return this.fee;
|
||||
}
|
||||
|
||||
public UserAccount getRedeemer() {
|
||||
return redeemer;
|
||||
}
|
||||
|
||||
public void setRedeemer(UserAccount redeemer) {
|
||||
this.redeemer = redeemer;
|
||||
}
|
||||
|
||||
public Htlc getHtlc() {
|
||||
return htlc;
|
||||
}
|
||||
|
||||
public void setHtlc(Htlc htlc) {
|
||||
this.htlc = htlc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toBytes() {
|
||||
byte[] feeBytes = this.fee.toBytes();
|
||||
byte[] htlcBytes = this.htlc.toBytes();
|
||||
byte[] redeemerBytes = this.redeemer.toBytes();
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
DataOutput out = new DataOutputStream(byteArrayOutputStream);
|
||||
try{
|
||||
Varint.writeUnsignedVarLong(this.preimage.length, out);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
byte[] preimageLength = byteArrayOutputStream.toByteArray();
|
||||
byte[] extensionsBytes = extensions.toBytes();
|
||||
return Bytes.concat(feeBytes, htlcBytes, redeemerBytes, preimageLength, this.preimage, extensionsBytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement toJsonObject() {
|
||||
JsonArray array = new JsonArray();
|
||||
array.add(this.getId());
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
jsonObject.add(KEY_FEE, fee.toJsonObject());
|
||||
jsonObject.addProperty(KEY_REDEEMER, this.redeemer.getObjectId());
|
||||
jsonObject.addProperty(KEY_PREIMAGE, Util.bytesToHex(this.preimage));
|
||||
jsonObject.addProperty(KEY_HTLC_ID, this.htlc.getObjectId());
|
||||
jsonObject.add(KEY_EXTENSIONS, new JsonArray());
|
||||
array.add(jsonObject);
|
||||
return array;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toJsonString() {
|
||||
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||
return gsonBuilder.create().toJson(this);
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ import org.junit.Test;
|
|||
import java.util.HashMap;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
|
||||
/**
|
||||
* Created by nelson on 12/16/16.
|
||||
|
@ -48,7 +49,7 @@ public class AuthorityTest {
|
|||
@Test
|
||||
public void equals() throws Exception {
|
||||
assertEquals("Equal authorities", authority, sameAuthority);
|
||||
assertEquals("Different authorities ", authority, differentAuthority);
|
||||
assertNotEquals("Different authorities ", authority, differentAuthority);
|
||||
assertEquals("Two public keys with the same public key should be equal", keyAuthority1, keyAuthority2);
|
||||
}
|
||||
|
||||
|
|
26
graphenej/src/test/java/cy/agorise/graphenej/HtlcTest.java
Normal file
26
graphenej/src/test/java/cy/agorise/graphenej/HtlcTest.java
Normal file
|
@ -0,0 +1,26 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class HtlcTest {
|
||||
private final Htlc htlc = new Htlc("1.16.124");
|
||||
|
||||
@Test
|
||||
public void testByteSerialization(){
|
||||
Htlc htlc1 = new Htlc("1.16.1");
|
||||
Htlc htlc2 = new Htlc("1.16.100");
|
||||
Htlc htlc3 = new Htlc("1.16.500");
|
||||
Htlc htlc4 = new Htlc("1.16.1000");
|
||||
|
||||
byte[] expected_1 = Util.hexToBytes("01");
|
||||
byte[] expected_2 = Util.hexToBytes("64");
|
||||
byte[] expected_3 = Util.hexToBytes("f403");
|
||||
byte[] expected_4 = Util.hexToBytes("e807");
|
||||
|
||||
Assert.assertArrayEquals(expected_1, htlc1.toBytes());
|
||||
Assert.assertArrayEquals(expected_2, htlc2.toBytes());
|
||||
Assert.assertArrayEquals(expected_3, htlc3.toBytes());
|
||||
Assert.assertArrayEquals(expected_4, htlc4.toBytes());
|
||||
}
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
package cy.agorise.graphenej.network;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class LatencyNodeProviderTest {
|
||||
private FullNode nodeA, nodeB, nodeC;
|
||||
private LatencyNodeProvider latencyNodeProvider;
|
||||
|
@ -34,9 +34,9 @@ public class LatencyNodeProviderTest {
|
|||
|
||||
// Confirming that the getSortedNodes gives us a sorted list of nodes in increasing latency order
|
||||
List<FullNode> fullNodeList = latencyNodeProvider.getSortedNodes();
|
||||
Assert.assertSame(nodeC, fullNodeList.get(0));
|
||||
Assert.assertSame(nodeB, fullNodeList.get(1));
|
||||
Assert.assertSame(nodeA, fullNodeList.get(2));
|
||||
assertEquals(nodeC, fullNodeList.get(0));
|
||||
assertEquals(nodeB, fullNodeList.get(1));
|
||||
assertEquals(nodeA, fullNodeList.get(2));
|
||||
|
||||
// Adding more nodes with different latencies measurements
|
||||
FullNode nodeD = new FullNode("wss://nodeD");
|
||||
|
@ -55,11 +55,11 @@ public class LatencyNodeProviderTest {
|
|||
|
||||
FullNode bestNode = latencyNodeProvider.getBestNode();
|
||||
// Checking for best node
|
||||
Assert.assertSame("Verifying that the nodeE is the best now", nodeE, bestNode);
|
||||
assertEquals("Verifying that the nodeE is the best now", nodeE, bestNode);
|
||||
fullNodeList = latencyNodeProvider.getSortedNodes();
|
||||
FullNode worstNode = fullNodeList.get(fullNodeList.size() - 1);
|
||||
// Checking for worst node
|
||||
Assert.assertSame("Verifying that the nodeF is the worst now", nodeF, worstNode);
|
||||
assertEquals("Verifying that the nodeF is the worst now", nodeF, worstNode);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -68,7 +68,7 @@ public class LatencyNodeProviderTest {
|
|||
|
||||
// Confirming that the best node is nodeC
|
||||
FullNode bestNode = latencyNodeProvider.getBestNode();
|
||||
Assert.assertSame("Check that the best node is nodeC", nodeC, bestNode);
|
||||
assertEquals("Check that the best node is nodeC", nodeC, bestNode);
|
||||
|
||||
// Improving nodeA score by feeding it with new better latency measurements
|
||||
latencyNodeProvider.updateNode(nodeA, 10);
|
||||
|
@ -80,7 +80,7 @@ public class LatencyNodeProviderTest {
|
|||
latencyNodeProvider.updateNode(nodeA);
|
||||
bestNode = latencyNodeProvider.getBestNode();
|
||||
System.out.println("Best node latency after update: "+bestNode.getLatencyValue());
|
||||
Assert.assertSame("Check that the best node now is the nodeA", nodeA, bestNode);
|
||||
assertEquals("Check that the best node now is the nodeA", nodeA, bestNode);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -89,7 +89,7 @@ public class LatencyNodeProviderTest {
|
|||
nodeA.addLatencyValue(Long.MAX_VALUE);
|
||||
latencyNodeProvider.updateNode(nodeA);
|
||||
FullNode best = latencyNodeProvider.getBestNode();
|
||||
Assert.assertSame(nodeC, best);
|
||||
assertEquals(nodeC, best);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -174,6 +174,6 @@ public class LatencyNodeProviderTest {
|
|||
provider.updateNode(node20);
|
||||
|
||||
FullNode best = provider.getBestNode();
|
||||
Assert.assertSame("Expects node4 to be the best", node4, best);
|
||||
assertEquals("Expects node4 to be the best", node4, best);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
package cy.agorise.graphenej.operations;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import cy.agorise.graphenej.Asset;
|
||||
import cy.agorise.graphenej.AssetAmount;
|
||||
import cy.agorise.graphenej.BaseOperation;
|
||||
import cy.agorise.graphenej.BlockData;
|
||||
import cy.agorise.graphenej.Chains;
|
||||
import cy.agorise.graphenej.HtlcHash;
|
||||
import cy.agorise.graphenej.HtlcHashType;
|
||||
import cy.agorise.graphenej.Transaction;
|
||||
import cy.agorise.graphenej.UserAccount;
|
||||
import cy.agorise.graphenej.Util;
|
||||
|
||||
public class CreateHtlcOperationTest {
|
||||
private final String SERIALIZED_OP = "0000000000000000007b7c80241100000000000000a06e327ea7388c18e4740e350ed4e60f2e04fc41c8007800000000";
|
||||
private final String SERIALIZED_TX = "f68585abf4dce7c8045701310000000000000000007b7c80241100000000000000a06e327ea7388c18e4740e350ed4e60f2e04fc41c800780000000000";
|
||||
private final String JSON_SERIALIZED_TX = "{\"expiration\":\"2016-04-06T08:29:27\",\"extensions\":[],\"operations\":[[49,{\"amount\":{\"amount\":1123456,\"asset_id\":\"1.3.0\"},\"claim_period_seconds\":120,\"extensions\":[],\"fee\":{\"amount\":0,\"asset_id\":\"1.3.0\"},\"from\":\"1.2.123\",\"preimage_hash\":[0,\"a06e327ea7388c18e4740e350ed4e60f2e04fc41\"],\"preimage_size\":200,\"to\":\"1.2.124\"}]],\"ref_block_num\":34294,\"ref_block_prefix\":3707022213,\"signatures\":[]}";
|
||||
private final String PREIMAGE_HEX = "666f6f626172";
|
||||
private final String HASH_RIPEMD160 = "a06e327ea7388c18e4740e350ed4e60f2e04fc41";
|
||||
private final String HASH_SHA1 = "8843d7f92416211de9ebb963ff4ce28125932878";
|
||||
private final String HASH_SHA256 = "c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2";
|
||||
|
||||
private final Asset CORE = new Asset("1.3.0");
|
||||
|
||||
private CreateHtlcOperation buildCreateHtlcOperation() throws NoSuchAlgorithmException {
|
||||
UserAccount from = new UserAccount("1.2.123");
|
||||
UserAccount to = new UserAccount("1.2.124");
|
||||
AssetAmount fee = new AssetAmount(UnsignedLong.valueOf(0), CORE);
|
||||
AssetAmount amount = new AssetAmount(UnsignedLong.valueOf(1123456), CORE);
|
||||
byte[] hashBytes = Util.htlcHash("foobar".getBytes(), HtlcHashType.RIPEMD160);
|
||||
HtlcHash preimageHash = new HtlcHash(HtlcHashType.RIPEMD160, hashBytes);
|
||||
return new CreateHtlcOperation(fee, from, to, amount, preimageHash, (short) 200, 120);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRipemd160(){
|
||||
try {
|
||||
byte[] hashRipemd160 = Util.htlcHash(Util.hexToBytes(PREIMAGE_HEX), HtlcHashType.RIPEMD160);
|
||||
String hexHash = Util.bytesToHex(hashRipemd160);
|
||||
Assert.assertEquals(HASH_RIPEMD160, hexHash);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSha1(){
|
||||
try {
|
||||
byte[] hashSha1 = Util.htlcHash(Util.hexToBytes(PREIMAGE_HEX), HtlcHashType.SHA1);
|
||||
String hexHash = Util.bytesToHex(hashSha1);
|
||||
Assert.assertEquals(HASH_SHA1, hexHash);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSha256(){
|
||||
try {
|
||||
byte[] hashSha256 = Util.htlcHash(Util.hexToBytes(PREIMAGE_HEX), HtlcHashType.SHA256);
|
||||
String hexHash = Util.bytesToHex(hashSha256);
|
||||
Assert.assertEquals(HASH_SHA256, hexHash);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOperationSerialization() throws NoSuchAlgorithmException {
|
||||
CreateHtlcOperation operation = this.buildCreateHtlcOperation();
|
||||
byte[] opBytes = operation.toBytes();
|
||||
Assert.assertArrayEquals(Util.hexToBytes(SERIALIZED_OP), opBytes);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransactionSerialization() throws NoSuchAlgorithmException, ParseException {
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
|
||||
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
Date expirationDate = dateFormat.parse("2016-04-06T08:29:27");
|
||||
BlockData blockData = new BlockData(34294, 3707022213L, (expirationDate.getTime() / 1000));
|
||||
ArrayList<BaseOperation> operations = new ArrayList<>();
|
||||
operations.add(buildCreateHtlcOperation());
|
||||
Transaction transaction = new Transaction(blockData, operations);
|
||||
// Checking byte serialization
|
||||
byte[] txBytes = transaction.toBytes();
|
||||
byte[] expected = Bytes.concat(Util.hexToBytes(Chains.BITSHARES.CHAIN_ID), Util.hexToBytes(SERIALIZED_TX));
|
||||
Assert.assertArrayEquals(expected, txBytes);
|
||||
// Checking JSON serialization
|
||||
JsonObject jsonObject = transaction.toJsonObject();
|
||||
JsonArray operationsArray = jsonObject.get("operations").getAsJsonArray().get(0).getAsJsonArray();
|
||||
JsonArray hashArray = operationsArray.get(1).getAsJsonObject().get("preimage_hash").getAsJsonArray();
|
||||
Assert.assertEquals("2016-04-06T08:29:27", jsonObject.get("expiration").getAsString());
|
||||
Assert.assertEquals(49, operationsArray.get(0).getAsInt());
|
||||
Assert.assertEquals("1.2.123", operationsArray.get(1).getAsJsonObject().get("from").getAsString());
|
||||
Assert.assertEquals("1.2.124", operationsArray.get(1).getAsJsonObject().get("to").getAsString());
|
||||
Assert.assertEquals(0, hashArray.get(0).getAsInt());
|
||||
Assert.assertEquals(HASH_RIPEMD160, hashArray.get(1).getAsString());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package cy.agorise.graphenej.operations;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import cy.agorise.graphenej.Asset;
|
||||
import cy.agorise.graphenej.AssetAmount;
|
||||
import cy.agorise.graphenej.BaseOperation;
|
||||
import cy.agorise.graphenej.BlockData;
|
||||
import cy.agorise.graphenej.Chains;
|
||||
import cy.agorise.graphenej.Htlc;
|
||||
import cy.agorise.graphenej.Transaction;
|
||||
import cy.agorise.graphenej.UserAccount;
|
||||
import cy.agorise.graphenej.Util;
|
||||
|
||||
public class RedeemHtlcOperationTest {
|
||||
private final String SERIALIZED_OP = "00000000000000000084017c06666f6f62617200";
|
||||
private final String SERIALIZED_TX = "f68585abf4dce7c80457013200000000000000000084017c06666f6f6261720000";
|
||||
private final Asset CORE = new Asset("1.3.0");
|
||||
private final String PREIMAGE_HEX = "666f6f626172";
|
||||
|
||||
private RedeemHtlcOperation buildRedeemdHtlcOperation(){
|
||||
AssetAmount fee = new AssetAmount(UnsignedLong.valueOf("0"), CORE);
|
||||
UserAccount redeemer = new UserAccount("1.2.124");
|
||||
Htlc htlc = new Htlc("1.16.132");
|
||||
byte[] preimage = Util.hexToBytes(PREIMAGE_HEX);
|
||||
return new RedeemHtlcOperation(fee, redeemer, htlc, preimage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOperationSerialization(){
|
||||
RedeemHtlcOperation redeemHtlcOperation = this.buildRedeemdHtlcOperation();
|
||||
byte[] opBytes = redeemHtlcOperation.toBytes();
|
||||
Assert.assertArrayEquals(Util.hexToBytes(SERIALIZED_OP), opBytes);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransactionSerialization() throws ParseException {
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
|
||||
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
Date expirationDate = dateFormat.parse("2016-04-06T08:29:27");
|
||||
BlockData blockData = new BlockData(34294, 3707022213L, (expirationDate.getTime() / 1000));
|
||||
ArrayList<BaseOperation> operations = new ArrayList<>();
|
||||
operations.add(buildRedeemdHtlcOperation());
|
||||
Transaction transaction = new Transaction(blockData, operations);
|
||||
// Checking byte serialization
|
||||
byte[] txBytes = transaction.toBytes();
|
||||
byte[] expected = Bytes.concat(Util.hexToBytes(Chains.BITSHARES.CHAIN_ID), Util.hexToBytes(SERIALIZED_TX));
|
||||
Assert.assertArrayEquals(expected, txBytes);
|
||||
// Checking JSON serialization
|
||||
JsonObject jsonObject = transaction.toJsonObject();
|
||||
JsonArray operationsArray = jsonObject.get("operations").getAsJsonArray().get(0).getAsJsonArray();
|
||||
int operationId = operationsArray.get(0).getAsInt();
|
||||
JsonObject operationJson = operationsArray.get(1).getAsJsonObject();
|
||||
Assert.assertEquals("2016-04-06T08:29:27", jsonObject.get("expiration").getAsString());
|
||||
Assert.assertEquals(50, operationId);
|
||||
Assert.assertEquals("1.16.132", operationJson.getAsJsonObject().get("htlc_id").getAsString());
|
||||
Assert.assertEquals(PREIMAGE_HEX, operationJson.getAsJsonObject().get("preimage").getAsString());
|
||||
Assert.assertEquals("1.2.124", operationJson.getAsJsonObject().get("redeemer").getAsString());
|
||||
}
|
||||
}
|
|
@ -34,15 +34,15 @@ dependencies {
|
|||
implementation 'com.android.support:appcompat-v7:27.1.1'
|
||||
implementation 'com.android.support:recyclerview-v7:27.1.1'
|
||||
implementation 'com.android.support:design:27.1.1'
|
||||
implementation 'com.android.support.constraint:constraint-layout:1.1.2'
|
||||
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
|
||||
implementation 'com.jakewharton.rxbinding2:rxbinding:2.1.1'
|
||||
implementation 'com.jakewharton:butterknife:8.8.1'
|
||||
implementation 'com.google.code.gson:gson:2.8.4'
|
||||
implementation 'com.google.code.gson:gson:2.8.5'
|
||||
implementation 'com.google.guava:guava:25.0-jre'
|
||||
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation('com.android.support.test.espresso:espresso-core:3.0.1', {
|
||||
exclude group: 'com.android.support', module: 'support-annotations'
|
||||
})
|
||||
implementation 'com.android.support:multidex:1.0.1'
|
||||
implementation 'com.android.support:multidex:1.0.3'
|
||||
}
|
||||
|
|
|
@ -14,10 +14,12 @@
|
|||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
<activity android:name=".HtlcActivity"
|
||||
android:theme="@style/AppTheme.NoActionBar"/>
|
||||
<activity
|
||||
android:name=".BrainkeyActivity"
|
||||
android:label="@string/title_activity_brainkey"
|
||||
android:theme="@style/AppTheme.NoActionBar"></activity>
|
||||
android:theme="@style/AppTheme.NoActionBar" />
|
||||
<activity android:name=".SubscriptionActivity" />
|
||||
<activity android:name=".CallsActivity">
|
||||
<intent-filter>
|
||||
|
|
|
@ -27,6 +27,8 @@ public class CallsActivity extends AppCompatActivity {
|
|||
|
||||
private static final String RECONNECT_NODE = "reconnect_node";
|
||||
private static final String TEST_BRAINKEY_DERIVATION = "test_brainkey_derivation";
|
||||
public static final String CREATE_HTLC = "create_htlc";
|
||||
public static final String REDEEM_HTLC = "redeem_htlc";
|
||||
|
||||
@BindView(R.id.call_list)
|
||||
RecyclerView mRecyclerView;
|
||||
|
@ -83,7 +85,9 @@ public class CallsActivity extends AppCompatActivity {
|
|||
RPC.CALL_BROADCAST_TRANSACTION,
|
||||
RPC.CALL_GET_TRANSACTION,
|
||||
RECONNECT_NODE,
|
||||
TEST_BRAINKEY_DERIVATION
|
||||
TEST_BRAINKEY_DERIVATION,
|
||||
CREATE_HTLC,
|
||||
REDEEM_HTLC
|
||||
};
|
||||
|
||||
@NonNull
|
||||
|
@ -108,6 +112,9 @@ public class CallsActivity extends AppCompatActivity {
|
|||
intent = new Intent(CallsActivity.this, RemoveNodeActivity.class);
|
||||
} else if (selectedCall.equals(TEST_BRAINKEY_DERIVATION)){
|
||||
intent = new Intent(CallsActivity.this, BrainkeyActivity.class);
|
||||
} else if (selectedCall.equals(CREATE_HTLC) || selectedCall.equals(REDEEM_HTLC)){
|
||||
intent = new Intent(CallsActivity.this, HtlcActivity.class);
|
||||
intent.putExtra(Constants.KEY_SELECTED_CALL, selectedCall);
|
||||
} else {
|
||||
intent = new Intent(CallsActivity.this, PerformCallActivity.class);
|
||||
intent.putExtra(Constants.KEY_SELECTED_CALL, selectedCall);
|
||||
|
|
248
sample/src/main/java/cy/agorise/labs/sample/HtlcActivity.java
Normal file
248
sample/src/main/java/cy/agorise/labs/sample/HtlcActivity.java
Normal file
|
@ -0,0 +1,248 @@
|
|||
package cy.agorise.labs.sample;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
|
||||
import org.bitcoinj.core.ECKey;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
import cy.agorise.graphenej.Asset;
|
||||
import cy.agorise.graphenej.AssetAmount;
|
||||
import cy.agorise.graphenej.BaseOperation;
|
||||
import cy.agorise.graphenej.BlockData;
|
||||
import cy.agorise.graphenej.BrainKey;
|
||||
import cy.agorise.graphenej.Htlc;
|
||||
import cy.agorise.graphenej.HtlcHash;
|
||||
import cy.agorise.graphenej.HtlcHashType;
|
||||
import cy.agorise.graphenej.Transaction;
|
||||
import cy.agorise.graphenej.UserAccount;
|
||||
import cy.agorise.graphenej.Util;
|
||||
import cy.agorise.graphenej.api.ConnectionStatusUpdate;
|
||||
import cy.agorise.graphenej.api.android.RxBus;
|
||||
import cy.agorise.graphenej.api.calls.BroadcastTransaction;
|
||||
import cy.agorise.graphenej.api.calls.GetDynamicGlobalProperties;
|
||||
import cy.agorise.graphenej.models.DynamicGlobalProperties;
|
||||
import cy.agorise.graphenej.models.JsonRpcResponse;
|
||||
import cy.agorise.graphenej.operations.CreateHtlcOperation;
|
||||
import cy.agorise.graphenej.operations.RedeemHtlcOperation;
|
||||
import cy.agorise.labs.sample.fragments.CreateHtlcFragment;
|
||||
import cy.agorise.labs.sample.fragments.RedeemHtlcFragment;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.functions.Consumer;
|
||||
|
||||
public class HtlcActivity extends ConnectedActivity implements
|
||||
CreateHtlcFragment.CreateHtlcListener,
|
||||
RedeemHtlcFragment.RedeemHtlcListener {
|
||||
private String TAG = this.getClass().getName();
|
||||
private final short PREIMAGE_LENGTH = 32;
|
||||
private byte[] mPreimage = new byte[PREIMAGE_LENGTH];
|
||||
|
||||
private CreateHtlcOperation createHtlcOperation;
|
||||
private RedeemHtlcOperation redeemHtlcOperation;
|
||||
private Disposable mDisposable;
|
||||
private String mHtlcMode;
|
||||
|
||||
private Fragment mActiveBottomFragment;
|
||||
private CreateHtlcFragment mCreateHtlcFragment;
|
||||
private RedeemHtlcFragment mRedeemHtlcFragment;
|
||||
|
||||
private HashMap<Long, String> mResponseMap = new HashMap<>();
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_htlc);
|
||||
|
||||
mHtlcMode = getIntent().getStringExtra(Constants.KEY_SELECTED_CALL);
|
||||
|
||||
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||
if(mHtlcMode.equals(CallsActivity.CREATE_HTLC)){
|
||||
mCreateHtlcFragment = new CreateHtlcFragment();
|
||||
mActiveBottomFragment = mCreateHtlcFragment;
|
||||
}else{
|
||||
mRedeemHtlcFragment = new RedeemHtlcFragment();
|
||||
mActiveBottomFragment = mRedeemHtlcFragment;
|
||||
}
|
||||
fragmentTransaction.add(R.id.fragment_root, mActiveBottomFragment, "active-fragment").commit();
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
if(toolbar != null && mHtlcMode != null){
|
||||
toolbar.setTitle(mHtlcMode.replace("_", " ").toUpperCase());
|
||||
setSupportActionBar(toolbar);
|
||||
}
|
||||
|
||||
// While for the real world is best to use a random pre-image, for testing purposes it is more
|
||||
// convenient to make use of a fixed one.
|
||||
// SecureRandom secureRandom = new SecureRandom();
|
||||
// secureRandom.nextBytes(mPreimage);
|
||||
mPreimage = Util.hexToBytes("efb79df9b0fd0d27b405e4decf9a2534efc1531f9e133915981fe27cd031ba32");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
// Inflate the menu; this adds items to the action bar if it is present.
|
||||
getMenuInflater().inflate(R.menu.htlc_menu, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
this.switchHtlcMode();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachFragment(Fragment fragment) {
|
||||
super.onAttachFragment(fragment);
|
||||
if(fragment instanceof CreateHtlcFragment){
|
||||
mCreateHtlcFragment = (CreateHtlcFragment) fragment;
|
||||
}
|
||||
}
|
||||
|
||||
private void switchHtlcMode(){
|
||||
if(mHtlcMode.equals(CallsActivity.CREATE_HTLC)){
|
||||
mHtlcMode = CallsActivity.REDEEM_HTLC;
|
||||
if(mRedeemHtlcFragment == null)
|
||||
mRedeemHtlcFragment = new RedeemHtlcFragment();
|
||||
mActiveBottomFragment = mRedeemHtlcFragment;
|
||||
}else{
|
||||
mHtlcMode = CallsActivity.CREATE_HTLC;
|
||||
if(mCreateHtlcFragment == null)
|
||||
mCreateHtlcFragment = new CreateHtlcFragment();
|
||||
mActiveBottomFragment = mCreateHtlcFragment;
|
||||
}
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.replace(R.id.fragment_root, mActiveBottomFragment, "active-fragment")
|
||||
.commit();
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
if(toolbar != null)
|
||||
toolbar.setTitle(mHtlcMode.replace("_", " ").toUpperCase());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
mDisposable = RxBus.getBusInstance()
|
||||
.asFlowable()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(new Consumer<Object>() {
|
||||
|
||||
@Override
|
||||
public void accept(Object message) throws Exception {
|
||||
if(message instanceof ConnectionStatusUpdate){
|
||||
// TODO: Update UI ?
|
||||
}else if(message instanceof JsonRpcResponse){
|
||||
handleJsonRpcResponse((JsonRpcResponse) message);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
if(!mDisposable.isDisposed())
|
||||
mDisposable.dispose();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHtlcProposal(String from, String to, Double amount, Long timelock) {
|
||||
UserAccount sourceAccount = new UserAccount(from);
|
||||
UserAccount destinationAccount = new UserAccount(to);
|
||||
AssetAmount fee = new AssetAmount(UnsignedLong.valueOf("86726"), new Asset("1.3.0"));
|
||||
AssetAmount operationAmount = new AssetAmount(UnsignedLong.valueOf(Double.valueOf(amount * 100000).longValue()), new Asset("1.3.0"));
|
||||
try {
|
||||
byte[] hash = Util.htlcHash(mPreimage, HtlcHashType.RIPEMD160);
|
||||
HtlcHash htlcHash = new HtlcHash(HtlcHashType.RIPEMD160, hash);
|
||||
// Creating a HTLC operation, used later.
|
||||
createHtlcOperation = new CreateHtlcOperation(fee, sourceAccount, destinationAccount, operationAmount, htlcHash, PREIMAGE_LENGTH, timelock.intValue());
|
||||
// Requesting dynamic network parameters
|
||||
long id = mNetworkService.sendMessage(new GetDynamicGlobalProperties(), GetDynamicGlobalProperties.REQUIRED_API);
|
||||
mResponseMap.put(id, CallsActivity.CREATE_HTLC);
|
||||
Log.d(TAG,"sendMessage returned: " + id);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Log.e(TAG,"NoSuchAlgorithmException while trying to create HTLC operation. Msg: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRedeemProposal(String userId, String htlcId) {
|
||||
AssetAmount fee = new AssetAmount(UnsignedLong.valueOf("255128"), new Asset("1.3.0"));
|
||||
UserAccount redeemer = new UserAccount(userId);
|
||||
Htlc htlc = new Htlc(htlcId);
|
||||
redeemHtlcOperation = new RedeemHtlcOperation(fee, redeemer, htlc, mPreimage);
|
||||
long id = mNetworkService.sendMessage(new GetDynamicGlobalProperties(), GetDynamicGlobalProperties.REQUIRED_API);
|
||||
mResponseMap.put(id, CallsActivity.REDEEM_HTLC);
|
||||
Log.d(TAG,"sendMessage returned: " + id);
|
||||
}
|
||||
|
||||
private void handleJsonRpcResponse(JsonRpcResponse jsonRpcResponse){
|
||||
Log.d(TAG,"handleJsonRpcResponse");
|
||||
if(jsonRpcResponse.error == null && jsonRpcResponse.result instanceof DynamicGlobalProperties){
|
||||
DynamicGlobalProperties dynamicGlobalProperties = (DynamicGlobalProperties) jsonRpcResponse.result;
|
||||
Transaction tx = buildHltcTransaction(dynamicGlobalProperties, jsonRpcResponse.id);
|
||||
long id = mNetworkService.sendMessage(new BroadcastTransaction(tx), BroadcastTransaction.REQUIRED_API);
|
||||
Log.d(TAG,"sendMessage returned: " + id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Private method used to build a transaction containing a specific HTLC operation.
|
||||
*
|
||||
* @param dynamicProperties The current dynamic properties.
|
||||
* @param responseId The response id, used to decide whether to build a CREATE_HTLC or REDEEM_HTLC operation.
|
||||
* @return A transaction that contains an HTLC operation.
|
||||
*/
|
||||
private Transaction buildHltcTransaction(DynamicGlobalProperties dynamicProperties, long responseId){
|
||||
// Private key, to be obtained differently below depending on which operation we'll be performing.
|
||||
ECKey privKey = null;
|
||||
|
||||
// Use the valid BlockData just obtained from the blockchain
|
||||
long expirationTime = (dynamicProperties.time.getTime() / 1000) + Transaction.DEFAULT_EXPIRATION_TIME;
|
||||
String headBlockId = dynamicProperties.head_block_id;
|
||||
long headBlockNumber = dynamicProperties.head_block_number;
|
||||
BlockData blockData = new BlockData(headBlockNumber, headBlockId, expirationTime);
|
||||
|
||||
// Using the HTLC create operaton just obtained before
|
||||
ArrayList<BaseOperation> operations = new ArrayList<>();
|
||||
|
||||
if(mResponseMap.get(responseId).equals(CallsActivity.CREATE_HTLC)){
|
||||
// Deriving private key
|
||||
BrainKey brainKey = new BrainKey(">> Place brainkey of HTLC creator here <<", 0);
|
||||
privKey = brainKey.getPrivateKey();
|
||||
|
||||
operations.add(this.createHtlcOperation);
|
||||
}else if(mResponseMap.get(responseId).equals(CallsActivity.REDEEM_HTLC)){
|
||||
// Deriving private key
|
||||
BrainKey brainKey = new BrainKey(">> Place brainkey of redeemer here <<", 0);
|
||||
privKey = brainKey.getPrivateKey();
|
||||
|
||||
operations.add(this.redeemHtlcOperation);
|
||||
}
|
||||
|
||||
// Return a newly built transaction
|
||||
return new Transaction(privKey, blockData, operations);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName componentName, IBinder iBinder) { }
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName componentName) { }
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
package cy.agorise.labs.sample.fragments;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.design.widget.TextInputEditText;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.OnClick;
|
||||
import cy.agorise.labs.sample.R;
|
||||
|
||||
/**
|
||||
* A simple {@link Fragment} subclass.
|
||||
*/
|
||||
public class CreateHtlcFragment extends Fragment {
|
||||
private final String TAG = this.getClass().getName();
|
||||
|
||||
@BindView(R.id.from)
|
||||
TextInputEditText fromField;
|
||||
|
||||
@BindView(R.id.to)
|
||||
TextInputEditText toField;
|
||||
|
||||
@BindView(R.id.amount)
|
||||
TextInputEditText amountField;
|
||||
|
||||
@BindView(R.id.timelock)
|
||||
TextInputEditText timelockField;
|
||||
|
||||
// Parent activity, which must implement the CreateHtlcListener interface.
|
||||
private CreateHtlcListener mListener;
|
||||
|
||||
public CreateHtlcFragment() {
|
||||
// Required empty public constructor
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_create_htlc, container, false);
|
||||
ButterKnife.bind(this, view);
|
||||
return view;
|
||||
}
|
||||
|
||||
@OnClick(R.id.button_create)
|
||||
public void onSendClicked(View v){
|
||||
String from = fromField.getText().toString();
|
||||
String to = toField.getText().toString();
|
||||
Double amount = null;
|
||||
Long timeLock = null;
|
||||
try{
|
||||
amount = Double.parseDouble(amountField.getText().toString());
|
||||
}catch(NumberFormatException e){
|
||||
amountField.setError("Invalid amount");
|
||||
}
|
||||
try{
|
||||
timeLock = Long.parseLong(timelockField.getText().toString());
|
||||
}catch(NumberFormatException e){
|
||||
timelockField.setError("Invalid value");
|
||||
}
|
||||
if(amount != null && timeLock != null){
|
||||
Toast.makeText(getContext(), "Should be sending message up", Toast.LENGTH_SHORT).show();
|
||||
mListener.onHtlcProposal(from, to, amount, timeLock);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
if(context instanceof CreateHtlcListener){
|
||||
mListener = (CreateHtlcListener) context;
|
||||
}else{
|
||||
throw new ClassCastException(context.toString() + " must implement the CreateHtlcListener interface!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface to be implemented by the parent activity.
|
||||
*/
|
||||
public interface CreateHtlcListener {
|
||||
/**
|
||||
* Method used to notify the parent activity of the request to create an HTLC with the following parameters.
|
||||
*
|
||||
* @param from Source account id.
|
||||
* @param to Destination account id.
|
||||
* @param amount The amount of BTS to propose the HTLC.
|
||||
* @param timelock The timelock in seconds.
|
||||
*/
|
||||
void onHtlcProposal(String from, String to, Double amount, Long timelock);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package cy.agorise.labs.sample.fragments;
|
||||
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import cy.agorise.labs.sample.R;
|
||||
|
||||
/**
|
||||
* A simple {@link Fragment} subclass.
|
||||
*/
|
||||
public class PrintResponseFragment extends Fragment {
|
||||
|
||||
|
||||
public PrintResponseFragment() {
|
||||
// Required empty public constructor
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
// Inflate the layout for this fragment
|
||||
return inflater.inflate(R.layout.fragment_print_response, container, false);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
package cy.agorise.labs.sample.fragments;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.design.widget.TextInputEditText;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.OnClick;
|
||||
import cy.agorise.labs.sample.R;
|
||||
|
||||
/**
|
||||
* A simple {@link Fragment} subclass.
|
||||
*/
|
||||
public class RedeemHtlcFragment extends Fragment {
|
||||
|
||||
@BindView(R.id.redeemer)
|
||||
TextInputEditText mRedeemer;
|
||||
|
||||
@BindView(R.id.htlc_id)
|
||||
TextInputEditText mHtlcId;
|
||||
|
||||
// Parent activity, which must implement the RedeemHtlcListener interface.
|
||||
private RedeemHtlcListener mListener;
|
||||
|
||||
|
||||
public RedeemHtlcFragment() {
|
||||
// Required empty public constructor
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_redeem_htlc, container, false);
|
||||
ButterKnife.bind(this, view);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
if(context instanceof RedeemHtlcListener){
|
||||
mListener = (RedeemHtlcListener) context;
|
||||
}else{
|
||||
throw new ClassCastException(context.toString() + " must implement the RedeemHtlcListener interface!");
|
||||
}
|
||||
}
|
||||
|
||||
@OnClick(R.id.button_create)
|
||||
public void onSendClicked(View v){
|
||||
String redeemerId = mRedeemer.getText().toString();
|
||||
String htlcId = mHtlcId.getText().toString();
|
||||
Toast.makeText(getContext(), "Should be sending message up", Toast.LENGTH_SHORT).show();
|
||||
mListener.onRedeemProposal(redeemerId, htlcId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface to be implemented by the parent activity.
|
||||
*/
|
||||
public interface RedeemHtlcListener {
|
||||
/**
|
||||
* Method used to notify the parent activity about the creation of an HTLC redeem operation.
|
||||
*
|
||||
* @param userId The id of the user that wishes to redeem an HTLC.
|
||||
* @param htlcId The HTLC id.
|
||||
*/
|
||||
void onRedeemProposal(String userId, String htlcId);
|
||||
}
|
||||
}
|
5
sample/src/main/res/drawable/ic_switch_hltc_mode.xml
Normal file
5
sample/src/main/res/drawable/ic_switch_hltc_mode.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="24dp" android:tint="#333333"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF000000" android:pathData="M12,6v3l4,-4 -4,-4v3c-4.42,0 -8,3.58 -8,8 0,1.57 0.46,3.03 1.24,4.26L6.7,14.8c-0.45,-0.83 -0.7,-1.79 -0.7,-2.8 0,-3.31 2.69,-6 6,-6zM18.76,7.74L17.3,9.2c0.44,0.84 0.7,1.79 0.7,2.8 0,3.31 -2.69,6 -6,6v-3l-4,4 4,4v-3c4.42,0 8,-3.58 8,-8 0,-1.57 -0.46,-3.03 -1.24,-4.26z"/>
|
||||
</vector>
|
28
sample/src/main/res/layout/activity_htlc.xml
Normal file
28
sample/src/main/res/layout/activity_htlc.xml
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
tools:context=".HtlcActivity">
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:elevation="4dp"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
|
||||
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
|
||||
<fragment android:name="cy.agorise.labs.sample.fragments.PrintResponseFragment"
|
||||
android:id="@+id/fragment_print_response"
|
||||
android:layout_weight="0.4"
|
||||
android:layout_height="0dp"
|
||||
android:layout_width="match_parent"/>
|
||||
<FrameLayout
|
||||
android:id="@+id/fragment_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="0.6">
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
72
sample/src/main/res/layout/fragment_create_htlc.xml
Normal file
72
sample/src/main/res/layout/fragment_create_htlc.xml
Normal file
|
@ -0,0 +1,72 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="16dp"
|
||||
android:clickable="false"
|
||||
tools:context=".fragments.CreateHtlcFragment">
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:id="@+id/container_from"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/hint_from"
|
||||
app:layout_constraintBottom_toTopOf="@+id/container_to"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<android.support.design.widget.TextInputEditText
|
||||
android:id="@+id/from"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="1.2.1029856" />
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:id="@+id/container_to"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/hint_to"
|
||||
app:layout_constraintBottom_toTopOf="@+id/container_amount"
|
||||
app:layout_constraintTop_toBottomOf="@id/container_from">
|
||||
|
||||
<android.support.design.widget.TextInputEditText
|
||||
android:id="@+id/to"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="1.2.143563" />
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:id="@+id/container_amount"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/hint_amount"
|
||||
app:layout_constraintBottom_toTopOf="@+id/container_timelock"
|
||||
app:layout_constraintTop_toBottomOf="@+id/container_to">
|
||||
<android.support.design.widget.TextInputEditText
|
||||
android:id="@+id/amount"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="numberDecimal"
|
||||
android:singleLine="true"
|
||||
android:nextFocusDown="@+id/timelock"/>
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:id="@+id/container_timelock"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/hint_timelock"
|
||||
app:layout_constraintBottom_toTopOf="@+id/button_create"
|
||||
app:layout_constraintTop_toBottomOf="@id/container_amount">
|
||||
<android.support.design.widget.TextInputEditText
|
||||
android:id="@+id/timelock"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="number"/>
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
<Button
|
||||
android:id="@+id/button_create"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/action_send"
|
||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||
</android.support.constraint.ConstraintLayout>
|
10
sample/src/main/res/layout/fragment_print_response.xml
Normal file
10
sample/src/main/res/layout/fragment_print_response.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".fragments.PrintResponseFragment">
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
</FrameLayout>
|
42
sample/src/main/res/layout/fragment_redeem_htlc.xml
Normal file
42
sample/src/main/res/layout/fragment_redeem_htlc.xml
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="16dp"
|
||||
android:clickable="false"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
tools:context=".fragments.RedeemHtlcFragment">
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:id="@+id/container_redeemer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/hint_redeemer"
|
||||
app:layout_constraintBottom_toTopOf="@+id/container_htlc_id"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
<android.support.design.widget.TextInputEditText
|
||||
android:id="@+id/redeemer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="1.2.143563" />
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:id="@+id/container_htlc_id"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/hint_htlc_id"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/container_redeemer">
|
||||
<android.support.design.widget.TextInputEditText
|
||||
android:id="@+id/htlc_id"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="1.16.x" />
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
<Button
|
||||
android:id="@+id/button_create"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/action_send"
|
||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||
</android.support.constraint.ConstraintLayout>
|
8
sample/src/main/res/menu/htlc_menu.xml
Normal file
8
sample/src/main/res/menu/htlc_menu.xml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item android:id="@+id/action_selected_operation"
|
||||
android:title="Create HTLC"
|
||||
android:icon="@drawable/ic_switch_hltc_mode"
|
||||
app:showAsAction="always"/>
|
||||
</menu>
|
|
@ -3,4 +3,6 @@
|
|||
<color name="colorPrimary">#3F51B5</color>
|
||||
<color name="colorPrimaryDark">#303F9F</color>
|
||||
<color name="colorAccent">#FF4081</color>
|
||||
|
||||
<color name="white">#FFFFFF</color>
|
||||
</resources>
|
||||
|
|
|
@ -70,4 +70,14 @@
|
|||
<string name="hint_brainkey">Brainkey</string>
|
||||
<string name="hint_pubkey">Desired public key / address</string>
|
||||
<string name="button_generate">Generate public key</string>
|
||||
|
||||
<!-- CreateHtlcFragment strings -->
|
||||
<string name="hint_from">From account</string>
|
||||
<string name="hint_to">To account</string>
|
||||
<string name="hint_amount">Amount</string>
|
||||
<string name="hint_timelock">Time to lock (in seconds)</string>
|
||||
|
||||
<!-- RedeemHtlcFragment strings -->
|
||||
<string name="hint_redeemer">Redeemer user id</string>
|
||||
<string name="hint_htlc_id">HTLC id</string>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in a new issue