diff --git a/app/src/main/java/cy/agorise/crystalwallet/dao/BitcoinAddressDao.java b/app/src/main/java/cy/agorise/crystalwallet/dao/BitcoinAddressDao.java index aaef76b..5260595 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/dao/BitcoinAddressDao.java +++ b/app/src/main/java/cy/agorise/crystalwallet/dao/BitcoinAddressDao.java @@ -25,6 +25,12 @@ public interface BitcoinAddressDao { @Query("SELECT * FROM bitcoin_address ba WHERE ba.address = :address") BitcoinAddress getdadress(String address); + @Query("SELECT * FROM bitcoin_address ba WHERE ba.address_index = :index and ba.is_change = 'true'") + BitcoinAddress getChangeByIndex(long index); + + @Query("SELECT MAX(ba.address_index) FROM bitcoin_address ba WHERE ba.account_id = :accountId and ba.is_change = 'true' ") + long getLastChangeAddress(long accountId); + @Insert(onConflict = OnConflictStrategy.REPLACE) public long[] insertBitcoinAddresses(BitcoinAddress... addresses); } diff --git a/app/src/main/java/cy/agorise/crystalwallet/dao/BitcoinTransactionDao.java b/app/src/main/java/cy/agorise/crystalwallet/dao/BitcoinTransactionDao.java index 7244c81..347efd1 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/dao/BitcoinTransactionDao.java +++ b/app/src/main/java/cy/agorise/crystalwallet/dao/BitcoinTransactionDao.java @@ -24,6 +24,15 @@ public interface BitcoinTransactionDao { @Query("SELECT * FROM bitcoin_transaction bt WHERE bt.tx_id = :txid") List getTransactionsByTxid(String txid); + @Query("SELECT * FROM bitcoin_transaction bt WHERE bt.crypto_coin_transaction_id = :idCryptoCoinTransaction") + BitcoinTransaction getBitcoinTransactionByCryptoCoinTransaction(long idCryptoCoinTransaction); + + @Query("SELECT * FROM bitcoin_transaction_gt_io bt WHERE bt.bitcoin_transaction_id= :idBitcoinTransaction") + List getGtxIOByTransaction(long idBitcoinTransaction); + + @Query("SELECT * FROM bitcoin_transaction_gt_io bt WHERE bt.address= :address") + List getGtxIOByAddress(String address); + @Insert(onConflict = OnConflictStrategy.REPLACE) public long[] insertBitcoinTransaction(BitcoinTransaction... transactions); diff --git a/app/src/main/java/cy/agorise/crystalwallet/enums/CryptoCoin.java b/app/src/main/java/cy/agorise/crystalwallet/enums/CryptoCoin.java index 6d06e9a..bafc31f 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/enums/CryptoCoin.java +++ b/app/src/main/java/cy/agorise/crystalwallet/enums/CryptoCoin.java @@ -1,5 +1,7 @@ package cy.agorise.crystalwallet.enums; +import org.bitcoinj.core.NetworkParameters; + import java.io.Serializable; import java.util.ArrayList; import java.util.List; @@ -11,21 +13,25 @@ import java.util.List; */ public enum CryptoCoin implements Serializable { - BITCOIN(CryptoNet.BITCOIN,"BTC",8), - BITCOIN_TEST(CryptoNet.BITCOIN_TEST,"BTC",8), - LITECOIN(CryptoNet.LITECOIN,"LTC",8), - DASH(CryptoNet.DASH,"DASH",8), - DOGECOIN(CryptoNet.DOGECOIN,"DOGE",8), - BITSHARES(CryptoNet.BITSHARES,"BTS",5); + BITCOIN(CryptoNet.BITCOIN,"BTC",8,0,NetworkParameters.fromID(NetworkParameters.ID_MAINNET)), + BITCOIN_TEST(CryptoNet.BITCOIN_TEST,"BTC",8,1,NetworkParameters.fromID(NetworkParameters.ID_TESTNET)), + LITECOIN(CryptoNet.LITECOIN,"LTC",8,2,null), + DASH(CryptoNet.DASH,"DASH",8,5,null), + DOGECOIN(CryptoNet.DOGECOIN,"DOGE",8,3,null), + BITSHARES(CryptoNet.BITSHARES,"BTS",5,0,null); protected CryptoNet cryptoNet; protected String label; protected int precision; + protected int coinNumber; + protected NetworkParameters parameters; - CryptoCoin(CryptoNet cryptoNet, String label, int precision){ + CryptoCoin(CryptoNet cryptoNet, String label, int precision, int coinNumber, NetworkParameters parameters){ this.cryptoNet = cryptoNet; this.label = label; this.precision = precision; + this.coinNumber = coinNumber; + this.parameters = parameters; } @@ -38,6 +44,14 @@ public enum CryptoCoin implements Serializable { public int getPrecision(){ return this.precision; } + public NetworkParameters getParameters() { + return parameters; + } + + public int getCoinNumber() { + return coinNumber; + } + public static List getByCryptoNet(CryptoNet cryptoNet){ List result = new ArrayList(); diff --git a/app/src/main/java/cy/agorise/crystalwallet/manager/GeneralAccountManager.java b/app/src/main/java/cy/agorise/crystalwallet/manager/GeneralAccountManager.java index 7e2d753..3895f73 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/manager/GeneralAccountManager.java +++ b/app/src/main/java/cy/agorise/crystalwallet/manager/GeneralAccountManager.java @@ -1,13 +1,16 @@ package cy.agorise.crystalwallet.manager; +import android.accounts.Account; import android.content.Context; import org.bitcoinj.core.Address; import org.bitcoinj.core.Coin; +import org.bitcoinj.core.ECKey; import org.bitcoinj.core.Sha256Hash; import org.bitcoinj.core.Transaction; import org.bitcoinj.core.TransactionOutPoint; import org.bitcoinj.crypto.ChildNumber; +import org.bitcoinj.crypto.DeterministicKey; import org.bitcoinj.crypto.HDKeyDerivation; import org.bitcoinj.script.Script; @@ -23,11 +26,16 @@ import cy.agorise.crystalwallet.apigenerator.InsightApiGenerator; import cy.agorise.crystalwallet.apigenerator.insightapi.models.Txi; import cy.agorise.crystalwallet.apigenerator.insightapi.models.Vin; import cy.agorise.crystalwallet.apigenerator.insightapi.models.Vout; +import cy.agorise.crystalwallet.application.CrystalApplication; import cy.agorise.crystalwallet.dao.CrystalDatabase; import cy.agorise.crystalwallet.enums.CryptoCoin; +import cy.agorise.crystalwallet.models.AccountSeed; +import cy.agorise.crystalwallet.models.BitcoinAddress; import cy.agorise.crystalwallet.models.BitcoinTransaction; import cy.agorise.crystalwallet.models.BitcoinTransactionGTxIO; +import cy.agorise.crystalwallet.models.CryptoCoinBalance; import cy.agorise.crystalwallet.models.CryptoCoinTransaction; +import cy.agorise.crystalwallet.models.CryptoCurrency; import cy.agorise.crystalwallet.models.CryptoNetAccount; import cy.agorise.crystalwallet.models.GTxIO; import cy.agorise.crystalwallet.models.GeneralCoinAddress; @@ -102,6 +110,7 @@ public class GeneralAccountManager implements CryptoAccountManager, CryptoNetInf if (!ccTransaction.isConfirmed() && btTransaction.getConfirmations() >= cryptoCoin.getCryptoNet().getConfirmationsNeeded()) { ccTransaction.setConfirmed(true); db.transactionDao().insertTransaction(ccTransaction); + updateBalance(ccTransaction,(ccTransaction.getInput()?1:-1)*ccTransaction.getAmount(),db); } db.bitcoinTransactionDao().insertBitcoinTransaction(btTransaction); @@ -140,8 +149,26 @@ public class GeneralAccountManager implements CryptoAccountManager, CryptoNetInf input.setOriginalTxId(vin.txid); input.setScriptHex(vin.scriptSig.hex); + BitcoinAddress address = db.bitcoinAddressDao().getdadress(addr); + if(address != null){ + if(ccTransaction.getAccountId() < 0){ + ccTransaction.setAccountId(address.getAccountId()); + ccTransaction.setFrom(addr); + ccTransaction.setInput(false); + } + + if(ccTransaction.getAccountId()== address.getAccountId()){ + amount -= vin.value; + } + } + + if(ccTransaction.getFrom() == null || ccTransaction.getFrom().isEmpty()){ + ccTransaction.setFrom(addr); + } + gtxios.add(input); + } for (Vout vout : txi.vout) { @@ -155,90 +182,118 @@ public class GeneralAccountManager implements CryptoAccountManager, CryptoNetInf output.setOutput(false); output.setAmount((long) (vout.value * Math.pow(10, cryptoCoin.getPrecision()))); output.setScriptHex(vout.scriptPubKey.hex); + output.setOriginalTxId(txi.txid); gtxios.add(output); - /*for (GeneralCoinAddress address : this.mAddresses) { - if (address.getAddressString(this.mAccount.getNetworkParam()).equals(addr)) { - output.setAddress(address); - tempAccount = address.getAccount(); - - if (!address.hasTransactionInput(output, this.mAccount.getNetworkParam())) { - address.getTransactionInput().add(output); + BitcoinAddress address = db.bitcoinAddressDao().getdadress(addr); + if(address != null){ + if(ccTransaction.getAccountId() < 0){ + ccTransaction.setAccountId(address.getAccountId()); + ccTransaction.setInput(true); + ccTransaction.setTo(addr); + } + + if(ccTransaction.getAccountId()== address.getAccountId()){ + amount += vout.value; + } + }else{ + //TOOD multiple send address + if(ccTransaction.getTo() == null || ccTransaction.getTo().isEmpty()){ + ccTransaction.setTo(addr); } - changed = true; } - }*/ } } + ccTransaction.setAmount(amount); + CryptoCurrency currency = db.cryptoCurrencyDao().getByNameAndCryptoNet(this.cryptoCoin.name(), this.cryptoCoin.getCryptoNet().name()); + if (currency == null) { + currency = new CryptoCurrency(); + currency.setCryptoNet(this.cryptoCoin.getCryptoNet()); + currency.setName(this.cryptoCoin.name()); + currency.setPrecision(this.cryptoCoin.getPrecision()); + long idCurrency = db.cryptoCurrencyDao().insertCryptoCurrency(currency)[0]; + currency.setId(idCurrency); + } + + ccTransaction.setIdCurrency((int)currency.getId()); + long ccId = db.transactionDao().insertTransaction(ccTransaction)[0]; btTransaction.setCryptoCoinTransactionId(ccId); long btId = db.bitcoinTransactionDao().insertBitcoinTransaction(btTransaction)[0]; for(BitcoinTransactionGTxIO gtxio : gtxios){ gtxio.setBitcoinTransactionId(btId); + db.bitcoinTransactionDao().insertBitcoinTransactionGTxIO(gtxio); } - //TODO database - /*SCWallDatabase db = new SCWallDatabase(this.mContext); - long idTransaction = db.getGeneralTransactionId(transaction); - if (idTransaction == -1) { - db.putGeneralTransaction(transaction); - } else { - transaction.setId(idTransaction); - db.updateGeneralTransaction(transaction); - }*/ - /*if (tempAccount != null && transaction.getConfirm() < this.mAccount.getCryptoNet().getConfirmationsNeeded()) { - InsightApiGenerator.followTransaction(); - new GetTransactionData(transaction.getTxid(), tempAccount, this.serverUrl, this.mContext, true).start(); - } - for (GeneralCoinAddress address : this.mAddresses) { - if (address.updateTransaction(transaction)) { - break; + if(ccTransaction.isConfirmed()) { + updateBalance(ccTransaction,amount,db); } - }*/ } } + private void updateBalance(CryptoCoinTransaction ccTransaction, long amount, CrystalDatabase db){ + CryptoCurrency currency = db.cryptoCurrencyDao().getByNameAndCryptoNet(this.cryptoCoin.name(), this.cryptoCoin.getCryptoNet().name()); + if (currency == null) { + currency = new CryptoCurrency(); + currency.setCryptoNet(this.cryptoCoin.getCryptoNet()); + currency.setName(this.cryptoCoin.name()); + currency.setPrecision(this.cryptoCoin.getPrecision()); + long idCurrency = db.cryptoCurrencyDao().insertCryptoCurrency(currency)[0]; + currency.setId(idCurrency); + } + + CryptoCoinBalance balance = db.cryptoCoinBalanceDao().getBalanceFromAccount(ccTransaction.getAccountId(), currency.getId()); + if (balance == null) { + balance = new CryptoCoinBalance(); + balance.setAccountId(ccTransaction.getAccountId()); + balance.setCryptoCurrencyId(currency.getId()); + long idBalance = db.cryptoCoinBalanceDao().insertCryptoCoinBalance(balance)[0]; + balance.setId(idBalance); + } + balance.setBalance(balance.getBalance()+amount); + db.cryptoCoinBalanceDao().insertCryptoCoinBalance(balance); + } + public void send(final GeneralAccountSendRequest request){ //TODO check server connection //TODO validate to address - InsightApiGenerator.getEstimateFee(request.getAccount().getCryptoCoin(),new ApiRequest(1, new ApiRequestListener() { + InsightApiGenerator.getEstimateFee(this.cryptoCoin,new ApiRequest(1, new ApiRequestListener() { @Override public void success(Object answer, int idPetition) { - Transaction tx = new Transaction(request.getAccount().getNetworkParam()); + Transaction tx = new Transaction(cryptoCoin.getParameters()); long currentAmount = 0; long fee = -1; long feeRate = (Long) answer; fee = 226 * feeRate; - List addresses = request.getAccount().getAddresses(); - List utxos = new ArrayList(); - for(GeneralCoinAddress address : addresses){ - List addrUtxos = address.getUTXos(); - for(GTxIO addrUtxo : addrUtxos){ - utxos.add(addrUtxo); - currentAmount += addrUtxo.getAmount(); - if(currentAmount >= request.getAmount()+ fee){ - break; - } - } - if(currentAmount >= request.getAmount() + fee){ - break; - } - } + CrystalDatabase db = CrystalDatabase.getAppDatabase(request.getContext()); + db.bitcoinTransactionDao(); + List utxos = getUtxos(request.getAccount().getId(),db); if(currentAmount< request.getAmount() + fee){ request.setStatus(GeneralAccountSendRequest.StatusCode.NO_BALANCE); return; } + AccountSeed seed = db.accountSeedDao().findById(request.getAccount().getSeedId()); + DeterministicKey purposeKey = HDKeyDerivation.deriveChildKey((DeterministicKey) seed.getPrivateKey(), + new ChildNumber(44, true)); + DeterministicKey coinKey = HDKeyDerivation.deriveChildKey(purposeKey, + new ChildNumber(cryptoCoin.getCoinNumber(), true)); + DeterministicKey accountKey = HDKeyDerivation.deriveChildKey(coinKey, + new ChildNumber(request.getAccount().getAccountIndex(), true)); + DeterministicKey externalKey = HDKeyDerivation.deriveChildKey(accountKey, + new ChildNumber(0, false)); + DeterministicKey changeKey = HDKeyDerivation.deriveChildKey(accountKey, + new ChildNumber(1, false)); //String to an address - Address toAddr = Address.fromBase58(request.getAccount().getNetworkParam(), request.getToAccount()); + Address toAddr = Address.fromBase58(cryptoCoin.getParameters(), request.getToAccount()); tx.addOutput(Coin.valueOf(request.getAmount()), toAddr); - if(request.getMemo()!= null && !request.getMemo().isEmpty()){ + /*if(request.getMemo()!= null && !request.getMemo().isEmpty()){ String memo = request.getMemo(); if(request.getMemo().length()>40){ memo = memo.substring(0,40); @@ -249,30 +304,50 @@ public class GeneralAccountManager implements CryptoAccountManager, CryptoNetInf System.arraycopy(memo.getBytes(),0,scriptByte,2,memo.length()); Script memoScript = new Script(scriptByte); tx.addOutput(Coin.valueOf(0),memoScript); - } + }*/ //Change address long remain = currentAmount - request.getAmount() - fee; if( remain > 0 ) { - Address changeAddr = Address.fromBase58(request.getAccount().getNetworkParam(), request.getAccount().getNextChangeAddress()); + long index = db.bitcoinAddressDao().getLastChangeAddress(request.getAccount().getId()); + BitcoinAddress btAddress = db.bitcoinAddressDao().getChangeByIndex(index); + Address changeAddr; + if(btAddress != null && db.bitcoinTransactionDao().getGtxIOByAddress(btAddress.getAddress()).size()<=0){ + changeAddr = Address.fromBase58(cryptoCoin.getParameters(), btAddress.getAddress()); + + }else{ + if(btAddress == null){ + index = 0; + }else{ + index++; + } + btAddress = new BitcoinAddress(); + btAddress.setIndex(index); + btAddress.setAccountId(request.getAccount().getId()); + btAddress.setChange(true); + btAddress.setAddress(HDKeyDerivation.deriveChildKey(changeKey, new ChildNumber((int) btAddress.getIndex(), false)).toAddress(cryptoCoin.getParameters()).toString()); + db.bitcoinAddressDao().insertBitcoinAddresses(btAddress); + changeAddr = Address.fromBase58(cryptoCoin.getParameters(), btAddress.getAddress()); + } tx.addOutput(Coin.valueOf(remain), changeAddr); } - for(GTxIO utxo: utxos) { - Sha256Hash txHash = Sha256Hash.wrap(utxo.getTransaction().getTxid()); + for(BitcoinTransactionGTxIO utxo: utxos) { + Sha256Hash txHash = Sha256Hash.wrap(utxo.getOriginalTxId()); Script script = new Script(Util.hexToBytes(utxo.getScriptHex())); - TransactionOutPoint outPoint = new TransactionOutPoint(request.getAccount().getNetworkParam(), utxo.getIndex(), txHash); - if(utxo.getAddress().getKey().isPubKeyOnly()){ - if(utxo.getAddress().isIsChange()){ - utxo.getAddress().setKey(HDKeyDerivation.deriveChildKey(request.getAccount().getChangeKey(), new ChildNumber(utxo.getAddress().getIndex(), false))); - }else{ - utxo.getAddress().setKey(HDKeyDerivation.deriveChildKey(request.getAccount().getExternalKey(), new ChildNumber(utxo.getAddress().getIndex(), false))); - } + TransactionOutPoint outPoint = new TransactionOutPoint(cryptoCoin.getParameters(), utxo.getIndex(), txHash); + BitcoinAddress btAddress = db.bitcoinAddressDao().getdadress(utxo.getAddress()); + ECKey addrKey; + + if(btAddress.isChange()){ + addrKey = HDKeyDerivation.deriveChildKey(changeKey, new ChildNumber((int) btAddress.getIndex(), false)); + }else{ + addrKey = HDKeyDerivation.deriveChildKey(changeKey, new ChildNumber((int) btAddress.getIndex(), true)); } - tx.addSignedInput(outPoint, script, utxo.getAddress().getKey(), Transaction.SigHash.ALL, true); + tx.addSignedInput(outPoint, script, addrKey, Transaction.SigHash.ALL, true); } - InsightApiGenerator.broadcastTransaction(request.getAccount().getCryptoCoin(),Util.bytesToHex(tx.bitcoinSerialize()),new ApiRequest(1, new ApiRequestListener() { + InsightApiGenerator.broadcastTransaction(cryptoCoin,Util.bytesToHex(tx.bitcoinSerialize()),new ApiRequest(1, new ApiRequestListener() { @Override public void success(Object answer, int idPetition) { request.setStatus(GeneralAccountSendRequest.StatusCode.SUCCEEDED); @@ -292,4 +367,37 @@ public class GeneralAccountManager implements CryptoAccountManager, CryptoNetInf } })); } + + private List getUtxos(long accountId, CrystalDatabase db){ + List answer = new ArrayList<>(); + List bTGTxI = new ArrayList<>(); + List bTGTxO = new ArrayList<>(); + List ccTransactions = db.transactionDao().getByIdAccount(accountId); + for(CryptoCoinTransaction ccTransaction : ccTransactions) { + List gtxios = db.bitcoinTransactionDao().getGtxIOByTransaction(ccTransaction.getId()); + for(BitcoinTransactionGTxIO gtxio : gtxios){ + if(db.bitcoinAddressDao().addressExists(gtxio.getAddress())){ + if(gtxio.isOutput()){ + bTGTxO.add(gtxio); + }else{ + bTGTxI.add(gtxio); + } + } + } + } + for(BitcoinTransactionGTxIO gtxi : bTGTxI){ + boolean find = false; + for(BitcoinTransactionGTxIO gtxo : bTGTxO){ + if(gtxo.getOriginalTxId().equals(gtxi.getOriginalTxId())){ + find = true; + break; + } + } + if(!find){ + answer.add(gtxi); + } + } + + return answer; + } } diff --git a/app/src/main/java/cy/agorise/crystalwallet/models/BitcoinAddress.java b/app/src/main/java/cy/agorise/crystalwallet/models/BitcoinAddress.java index 2ecb704..ac076f7 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/models/BitcoinAddress.java +++ b/app/src/main/java/cy/agorise/crystalwallet/models/BitcoinAddress.java @@ -55,6 +55,9 @@ public class BitcoinAddress { this.address = address; } + public BitcoinAddress() { + } + public long getAccountId() { return accountId; } diff --git a/app/src/main/java/cy/agorise/crystalwallet/models/CryptoCoinTransaction.java b/app/src/main/java/cy/agorise/crystalwallet/models/CryptoCoinTransaction.java index 7ac7236..4b06ee4 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/models/CryptoCoinTransaction.java +++ b/app/src/main/java/cy/agorise/crystalwallet/models/CryptoCoinTransaction.java @@ -66,7 +66,7 @@ public class CryptoCoinTransaction { * The id of the account assoiciated, this is used for the foreign key definition */ @ColumnInfo(name="account_id") - protected long accountId; + protected long accountId = -1; /** * The amount of asset is moved in this transaction */ diff --git a/app/src/main/java/cy/agorise/crystalwallet/requestmanagers/GeneralAccountSendRequest.java b/app/src/main/java/cy/agorise/crystalwallet/requestmanagers/GeneralAccountSendRequest.java index 2922680..de98bb5 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/requestmanagers/GeneralAccountSendRequest.java +++ b/app/src/main/java/cy/agorise/crystalwallet/requestmanagers/GeneralAccountSendRequest.java @@ -3,7 +3,7 @@ package cy.agorise.crystalwallet.requestmanagers; import android.content.Context; import cy.agorise.crystalwallet.enums.CryptoCoin; -import cy.agorise.crystalwallet.models.GeneralCoinAccount; +import cy.agorise.crystalwallet.models.CryptoNetAccount; public class GeneralAccountSendRequest extends CryptoNetInfoRequest { /** @@ -23,7 +23,7 @@ public class GeneralAccountSendRequest extends CryptoNetInfoRequest { // The app context private Context mContext; //The soruce Account - private GeneralCoinAccount mAccount; + private CryptoNetAccount mAccount; // The destination account address private String mToAccount; // The amount of the transaction @@ -33,7 +33,7 @@ public class GeneralAccountSendRequest extends CryptoNetInfoRequest { // The state of this request private StatusCode status = StatusCode.NOT_STARTED; - public GeneralAccountSendRequest(CryptoCoin coin, Context context, GeneralCoinAccount account, String toAccount, long amount, String memo) { + public GeneralAccountSendRequest(CryptoCoin coin, Context context, CryptoNetAccount account, String toAccount, long amount, String memo) { super(coin); this.mContext = context; this.mAccount = account; @@ -42,7 +42,7 @@ public class GeneralAccountSendRequest extends CryptoNetInfoRequest { this.mMemo = memo; } - public GeneralAccountSendRequest(CryptoCoin coin, Context context, GeneralCoinAccount account, String toAccount, long amount) { + public GeneralAccountSendRequest(CryptoCoin coin, Context context, CryptoNetAccount account, String toAccount, long amount) { this(coin,context,account,toAccount,amount,null); } @@ -51,7 +51,7 @@ public class GeneralAccountSendRequest extends CryptoNetInfoRequest { return mContext; } - public GeneralCoinAccount getAccount() { + public CryptoNetAccount getAccount() { return mAccount; }