Skip to content
This repository has been archived by the owner on Nov 17, 2023. It is now read-only.

Commit

Permalink
Bip21 - lightning parameter support (#273)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelWuensch authored Dec 5, 2020
1 parent bc29fca commit 2615742
Show file tree
Hide file tree
Showing 11 changed files with 157 additions and 42 deletions.
19 changes: 2 additions & 17 deletions app/src/main/java/zapsolutions/zap/GeneratedRequestActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

import zapsolutions.zap.baseClasses.BaseAppCompatActivity;
import zapsolutions.zap.util.ClipBoardUtil;
import zapsolutions.zap.util.InvoiceUtil;
import zapsolutions.zap.util.MonetaryUtil;
import zapsolutions.zap.util.PrefsUtil;
import zapsolutions.zap.util.UriUtil;
Expand Down Expand Up @@ -84,17 +85,8 @@ protected void onCreate(Bundle savedInstanceState) {


// Generate on-chain request data to encode
mDataToEncode = InvoiceUtil.generateBitcoinInvoice(mAddress, mAmount, mMemo, null);

mDataToEncode = UriUtil.generateBitcoinUri(mAddress);
mMemo = UrlEscapers.urlPathSegmentEscaper().escape(mMemo);

// Append amount and memo to the invoice
if (mAmount != null)
if (!(mAmount.isEmpty() || mAmount.equals("0")))
mDataToEncode = appendParameter(mDataToEncode, "amount", mAmount);
if (mMemo != null)
if (!mMemo.isEmpty())
mDataToEncode = appendParameter(mDataToEncode, "message", mMemo);
} else {
// Generate lightning request data to encode
mDataToEncode = UriUtil.generateLightningUri(mLnInvoice);
Expand Down Expand Up @@ -193,13 +185,6 @@ public void onClick(View v) {

}

private String appendParameter(String base, String name, String value) {
if (!base.contains("?"))
return base + "?" + name + "=" + value;
else
return base + "&" + name + "=" + value;
}

@Override
protected void onDestroy() {
super.onDestroy();
Expand Down
54 changes: 47 additions & 7 deletions app/src/main/java/zapsolutions/zap/HomeActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@
import zapsolutions.zap.baseClasses.BaseAppCompatActivity;
import zapsolutions.zap.channelManagement.ManageChannelsActivity;
import zapsolutions.zap.connection.RemoteConfiguration;
import zapsolutions.zap.connection.lndConnection.LndConnection;
import zapsolutions.zap.connection.internetConnectionStatus.NetworkChangeReceiver;
import zapsolutions.zap.connection.lndConnection.LndConnection;
import zapsolutions.zap.connection.manageWalletConfigs.WalletConfigsManager;
import zapsolutions.zap.customView.CustomViewPager;
import zapsolutions.zap.fragments.OpenChannelBSDFragment;
Expand All @@ -63,6 +63,8 @@
import zapsolutions.zap.util.BitcoinStringAnalyzer;
import zapsolutions.zap.util.ClipBoardUtil;
import zapsolutions.zap.util.ExchangeRateUtil;
import zapsolutions.zap.util.InvoiceUtil;
import zapsolutions.zap.util.MonetaryUtil;
import zapsolutions.zap.util.NfcUtil;
import zapsolutions.zap.util.OnSingleClickListener;
import zapsolutions.zap.util.PinScreenUtil;
Expand Down Expand Up @@ -442,7 +444,7 @@ public void onLndConnectSuccess() {
App.getAppContext().connectionToLNDEstablished = true;

// Warn the user if an old LND version is used.
if (Wallet.getInstance().getLNDVersion().compareTo(new Version("0.11"))<0) {
if (Wallet.getInstance().getLNDVersion().compareTo(new Version("0.11")) < 0) {
new UserGuardian(this).securityOldLndVersion("v0.11.0-beta");
}

Expand Down Expand Up @@ -542,18 +544,56 @@ public void onSuccess(String payload) {
});
}

private void analyzeString(String input) {
public void analyzeString(String input) {
BitcoinStringAnalyzer.analyze(HomeActivity.this, compositeDisposable, input, new BitcoinStringAnalyzer.OnDataDecodedListener() {
@Override
public void onValidLightningInvoice(PayReq paymentRequest, String invoice) {
SendBSDFragment sendBSDFragment = SendBSDFragment.createLightningDialog(paymentRequest, invoice);
SendBSDFragment sendBSDFragment = SendBSDFragment.createLightningDialog(paymentRequest, invoice, null);
sendBSDFragment.show(getSupportFragmentManager(), "sendBottomSheetDialog");
}

@Override
public void onValidBitcoinInvoice(String address, long amount, String message) {
SendBSDFragment sendBSDFragment = SendBSDFragment.createOnChainDialog(address, amount, message);
sendBSDFragment.show(getSupportFragmentManager(), "sendBottomSheetDialog");
public void onValidBitcoinInvoice(String address, long amount, String message, String lightningInvoice) {
if (lightningInvoice == null) {
SendBSDFragment sendBSDFragment = SendBSDFragment.createOnChainDialog(address, amount, message);
sendBSDFragment.show(getSupportFragmentManager(), "sendBottomSheetDialog");
} else {
InvoiceUtil.readInvoice(HomeActivity.this, compositeDisposable, lightningInvoice, new InvoiceUtil.OnReadInvoiceCompletedListener() {
@Override
public void onValidLightningInvoice(PayReq paymentRequest, String invoice) {
if (Wallet.getInstance().getMaxLightningSendAmount() < paymentRequest.getNumSatoshis()) {
// Not enough funds available in channels to send this lightning payment. Fallback to onChain.
SendBSDFragment sendBSDFragment = SendBSDFragment.createOnChainDialog(address, amount, message);
sendBSDFragment.show(getSupportFragmentManager(), "sendBottomSheetDialog");
} else {
String amountString = MonetaryUtil.getInstance().convertSatoshiToBitcoin(String.valueOf(amount));
String onChainInvoice = InvoiceUtil.generateBitcoinInvoice(address, amountString, message, null);
SendBSDFragment sendBSDFragment = SendBSDFragment.createLightningDialog(paymentRequest, invoice, onChainInvoice);
sendBSDFragment.show(getSupportFragmentManager(), "sendBottomSheetDialog");
}
}

@Override
public void onValidBitcoinInvoice(String address, long amount, String message, String lightningInvoice) {
// never reached
}

@Override
public void onError(String error, int duration) {
// If the added lightning parameter contains an invalid lightning invoice, we fall back to the onChain invoice.
ZapLog.d(LOG_TAG, "Falling back to onChain Invoice: " + error);
SendBSDFragment sendBSDFragment = SendBSDFragment.createOnChainDialog(address, amount, message);
sendBSDFragment.show(getSupportFragmentManager(), "sendBottomSheetDialog");
}

@Override
public void onNoInvoiceData() {
// If the added lightning parameter contains an invalid lightning invoice, we fall back to the onChain invoice.
SendBSDFragment sendBSDFragment = SendBSDFragment.createOnChainDialog(address, amount, message);
sendBSDFragment.show(getSupportFragmentManager(), "sendBottomSheetDialog");
}
});
}
}

@Override
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/zapsolutions/zap/ScanActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public void onValidLightningInvoice(PayReq paymentRequest, String invoice) {
}

@Override
public void onValidBitcoinInvoice(String address, long amount, String message) {
public void onValidBitcoinInvoice(String address, long amount, String message, String lightningInvoice) {
readableDataFound(data);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ public void onValidLightningInvoice(PayReq paymentRequest, String invoice) {
}

@Override
public void onValidBitcoinInvoice(String address, long amount, String message) {
public void onValidBitcoinInvoice(String address, long amount, String message, String lightningInvoice) {
showError(getResources().getString(R.string.error_lightning_uri_invalid), RefConstants.ERROR_DURATION_LONG);
}

Expand Down
22 changes: 20 additions & 2 deletions app/src/main/java/zapsolutions/zap/fragments/SendBSDFragment.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.protobuf.InvalidProtocolBufferException;

import zapsolutions.zap.HomeActivity;
import zapsolutions.zap.R;
import zapsolutions.zap.channelManagement.ManageChannelsActivity;
import zapsolutions.zap.connection.lndConnection.LndConnection;
Expand All @@ -69,6 +70,7 @@ public class SendBSDFragment extends RxBSDFragment {
private ImageView mIvBsdIcon;
private ConstraintLayout mIconAnchor;
private TextView mTvTitle;
private String mFallbackOnChainInvoice;

private View mSendAmountView;
private EditText mEtAmount;
Expand All @@ -84,6 +86,7 @@ public class SendBSDFragment extends RxBSDFragment {
private View mProgressScreen;
private View mFinishedScreen;
private Button mOkButton;
private Button mFallbackButton;
private ImageView mProgressFinishedIcon;
private ImageView mIvProgressPaymentTypeIcon;
private ImageView mIvFinishedPaymentTypeIcon;
Expand All @@ -109,11 +112,12 @@ public class SendBSDFragment extends RxBSDFragment {
private float mLnFeePercentCalculated;
private float mLnFeePercentSettingLimit;

public static SendBSDFragment createLightningDialog(PayReq paymentRequest, String invoice) {
public static SendBSDFragment createLightningDialog(PayReq paymentRequest, String invoice, String fallbackOnChainInvoice) {
Intent intent = new Intent();
intent.putExtra("onChain", false);
intent.putExtra("lnPaymentRequest", paymentRequest.toByteArray());
intent.putExtra("lnInvoice", invoice);
intent.putExtra("fallbackOnChainInvoice", fallbackOnChainInvoice);
SendBSDFragment sendBottomSheetDialog = new SendBSDFragment();
sendBottomSheetDialog.setArguments(intent.getExtras());
return sendBottomSheetDialog;
Expand Down Expand Up @@ -150,6 +154,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c

mLnPaymentRequest = paymentRequest;
mLnInvoice = args.getString("lnInvoice");
mFallbackOnChainInvoice = args.getString("fallbackOnChainInvoice");
} catch (InvalidProtocolBufferException e) {
throw new RuntimeException("Invalid payment request forwarded." + e.getMessage());
}
Expand Down Expand Up @@ -183,6 +188,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c
mProgressScreen = view.findViewById(R.id.paymentProgressLayout);
mFinishedScreen = view.findViewById(R.id.paymentFinishedLayout);
mOkButton = view.findViewById(R.id.okButton);
mFallbackButton = view.findViewById(R.id.fallbackButton);
mProgressFinishedIcon = view.findViewById(R.id.progressFinishedIcon);
mIvProgressPaymentTypeIcon = view.findViewById(R.id.progressPaymentTypeIcon);
mIvFinishedPaymentTypeIcon = view.findViewById(R.id.finishedPaymentTypeIcon);
Expand Down Expand Up @@ -520,6 +526,14 @@ public void onClick(View v) {
}
});

mFallbackButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
((HomeActivity) getActivity()).analyzeString(mFallbackOnChainInvoice);
dismiss();
}
});

return view;
}

Expand Down Expand Up @@ -638,7 +652,6 @@ private void switchToSendProgressScreen() {
mBtnSend.setEnabled(false);
mBtnManageChannels.setEnabled(false);
mEtAmount.setEnabled(false);
mNumpad.setEnabled(false);

// Animate out

Expand Down Expand Up @@ -790,6 +803,11 @@ private void switchToFailedScreen(String error) {

mFinishedScreen.startAnimation(animateIn);


if (!mOnChain) {
mFallbackButton.setEnabled(true);
mFallbackButton.setVisibility(View.VISIBLE);
}
// Enable Ok button
mOkButton.setEnabled(true);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@ public void onValidLightningInvoice(PayReq paymentRequest, String invoice) {
}

@Override
public void onValidBitcoinInvoice(String address, long amount, String message) {
listener.onValidBitcoinInvoice(address, amount, message);
public void onValidBitcoinInvoice(String address, long amount, String message, String lightningInvoice) {
listener.onValidBitcoinInvoice(address, amount, message, lightningInvoice);
}

@Override
Expand All @@ -149,7 +149,7 @@ public void onNoInvoiceData() {
public interface OnDataDecodedListener {
void onValidLightningInvoice(PayReq paymentRequest, String invoice);

void onValidBitcoinInvoice(String address, long amount, String message);
void onValidBitcoinInvoice(String address, long amount, String message, String lightningInvoice);

void onValidLnUrlWithdraw(LnUrlWithdrawResponse withdrawResponse);

Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/zapsolutions/zap/util/ClipBoardUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public void onValidLightningInvoice(PayReq paymentRequest, String invoice) {
}

@Override
public void onValidBitcoinInvoice(String address, long amount, String message) {
public void onValidBitcoinInvoice(String address, long amount, String message, String lightningInvoice) {
showProceedQuestion(R.string.clipboard_scan_payment, context, listener);
}

Expand Down
45 changes: 38 additions & 7 deletions app/src/main/java/zapsolutions/zap/util/InvoiceUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import android.content.Context;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.github.lightningnetwork.lnd.lnrpc.PayReq;
import com.github.lightningnetwork.lnd.lnrpc.PayReqString;
import com.google.common.net.UrlEscapers;

import java.net.URI;
import java.net.URISyntaxException;
Expand Down Expand Up @@ -115,6 +117,7 @@ public static void readInvoice(Context ctx, CompositeDisposable compositeDisposa

long onChainInvoiceAmount = 0L;
String onChainInvoiceMessage = null;
String lightningInvoice = null;

// Fetch params
if (bitcoinURI.getQuery() != null) {
Expand All @@ -127,9 +130,12 @@ public static void readInvoice(Context ctx, CompositeDisposable compositeDisposa
if (param[0].equals("message")) {
onChainInvoiceMessage = param[1];
}
if (param[0].equals("lightning")) {
lightningInvoice = param[1];
}
}
}
validateOnChainAddress(ctx, listener, onChainAddress, onChainInvoiceAmount, onChainInvoiceMessage);
validateOnChainAddress(ctx, listener, onChainAddress, onChainInvoiceAmount, onChainInvoiceMessage, lightningInvoice);

} catch (URISyntaxException e) {
ZapLog.w(LOG_TAG, "URI could not be parsed");
Expand All @@ -139,33 +145,33 @@ public static void readInvoice(Context ctx, CompositeDisposable compositeDisposa

} else {
// We also don't have a bitcoin invoice, check if the data is a valid bitcoin address
validateOnChainAddress(ctx, listener, data, 0L, null);
validateOnChainAddress(ctx, listener, data, 0L, null, null);
}

}

}

private static void validateOnChainAddress(Context ctx, OnReadInvoiceCompletedListener listener, String address, long amount, String message) {
private static void validateOnChainAddress(Context ctx, OnReadInvoiceCompletedListener listener, String address, long amount, String message, String lightningInvoice) {
if (address != null && isBitcoinAddress(address)) {
switch (Wallet.getInstance().getNetwork()) {
case MAINNET:
if (hasPrefix(ADDRESS_PREFIX_ONCHAIN_MAINNET, address)) {
listener.onValidBitcoinInvoice(address, amount, message);
listener.onValidBitcoinInvoice(address, amount, message, lightningInvoice);
} else {
listener.onError(ctx.getString(R.string.error_useMainnetRequest), RefConstants.ERROR_DURATION_MEDIUM);
}
break;
case TESTNET:
if (hasPrefix((ArrayList<String>) ADDRESS_PREFIX_ONCHAIN_TESTNET, address)) {
listener.onValidBitcoinInvoice(address, amount, message);
listener.onValidBitcoinInvoice(address, amount, message, lightningInvoice);
} else {
listener.onError(ctx.getString(R.string.error_useTestnetRequest), RefConstants.ERROR_DURATION_MEDIUM);
}
break;
case REGTEST:
if (hasPrefix((ArrayList<String>) ADDRESS_PREFIX_ONCHAIN_REGTEST, address)) {
listener.onValidBitcoinInvoice(address, amount, message);
listener.onValidBitcoinInvoice(address, amount, message, lightningInvoice);
} else {
listener.onError(ctx.getString(R.string.error_useRegtestRequest), RefConstants.ERROR_DURATION_MEDIUM);
}
Expand Down Expand Up @@ -205,6 +211,31 @@ private static void decodeLightningInvoice(Context ctx, OnReadInvoiceCompletedLi

}

private static String appendParameter(String base, String name, String value) {
if (!base.contains("?"))
return base + "?" + name + "=" + value;
else
return base + "&" + name + "=" + value;
}

public static String generateBitcoinInvoice(@NonNull String address, @Nullable String amount, @Nullable String message, @Nullable String lightningInvoice) {
String bitcoinInvoice = UriUtil.generateBitcoinUri(address);

if (amount != null)
if (!(amount.isEmpty() || amount.equals("0")))
bitcoinInvoice = InvoiceUtil.appendParameter(bitcoinInvoice, "amount", amount);
if (message != null)
if (!message.isEmpty()) {
String escapedMessage = UrlEscapers.urlPathSegmentEscaper().escape(message);
bitcoinInvoice = appendParameter(bitcoinInvoice, "message", escapedMessage);
}
if (lightningInvoice != null)
if (!lightningInvoice.isEmpty())
bitcoinInvoice = appendParameter(bitcoinInvoice, "lightning", message);

return bitcoinInvoice;
}

private static boolean hasPrefix(@NonNull String prefix, @NonNull String data) {
if (data.isEmpty() || data.length() < prefix.length()) {
return false;
Expand All @@ -230,7 +261,7 @@ private static boolean hasPrefix(@NonNull ArrayList<String> prefixes, @NonNull S
public interface OnReadInvoiceCompletedListener {
void onValidLightningInvoice(PayReq paymentRequest, String invoice);

void onValidBitcoinInvoice(String address, long amount, String message);
void onValidBitcoinInvoice(String address, long amount, String message, String lightningInvoice);

void onError(String error, int duration);

Expand Down
Loading

0 comments on commit 2615742

Please sign in to comment.