Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support to pay to amp invoices #1397

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/lndmobile/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ export const sendPaymentV2Sync = (
multiPath?: boolean,
maxLNFeePercentage: number = 2,
outgoingChanId?: Long,
isAmp?: boolean,
): Promise<lnrpc.Payment> => {
const maxFeeRatio = (maxLNFeePercentage ?? 2) / 100;

Expand All @@ -256,6 +257,7 @@ export const sendPaymentV2Sync = (
feeLimitSat: Long.fromValue(Math.max(10, (payAmount?.toNumber() || 0) * maxFeeRatio)),
cltvLimit: 0,
outgoingChanId,
amp: isAmp,
};
if (amount) {
options.amt = amount;
Expand Down
1 change: 1 addition & 0 deletions src/state/LndMobileInjection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ export interface ILndMobileInjections {
multiPath?: boolean,
maxLNFeePercentage?: number,
outgoingChannelId?: Long,
isAmp?: boolean,
) => Promise<lnrpc.Payment>;
queryRoutes: (
pubkey: string,
Expand Down
16 changes: 14 additions & 2 deletions src/state/Send.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as Bech32 from "bech32";

import { Action, Thunk, action, thunk } from "easy-peasy";
import { getGeolocation, hexToUint8Array } from "../utils";
import { getGeolocation, hexToUint8Array, uint8ArrayToString } from "../utils";
import { lnrpc, routerrpc } from "../../proto/lightning";

import { ILNUrlPayResponse } from "./LNURL";
Expand Down Expand Up @@ -32,6 +32,7 @@ export interface ISendModelSetPaymentPayload {
export interface IModelSendPaymentPayload {
amount?: Long;
outgoingChannelId?: Long;
isAmpInvoice?: boolean;
}

export interface IModelQueryRoutesPayload {
Expand Down Expand Up @@ -180,8 +181,15 @@ export const send: ISendModel = {
const getTransactionByPaymentRequest =
getStoreState().transaction.getTransactionByPaymentRequest;

const isAmpInvoice = payload && payload.isAmpInvoice ? true : false;

// getTransactionByPaymentRequest only if isAmpInvoice is false
const transactionByPaymentRequest = !isAmpInvoice
? getTransactionByPaymentRequest(paymentRequestStr)
: undefined;

// Pre-settlement tx insert
const preTransaction: ITransaction = getTransactionByPaymentRequest(paymentRequestStr) ?? {
const preTransaction: ITransaction = transactionByPaymentRequest ?? {
date: paymentRequest.timestamp,
description: extraData.lnurlPayTextPlain ?? paymentRequest.description,
duration: 0,
Expand Down Expand Up @@ -212,12 +220,14 @@ export const send: ISendModel = {
paymentRequest.description,
extraData.website,
),
ampInvoice: isAmpInvoice,
//note: // TODO: Why wasn't this added
lightningAddress: extraData.lightningAddress ?? null,
lud16IdentifierMimeType: extraData.lud16IdentifierMimeType ?? null,

preimage: hexToUint8Array("0"),
lnurlPayResponse: extraData.lnurlPayResponse,
lud18PayerData: null,

hops: [],
};
Expand All @@ -235,6 +245,7 @@ export const send: ISendModel = {
multiPathPaymentsEnabled,
maxLNFeePercentage,
outgoingChannelId,
(payload && payload.isAmpInvoice) ?? false,
);
} catch (error) {
await dispatch.transaction.syncTransaction({
Expand Down Expand Up @@ -263,6 +274,7 @@ export const send: ISendModel = {
feeMsat: sendPaymentResult.feeMsat || Long.fromInt(0),

preimage: hexToUint8Array(sendPaymentResult.paymentPreimage),
ampInvoice: isAmpInvoice,

hops:
sendPaymentResult.htlcs[0].route?.hops?.map((hop) => ({
Expand Down
137 changes: 85 additions & 52 deletions src/state/Transaction.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { LayoutAnimation } from "react-native";
import { Thunk, thunk, Action, action, Computed, computed } from "easy-peasy";
import { ITransaction, getTransactions, createTransaction, updateTransaction } from "../storage/database/transaction";
import {
ITransaction,
getTransactions,
createTransaction,
updateTransaction,
} from "../storage/database/transaction";

import { IStoreModel } from "./index";
import { IStoreInjections } from "./store";
Expand All @@ -26,8 +31,14 @@ export interface ITransactionModel {

transactions: ITransaction[];
getTransactionByRHash: Computed<ITransactionModel, (rHash: string) => ITransaction | undefined>;
getTransactionByPreimage: Computed<ITransactionModel, (preimage: Uint8Array) => ITransaction | undefined>;
getTransactionByPaymentRequest: Computed<ITransactionModel, (paymentRequest: string) => ITransaction | undefined>;
getTransactionByPreimage: Computed<
ITransactionModel,
(preimage: Uint8Array) => ITransaction | undefined
>;
getTransactionByPaymentRequest: Computed<
ITransactionModel,
(paymentRequest: string) => ITransaction | undefined
>;
}

export const transaction: ITransactionModel = {
Expand All @@ -42,13 +53,28 @@ export const transaction: ITransactionModel = {
throw new Error("syncTransaction(): db not ready");
}

// Don't insert open transactions for AMP invoices
if (tx.ampInvoice) {
if (tx.status === "OPEN") {
return;
}

// If AMP invoice settles, insert a new tx
if (tx.status === "SETTLED") {
const id = await createTransaction(db, tx);
actions.addTransaction({ ...tx, id });

return;
}
}
niteshbalusu11 marked this conversation as resolved.
Show resolved Hide resolved

const transactions = getState().transactions;
let foundTransaction = false;

for (const txIt of transactions) {
if (txIt.paymentRequest === tx.paymentRequest) {
await updateTransaction(db, { ...txIt, ...tx });
actions.updateTransaction({ transaction: { ...txIt, ...tx }});
actions.updateTransaction({ transaction: { ...txIt, ...tx } });
foundTransaction = true;
}
}
Expand Down Expand Up @@ -109,53 +135,62 @@ export const transaction: ITransactionModel = {
throw new Error("checkOpenTransactions(): db not ready");
}


for (const tx of getState().transactions) {
if (tx.status === "OPEN") {
log.i("trackpayment tx", [tx.rHash]);
if (tx.valueMsat.isNegative()) {
trackPayment(tx.rHash).then((trackPaymentResult) => {
log.i("trackpayment status", [trackPaymentResult.status, trackPaymentResult.paymentHash]);
log.i("trackpayment status", [
trackPaymentResult.status,
trackPaymentResult.paymentHash,
]);
if (trackPaymentResult.status === lnrpc.Payment.PaymentStatus.SUCCEEDED) {
log.i("trackpayment updating tx [settled]");
const updated: ITransaction = {
...tx,
status: "SETTLED",
preimage: hexToUint8Array(trackPaymentResult.paymentPreimage),
hops: trackPaymentResult.htlcs[0].route?.hops?.map((hop) => ({
chanId: hop.chanId ?? null,
chanCapacity: hop.chanCapacity ?? null,
amtToForward: hop.amtToForward || Long.fromInt(0),
amtToForwardMsat: hop.amtToForwardMsat || Long.fromInt(0),
fee: hop.fee || Long.fromInt(0),
feeMsat: hop.feeMsat || Long.fromInt(0),
expiry: hop.expiry || null,
pubKey: hop.pubKey || null,
})) ?? [],
hops:
trackPaymentResult.htlcs[0].route?.hops?.map((hop) => ({
chanId: hop.chanId ?? null,
chanCapacity: hop.chanCapacity ?? null,
amtToForward: hop.amtToForward || Long.fromInt(0),
amtToForwardMsat: hop.amtToForwardMsat || Long.fromInt(0),
fee: hop.fee || Long.fromInt(0),
feeMsat: hop.feeMsat || Long.fromInt(0),
expiry: hop.expiry || null,
pubKey: hop.pubKey || null,
})) ?? [],
};
// tslint:disable-next-line
updateTransaction(db, updated).then(() => actions.updateTransaction({ transaction: updated }));
updateTransaction(db, updated).then(() =>
actions.updateTransaction({ transaction: updated }),
);
} else if (trackPaymentResult.status === lnrpc.Payment.PaymentStatus.UNKNOWN) {
log.i("trackpayment updating tx [unknown]");
const updated: ITransaction = {
...tx,
status: "UNKNOWN",
};
// tslint:disable-next-line
updateTransaction(db, updated).then(() => actions.updateTransaction({ transaction: updated }));
updateTransaction(db, updated).then(() =>
actions.updateTransaction({ transaction: updated }),
);
} else if (trackPaymentResult.status === lnrpc.Payment.PaymentStatus.FAILED) {
log.i("trackpayment updating tx [failed]");
const updated: ITransaction = {
...tx,
status: "CANCELED",
};
// tslint:disable-next-line
updateTransaction(db, updated).then(() => actions.updateTransaction({ transaction: updated }));
updateTransaction(db, updated).then(() =>
actions.updateTransaction({ transaction: updated }),
);
}
});
} else {
const check = await lookupInvoice(tx.rHash);
if ((Date.now() / 1000) > (check.creationDate.add(check.expiry).toNumber())) {
if (Date.now() / 1000 > check.creationDate.add(check.expiry).toNumber()) {
const updated: ITransaction = {
...tx,
status: "EXPIRED",
Expand All @@ -164,8 +199,7 @@ export const transaction: ITransactionModel = {
updateTransaction(db, updated).then(() => {
actions.updateTransaction({ transaction: updated });
});
}
else if (check.settled) {
} else if (check.settled) {
const updated: ITransaction = {
...tx,
status: "SETTLED",
Expand All @@ -174,16 +208,17 @@ export const transaction: ITransactionModel = {
// TODO add valueUSD, valueFiat and valueFiatCurrency?
};
// tslint:disable-next-line
updateTransaction(db, updated).then(() => actions.updateTransaction({ transaction: updated }));
}
else if (check.state === lnrpc.Invoice.InvoiceState.CANCELED) {
updateTransaction(db, updated).then(() =>
actions.updateTransaction({ transaction: updated }),
);
} else if (check.state === lnrpc.Invoice.InvoiceState.CANCELED) {
const updated: ITransaction = {
...tx,
status: "CANCELED",
};
// tslint:disable-next-line
updateTransaction(db, updated).then(() => {
actions.updateTransaction({ transaction: updated })
actions.updateTransaction({ transaction: updated });
});
}
}
Expand All @@ -195,32 +230,30 @@ export const transaction: ITransactionModel = {
/**
* Set transactions to our transaction array
*/
setTransactions: action((state, transactions) => { state.transactions = transactions; }),
setTransactions: action((state, transactions) => {
state.transactions = transactions;
}),

transactions: [],
getTransactionByRHash: computed(
(state) => {
return (rHash: string) => {
return state.transactions.find((tx) => rHash === tx.rHash);
};
},
),

getTransactionByPreimage: computed(
(state) => {
return (preimage: Uint8Array) => {
return state.transactions.find((tx) => bytesToHexString(preimage) === bytesToHexString(tx.preimage));
};
},
),

getTransactionByPaymentRequest: computed(
(state) => {
return (paymentRequest: string) => {
return state.transactions.find((tx) => {
return paymentRequest === tx.paymentRequest;
});
};
},
),
getTransactionByRHash: computed((state) => {
return (rHash: string) => {
return state.transactions.find((tx) => rHash === tx.rHash);
};
}),

getTransactionByPreimage: computed((state) => {
return (preimage: Uint8Array) => {
return state.transactions.find(
(tx) => bytesToHexString(preimage) === bytesToHexString(tx.preimage),
);
};
}),

getTransactionByPaymentRequest: computed((state) => {
return (paymentRequest: string) => {
return state.transactions.find((tx) => {
return paymentRequest === tx.paymentRequest;
});
};
}),
};
5 changes: 5 additions & 0 deletions src/storage/database/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export interface IDBTransaction {
status: "ACCEPTED" | "CANCELED" | "OPEN" | "SETTLED" | "UNKNOWN" | "EXPIRED";
paymentRequest: string;
rHash: string;
ampInvoice: boolean;
nodeAliasCached: string | null;
payer: string | null;
valueUSD: number | null;
Expand Down Expand Up @@ -58,6 +59,7 @@ export interface ITransaction {
paymentRequest: string;
status: "ACCEPTED" | "CANCELED" | "OPEN" | "SETTLED" | "UNKNOWN" | "EXPIRED"; // Note: EXPIRED does not exist in lnd
rHash: string;
ampInvoice: boolean;
niteshbalusu11 marked this conversation as resolved.
Show resolved Hide resolved
nodeAliasCached: string | null;
payer?: string | null;
valueUSD: number | null;
Expand Down Expand Up @@ -172,6 +174,7 @@ export const createTransaction = async (
?,
?,
?,
?,
?
)`,
[
Expand All @@ -189,6 +192,7 @@ export const createTransaction = async (
transaction.status,
transaction.paymentRequest,
transaction.rHash,
transaction.ampInvoice.toString(),
transaction.nodeAliasCached ?? null,
transaction.payer ?? null,
transaction.valueUSD,
Expand Down Expand Up @@ -389,6 +393,7 @@ const convertDBTransaction = (transaction: IDBTransaction): ITransaction => {
paymentRequest: transaction.paymentRequest,
status: transaction.status,
rHash: transaction.rHash,
ampInvoice: transaction.ampInvoice,
nodeAliasCached: transaction.nodeAliasCached,
payer: transaction.payer,
valueUSD: transaction.valueUSD,
Expand Down
1 change: 1 addition & 0 deletions src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const TLV_RECORD_NAME = 128101;
export const TLV_KEYSEND = 5482373484;
export const TLV_WHATSAT_MESSAGE = 34349334;
export const TLV_SATOGRAM = 6789998212;
export const AMP_FEATURE_BIT = "30";

export const GITHUB_REPO_URL = "https://github.com/hsjoberg/blixt-wallet";
export const HAMPUS_EMAIL = "mailto:hampus.sjoberg💩protonmail.com".replace("💩", "@");
Expand Down
Loading
Loading