use of com.samourai.wallet.send.RBFSpend in project samourai-wallet-android by Samourai-Wallet.
the class BatchSendActivity method doSpend.
private void doSpend() {
final HashMap<String, BigInteger> receivers = new HashMap<String, BigInteger>();
long amount = 0L;
for (BatchSendUtil.BatchSend _data : data) {
Log.d("BatchSendActivity", "output:" + _data.amount);
Log.d("BatchSendActivity", "output:" + _data.addr);
Log.d("BatchSendActivity", "output:" + _data.pcode);
amount += _data.amount;
if (receivers.containsKey(_data.addr)) {
BigInteger _amount = receivers.get(_data.addr);
receivers.put(_data.addr, _amount.add(BigInteger.valueOf(_data.amount)));
} else {
receivers.put(_data.addr, BigInteger.valueOf(_data.amount));
}
}
Log.d("BatchSendActivity", "amount:" + amount);
List<UTXO> utxos = APIFactory.getInstance(BatchSendActivity.this).getUtxos(true);
Collections.sort(utxos, new UTXO.UTXOComparator());
List<UTXO> selectedUTXO = new ArrayList<UTXO>();
int p2pkh = 0;
int p2sh_p2wpkh = 0;
int p2wpkh = 0;
long totalValueSelected = 0L;
int totalSelected = 0;
for (UTXO utxo : utxos) {
Log.d("BatchSendActivity", "utxo value:" + utxo.getValue());
selectedUTXO.add(utxo);
totalValueSelected += utxo.getValue();
totalSelected += utxo.getOutpoints().size();
Triple<Integer, Integer, Integer> outpointTypes = FeeUtil.getInstance().getOutpointCount(new Vector(utxo.getOutpoints()));
p2pkh += outpointTypes.getLeft();
p2sh_p2wpkh += outpointTypes.getMiddle();
p2wpkh += outpointTypes.getRight();
if (totalValueSelected >= (amount + SamouraiWallet.bDust.longValue() + FeeUtil.getInstance().estimatedFeeSegwit(p2pkh, p2sh_p2wpkh, p2wpkh, receivers.size() + 1).longValue())) {
break;
}
}
Log.d("BatchSendActivity", "totalSelected:" + totalSelected);
Log.d("BatchSendActivity", "totalValueSelected:" + totalValueSelected);
final List<MyTransactionOutPoint> outpoints = new ArrayList<MyTransactionOutPoint>();
for (UTXO utxo : selectedUTXO) {
outpoints.addAll(utxo.getOutpoints());
for (MyTransactionOutPoint out : utxo.getOutpoints()) {
Log.d("BatchSendActivity", "outpoint hash:" + out.getTxHash().toString());
Log.d("BatchSendActivity", "outpoint idx:" + out.getTxOutputN());
Log.d("BatchSendActivity", "outpoint address:" + out.getAddress());
}
}
Triple<Integer, Integer, Integer> outpointTypes = FeeUtil.getInstance().getOutpointCount(new Vector(outpoints));
BigInteger fee = FeeUtil.getInstance().estimatedFeeSegwit(outpointTypes.getLeft(), outpointTypes.getMiddle(), outpointTypes.getRight(), receivers.size() + 1);
Log.d("BatchSendActivity", "fee:" + fee.longValue());
if (amount + fee.longValue() > balance) {
Toast.makeText(BatchSendActivity.this, R.string.insufficient_funds, Toast.LENGTH_SHORT).show();
return;
}
long changeAmount = totalValueSelected - (amount + fee.longValue());
String change_address = null;
int change_idx = 0;
if (changeAmount > 0L) {
change_idx = BIP84Util.getInstance(BatchSendActivity.this).getWallet().getAccount(0).getChange().getAddrIdx();
change_address = BIP84Util.getInstance(BatchSendActivity.this).getAddressAt(AddressFactory.CHANGE_CHAIN, change_idx).getBech32AsString();
receivers.put(change_address, BigInteger.valueOf(changeAmount));
Log.d("BatchSendActivity", "change output:" + changeAmount);
Log.d("BatchSendActivity", "change output:" + change_address);
}
Transaction tx = SendFactory.getInstance(BatchSendActivity.this).makeTransaction(0, outpoints, receivers);
if (tx != null) {
final RBFSpend rbf;
if (PrefsUtil.getInstance(BatchSendActivity.this).getValue(PrefsUtil.RBF_OPT_IN, false) == true) {
rbf = new RBFSpend();
for (TransactionInput input : tx.getInputs()) {
boolean _isBIP49 = false;
boolean _isBIP84 = false;
String _addr = null;
String script = Hex.toHexString(input.getConnectedOutput().getScriptBytes());
if (Bech32Util.getInstance().isBech32Script(script)) {
try {
_addr = Bech32Util.getInstance().getAddressFromScript(script);
_isBIP84 = true;
} catch (Exception e) {
;
}
} else {
Address _address = input.getConnectedOutput().getAddressFromP2SH(SamouraiWallet.getInstance().getCurrentNetworkParams());
if (_address != null) {
_addr = _address.toString();
_isBIP49 = true;
}
}
if (_addr == null) {
_addr = input.getConnectedOutput().getAddressFromP2PKHScript(SamouraiWallet.getInstance().getCurrentNetworkParams()).toString();
}
String path = APIFactory.getInstance(BatchSendActivity.this).getUnspentPaths().get(_addr);
if (path != null) {
if (_isBIP84) {
rbf.addKey(input.getOutpoint().toString(), path + "/84");
} else if (_isBIP49) {
rbf.addKey(input.getOutpoint().toString(), path + "/49");
} else {
rbf.addKey(input.getOutpoint().toString(), path);
}
} else {
String pcode = BIP47Meta.getInstance().getPCode4Addr(_addr);
int idx = BIP47Meta.getInstance().getIdx4Addr(_addr);
rbf.addKey(input.getOutpoint().toString(), pcode + "/" + idx);
}
}
} else {
rbf = null;
}
String strChangeIsDust = "";
String strPrivacyWarning = "";
String strMessage = strChangeIsDust + strPrivacyWarning + "Send " + Coin.valueOf(amount).toPlainString() + " BTC. (fee:" + Coin.valueOf(fee.longValue()).toPlainString() + ")?\n";
final long _change = changeAmount;
final String _change_address = change_address;
final int _change_idx = change_idx;
final long _amount = amount;
tx = SendFactory.getInstance(BatchSendActivity.this).signTransaction(tx, 0);
final String hexTx = new String(Hex.encode(tx.bitcoinSerialize()));
final String strTxHash = tx.getHashAsString();
// Log.d("BatchSendActivity", "tx hash:" + tx.getHashAsString());
// Log.d("BatchSendActivity", "hex signed tx:" + hexTx);
AlertDialog.Builder dlg = new AlertDialog.Builder(BatchSendActivity.this).setTitle(R.string.app_name).setMessage(strMessage).setCancelable(false).setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(final DialogInterface dialog, int whichButton) {
dialog.dismiss();
if (PrefsUtil.getInstance(BatchSendActivity.this).getValue(PrefsUtil.BROADCAST_TX, true) == false) {
doShowTx(hexTx, strTxHash);
return;
}
SendParams.getInstance().setParams(outpoints, receivers, data, SendActivity.SPEND_SIMPLE, _change, 84, 0, "", false, false, _amount, _change_idx);
Intent _intent = new Intent(BatchSendActivity.this, TxAnimUIActivity.class);
startActivity(_intent);
}
}).setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
dialog.dismiss();
}
});
if (!isFinishing()) {
dlg.show();
}
} else {
// Log.d("SendActivity", "tx error");
Toast.makeText(BatchSendActivity.this, "tx error", Toast.LENGTH_SHORT).show();
}
}
use of com.samourai.wallet.send.RBFSpend in project samourai-wallet-android by Samourai-Wallet.
the class TxAnimUIActivity method onCreate.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tx_anim_ui);
progressView = findViewById(R.id.transactionProgressView);
progressView.reset();
progressView.setTxStatusMessage(R.string.tx_creating_ok);
progressView.getmArcProgress().startArc1(arcdelay);
// make tx
final Transaction tx = SendFactory.getInstance(TxAnimUIActivity.this).makeTransaction(SendParams.getInstance().getAccount(), SendParams.getInstance().getOutpoints(), SendParams.getInstance().getReceivers());
if (tx == null) {
failTx(R.string.tx_creating_ko);
} else {
// Toast.makeText(TxAnimUIActivity.this, "tx created OK", Toast.LENGTH_SHORT).show();
final RBFSpend rbf;
if (PrefsUtil.getInstance(TxAnimUIActivity.this).getValue(PrefsUtil.RBF_OPT_IN, false) == true) {
rbf = new RBFSpend();
for (TransactionInput input : tx.getInputs()) {
boolean _isBIP49 = false;
boolean _isBIP84 = false;
String _addr = null;
String script = Hex.toHexString(input.getConnectedOutput().getScriptBytes());
if (Bech32Util.getInstance().isBech32Script(script)) {
try {
_addr = Bech32Util.getInstance().getAddressFromScript(script);
_isBIP84 = true;
} catch (Exception e) {
;
}
} else {
Address _address = input.getConnectedOutput().getAddressFromP2SH(SamouraiWallet.getInstance().getCurrentNetworkParams());
if (_address != null) {
_addr = _address.toString();
_isBIP49 = true;
}
}
if (_addr == null) {
_addr = input.getConnectedOutput().getAddressFromP2PKHScript(SamouraiWallet.getInstance().getCurrentNetworkParams()).toString();
}
String path = APIFactory.getInstance(TxAnimUIActivity.this).getUnspentPaths().get(_addr);
if (path != null) {
if (_isBIP84) {
rbf.addKey(input.getOutpoint().toString(), path + "/84");
} else if (_isBIP49) {
rbf.addKey(input.getOutpoint().toString(), path + "/49");
} else {
rbf.addKey(input.getOutpoint().toString(), path);
}
} else {
String pcode = BIP47Meta.getInstance().getPCode4Addr(_addr);
int idx = BIP47Meta.getInstance().getIdx4Addr(_addr);
rbf.addKey(input.getOutpoint().toString(), pcode + "/" + idx);
}
}
} else {
rbf = null;
}
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
TxAnimUIActivity.this.runOnUiThread(() -> {
progressView.getmArcProgress().startArc2(arcdelay);
progressView.setTxStatusMessage(R.string.tx_signing_ok);
});
final Transaction _tx = SendFactory.getInstance(TxAnimUIActivity.this).signTransaction(tx, SendParams.getInstance().getAccount());
if (_tx == null) {
failTx(R.string.tx_signing_ko);
} else {
// Toast.makeText(TxAnimUIActivity.this, "tx signed OK", Toast.LENGTH_SHORT).show();
}
final String hexTx = new String(Hex.encode(_tx.bitcoinSerialize()));
Log.d("TxAnimUIActivity", "hex tx:" + hexTx);
final String strTxHash = _tx.getHashAsString();
resultHandler = new Handler();
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
TxAnimUIActivity.this.runOnUiThread(() -> {
progressView.getmArcProgress().startArc3(arcdelay);
progressView.setTxStatusMessage(R.string.tx_broadcast_ok);
});
if (PrefsUtil.getInstance(TxAnimUIActivity.this).getValue(PrefsUtil.BROADCAST_TX, true) == false) {
offlineTx(R.string.broadcast_off, hexTx, strTxHash);
return;
}
if (AppUtil.getInstance(TxAnimUIActivity.this).isOfflineMode()) {
offlineTx(R.string.offline_mode, hexTx, strTxHash);
return;
}
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
boolean isOK = false;
String response = PushTx.getInstance(TxAnimUIActivity.this).samourai(hexTx);
try {
if (PrefsUtil.getInstance(TxAnimUIActivity.this).getValue(PrefsUtil.USE_TRUSTED_NODE, false) == true) {
if (TrustedNodeUtil.getInstance().isSet()) {
response = PushTx.getInstance(TxAnimUIActivity.this).trustedNode(hexTx);
JSONObject jsonObject = new org.json.JSONObject(response);
if (jsonObject.has("result")) {
if (jsonObject.getString("result").matches("^[A-Za-z0-9]{64}$")) {
isOK = true;
} else {
Toast.makeText(TxAnimUIActivity.this, R.string.trusted_node_tx_error, Toast.LENGTH_SHORT).show();
failTx(R.string.tx_broadcast_ko);
}
}
} else {
Toast.makeText(TxAnimUIActivity.this, R.string.trusted_node_not_valid, Toast.LENGTH_SHORT).show();
failTx(R.string.tx_broadcast_ko);
}
} else {
if (response != null) {
JSONObject jsonObject = new org.json.JSONObject(response);
if (jsonObject.has("status")) {
if (jsonObject.getString("status").equals("ok")) {
isOK = true;
}
}
} else {
Toast.makeText(TxAnimUIActivity.this, R.string.pushtx_returns_null, Toast.LENGTH_SHORT).show();
failTx(R.string.tx_broadcast_ko);
}
}
} catch (JSONException je) {
failTx(R.string.tx_broadcast_ko);
}
final boolean _isOK = isOK;
resultHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (_isOK) {
progressView.showCheck();
progressView.setTxStatusMessage(R.string.tx_sent_ok);
} else {
failTx(R.string.tx_sent_ko);
}
handleResult(_isOK, rbf, strTxHash, hexTx, _tx);
}
}, resultDelay);
Looper.loop();
}
}).start();
}
}, broadcastDelay);
}
}, signDelay);
}
}
use of com.samourai.wallet.send.RBFSpend in project samourai-wallet-android by Samourai-Wallet.
the class SendActivity method onCreate.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_send);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
SendActivity.this.getActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
if (SamouraiWallet.getInstance().getShowTotalBalance()) {
if (SamouraiWallet.getInstance().getCurrentSelectedAccount() == 2) {
selectedAccount = 1;
} else {
selectedAccount = 0;
}
} else {
selectedAccount = 0;
}
tvMaxPrompt = (TextView) findViewById(R.id.max_prompt);
tvMax = (TextView) findViewById(R.id.max);
try {
balance = APIFactory.getInstance(SendActivity.this).getXpubAmounts().get(HD_WalletFactory.getInstance(SendActivity.this).get().getAccount(selectedAccount).xpubstr());
} catch (IOException ioe) {
balance = 0L;
} catch (MnemonicException.MnemonicLengthException mle) {
balance = 0L;
} catch (java.lang.NullPointerException npe) {
balance = 0L;
}
final String strAmount;
NumberFormat nf = NumberFormat.getInstance(Locale.US);
nf.setMaximumFractionDigits(8);
nf.setMinimumFractionDigits(1);
nf.setMinimumIntegerDigits(1);
strAmount = nf.format(balance / 1e8);
tvMax.setText(strAmount + " " + getDisplayUnits());
tvMaxPrompt.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
edAmountBTC.setText(strAmount);
return false;
}
});
tvMax.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
edAmountBTC.setText(strAmount);
return false;
}
});
DecimalFormat format = (DecimalFormat) DecimalFormat.getInstance(Locale.US);
DecimalFormatSymbols symbols = format.getDecimalFormatSymbols();
defaultSeparator = Character.toString(symbols.getDecimalSeparator());
strFiat = PrefsUtil.getInstance(SendActivity.this).getValue(PrefsUtil.CURRENT_FIAT, "USD");
btc_fx = ExchangeRateFactory.getInstance(SendActivity.this).getAvgPrice(strFiat);
tvFiatSymbol = (TextView) findViewById(R.id.fiatSymbol);
tvFiatSymbol.setText(getDisplayUnits() + "-" + strFiat);
edAddress = (EditText) findViewById(R.id.destination);
textWatcherAddress = new TextWatcher() {
public void afterTextChanged(Editable s) {
validateSpend();
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
;
}
public void onTextChanged(CharSequence s, int start, int before, int count) {
;
}
};
edAddress.addTextChangedListener(textWatcherAddress);
edAddress.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// final int DRAWABLE_LEFT = 0;
// final int DRAWABLE_TOP = 1;
final int DRAWABLE_RIGHT = 2;
if (event.getAction() == MotionEvent.ACTION_UP && event.getRawX() >= (edAddress.getRight() - edAddress.getCompoundDrawables()[DRAWABLE_RIGHT].getBounds().width())) {
final List<String> entries = new ArrayList<String>();
entries.addAll(BIP47Meta.getInstance().getSortedByLabels(false));
final ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(SendActivity.this, android.R.layout.select_dialog_singlechoice);
for (int i = 0; i < entries.size(); i++) {
arrayAdapter.add(BIP47Meta.getInstance().getDisplayLabel(entries.get(i)));
}
AlertDialog.Builder dlg = new AlertDialog.Builder(SendActivity.this);
dlg.setIcon(R.drawable.ic_launcher);
dlg.setTitle(R.string.app_name);
dlg.setAdapter(arrayAdapter, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// Toast.makeText(SendActivity.this, BIP47Meta.getInstance().getDisplayLabel(entries.get(which)), Toast.LENGTH_SHORT).show();
// Toast.makeText(SendActivity.this, entries.get(which), Toast.LENGTH_SHORT).show();
processPCode(entries.get(which), null);
}
});
dlg.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
dlg.show();
return true;
}
return false;
}
});
edAmountBTC = (EditText) findViewById(R.id.amountBTC);
edAmountFiat = (EditText) findViewById(R.id.amountFiat);
textWatcherBTC = new TextWatcher() {
public void afterTextChanged(Editable s) {
edAmountBTC.removeTextChangedListener(this);
edAmountFiat.removeTextChangedListener(textWatcherFiat);
int max_len = 8;
NumberFormat btcFormat = NumberFormat.getInstance(Locale.US);
btcFormat.setMaximumFractionDigits(max_len + 1);
btcFormat.setMinimumFractionDigits(0);
double d = 0.0;
try {
d = NumberFormat.getInstance(Locale.US).parse(s.toString()).doubleValue();
String s1 = btcFormat.format(d);
if (s1.indexOf(defaultSeparator) != -1) {
String dec = s1.substring(s1.indexOf(defaultSeparator));
if (dec.length() > 0) {
dec = dec.substring(1);
if (dec.length() > max_len) {
edAmountBTC.setText(s1.substring(0, s1.length() - 1));
edAmountBTC.setSelection(edAmountBTC.getText().length());
s = edAmountBTC.getEditableText();
}
}
}
} catch (NumberFormatException nfe) {
;
} catch (ParseException pe) {
;
}
if (d > 21000000.0) {
edAmountFiat.setText("0.00");
edAmountFiat.setSelection(edAmountFiat.getText().length());
edAmountBTC.setText("0");
edAmountBTC.setSelection(edAmountBTC.getText().length());
Toast.makeText(SendActivity.this, R.string.invalid_amount, Toast.LENGTH_SHORT).show();
} else {
edAmountFiat.setText(MonetaryUtil.getInstance().getFiatFormat(strFiat).format(d * btc_fx));
edAmountFiat.setSelection(edAmountFiat.getText().length());
}
edAmountFiat.addTextChangedListener(textWatcherFiat);
edAmountBTC.addTextChangedListener(this);
validateSpend();
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
;
}
public void onTextChanged(CharSequence s, int start, int before, int count) {
;
}
};
edAmountBTC.addTextChangedListener(textWatcherBTC);
textWatcherFiat = new TextWatcher() {
public void afterTextChanged(Editable s) {
edAmountFiat.removeTextChangedListener(this);
edAmountBTC.removeTextChangedListener(textWatcherBTC);
int max_len = 2;
NumberFormat fiatFormat = NumberFormat.getInstance(Locale.US);
fiatFormat.setMaximumFractionDigits(max_len + 1);
fiatFormat.setMinimumFractionDigits(0);
double d = 0.0;
try {
d = NumberFormat.getInstance(Locale.US).parse(s.toString()).doubleValue();
String s1 = fiatFormat.format(d);
if (s1.indexOf(defaultSeparator) != -1) {
String dec = s1.substring(s1.indexOf(defaultSeparator));
if (dec.length() > 0) {
dec = dec.substring(1);
if (dec.length() > max_len) {
edAmountFiat.setText(s1.substring(0, s1.length() - 1));
edAmountFiat.setSelection(edAmountFiat.getText().length());
}
}
}
} catch (NumberFormatException nfe) {
;
} catch (ParseException pe) {
;
}
if ((d / btc_fx) > 21000000.0) {
edAmountFiat.setText("0.00");
edAmountFiat.setSelection(edAmountFiat.getText().length());
edAmountBTC.setText("0");
edAmountBTC.setSelection(edAmountBTC.getText().length());
Toast.makeText(SendActivity.this, R.string.invalid_amount, Toast.LENGTH_SHORT).show();
} else {
edAmountBTC.setText(MonetaryUtil.getInstance().getBTCFormat().format(d / btc_fx));
edAmountBTC.setSelection(edAmountBTC.getText().length());
}
edAmountBTC.addTextChangedListener(textWatcherBTC);
edAmountFiat.addTextChangedListener(this);
validateSpend();
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
;
}
public void onTextChanged(CharSequence s, int start, int before, int count) {
;
}
};
edAmountFiat.addTextChangedListener(textWatcherFiat);
SPEND_TYPE = PrefsUtil.getInstance(SendActivity.this).getValue(PrefsUtil.USE_BIP126, true) ? SPEND_BIP126 : SPEND_SIMPLE;
if (SPEND_TYPE > SPEND_BIP126) {
SPEND_TYPE = SPEND_BIP126;
PrefsUtil.getInstance(SendActivity.this).setValue(PrefsUtil.SPEND_TYPE, SPEND_BIP126);
}
swRicochet = (Switch) findViewById(R.id.ricochet);
swRicochet.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
SPEND_TYPE = SPEND_RICOCHET;
} else {
SPEND_TYPE = PrefsUtil.getInstance(SendActivity.this).getValue(PrefsUtil.SPEND_TYPE, SPEND_BIP126);
}
}
});
btLowFee = (Button) findViewById(R.id.low_fee);
btAutoFee = (Button) findViewById(R.id.auto_fee);
btPriorityFee = (Button) findViewById(R.id.priority_fee);
btCustomFee = (Button) findViewById(R.id.custom_fee);
tvFeePrompt = (TextView) findViewById(R.id.current_fee_prompt);
FEE_TYPE = PrefsUtil.getInstance(SendActivity.this).getValue(PrefsUtil.CURRENT_FEE_TYPE, FEE_NORMAL);
long lo = FeeUtil.getInstance().getLowFee().getDefaultPerKB().longValue() / 1000L;
long mi = FeeUtil.getInstance().getNormalFee().getDefaultPerKB().longValue() / 1000L;
long hi = FeeUtil.getInstance().getHighFee().getDefaultPerKB().longValue() / 1000L;
if (lo == mi && mi == hi) {
lo = (long) ((double) mi * 0.85);
hi = (long) ((double) mi * 1.15);
SuggestedFee lo_sf = new SuggestedFee();
lo_sf.setDefaultPerKB(BigInteger.valueOf(lo * 1000L));
FeeUtil.getInstance().setLowFee(lo_sf);
SuggestedFee hi_sf = new SuggestedFee();
hi_sf.setDefaultPerKB(BigInteger.valueOf(hi * 1000L));
FeeUtil.getInstance().setHighFee(hi_sf);
} else if (lo == mi || mi == hi) {
mi = (lo + hi) / 2L;
SuggestedFee mi_sf = new SuggestedFee();
mi_sf.setDefaultPerKB(BigInteger.valueOf(mi * 1000L));
FeeUtil.getInstance().setNormalFee(mi_sf);
} else {
;
}
switch(FEE_TYPE) {
case FEE_LOW:
FeeUtil.getInstance().setSuggestedFee(FeeUtil.getInstance().getLowFee());
btLowFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.blue));
btAutoFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.darkgrey));
btPriorityFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.darkgrey));
btCustomFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.darkgrey));
btLowFee.setTypeface(null, Typeface.BOLD);
btAutoFee.setTypeface(null, Typeface.NORMAL);
btPriorityFee.setTypeface(null, Typeface.NORMAL);
btCustomFee.setTypeface(null, Typeface.NORMAL);
tvFeePrompt.setText(getText(R.string.fee_low_priority) + " " + getText(R.string.blocks_to_cf));
break;
case FEE_PRIORITY:
FeeUtil.getInstance().setSuggestedFee(FeeUtil.getInstance().getHighFee());
btLowFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.darkgrey));
btAutoFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.darkgrey));
btPriorityFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.blue));
btCustomFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.darkgrey));
btLowFee.setTypeface(null, Typeface.NORMAL);
btAutoFee.setTypeface(null, Typeface.NORMAL);
btPriorityFee.setTypeface(null, Typeface.BOLD);
btCustomFee.setTypeface(null, Typeface.NORMAL);
tvFeePrompt.setText(getText(R.string.fee_high_priority) + " " + getText(R.string.blocks_to_cf));
break;
default:
FeeUtil.getInstance().setSuggestedFee(FeeUtil.getInstance().getNormalFee());
btLowFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.darkgrey));
btAutoFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.blue));
btPriorityFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.darkgrey));
btCustomFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.darkgrey));
btLowFee.setTypeface(null, Typeface.NORMAL);
btAutoFee.setTypeface(null, Typeface.BOLD);
btPriorityFee.setTypeface(null, Typeface.NORMAL);
btCustomFee.setTypeface(null, Typeface.NORMAL);
tvFeePrompt.setText(getText(R.string.fee_mid_priority) + " " + getText(R.string.blocks_to_cf));
break;
}
btLowFee.setText((FeeUtil.getInstance().getLowFee().getDefaultPerKB().longValue() / 1000L) + "\n" + getString(R.string.sat_b));
btPriorityFee.setText((FeeUtil.getInstance().getHighFee().getDefaultPerKB().longValue() / 1000L) + "\n" + getString(R.string.sat_b));
btAutoFee.setText((FeeUtil.getInstance().getNormalFee().getDefaultPerKB().longValue() / 1000L) + "\n" + getString(R.string.sat_b));
btLowFee.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
FeeUtil.getInstance().setSuggestedFee(FeeUtil.getInstance().getLowFee());
PrefsUtil.getInstance(SendActivity.this).setValue(PrefsUtil.CURRENT_FEE_TYPE, FEE_LOW);
btLowFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.blue));
btAutoFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.darkgrey));
btPriorityFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.darkgrey));
btCustomFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.darkgrey));
btLowFee.setTypeface(null, Typeface.BOLD);
btAutoFee.setTypeface(null, Typeface.NORMAL);
btPriorityFee.setTypeface(null, Typeface.NORMAL);
btCustomFee.setTypeface(null, Typeface.NORMAL);
btCustomFee.setText(R.string.custom_fee);
tvFeePrompt.setText(getText(R.string.fee_low_priority) + " " + getText(R.string.blocks_to_cf));
}
});
btAutoFee.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
FeeUtil.getInstance().setSuggestedFee(FeeUtil.getInstance().getNormalFee());
PrefsUtil.getInstance(SendActivity.this).setValue(PrefsUtil.CURRENT_FEE_TYPE, FEE_NORMAL);
btLowFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.darkgrey));
btAutoFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.blue));
btPriorityFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.darkgrey));
btCustomFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.darkgrey));
btLowFee.setTypeface(null, Typeface.NORMAL);
btAutoFee.setTypeface(null, Typeface.BOLD);
btPriorityFee.setTypeface(null, Typeface.NORMAL);
btCustomFee.setTypeface(null, Typeface.NORMAL);
btCustomFee.setText(R.string.custom_fee);
tvFeePrompt.setText(getText(R.string.fee_mid_priority) + " " + getText(R.string.blocks_to_cf));
}
});
btPriorityFee.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
FeeUtil.getInstance().setSuggestedFee(FeeUtil.getInstance().getHighFee());
PrefsUtil.getInstance(SendActivity.this).setValue(PrefsUtil.CURRENT_FEE_TYPE, FEE_PRIORITY);
btLowFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.darkgrey));
btAutoFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.darkgrey));
btPriorityFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.blue));
btCustomFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.darkgrey));
btLowFee.setTypeface(null, Typeface.NORMAL);
btAutoFee.setTypeface(null, Typeface.NORMAL);
btPriorityFee.setTypeface(null, Typeface.BOLD);
btCustomFee.setTypeface(null, Typeface.NORMAL);
btCustomFee.setText(R.string.custom_fee);
tvFeePrompt.setText(getText(R.string.fee_high_priority) + " " + getText(R.string.blocks_to_cf));
}
});
btCustomFee.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
doCustomFee();
}
});
btSend = (Button) findViewById(R.id.send);
btSend.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
btSend.setClickable(false);
btSend.setActivated(false);
double btc_amount = 0.0;
try {
btc_amount = NumberFormat.getInstance(Locale.US).parse(edAmountBTC.getText().toString().trim()).doubleValue();
// Log.i("SendFragment", "amount entered:" + btc_amount);
} catch (NumberFormatException nfe) {
btc_amount = 0.0;
} catch (ParseException pe) {
btc_amount = 0.0;
}
double dAmount = btc_amount;
long amount = (long) (Math.round(dAmount * 1e8));
;
// Log.i("SendActivity", "amount:" + amount);
final String address = strDestinationBTCAddress == null ? edAddress.getText().toString() : strDestinationBTCAddress;
final int accountIdx = selectedAccount;
final boolean isSegwitChange = (FormatsUtil.getInstance().isValidBech32(address) || Address.fromBase58(SamouraiWallet.getInstance().getCurrentNetworkParams(), address).isP2SHAddress()) || PrefsUtil.getInstance(SendActivity.this).getValue(PrefsUtil.USE_LIKE_TYPED_CHANGE, true) == false;
final HashMap<String, BigInteger> receivers = new HashMap<String, BigInteger>();
receivers.put(address, BigInteger.valueOf(amount));
// store current change index to restore value in case of sending fail
int change_index = 0;
if (isSegwitChange) {
change_index = BIP49Util.getInstance(SendActivity.this).getWallet().getAccount(0).getChange().getAddrIdx();
} else {
try {
change_index = HD_WalletFactory.getInstance(SendActivity.this).get().getAccount(0).getChange().getAddrIdx();
// Log.d("SendActivity", "storing change index:" + change_index);
} catch (IOException ioe) {
;
} catch (MnemonicException.MnemonicLengthException mle) {
;
}
}
// get all UTXO
List<UTXO> utxos = null;
// if possible, get UTXO by input 'type': p2pkh or p2sh-p2wpkh, else get all UTXO
long neededAmount = 0L;
if (FormatsUtil.getInstance().isValidBech32(address) || Address.fromBase58(SamouraiWallet.getInstance().getCurrentNetworkParams(), address).isP2SHAddress()) {
neededAmount += FeeUtil.getInstance().estimatedFeeSegwit(0, UTXOFactory.getInstance().getCountP2SH_P2WPKH(), 4).longValue();
// Log.d("SendActivity", "segwit:" + neededAmount);
} else {
neededAmount += FeeUtil.getInstance().estimatedFeeSegwit(UTXOFactory.getInstance().getCountP2PKH(), 0, 4).longValue();
// Log.d("SendActivity", "p2pkh:" + neededAmount);
}
neededAmount += amount;
neededAmount += SamouraiWallet.bDust.longValue();
if ((FormatsUtil.getInstance().isValidBech32(address) || Address.fromBase58(SamouraiWallet.getInstance().getCurrentNetworkParams(), address).isP2SHAddress()) && (UTXOFactory.getInstance().getP2SH_P2WPKH().size() > 0 && UTXOFactory.getInstance().getTotalP2SH_P2WPKH() > neededAmount)) {
utxos = new ArrayList<UTXO>(UTXOFactory.getInstance().getP2SH_P2WPKH().values());
// Log.d("SendActivity", "segwit utxos:" + utxos.size());
} else if ((UTXOFactory.getInstance().getP2PKH().size() > 0) && (UTXOFactory.getInstance().getTotalP2PKH() > neededAmount)) {
utxos = new ArrayList<UTXO>(UTXOFactory.getInstance().getP2PKH().values());
// Log.d("SendActivity", "p2pkh utxos:" + utxos.size());
} else {
utxos = APIFactory.getInstance(SendActivity.this).getUtxos(true);
// Log.d("SendActivity", "all filtered utxos:" + utxos.size());
}
final List<UTXO> selectedUTXO = new ArrayList<UTXO>();
long totalValueSelected = 0L;
long change = 0L;
BigInteger fee = null;
// insufficient funds
if (amount > balance) {
Toast.makeText(SendActivity.this, R.string.insufficient_funds, Toast.LENGTH_SHORT).show();
} else // entire balance (can only be simple spend)
if (amount == balance) {
// make sure we are using simple spend
SPEND_TYPE = SPEND_SIMPLE;
// Log.d("SendActivity", "amount == balance");
// take all utxos, deduct fee
selectedUTXO.addAll(utxos);
for (UTXO u : selectedUTXO) {
totalValueSelected += u.getValue();
}
// Log.d("SendActivity", "balance:" + balance);
// Log.d("SendActivity", "total value selected:" + totalValueSelected);
} else {
;
}
org.apache.commons.lang3.tuple.Pair<ArrayList<MyTransactionOutPoint>, ArrayList<TransactionOutput>> pair = null;
if (SPEND_TYPE == SPEND_RICOCHET) {
boolean samouraiFeeViaBIP47 = false;
if (BIP47Meta.getInstance().getOutgoingStatus(BIP47Meta.strSamouraiDonationPCode) == BIP47Meta.STATUS_SENT_CFM) {
samouraiFeeViaBIP47 = true;
}
final JSONObject jObj = RicochetMeta.getInstance(SendActivity.this).script(amount, FeeUtil.getInstance().getSuggestedFee().getDefaultPerKB().longValue(), address, 4, strPCode, samouraiFeeViaBIP47);
if (jObj != null) {
try {
long totalAmount = jObj.getLong("total_spend");
if (totalAmount > balance) {
Toast.makeText(SendActivity.this, R.string.insufficient_funds, Toast.LENGTH_SHORT).show();
return;
}
String msg = getText(R.string.ricochet_spend1) + " " + address + " " + getText(R.string.ricochet_spend2) + " " + Coin.valueOf(totalAmount).toPlainString() + " " + getText(R.string.ricochet_spend3);
AlertDialog.Builder dlg = new AlertDialog.Builder(SendActivity.this).setTitle(R.string.app_name).setMessage(msg).setCancelable(false).setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
RicochetMeta.getInstance(SendActivity.this).add(jObj);
dialog.dismiss();
Intent intent = new Intent(SendActivity.this, RicochetActivity.class);
startActivityForResult(intent, RICOCHET);
}
}).setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
dialog.dismiss();
}
});
if (!isFinishing()) {
dlg.show();
}
return;
} catch (JSONException je) {
return;
}
}
return;
} else // if BIP126 try both hetero/alt, if fails change type to SPEND_SIMPLE
if (SPEND_TYPE == SPEND_BIP126) {
List<UTXO> _utxos = utxos;
// Collections.shuffle(_utxos);
// sort in descending order by value
Collections.sort(_utxos, new UTXO.UTXOComparator());
// hetero
pair = SendFactory.getInstance(SendActivity.this).heterogeneous(_utxos, BigInteger.valueOf(amount), address);
if (pair == null) {
// Collections.sort(_utxos, new UTXO.UTXOComparator());
// alt
pair = SendFactory.getInstance(SendActivity.this).altHeterogeneous(_utxos, BigInteger.valueOf(amount), address);
}
if (pair == null) {
// can't do BIP126, revert to SPEND_SIMPLE
SPEND_TYPE = SPEND_SIMPLE;
}
} else {
;
}
// simple spend (less than balance)
if (SPEND_TYPE == SPEND_SIMPLE) {
List<UTXO> _utxos = utxos;
// sort in ascending order by value
Collections.sort(_utxos, new UTXO.UTXOComparator());
Collections.reverse(_utxos);
// get smallest 1 UTXO > than spend + fee + dust
for (UTXO u : _utxos) {
Pair<Integer, Integer> outpointTypes = FeeUtil.getInstance().getOutpointCount(u.getOutpoints());
if (u.getValue() >= (amount + SamouraiWallet.bDust.longValue() + FeeUtil.getInstance().estimatedFeeSegwit(outpointTypes.getLeft(), outpointTypes.getRight(), 2).longValue())) {
selectedUTXO.add(u);
totalValueSelected += u.getValue();
// Log.d("SendActivity", "nb inputs:" + u.getOutpoints().size());
break;
}
}
if (selectedUTXO.size() == 0) {
// sort in descending order by value
Collections.sort(_utxos, new UTXO.UTXOComparator());
int selected = 0;
int p2pkh = 0;
int p2wpkh = 0;
// get largest UTXOs > than spend + fee + dust
for (UTXO u : _utxos) {
selectedUTXO.add(u);
totalValueSelected += u.getValue();
selected += u.getOutpoints().size();
// Log.d("SendActivity", "value selected:" + u.getValue());
// Log.d("SendActivity", "total value selected/threshold:" + totalValueSelected + "/" + (amount + SamouraiWallet.bDust.longValue() + FeeUtil.getInstance().estimatedFee(selected, 2).longValue()));
Pair<Integer, Integer> outpointTypes = FeeUtil.getInstance().getOutpointCount(u.getOutpoints());
p2pkh += outpointTypes.getLeft();
p2wpkh += outpointTypes.getRight();
if (totalValueSelected >= (amount + SamouraiWallet.bDust.longValue() + FeeUtil.getInstance().estimatedFeeSegwit(p2pkh, p2wpkh, 2).longValue())) {
// Log.d("SendActivity", "nb inputs:" + selected);
break;
}
}
}
} else if (pair != null) {
selectedUTXO.clear();
receivers.clear();
long inputAmount = 0L;
long outputAmount = 0L;
for (MyTransactionOutPoint outpoint : pair.getLeft()) {
UTXO u = new UTXO();
List<MyTransactionOutPoint> outs = new ArrayList<MyTransactionOutPoint>();
outs.add(outpoint);
u.setOutpoints(outs);
totalValueSelected += u.getValue();
selectedUTXO.add(u);
inputAmount += u.getValue();
}
for (TransactionOutput output : pair.getRight()) {
try {
Script script = new Script(output.getScriptBytes());
receivers.put(script.getToAddress(SamouraiWallet.getInstance().getCurrentNetworkParams()).toString(), BigInteger.valueOf(output.getValue().longValue()));
outputAmount += output.getValue().longValue();
} catch (Exception e) {
Toast.makeText(SendActivity.this, R.string.error_bip126_output, Toast.LENGTH_SHORT).show();
return;
}
}
change = outputAmount - amount;
fee = BigInteger.valueOf(inputAmount - outputAmount);
} else {
Toast.makeText(SendActivity.this, R.string.cannot_select_utxo, Toast.LENGTH_SHORT).show();
return;
}
// do spend here
if (selectedUTXO.size() > 0) {
// estimate fee for simple spend, already done if BIP126
if (SPEND_TYPE == SPEND_SIMPLE) {
List<MyTransactionOutPoint> outpoints = new ArrayList<MyTransactionOutPoint>();
for (UTXO utxo : selectedUTXO) {
outpoints.addAll(utxo.getOutpoints());
}
Pair<Integer, Integer> outpointTypes = FeeUtil.getInstance().getOutpointCount(outpoints);
fee = FeeUtil.getInstance().estimatedFeeSegwit(outpointTypes.getLeft(), outpointTypes.getRight(), 2);
}
// Log.d("SendActivity", "spend type:" + SPEND_TYPE);
// Log.d("SendActivity", "amount:" + amount);
// Log.d("SendActivity", "total value selected:" + totalValueSelected);
// Log.d("SendActivity", "fee:" + fee.longValue());
// Log.d("SendActivity", "nb inputs:" + selectedUTXO.size());
change = totalValueSelected - (amount + fee.longValue());
// Log.d("SendActivity", "change:" + change);
boolean changeIsDust = false;
if (change < SamouraiWallet.bDust.longValue() && SPEND_TYPE == SPEND_SIMPLE) {
change = 0L;
fee = fee.add(BigInteger.valueOf(change));
amount = totalValueSelected - fee.longValue();
// Log.d("SendActivity", "fee:" + fee.longValue());
// Log.d("SendActivity", "change:" + change);
// Log.d("SendActivity", "amount:" + amount);
receivers.put(address, BigInteger.valueOf(amount));
changeIsDust = true;
}
final long _change = change;
final BigInteger _fee = fee;
final int _change_index = change_index;
String dest = null;
if (strPCode != null && strPCode.length() > 0) {
dest = BIP47Meta.getInstance().getDisplayLabel(strPCode);
} else {
dest = address;
}
final String strPrivacyWarning;
if (SendAddressUtil.getInstance().get(address) == 1) {
strPrivacyWarning = getString(R.string.send_privacy_warning) + "\n\n";
} else {
strPrivacyWarning = "";
}
String strChangeIsDust = null;
if (changeIsDust) {
strChangeIsDust = getString(R.string.change_is_dust) + "\n\n";
} else {
strChangeIsDust = "";
}
String message = strChangeIsDust + strPrivacyWarning + "Send " + Coin.valueOf(amount).toPlainString() + " to " + dest + " (fee:" + Coin.valueOf(_fee.longValue()).toPlainString() + ")?\n";
final long _amount = amount;
AlertDialog.Builder builder = new AlertDialog.Builder(SendActivity.this);
builder.setTitle(R.string.app_name);
builder.setMessage(message);
final CheckBox cbShowAgain;
if (strPrivacyWarning.length() > 0) {
cbShowAgain = new CheckBox(SendActivity.this);
cbShowAgain.setText(R.string.do_not_repeat_sent_to);
cbShowAgain.setChecked(false);
builder.setView(cbShowAgain);
} else {
cbShowAgain = null;
}
builder.setCancelable(false);
builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(final DialogInterface dialog, int whichButton) {
final ProgressDialog progress = new ProgressDialog(SendActivity.this);
progress.setCancelable(false);
progress.setTitle(R.string.app_name);
progress.setMessage(getString(R.string.please_wait_sending));
progress.show();
final List<MyTransactionOutPoint> outPoints = new ArrayList<MyTransactionOutPoint>();
for (UTXO u : selectedUTXO) {
outPoints.addAll(u.getOutpoints());
}
// add change
if (_change > 0L) {
if (SPEND_TYPE == SPEND_SIMPLE) {
if (isSegwitChange) {
String change_address = BIP49Util.getInstance(SendActivity.this).getAddressAt(AddressFactory.CHANGE_CHAIN, BIP49Util.getInstance(SendActivity.this).getWallet().getAccount(0).getChange().getAddrIdx()).getAddressAsString();
receivers.put(change_address, BigInteger.valueOf(_change));
} else {
try {
String change_address = HD_WalletFactory.getInstance(SendActivity.this).get().getAccount(0).getChange().getAddressAt(HD_WalletFactory.getInstance(SendActivity.this).get().getAccount(0).getChange().getAddrIdx()).getAddressString();
receivers.put(change_address, BigInteger.valueOf(_change));
} catch (IOException ioe) {
Toast.makeText(SendActivity.this, R.string.error_change_output, Toast.LENGTH_SHORT).show();
return;
} catch (MnemonicException.MnemonicLengthException mle) {
Toast.makeText(SendActivity.this, R.string.error_change_output, Toast.LENGTH_SHORT).show();
return;
}
}
} else if (SPEND_TYPE == SPEND_BIP126) {
// do nothing, change addresses included
} else {
;
}
}
// make tx
Transaction tx = SendFactory.getInstance(SendActivity.this).makeTransaction(0, outPoints, receivers);
final RBFSpend rbf;
if (PrefsUtil.getInstance(SendActivity.this).getValue(PrefsUtil.RBF_OPT_IN, false) == true) {
rbf = new RBFSpend();
for (TransactionInput input : tx.getInputs()) {
boolean _isBIP49 = false;
String _addr = null;
Address _address = input.getConnectedOutput().getAddressFromP2PKHScript(SamouraiWallet.getInstance().getCurrentNetworkParams());
if (_address != null) {
_addr = _address.toString();
}
if (_addr == null) {
_addr = input.getConnectedOutput().getAddressFromP2SH(SamouraiWallet.getInstance().getCurrentNetworkParams()).toString();
_isBIP49 = true;
}
String path = APIFactory.getInstance(SendActivity.this).getUnspentPaths().get(_addr);
if (path != null) {
if (_isBIP49) {
rbf.addKey(input.getOutpoint().toString(), path + "/49");
} else {
rbf.addKey(input.getOutpoint().toString(), path);
}
} else {
String pcode = BIP47Meta.getInstance().getPCode4Addr(_addr);
int idx = BIP47Meta.getInstance().getIdx4Addr(_addr);
rbf.addKey(input.getOutpoint().toString(), pcode + "/" + idx);
}
}
} else {
rbf = null;
}
if (tx != null) {
tx = SendFactory.getInstance(SendActivity.this).signTransaction(tx);
final Transaction _tx = tx;
final String hexTx = new String(Hex.encode(tx.bitcoinSerialize()));
// Log.d("SendActivity", hexTx);
final String strTxHash = tx.getHashAsString();
if (PrefsUtil.getInstance(SendActivity.this).getValue(PrefsUtil.BROADCAST_TX, true) == false) {
if (progress != null && progress.isShowing()) {
progress.dismiss();
}
doShowTx(hexTx, strTxHash);
return;
}
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
boolean isOK = false;
String response = null;
try {
if (PrefsUtil.getInstance(SendActivity.this).getValue(PrefsUtil.USE_TRUSTED_NODE, false) == true) {
if (TrustedNodeUtil.getInstance().isSet()) {
response = PushTx.getInstance(SendActivity.this).trustedNode(hexTx);
JSONObject jsonObject = new org.json.JSONObject(response);
if (jsonObject.has("result")) {
if (jsonObject.getString("result").matches("^[A-Za-z0-9]{64}$")) {
isOK = true;
} else {
Toast.makeText(SendActivity.this, R.string.trusted_node_tx_error, Toast.LENGTH_SHORT).show();
}
}
} else {
Toast.makeText(SendActivity.this, R.string.trusted_node_not_valid, Toast.LENGTH_SHORT).show();
}
} else {
response = PushTx.getInstance(SendActivity.this).samourai(hexTx);
if (response != null) {
JSONObject jsonObject = new org.json.JSONObject(response);
if (jsonObject.has("status")) {
if (jsonObject.getString("status").equals("ok")) {
isOK = true;
}
}
} else {
Toast.makeText(SendActivity.this, R.string.pushtx_returns_null, Toast.LENGTH_SHORT).show();
}
}
if (isOK) {
if (PrefsUtil.getInstance(SendActivity.this).getValue(PrefsUtil.USE_TRUSTED_NODE, false) == false) {
Toast.makeText(SendActivity.this, R.string.tx_sent, Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(SendActivity.this, R.string.trusted_node_tx_sent, Toast.LENGTH_SHORT).show();
}
if (_change > 0L && SPEND_TYPE == SPEND_SIMPLE) {
if (isSegwitChange) {
BIP49Util.getInstance(SendActivity.this).getWallet().getAccount(0).getChange().incAddrIdx();
} else {
try {
HD_WalletFactory.getInstance(SendActivity.this).get().getAccount(0).getChange().incAddrIdx();
} catch (IOException ioe) {
;
} catch (MnemonicException.MnemonicLengthException mle) {
;
}
}
}
if (PrefsUtil.getInstance(SendActivity.this).getValue(PrefsUtil.RBF_OPT_IN, false) == true) {
for (TransactionOutput out : _tx.getOutputs()) {
try {
if (!isSegwitChange && !address.equals(out.getAddressFromP2PKHScript(SamouraiWallet.getInstance().getCurrentNetworkParams()).toString())) {
rbf.addChangeAddr(out.getAddressFromP2PKHScript(SamouraiWallet.getInstance().getCurrentNetworkParams()).toString());
} else if (isSegwitChange && !address.equals(out.getAddressFromP2SH(SamouraiWallet.getInstance().getCurrentNetworkParams()).toString())) {
rbf.addChangeAddr(out.getAddressFromP2SH(SamouraiWallet.getInstance().getCurrentNetworkParams()).toString());
} else {
;
}
} catch (NullPointerException npe) {
// test for bech32, skip for now as it's not a change address
;
}
}
rbf.setHash(strTxHash);
rbf.setSerializedTx(hexTx);
RBFUtil.getInstance().add(rbf);
}
// increment counter if BIP47 spend
if (strPCode != null && strPCode.length() > 0) {
BIP47Meta.getInstance().getPCode4AddrLookup().put(address, strPCode);
BIP47Meta.getInstance().inc(strPCode);
SimpleDateFormat sd = new SimpleDateFormat("dd MMM");
String strTS = sd.format(currentTimeMillis());
String event = strTS + " " + SendActivity.this.getString(R.string.sent) + " " + MonetaryUtil.getInstance().getBTCFormat().format((double) _amount / 1e8) + " BTC";
BIP47Meta.getInstance().setLatestEvent(strPCode, event);
strPCode = null;
}
if (strPrivacyWarning.length() > 0 && cbShowAgain != null) {
SendAddressUtil.getInstance().add(address, cbShowAgain.isChecked() ? false : true);
} else if (SendAddressUtil.getInstance().get(address) == 0) {
SendAddressUtil.getInstance().add(address, false);
} else {
SendAddressUtil.getInstance().add(address, true);
}
Intent intent = new Intent("com.samourai.wallet.BalanceFragment.REFRESH");
intent.putExtra("notifTx", false);
intent.putExtra("fetch", true);
LocalBroadcastManager.getInstance(SendActivity.this).sendBroadcast(intent);
View view = SendActivity.this.getCurrentFocus();
if (view != null) {
InputMethodManager imm = (InputMethodManager) SendActivity.this.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
if (bViaMenu) {
SendActivity.this.finish();
} else {
Intent _intent = new Intent(SendActivity.this, BalanceActivity.class);
_intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(_intent);
}
} else {
Toast.makeText(SendActivity.this, R.string.tx_failed, Toast.LENGTH_SHORT).show();
// reset change index upon tx fail
if (isSegwitChange) {
BIP49Util.getInstance(SendActivity.this).getWallet().getAccount(0).getChange().setAddrIdx(_change_index);
} else {
HD_WalletFactory.getInstance(SendActivity.this).get().getAccount(0).getChange().setAddrIdx(_change_index);
}
}
} catch (JSONException je) {
Toast.makeText(SendActivity.this, "pushTx:" + je.getMessage(), Toast.LENGTH_SHORT).show();
} catch (MnemonicException.MnemonicLengthException mle) {
Toast.makeText(SendActivity.this, "pushTx:" + mle.getMessage(), Toast.LENGTH_SHORT).show();
} catch (DecoderException de) {
Toast.makeText(SendActivity.this, "pushTx:" + de.getMessage(), Toast.LENGTH_SHORT).show();
} catch (IOException ioe) {
Toast.makeText(SendActivity.this, "pushTx:" + ioe.getMessage(), Toast.LENGTH_SHORT).show();
} finally {
SendActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
btSend.setActivated(true);
btSend.setClickable(true);
progress.dismiss();
dialog.dismiss();
}
});
}
Looper.loop();
}
}).start();
} else {
// Log.d("SendActivity", "tx error");
Toast.makeText(SendActivity.this, "tx error", Toast.LENGTH_SHORT).show();
}
}
});
builder.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
public void onClick(final DialogInterface dialog, int whichButton) {
try {
// reset change index upon 'NO'
if (isSegwitChange) {
BIP49Util.getInstance(SendActivity.this).getWallet().getAccount(0).getChange().setAddrIdx(_change_index);
} else {
HD_WalletFactory.getInstance(SendActivity.this).get().getAccount(0).getChange().setAddrIdx(_change_index);
}
} catch (Exception e) {
// Log.d("SendActivity", e.getMessage());
Toast.makeText(SendActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
} finally {
SendActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
btSend.setActivated(true);
btSend.setClickable(true);
dialog.dismiss();
}
});
}
}
});
AlertDialog alert = builder.create();
alert.show();
}
}
});
Bundle extras = getIntent().getExtras();
if (extras != null) {
bViaMenu = extras.getBoolean("via_menu", false);
String strUri = extras.getString("uri");
strPCode = extras.getString("pcode");
if (strUri != null && strUri.length() > 0) {
processScan(strUri);
}
if (strPCode != null && strPCode.length() > 0) {
processPCode(strPCode, null);
}
}
validateSpend();
}
use of com.samourai.wallet.send.RBFSpend in project samourai-wallet-android by Samourai-Wallet.
the class RBFTask method doInBackground.
@Override
protected String doInBackground(final String... params) {
Looper.prepare();
Log.d("RBF", "hash:" + params[0]);
rbf = RBFUtil.getInstance().get(params[0]);
Log.d("RBF", "rbf:" + rbf.toJSON().toString());
final Transaction tx = new Transaction(SamouraiWallet.getInstance().getCurrentNetworkParams(), Hex.decode(rbf.getSerializedTx()));
Log.d("RBF", "tx serialized:" + rbf.getSerializedTx());
Log.d("RBF", "tx inputs:" + tx.getInputs().size());
Log.d("RBF", "tx outputs:" + tx.getOutputs().size());
JSONObject txObj = APIFactory.getInstance(activity).getTxInfo(params[0]);
if (tx != null && txObj.has("inputs") && txObj.has("outputs")) {
try {
JSONArray inputs = txObj.getJSONArray("inputs");
JSONArray outputs = txObj.getJSONArray("outputs");
int p2pkh = 0;
int p2sh_p2wpkh = 0;
int p2wpkh = 0;
for (int i = 0; i < inputs.length(); i++) {
if (inputs.getJSONObject(i).has("outpoint") && inputs.getJSONObject(i).getJSONObject("outpoint").has("scriptpubkey")) {
String scriptpubkey = inputs.getJSONObject(i).getJSONObject("outpoint").getString("scriptpubkey");
Script script = new Script(Hex.decode(scriptpubkey));
String address = null;
if (Bech32Util.getInstance().isBech32Script(scriptpubkey)) {
try {
address = Bech32Util.getInstance().getAddressFromScript(scriptpubkey);
} catch (Exception e) {
;
}
} else {
address = script.getToAddress(SamouraiWallet.getInstance().getCurrentNetworkParams()).toString();
}
if (FormatsUtil.getInstance().isValidBech32(address)) {
p2wpkh++;
} else if (Address.fromBase58(SamouraiWallet.getInstance().getCurrentNetworkParams(), address).isP2SHAddress()) {
p2sh_p2wpkh++;
} else {
p2pkh++;
}
}
}
SuggestedFee suggestedFee = FeeUtil.getInstance().getSuggestedFee();
FeeUtil.getInstance().setSuggestedFee(FeeUtil.getInstance().getHighFee());
BigInteger estimatedFee = FeeUtil.getInstance().estimatedFeeSegwit(p2pkh, p2sh_p2wpkh, p2wpkh, outputs.length());
long total_inputs = 0L;
long total_outputs = 0L;
long fee = 0L;
long total_change = 0L;
List<String> selfAddresses = new ArrayList<String>();
for (int i = 0; i < inputs.length(); i++) {
JSONObject obj = inputs.getJSONObject(i);
if (obj.has("outpoint")) {
JSONObject objPrev = obj.getJSONObject("outpoint");
if (objPrev.has("value")) {
total_inputs += objPrev.getLong("value");
String key = objPrev.getString("txid") + ":" + objPrev.getLong("vout");
input_values.put(key, objPrev.getLong("value"));
}
}
}
for (int i = 0; i < outputs.length(); i++) {
JSONObject obj = outputs.getJSONObject(i);
if (obj.has("value")) {
total_outputs += obj.getLong("value");
String _addr = null;
if (obj.has("address")) {
_addr = obj.getString("address");
}
selfAddresses.add(_addr);
if (_addr != null && rbf.getChangeAddrs().contains(_addr.toString())) {
total_change += obj.getLong("value");
}
}
}
boolean feeWarning = false;
fee = total_inputs - total_outputs;
if (fee > estimatedFee.longValue()) {
feeWarning = true;
}
long remainingFee = (estimatedFee.longValue() > fee) ? estimatedFee.longValue() - fee : 0L;
Log.d("RBF", "total inputs:" + total_inputs);
Log.d("RBF", "total outputs:" + total_outputs);
Log.d("RBF", "total change:" + total_change);
Log.d("RBF", "fee:" + fee);
Log.d("RBF", "estimated fee:" + estimatedFee.longValue());
Log.d("RBF", "fee warning:" + feeWarning);
Log.d("RBF", "remaining fee:" + remainingFee);
List<TransactionOutput> txOutputs = new ArrayList<TransactionOutput>();
txOutputs.addAll(tx.getOutputs());
long remainder = remainingFee;
if (total_change > remainder) {
for (TransactionOutput output : txOutputs) {
Script script = output.getScriptPubKey();
String scriptPubKey = Hex.toHexString(script.getProgram());
Address _p2sh = output.getAddressFromP2SH(SamouraiWallet.getInstance().getCurrentNetworkParams());
Address _p2pkh = output.getAddressFromP2PKHScript(SamouraiWallet.getInstance().getCurrentNetworkParams());
try {
if ((Bech32Util.getInstance().isBech32Script(scriptPubKey) && rbf.getChangeAddrs().contains(Bech32Util.getInstance().getAddressFromScript(scriptPubKey))) || (_p2sh != null && rbf.getChangeAddrs().contains(_p2sh.toString())) || (_p2pkh != null && rbf.getChangeAddrs().contains(_p2pkh.toString()))) {
if (output.getValue().longValue() >= (remainder + SamouraiWallet.bDust.longValue())) {
output.setValue(Coin.valueOf(output.getValue().longValue() - remainder));
remainder = 0L;
break;
} else {
remainder -= output.getValue().longValue();
// output will be discarded later
output.setValue(Coin.valueOf(0L));
}
}
} catch (Exception e) {
;
}
}
}
//
// original inputs are not modified
//
List<MyTransactionInput> _inputs = new ArrayList<MyTransactionInput>();
List<TransactionInput> txInputs = tx.getInputs();
for (TransactionInput input : txInputs) {
MyTransactionInput _input = new MyTransactionInput(SamouraiWallet.getInstance().getCurrentNetworkParams(), null, new byte[0], input.getOutpoint(), input.getOutpoint().getHash().toString(), (int) input.getOutpoint().getIndex());
_input.setSequenceNumber(SamouraiWallet.RBF_SEQUENCE_VAL.longValue());
_inputs.add(_input);
Log.d("RBF", "add outpoint:" + _input.getOutpoint().toString());
}
Triple<Integer, Integer, Integer> outpointTypes = null;
if (remainder > 0L) {
List<UTXO> selectedUTXO = new ArrayList<UTXO>();
long selectedAmount = 0L;
int selected = 0;
long _remainingFee = remainder;
Collections.sort(utxos, new UTXO.UTXOComparator());
for (UTXO _utxo : utxos) {
Log.d("RBF", "utxo value:" + _utxo.getValue());
//
// do not select utxo that are change outputs in current rbf tx
//
boolean isChange = false;
boolean isSelf = false;
for (MyTransactionOutPoint outpoint : _utxo.getOutpoints()) {
if (rbf.containsChangeAddr(outpoint.getAddress())) {
Log.d("RBF", "is change:" + outpoint.getAddress());
Log.d("RBF", "is change:" + outpoint.getValue().longValue());
isChange = true;
break;
}
if (selfAddresses.contains(outpoint.getAddress())) {
Log.d("RBF", "is self:" + outpoint.getAddress());
Log.d("RBF", "is self:" + outpoint.getValue().longValue());
isSelf = true;
break;
}
}
if (isChange || isSelf) {
continue;
}
selectedUTXO.add(_utxo);
selected += _utxo.getOutpoints().size();
Log.d("RBF", "selected utxo:" + selected);
selectedAmount += _utxo.getValue();
Log.d("RBF", "selected utxo value:" + _utxo.getValue());
outpointTypes = FeeUtil.getInstance().getOutpointCount(new Vector(_utxo.getOutpoints()));
p2pkh += outpointTypes.getLeft();
p2sh_p2wpkh += outpointTypes.getMiddle();
p2wpkh += outpointTypes.getRight();
_remainingFee = FeeUtil.getInstance().estimatedFeeSegwit(p2pkh, p2sh_p2wpkh, p2wpkh, outputs.length() == 1 ? 2 : outputs.length()).longValue();
Log.d("RBF", "_remaining fee:" + _remainingFee);
if (selectedAmount >= (_remainingFee + SamouraiWallet.bDust.longValue())) {
break;
}
}
long extraChange = 0L;
if (selectedAmount < (_remainingFee + SamouraiWallet.bDust.longValue())) {
handler.post(new Runnable() {
public void run() {
Toast.makeText(activity, R.string.insufficient_funds, Toast.LENGTH_SHORT).show();
}
});
return "KO";
} else {
extraChange = selectedAmount - _remainingFee;
Log.d("RBF", "extra change:" + extraChange);
}
boolean addedChangeOutput = false;
// parent tx didn't have change output
if (outputs.length() == 1 && extraChange > 0L) {
try {
boolean isSegwitChange = (FormatsUtil.getInstance().isValidBech32(outputs.getJSONObject(0).getString("address")) || Address.fromBase58(SamouraiWallet.getInstance().getCurrentNetworkParams(), outputs.getJSONObject(0).getString("address")).isP2SHAddress()) || PrefsUtil.getInstance(activity).getValue(PrefsUtil.USE_LIKE_TYPED_CHANGE, true) == false;
String change_address = null;
if (isSegwitChange) {
int changeIdx = BIP49Util.getInstance(activity).getWallet().getAccount(0).getChange().getAddrIdx();
change_address = BIP49Util.getInstance(activity).getAddressAt(AddressFactory.CHANGE_CHAIN, changeIdx).getAddressAsString();
} else {
int changeIdx = HD_WalletFactory.getInstance(activity).get().getAccount(0).getChange().getAddrIdx();
change_address = HD_WalletFactory.getInstance(activity).get().getAccount(0).getChange().getAddressAt(changeIdx).getAddressString();
}
Script toOutputScript = ScriptBuilder.createOutputScript(Address.fromBase58(SamouraiWallet.getInstance().getCurrentNetworkParams(), change_address));
TransactionOutput output = new TransactionOutput(SamouraiWallet.getInstance().getCurrentNetworkParams(), null, Coin.valueOf(extraChange), toOutputScript.getProgram());
txOutputs.add(output);
addedChangeOutput = true;
} catch (MnemonicException.MnemonicLengthException | IOException e) {
handler.post(new Runnable() {
public void run() {
Toast.makeText(activity, e.getMessage(), Toast.LENGTH_SHORT).show();
Toast.makeText(activity, R.string.cannot_create_change_output, Toast.LENGTH_SHORT).show();
}
});
return "KO";
}
} else // parent tx had change output
{
for (TransactionOutput output : txOutputs) {
Script script = output.getScriptPubKey();
String scriptPubKey = Hex.toHexString(script.getProgram());
String _addr = null;
if (Bech32Util.getInstance().isBech32Script(scriptPubKey)) {
try {
_addr = Bech32Util.getInstance().getAddressFromScript(scriptPubKey);
} catch (Exception e) {
;
}
}
if (_addr == null) {
Address _address = output.getAddressFromP2PKHScript(SamouraiWallet.getInstance().getCurrentNetworkParams());
if (_address == null) {
_address = output.getAddressFromP2SH(SamouraiWallet.getInstance().getCurrentNetworkParams());
}
_addr = _address.toString();
}
Log.d("RBF", "checking for change:" + _addr);
if (rbf.containsChangeAddr(_addr)) {
Log.d("RBF", "before extra:" + output.getValue().longValue());
output.setValue(Coin.valueOf(extraChange + output.getValue().longValue()));
Log.d("RBF", "after extra:" + output.getValue().longValue());
addedChangeOutput = true;
break;
}
}
}
// sanity check
if (extraChange > 0L && !addedChangeOutput) {
handler.post(new Runnable() {
public void run() {
Toast.makeText(activity, R.string.cannot_create_change_output, Toast.LENGTH_SHORT).show();
}
});
return "KO";
}
//
// update keyBag w/ any new paths
//
final HashMap<String, String> keyBag = rbf.getKeyBag();
for (UTXO _utxo : selectedUTXO) {
for (MyTransactionOutPoint outpoint : _utxo.getOutpoints()) {
MyTransactionInput _input = new MyTransactionInput(SamouraiWallet.getInstance().getCurrentNetworkParams(), null, new byte[0], outpoint, outpoint.getTxHash().toString(), outpoint.getTxOutputN());
_input.setSequenceNumber(SamouraiWallet.RBF_SEQUENCE_VAL.longValue());
_inputs.add(_input);
Log.d("RBF", "add selected outpoint:" + _input.getOutpoint().toString());
String path = APIFactory.getInstance(activity).getUnspentPaths().get(outpoint.getAddress());
if (path != null) {
if (FormatsUtil.getInstance().isValidBech32(outpoint.getAddress())) {
rbf.addKey(outpoint.toString(), path + "/84");
} else if (Address.fromBase58(SamouraiWallet.getInstance().getCurrentNetworkParams(), outpoint.getAddress()) != null && Address.fromBase58(SamouraiWallet.getInstance().getCurrentNetworkParams(), outpoint.getAddress()).isP2SHAddress()) {
rbf.addKey(outpoint.toString(), path + "/49");
} else {
rbf.addKey(outpoint.toString(), path);
}
Log.d("RBF", "outpoint address:" + outpoint.getAddress());
} else {
String pcode = BIP47Meta.getInstance().getPCode4Addr(outpoint.getAddress());
int idx = BIP47Meta.getInstance().getIdx4Addr(outpoint.getAddress());
rbf.addKey(outpoint.toString(), pcode + "/" + idx);
}
}
}
rbf.setKeyBag(keyBag);
}
//
// BIP69 sort of outputs/inputs
//
final Transaction _tx = new Transaction(SamouraiWallet.getInstance().getCurrentNetworkParams());
List<TransactionOutput> _txOutputs = new ArrayList<TransactionOutput>();
_txOutputs.addAll(txOutputs);
Collections.sort(_txOutputs, new BIP69OutputComparator());
for (TransactionOutput to : _txOutputs) {
// zero value outputs discarded here
if (to.getValue().longValue() > 0L) {
_tx.addOutput(to);
}
}
List<MyTransactionInput> __inputs = new ArrayList<MyTransactionInput>();
__inputs.addAll(_inputs);
Collections.sort(__inputs, new SendFactory.BIP69InputComparator());
for (TransactionInput input : __inputs) {
_tx.addInput(input);
}
FeeUtil.getInstance().setSuggestedFee(suggestedFee);
String message = "";
if (feeWarning) {
message += activity.getString(R.string.fee_bump_not_necessary);
message += "\n\n";
}
message += activity.getString(R.string.bump_fee) + " " + Coin.valueOf(remainingFee).toPlainString() + " BTC";
AlertDialog.Builder dlg = new AlertDialog.Builder(activity).setTitle(R.string.app_name).setMessage(message).setCancelable(false).setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
Transaction __tx = signTx(_tx);
final String hexTx = new String(Hex.encode(__tx.bitcoinSerialize()));
Log.d("RBF", "hex tx:" + hexTx);
final String strTxHash = __tx.getHashAsString();
Log.d("RBF", "tx hash:" + strTxHash);
if (__tx != null) {
boolean isOK = false;
try {
isOK = PushTx.getInstance(activity).pushTx(hexTx);
if (isOK) {
handler.post(new Runnable() {
public void run() {
Toast.makeText(activity, R.string.rbf_spent, Toast.LENGTH_SHORT).show();
// includes updated 'keyBag'
RBFSpend _rbf = rbf;
_rbf.setSerializedTx(hexTx);
_rbf.setHash(strTxHash);
_rbf.setPrevHash(params[0]);
RBFUtil.getInstance().add(_rbf);
Intent _intent = new Intent(activity, MainActivity2.class);
_intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
activity.startActivity(_intent);
}
});
} else {
handler.post(new Runnable() {
public void run() {
Toast.makeText(activity, R.string.tx_failed, Toast.LENGTH_SHORT).show();
}
});
}
} catch (final DecoderException de) {
handler.post(new Runnable() {
public void run() {
Toast.makeText(activity, "pushTx:" + de.getMessage(), Toast.LENGTH_SHORT).show();
}
});
} finally {
;
}
}
}
}).setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
dialog.dismiss();
}
});
if (!activity.isFinishing()) {
dlg.show();
}
} catch (final JSONException je) {
handler.post(new Runnable() {
public void run() {
Toast.makeText(activity, "rbf:" + je.getMessage(), Toast.LENGTH_SHORT).show();
}
});
}
} else {
Toast.makeText(activity, R.string.cpfp_cannot_retrieve_tx, Toast.LENGTH_SHORT).show();
}
Looper.loop();
return "OK";
}
Aggregations