package de.petendi.ethereum.android.sample; import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.text.TextUtils; import android.util.Base64; import android.view.View; import android.widget.AutoCompleteTextView; import android.widget.Button; import android.widget.Toast; import java.lang.reflect.Field; import java.math.BigInteger; import java.util.HashMap; import de.petendi.ethereum.android.EthereumAndroid; import de.petendi.ethereum.android.EthereumAndroidFactory; import de.petendi.ethereum.android.EthereumNotInstalledException; import de.petendi.ethereum.android.Utils; import de.petendi.ethereum.android.contract.PendingTransaction; import de.petendi.ethereum.android.contract.model.ResponseNotOKException; import de.petendi.ethereum.android.sample.contract.SimpleOwnedStorage; import de.petendi.ethereum.android.service.model.RpcCommand; import de.petendi.ethereum.android.service.model.WrappedRequest; import de.petendi.ethereum.android.service.model.WrappedResponse; public class SimpleStorageActivity extends AppCompatActivity { private enum State { NO_CONTRACT_DEPLOYED, CONTRACT_NOT_MINED_YET, CONTRACT_DEPLOYED } private final static String CONTRACT_BYTECODCE = "6060604052604051610485380380610485833981016040528080518201919060200150505b5b33600060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908302179055505b8060016000509080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106100a057805160ff19168380011785556100d1565b828001600101855582156100d1579182015b828111156100d05782518260005055916020019190600101906100b2565b5b5090506100fc91906100de565b808211156100f857600081815060009055506001016100de565b5090565b505033600060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908302179055505b5061034c806101396000396000f360606040526000357c0100000000000000000000000000000000000000000000000000000000900480634ed3885e1461004f5780636d4ce63c146100a5578063b387ef92146101205761004d565b005b6100a36004808035906020019082018035906020019191908080601f016020809104026020016040519081016040528093929190818152602001838380828437820191505050505050909091905050610215565b005b6100b26004805050610159565b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156101125780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61012d600480505061031d565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b602060405190810160405280600081526020015060016000508054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156102065780601f106101db57610100808354040283529160200191610206565b820191906000526020600020905b8154815290600101906020018083116101e957829003601f168201915b50505050509050610212565b90565b600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610319578060016000509080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106102ba57805160ff19168380011785556102eb565b828001600101855582156102eb579182015b828111156102ea5782518260005055916020019190600101906102cc565b5b50905061031691906102f8565b8082111561031257600081815060009055506001016102f8565b5090565b50505b5b50565b6000600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050610349565b9056"; private final static String CONTRACT_ABI = "[{\"constant\":false,\"inputs\":[{\"name\":\"d\",\"type\":\"string\"}],\"name\":\"set\",\"outputs\":[],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"get\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"currentOwner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"type\":\"function\"},{\"inputs\":[{\"name\":\"d\",\"type\":\"string\"}],\"type\":\"constructor\"}]"; private static final String SIMPLE_STORAGE_PREFS = "simple_storage"; private static final String CONTRACT_ADDRESS = "contractAddress"; private static final String TRANSACTION = "transaction"; private final static int REQUEST_CODE_DEPLOY = 753; private final static int REQUEST_CODE_WRITE = 754; private EthereumAndroid ethereumAndroid; private State currentState = State.NO_CONTRACT_DEPLOYED; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //this is a hack to disable the signature check so that it also connects //to development versions of Ethereum Android try { Field devField = EthereumAndroidFactory.class.getDeclaredField("DEV"); devField.setAccessible(true); devField.set(null, true); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } initialize(); setContentView(R.layout.activity_simple_storage); applyState(); } private void initialize() { EthereumAndroidFactory ethereumAndroidFactory = new EthereumAndroidFactory(this); try { ethereumAndroid = ethereumAndroidFactory.create(); } catch (EthereumNotInstalledException e) { Toast.makeText(this, R.string.ethereum_ethereum_not_installed, Toast.LENGTH_LONG).show(); finish(); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_CODE_DEPLOY) { if (resultCode == RESULT_OK) { String transaction = data.getStringExtra(TRANSACTION); Toast.makeText(this, "deploy contract transaction: " + transaction, Toast.LENGTH_LONG).show(); getSharedPreferences(SIMPLE_STORAGE_PREFS, MODE_PRIVATE).edit().putString(TRANSACTION, transaction).commit(); applyState(); } else { String error = data.getStringExtra("error"); Toast.makeText(this, "deploying contract failed " + error, Toast.LENGTH_LONG).show(); } } else if (requestCode == REQUEST_CODE_WRITE) { if (resultCode == RESULT_OK) { String transaction = data.getStringExtra(TRANSACTION); Toast.makeText(this, "write value transaction " + transaction, Toast.LENGTH_LONG).show(); getSharedPreferences(SIMPLE_STORAGE_PREFS, MODE_PRIVATE).edit().putString(TRANSACTION, transaction).commit(); } else { String error = data.getStringExtra("error"); Toast.makeText(this, "write value failed " + error, Toast.LENGTH_LONG).show(); } } } private void applyState() { SharedPreferences prefs = getSharedPreferences(SIMPLE_STORAGE_PREFS, MODE_PRIVATE); String transaction = prefs.getString(TRANSACTION, null); if (transaction == null) { currentState = State.NO_CONTRACT_DEPLOYED; } else { String contractAddress = prefs.getString(CONTRACT_ADDRESS, null); if (contractAddress == null) { currentState = State.CONTRACT_NOT_MINED_YET; } else { currentState = State.CONTRACT_DEPLOYED; } } Button buttonRead = (Button) findViewById(R.id.read); buttonRead.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { readValue(); } }); Button buttonWrite = (Button) findViewById(R.id.write); buttonWrite.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { writeValue(); } }); Button buttonDeploy = (Button) findViewById(R.id.deploy_contract); buttonDeploy.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { deployContract(); } }); Button buttonReadReceipt = (Button) findViewById(R.id.read_receipt); buttonReadReceipt.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { readContractAddress(); } }); Button buttonReadOwner = (Button) findViewById(R.id.readOwner); buttonReadOwner.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { readOwner(); } }); AutoCompleteTextView valueTextview = (AutoCompleteTextView) findViewById(R.id.storage_input); switch (currentState) { case NO_CONTRACT_DEPLOYED: buttonWrite.setVisibility(View.GONE); buttonRead.setVisibility(View.GONE); buttonReadOwner.setVisibility(View.GONE); buttonDeploy.setVisibility(View.VISIBLE); buttonReadReceipt.setVisibility(View.GONE); valueTextview.setVisibility(View.GONE); break; case CONTRACT_NOT_MINED_YET: buttonWrite.setVisibility(View.GONE); buttonRead.setVisibility(View.GONE); buttonReadOwner.setVisibility(View.GONE); buttonDeploy.setVisibility(View.GONE); buttonReadReceipt.setVisibility(View.VISIBLE); valueTextview.setVisibility(View.GONE); break; case CONTRACT_DEPLOYED: buttonWrite.setVisibility(View.VISIBLE); buttonRead.setVisibility(View.VISIBLE); buttonReadOwner.setVisibility(View.VISIBLE); buttonDeploy.setVisibility(View.GONE); buttonReadReceipt.setVisibility(View.GONE); valueTextview.setVisibility(View.VISIBLE); break; } } private void readOwner() { SharedPreferences prefs = getSharedPreferences(SIMPLE_STORAGE_PREFS, MODE_PRIVATE); String contractAddress = prefs.getString(CONTRACT_ADDRESS, null); final SimpleOwnedStorage simpleOwnedStorage = ethereumAndroid.contracts().bind(contractAddress, CONTRACT_ABI, SimpleOwnedStorage.class); Runnable readTask = new Runnable() { @Override public void run() { String currentOwner; try { currentOwner = simpleOwnedStorage.currentOwner(); } catch(Exception e) { showError(e); return; } final byte[] owner = Base64.decode(currentOwner,Base64.DEFAULT); Runnable showResult = new Runnable() { @Override public void run() { Toast.makeText(SimpleStorageActivity.this, "owner: " + Utils.toHexString(new BigInteger(owner)), Toast.LENGTH_LONG).show(); } }; SimpleStorageActivity.this.runOnUiThread(showResult); } }; new Thread(readTask, "read owner thread").start(); } private void showError(final Exception e) { final String message; if (e instanceof ResponseNotOKException) { message = ((ResponseNotOKException) e).getErrorMessage(); } else { message = e.getMessage(); } Runnable showResult = new Runnable() { @Override public void run() { Toast.makeText(SimpleStorageActivity.this, "an error occurred: " + message, Toast.LENGTH_LONG).show(); if(!ethereumAndroid.hasServiceConnection()) { initialize(); } } }; SimpleStorageActivity.this.runOnUiThread(showResult); } private void readValue() { SharedPreferences prefs = getSharedPreferences(SIMPLE_STORAGE_PREFS, MODE_PRIVATE); String contractAddress = prefs.getString(CONTRACT_ADDRESS, null); final SimpleOwnedStorage simpleOwnedStorage = ethereumAndroid.contracts().bind(contractAddress, CONTRACT_ABI, SimpleOwnedStorage.class); Runnable readTask = new Runnable() { @Override public void run() { final String value; try { value = simpleOwnedStorage.get(); } catch (Exception e) { showError(e); return; } Runnable showResult = new Runnable() { @Override public void run() { Toast.makeText(SimpleStorageActivity.this, "stored value: " + value, Toast.LENGTH_LONG).show(); } }; SimpleStorageActivity.this.runOnUiThread(showResult); } }; new Thread(readTask, "read contract data thread").start(); } private void writeValue() { AutoCompleteTextView valueTextview = (AutoCompleteTextView) findViewById(R.id.storage_input); final String value = valueTextview.getText().toString(); if (TextUtils.isEmpty(value)) { valueTextview.setError(getString(R.string.error_field_required)); } else { SharedPreferences prefs = getSharedPreferences(SIMPLE_STORAGE_PREFS, MODE_PRIVATE); final String contractAddress = prefs.getString(CONTRACT_ADDRESS, null); final SimpleOwnedStorage simpleOwnedStorage = ethereumAndroid.contracts().bind(contractAddress, CONTRACT_ABI, SimpleOwnedStorage.class); Runnable writeTask = new Runnable() { @Override public void run() { final PendingTransaction<Void> pendingWrite; try { pendingWrite = simpleOwnedStorage.set(value); } catch (Exception e) { showError(e); return; } Runnable transactionTask = new Runnable() { @Override public void run() { ethereumAndroid.submitTransaction(SimpleStorageActivity.this, REQUEST_CODE_WRITE, pendingWrite.getUnsignedTransaction()); } }; SimpleStorageActivity.this.runOnUiThread(transactionTask); } }; new Thread(writeTask, "write contract data thread").start(); } } private void readContractAddress() { final SharedPreferences prefs = getSharedPreferences(SIMPLE_STORAGE_PREFS, MODE_PRIVATE); final String transaction = prefs.getString(TRANSACTION, null); Runnable readTask = new Runnable() { @Override public void run() { WrappedRequest wrappedRequest = new WrappedRequest(); wrappedRequest.setCommand(RpcCommand.eth_getTransactionReceipt.toString()); wrappedRequest.setParameters(new Object[]{transaction}); final WrappedResponse response = ethereumAndroid.send(wrappedRequest); if (response.isSuccess()) { HashMap<String, String> transactionObject = (HashMap<String, String>) response.getResponse(); final String contractAddress = transactionObject.get(CONTRACT_ADDRESS); if (contractAddress != null) { prefs.edit().putString(CONTRACT_ADDRESS, contractAddress).commit(); Runnable updateStateTask = new Runnable() { @Override public void run() { applyState(); } }; runOnUiThread(updateStateTask); } } else { Runnable showErrorTask = new Runnable() { @Override public void run() { Toast.makeText(SimpleStorageActivity.this, "reading address failed " + response.getErrorMessage(), Toast.LENGTH_LONG).show(); } }; runOnUiThread(showErrorTask); } } }; new Thread(readTask, "read contract address thread").start(); } private void deployContract() { Runnable deployContractTask = new Runnable() { @Override public void run() { final String transaction = ethereumAndroid.contracts().create(CONTRACT_BYTECODCE, CONTRACT_ABI, "initial value"); Runnable submitTransactionTask = new Runnable() { @Override public void run() { ethereumAndroid.submitTransaction(SimpleStorageActivity.this, REQUEST_CODE_DEPLOY, transaction); } }; runOnUiThread(submitTransactionTask); } }; new Thread(deployContractTask, "create contract thread").start(); } }