Extended the sample app's capabilities in order to be able to send a create_htlc operation

- The brainkey must be specified in code before trying this feature out.
This commit is contained in:
Nelson R. Perez 2019-07-26 17:16:35 -05:00
parent 9057121146
commit d82bc6add1
9 changed files with 381 additions and 3 deletions

View file

@ -14,10 +14,11 @@
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme" android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning"> tools:ignore="GoogleAppIndexingWarning">
<activity android:name=".HtlcActivity"></activity>
<activity <activity
android:name=".BrainkeyActivity" android:name=".BrainkeyActivity"
android:label="@string/title_activity_brainkey" android:label="@string/title_activity_brainkey"
android:theme="@style/AppTheme.NoActionBar"></activity> android:theme="@style/AppTheme.NoActionBar" />
<activity android:name=".SubscriptionActivity" /> <activity android:name=".SubscriptionActivity" />
<activity android:name=".CallsActivity"> <activity android:name=".CallsActivity">
<intent-filter> <intent-filter>

View file

@ -27,6 +27,7 @@ public class CallsActivity extends AppCompatActivity {
private static final String RECONNECT_NODE = "reconnect_node"; private static final String RECONNECT_NODE = "reconnect_node";
private static final String TEST_BRAINKEY_DERIVATION = "test_brainkey_derivation"; private static final String TEST_BRAINKEY_DERIVATION = "test_brainkey_derivation";
private static final String CREATE_HTLC = "create_htlc";
@BindView(R.id.call_list) @BindView(R.id.call_list)
RecyclerView mRecyclerView; RecyclerView mRecyclerView;
@ -83,7 +84,8 @@ public class CallsActivity extends AppCompatActivity {
RPC.CALL_BROADCAST_TRANSACTION, RPC.CALL_BROADCAST_TRANSACTION,
RPC.CALL_GET_TRANSACTION, RPC.CALL_GET_TRANSACTION,
RECONNECT_NODE, RECONNECT_NODE,
TEST_BRAINKEY_DERIVATION TEST_BRAINKEY_DERIVATION,
CREATE_HTLC
}; };
@NonNull @NonNull
@ -108,7 +110,9 @@ public class CallsActivity extends AppCompatActivity {
intent = new Intent(CallsActivity.this, RemoveNodeActivity.class); intent = new Intent(CallsActivity.this, RemoveNodeActivity.class);
} else if (selectedCall.equals(TEST_BRAINKEY_DERIVATION)){ } else if (selectedCall.equals(TEST_BRAINKEY_DERIVATION)){
intent = new Intent(CallsActivity.this, BrainkeyActivity.class); intent = new Intent(CallsActivity.this, BrainkeyActivity.class);
} else { } else if (selectedCall.equals(CREATE_HTLC)){
intent = new Intent(CallsActivity.this, HtlcActivity.class);
}else {
intent = new Intent(CallsActivity.this, PerformCallActivity.class); intent = new Intent(CallsActivity.this, PerformCallActivity.class);
intent.putExtra(Constants.KEY_SELECTED_CALL, selectedCall); intent.putExtra(Constants.KEY_SELECTED_CALL, selectedCall);
} }

View file

@ -0,0 +1,137 @@
package cy.agorise.labs.sample;
import android.content.ComponentName;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import com.google.common.primitives.UnsignedLong;
import org.bitcoinj.core.ECKey;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;
import cy.agorise.graphenej.Address;
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.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.labs.sample.fragments.CreateHtlcFragment;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
public class HtlcActivity extends ConnectedActivity implements CreateHtlcFragment.HtlcListener {
private String TAG = this.getClass().getName();
private final short PREIMAGE_LENGTH = 32;
private CreateHtlcOperation createHtlcOperation;
private Disposable mDisposable;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_htlc);
}
@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) {
Log.d(TAG,"onHtlcProposal");
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"));
SecureRandom secureRandom = new SecureRandom();
byte[] preimage = new byte[PREIMAGE_LENGTH];
secureRandom.nextBytes(preimage);
try {
byte[] hash = Util.htlcHash(preimage, 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);
Log.d(TAG,"sendMessage returned: " + id);
} catch (NoSuchAlgorithmException e) {
Log.e(TAG,"NoSuchAlgorithmException while trying to create HTLC operation. Msg: " + e.getMessage());
}
}
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);
long id = mNetworkService.sendMessage(new BroadcastTransaction(tx), BroadcastTransaction.REQUIRED_API);
Log.d(TAG,"sendMessage returned: " + id);
}
}
private Transaction buildHltcTransaction(DynamicGlobalProperties dynamicProperties){
// Deriving private key
BrainKey brainKey = new BrainKey(">> Enter your own test Brainkey here <<", 0);
ECKey privKey = brainKey.getPrivateKey();
Address address = new Address(ECKey.fromPublicOnly(privKey.getPubKey()));
// 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<>();
operations.add(this.createHtlcOperation);
// 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) { }
}

View file

@ -0,0 +1,101 @@
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.util.Log;
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 HtlcListener interface.
private HtlcListener 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){
Log.d(TAG,"onSendClicked");
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");
}
Log.d(TAG,"amount: " + amount + ", timelock: " + timeLock);
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 HtlcListener){
mListener = (HtlcListener) context;
}else{
throw new ClassCastException(context.toString() + " must implement the HtlcListener interface!");
}
}
/**
* Interface to be implemented by the parent activity.
*/
public interface HtlcListener {
/**
* 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);
}
}

View file

@ -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);
}
}

View file

@ -0,0 +1,18 @@
<?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"
android:orientation="vertical"
tools:context=".HtlcActivity">
<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"/>
<fragment android:name="cy.agorise.labs.sample.fragments.CreateHtlcFragment"
android:id="@+id/fragment_htlc_parameters"
android:layout_weight="0.6"
android:layout_height="0dp"
android:layout_width="match_parent"/>
</LinearLayout>

View file

@ -0,0 +1,71 @@
<?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"
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.153769" />
</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>

View 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>

View file

@ -70,4 +70,10 @@
<string name="hint_brainkey">Brainkey</string> <string name="hint_brainkey">Brainkey</string>
<string name="hint_pubkey">Desired public key / address</string> <string name="hint_pubkey">Desired public key / address</string>
<string name="button_generate">Generate public key</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>
</resources> </resources>