* remove unused imports * remove legacy liquid navigation listener * add extra details prop to spark receive payment wrapper * improve robusness of liquid swaps * bump breez sdk + change swap message * making sure to only mark failed if payment truly failed * improve fee
785 lines
25 KiB
JavaScript
785 lines
25 KiB
JavaScript
import {
|
|
getSparkBitcoinPaymentFeeEstimate,
|
|
getSparkLightningPaymentFeeEstimate,
|
|
getSparkPaymentFeeEstimate,
|
|
getSparkStaticBitcoinL1Address,
|
|
receiveSparkLightningPayment,
|
|
receiveSparkHodlLightningPayment,
|
|
sendSparkBitcoinPayment,
|
|
sendSparkLightningPayment,
|
|
sendSparkPayment,
|
|
getSparkAddress,
|
|
sparkWallet,
|
|
sendSparkTokens,
|
|
getSingleTxDetails,
|
|
getSparkPaymentStatus,
|
|
} from '.';
|
|
import {
|
|
isSendingPayingEventEmiiter,
|
|
SENDING_PAYMENT_EVENT_NAME,
|
|
} from '../../../context-store/sparkContext';
|
|
import {
|
|
DEFAULT_PAYMENT_EXPIRY_SEC,
|
|
IS_SPARK_ID,
|
|
SPEND_AND_REPLACE_STORAGE_KEY,
|
|
USDB_TOKEN_ID,
|
|
} from '../../constants';
|
|
import sha256Hash from '../hash';
|
|
import calculateProgressiveBracketFee from './calculateSupportFee';
|
|
import {
|
|
calculateFlashnetAmountIn,
|
|
dollarsToSats,
|
|
executeSwap,
|
|
getUserSwapHistory,
|
|
payLightningWithToken,
|
|
satsToDollars,
|
|
USD_ASSET_ADDRESS,
|
|
} from './flashnet';
|
|
import {
|
|
abortOptimization,
|
|
isOptimizationRunning,
|
|
scheduleOptimization,
|
|
} from '../spark/optimization';
|
|
import { setFlashnetTransfer } from './handleFlashnetTransferIds';
|
|
import {
|
|
addSingleUnpaidSparkLightningTransaction,
|
|
bulkUpdateSparkTransactions,
|
|
} from './transactions';
|
|
import { getLocalStorageItem } from '../localStorage';
|
|
|
|
export const sparkPaymenWrapper = async ({
|
|
getFee = false,
|
|
address = '',
|
|
paymentType,
|
|
amountSats = 0,
|
|
exitSpeed = 'FAST',
|
|
masterInfoObject,
|
|
fee,
|
|
memo = '',
|
|
userBalance = 0,
|
|
sparkInformation,
|
|
feeQuote,
|
|
usingZeroAmountInvoice = false,
|
|
seletctedToken = 'Bitcoin',
|
|
mnemonic,
|
|
sendWebViewRequest,
|
|
contactInfo,
|
|
fromMainSendScreen = false,
|
|
usablePaymentMethod = 'BTC',
|
|
swapPaymentQuote = {},
|
|
paymentInfo = {},
|
|
fiatValueConvertedSendAmount,
|
|
poolInfoRef,
|
|
extraDetails = {},
|
|
}) => {
|
|
try {
|
|
console.log('Begining spark payment');
|
|
let isSpendAndReplaceEnabled;
|
|
try {
|
|
isSpendAndReplaceEnabled = JSON.parse(
|
|
await getLocalStorageItem(SPEND_AND_REPLACE_STORAGE_KEY),
|
|
).isEnabled;
|
|
} catch (err) {
|
|
isSpendAndReplaceEnabled = false;
|
|
}
|
|
|
|
const sendingUUID =
|
|
contactInfo?.uuid || paymentInfo?.blitzContactInfo?.uuid || '';
|
|
|
|
if (!getFee && (await isOptimizationRunning(mnemonic))) {
|
|
console.log(
|
|
'Optimization in progress, aborting immediately for payment...',
|
|
);
|
|
await abortOptimization(mnemonic);
|
|
// Small delay to ensure abort completes
|
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
}
|
|
// if (!sparkWallet[sha256Hash(mnemonic)])
|
|
// throw new Error('sparkWallet not initialized');
|
|
const supportFee = 0;
|
|
if (getFee) {
|
|
console.log('Calculating spark payment fee');
|
|
let calculatedFee = 0;
|
|
let tempFeeQuote;
|
|
if (paymentType === 'lightning') {
|
|
const routingFee = await getSparkLightningPaymentFeeEstimate(
|
|
address,
|
|
amountSats,
|
|
mnemonic,
|
|
);
|
|
|
|
if (!routingFee.didWork)
|
|
throw new Error(routingFee.error || 'Unable to get routing fee');
|
|
calculatedFee = routingFee.response; //Math.ceil(routingFee.response * 1.5); //addding 50% buffer so we dont undershoot it
|
|
} else if (paymentType === 'bitcoin') {
|
|
const feeResponse = await getSparkBitcoinPaymentFeeEstimate({
|
|
amountSats,
|
|
withdrawalAddress: address,
|
|
mnemonic,
|
|
});
|
|
|
|
if (!feeResponse.didWork)
|
|
throw new Error(
|
|
feeResponse.error || 'Unable to get Bitcoin fee estimation',
|
|
);
|
|
console.log(feeResponse, 'onchain fee quote');
|
|
const data = feeResponse.response;
|
|
calculatedFee =
|
|
(data.l1BroadcastFeeFast?.originalValue || 0) +
|
|
(data.userFeeFast?.originalValue || 0);
|
|
tempFeeQuote = data;
|
|
} else if (paymentType === 'lrc20') {
|
|
calculatedFee = 0;
|
|
} else {
|
|
// Spark payments
|
|
const feeResponse = await getSparkPaymentFeeEstimate(
|
|
amountSats,
|
|
mnemonic,
|
|
);
|
|
calculatedFee = feeResponse;
|
|
}
|
|
return {
|
|
didWork: true,
|
|
fee: Math.round(calculatedFee),
|
|
supportFee: Math.round(supportFee),
|
|
feeQuote: tempFeeQuote,
|
|
};
|
|
}
|
|
let response;
|
|
let amountOutSats = amountSats;
|
|
let amountOutMicrodollars;
|
|
// if (
|
|
// seletctedToken === 'Bitcoin' &&
|
|
// userBalance < amountSats + (paymentType === 'bitcoin' ? supportFee : fee)
|
|
// )
|
|
// throw new Error('Insufficient funds');
|
|
|
|
isSendingPayingEventEmiiter.emit(SENDING_PAYMENT_EVENT_NAME, true);
|
|
|
|
if (paymentType === 'lightning') {
|
|
if (usablePaymentMethod === 'USD') {
|
|
const swapPaymentResponse = await payLightningWithToken(mnemonic, {
|
|
invoice: address,
|
|
tokenAddress: USD_ASSET_ADDRESS,
|
|
quote: swapPaymentQuote,
|
|
amountSats,
|
|
refundAddress: sparkInformation?.sparkAddress,
|
|
});
|
|
|
|
if (!swapPaymentResponse.didWork)
|
|
throw new Error(
|
|
swapPaymentResponse.error ||
|
|
'Error when sending lightning payment from USD balance',
|
|
);
|
|
|
|
const now = Date.now();
|
|
const tx = {
|
|
id: swapPaymentResponse.result.swapTransferId,
|
|
paymentStatus: 'pending',
|
|
paymentType: 'spark',
|
|
accountId: sparkInformation.identityPubKey,
|
|
details: {
|
|
sendingUUID,
|
|
fee: Math.round(
|
|
dollarsToSats(
|
|
swapPaymentResponse.result.ammFeePaid / 1000000,
|
|
poolInfoRef.currentPriceAInB,
|
|
),
|
|
),
|
|
totalFee: Math.round(
|
|
dollarsToSats(
|
|
swapPaymentResponse.result.ammFeePaid / 1000000,
|
|
poolInfoRef.currentPriceAInB,
|
|
),
|
|
),
|
|
supportFee: 0,
|
|
amount:
|
|
swapPaymentResponse.result.tokenAmountSpent -
|
|
swapPaymentResponse.result.ammFeePaid,
|
|
description: memo || '',
|
|
address: swapPaymentResponse.result.depositAddress,
|
|
sourceSparkAddress: swapPaymentResponse.result.sourceSparkAddress,
|
|
time: now,
|
|
createdAt: now,
|
|
direction: 'OUTGOING',
|
|
preimage: '',
|
|
isLRC20Payment: true,
|
|
LRC20Token: USDB_TOKEN_ID,
|
|
isFlashnetStablecoin: true,
|
|
quoteId: swapPaymentResponse.result.quoteId,
|
|
destinationAddress: address,
|
|
destinationChain: 'lightning',
|
|
destinationAsset: 'BTC',
|
|
sourceMethod: 'USD',
|
|
...(paymentInfo?.data?.successAction
|
|
? { successAction: paymentInfo.data.successAction }
|
|
: {}),
|
|
...(paymentInfo.blitzContactInfo
|
|
? { remoteContactPayment: paymentInfo.blitzContactInfo }
|
|
: {}),
|
|
},
|
|
};
|
|
response = tx;
|
|
} else {
|
|
const initialFee = Math.round(fee - supportFee);
|
|
|
|
// await handleSupportPayment(masterInfoObject, supportFee, mnemonic);
|
|
|
|
const lightningPayResponse = await sendSparkLightningPayment({
|
|
maxFeeSats: Math.max(initialFee, 1000),
|
|
invoice: address,
|
|
amountSats: usingZeroAmountInvoice ? Number(amountSats) : undefined,
|
|
mnemonic,
|
|
});
|
|
|
|
if (!lightningPayResponse.didWork)
|
|
throw new Error(
|
|
lightningPayResponse.error ||
|
|
'Error when sending lightning payment',
|
|
);
|
|
|
|
console.log(lightningPayResponse, 'lightniing pay response');
|
|
const data = lightningPayResponse.paymentResponse;
|
|
|
|
// check if lightning payment used LN or handled over spark
|
|
const paymentType = !!data?.type ? 'spark' : 'lightning';
|
|
|
|
if (paymentType === 'lightning') {
|
|
const realPaymentFee = data?.fee?.originalValue
|
|
? data?.fee?.originalValue /
|
|
(data?.fee?.originalUnit === 'MILLISATOSHI' ? 1000 : 1)
|
|
: initialFee;
|
|
|
|
const tx = {
|
|
id: data.id,
|
|
paymentStatus: 'pending',
|
|
paymentType: 'lightning',
|
|
accountId: sparkInformation.identityPubKey,
|
|
details: {
|
|
sendingUUID,
|
|
fee: realPaymentFee,
|
|
totalFee: supportFee + realPaymentFee,
|
|
supportFee: supportFee,
|
|
amount: amountSats,
|
|
description: memo || '',
|
|
address: address,
|
|
time: new Date(data.updatedAt).getTime(),
|
|
createdAt: new Date(data.createdAt).getTime(),
|
|
direction: 'OUTGOING',
|
|
preimage: '',
|
|
...(paymentInfo?.data?.successAction
|
|
? { successAction: paymentInfo.data.successAction }
|
|
: {}),
|
|
...(paymentInfo.blitzContactInfo
|
|
? { remoteContactPayment: paymentInfo.blitzContactInfo }
|
|
: {}),
|
|
...(isSpendAndReplaceEnabled
|
|
? {
|
|
isBitcoinFundedSend: true,
|
|
}
|
|
: {}),
|
|
},
|
|
};
|
|
response = tx;
|
|
} else {
|
|
const tx = {
|
|
id: data.id,
|
|
paymentStatus: 'completed',
|
|
paymentType: 'spark',
|
|
accountId: sparkInformation.identityPubKey,
|
|
details: {
|
|
sendingUUID,
|
|
fee: 0,
|
|
totalFee: 0 + supportFee,
|
|
supportFee: supportFee,
|
|
amount: amountSats,
|
|
address: address,
|
|
time: new Date(data.updatedTime).getTime(),
|
|
direction: 'OUTGOING',
|
|
description: memo || '',
|
|
senderIdentityPublicKey: data.receiverIdentityPublicKey,
|
|
isLRC20Payment: false,
|
|
LRC20Token: seletctedToken,
|
|
...(paymentInfo?.data?.successAction
|
|
? { successAction: paymentInfo.data.successAction }
|
|
: {}),
|
|
...(paymentInfo.blitzContactInfo
|
|
? { remoteContactPayment: paymentInfo.blitzContactInfo }
|
|
: {}),
|
|
...(isSpendAndReplaceEnabled
|
|
? {
|
|
isBitcoinFundedSend: true,
|
|
}
|
|
: {}),
|
|
},
|
|
};
|
|
response = tx;
|
|
}
|
|
}
|
|
} else if (paymentType === 'bitcoin') {
|
|
// make sure to import exist speed
|
|
// await handleSupportPayment(masterInfoObject, supportFee, mnemonic);
|
|
|
|
const feeFromQuote =
|
|
(feeQuote.l1BroadcastFeeFast?.originalValue || 0) +
|
|
(feeQuote.userFeeFast?.originalValue || 0);
|
|
const deductFeeFromAmount =
|
|
(amountSats + feeFromQuote) * 1.1 >= userBalance;
|
|
|
|
const onChainPayResponse = await sendSparkBitcoinPayment({
|
|
onchainAddress: address,
|
|
exitSpeed,
|
|
amountSats,
|
|
feeQuote,
|
|
deductFeeFromWithdrawalAmount: deductFeeFromAmount,
|
|
mnemonic,
|
|
});
|
|
|
|
if (!onChainPayResponse.didWork)
|
|
throw new Error(
|
|
onChainPayResponse.error || 'Error when sending bitcoin payment',
|
|
);
|
|
|
|
console.log(onChainPayResponse, 'on-chain pay response');
|
|
const data = onChainPayResponse.response;
|
|
|
|
const tx = {
|
|
id: data.id,
|
|
paymentStatus: 'pending',
|
|
paymentType: 'bitcoin',
|
|
accountId: sparkInformation.identityPubKey,
|
|
details: {
|
|
fee: fee,
|
|
totalFee: supportFee + fee,
|
|
supportFee: supportFee,
|
|
amount: amountSats,
|
|
address: address,
|
|
time: new Date(data.updatedAt).getTime(),
|
|
direction: 'OUTGOING',
|
|
description: memo || '',
|
|
onChainTxid: data.coopExitTxid || '',
|
|
...(isSpendAndReplaceEnabled
|
|
? {
|
|
isBitcoinFundedSend: true,
|
|
}
|
|
: {}),
|
|
},
|
|
};
|
|
response = tx;
|
|
} else {
|
|
let executionResponse;
|
|
|
|
const expectedReceiveType = paymentInfo?.data?.expectedReceive || 'sats';
|
|
const needsSwap =
|
|
((usablePaymentMethod === 'USD' && expectedReceiveType === 'sats') ||
|
|
(usablePaymentMethod === 'BTC' &&
|
|
expectedReceiveType === 'tokens')) &&
|
|
seletctedToken === 'Bitcoin';
|
|
|
|
let isSwap = false;
|
|
let usedUSDB = false;
|
|
if (needsSwap) {
|
|
if (usablePaymentMethod === 'USD') {
|
|
//add fee here
|
|
const dollarFee = satsToDollars(
|
|
swapPaymentQuote.satFee,
|
|
poolInfoRef.currentPriceAInB,
|
|
);
|
|
const formatted = calculateFlashnetAmountIn({
|
|
baseAmountIn: swapPaymentQuote.amountIn + dollarFee,
|
|
isUsdAssetIn: true,
|
|
dollarBalanceSat: swapPaymentQuote.dollarBalanceSat,
|
|
currentPriceAInB: poolInfoRef.currentPriceAInB,
|
|
});
|
|
executionResponse = await executeSwap(mnemonic, {
|
|
poolId: swapPaymentQuote.poolId,
|
|
assetInAddress: swapPaymentQuote.assetInAddress,
|
|
assetOutAddress: swapPaymentQuote.assetOutAddress,
|
|
amountIn: formatted,
|
|
});
|
|
usedUSDB = true;
|
|
} else {
|
|
const formatted = calculateFlashnetAmountIn({
|
|
baseAmountIn: swapPaymentQuote.amountIn + swapPaymentQuote.satFee,
|
|
isUsdAssetIn: false,
|
|
maxBalance: swapPaymentQuote.bitcoinBalance,
|
|
});
|
|
executionResponse = await executeSwap(mnemonic, {
|
|
poolId: swapPaymentQuote.poolId,
|
|
assetInAddress: swapPaymentQuote.assetInAddress,
|
|
assetOutAddress: swapPaymentQuote.assetOutAddress,
|
|
amountIn: formatted,
|
|
});
|
|
}
|
|
|
|
if (!executionResponse.didWork)
|
|
throw new Error(executionResponse.error);
|
|
|
|
const outboundTransferId = executionResponse.swap.outboundTransferId;
|
|
setFlashnetTransfer(outboundTransferId);
|
|
|
|
const userSwaps = await getUserSwapHistory(mnemonic, 5);
|
|
|
|
if (userSwaps.didWork) {
|
|
const swap = userSwaps.swaps.find(
|
|
savedSwap => savedSwap.outboundTransferId === outboundTransferId,
|
|
);
|
|
|
|
if (swap) {
|
|
setFlashnetTransfer(swap.inboundTransferId);
|
|
}
|
|
}
|
|
|
|
const MAX_WAIT_TIME = 60000;
|
|
const startTime = Date.now();
|
|
|
|
while (true) {
|
|
if (Date.now() - startTime > MAX_WAIT_TIME) {
|
|
throw new Error('Swap completion timeout');
|
|
}
|
|
|
|
if (!IS_SPARK_ID.test(outboundTransferId)) {
|
|
await new Promise(res => setTimeout(res, 2500));
|
|
break;
|
|
}
|
|
|
|
const sparkTransferResponse = await getSingleTxDetails(
|
|
mnemonic,
|
|
outboundTransferId,
|
|
);
|
|
|
|
const status = getSparkPaymentStatus(sparkTransferResponse?.status);
|
|
if (status === 'completed') break;
|
|
|
|
console.log('Swap is not complete, waiting for completion');
|
|
await new Promise(res => setTimeout(res, 1500));
|
|
}
|
|
|
|
isSwap = true;
|
|
// small buffer to help smooth things out
|
|
await new Promise(res => setTimeout(res, 1500));
|
|
}
|
|
|
|
let sparkPayResponse;
|
|
let useLRC20Format = false;
|
|
const maxAttempts = isSwap ? 3 : 1;
|
|
|
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
if (attempt > 0) {
|
|
await new Promise(res => setTimeout(res, 3500));
|
|
}
|
|
|
|
if (expectedReceiveType === 'tokens' && seletctedToken === 'Bitcoin') {
|
|
const originalSendAmount =
|
|
satsToDollars(amountSats, poolInfoRef.currentPriceAInB).toFixed(3) *
|
|
Math.pow(10, 6);
|
|
const usdbAmount = isSwap
|
|
? executionResponse.swap.amountOut >= originalSendAmount
|
|
? originalSendAmount
|
|
: executionResponse.swap.amountOut
|
|
: fiatValueConvertedSendAmount;
|
|
amountOutMicrodollars = usdbAmount;
|
|
useLRC20Format = true;
|
|
sparkPayResponse = await sendSparkTokens({
|
|
tokenIdentifier: USDB_TOKEN_ID,
|
|
tokenAmount: Number(Math.ceil(usdbAmount)),
|
|
receiverSparkAddress: address,
|
|
mnemonic,
|
|
});
|
|
} else if (seletctedToken !== 'Bitcoin') {
|
|
useLRC20Format = true;
|
|
sparkPayResponse = await sendSparkTokens({
|
|
tokenIdentifier: seletctedToken,
|
|
tokenAmount: Number(amountSats),
|
|
receiverSparkAddress: address,
|
|
mnemonic,
|
|
});
|
|
} else {
|
|
const finalSatAmount = isSwap
|
|
? executionResponse.swap.amountOut >= amountSats
|
|
? amountSats
|
|
: executionResponse.swap.amountOut
|
|
: amountSats;
|
|
amountOutSats = finalSatAmount;
|
|
sparkPayResponse = await sendSparkPayment({
|
|
receiverSparkAddress: address,
|
|
amountSats: Number(finalSatAmount),
|
|
mnemonic,
|
|
});
|
|
}
|
|
|
|
if (sparkPayResponse.didWork) break;
|
|
|
|
if (attempt === maxAttempts - 1) {
|
|
throw new Error(
|
|
sparkPayResponse.error || 'Error when sending spark payment',
|
|
);
|
|
}
|
|
}
|
|
|
|
// await handleSupportPayment(masterInfoObject, supportFee, mnemonic);
|
|
|
|
const data = sparkPayResponse.response;
|
|
|
|
const formattedToken =
|
|
seletctedToken !== 'Bitcoin'
|
|
? seletctedToken
|
|
: expectedReceiveType === 'tokens'
|
|
? USDB_TOKEN_ID
|
|
: '';
|
|
|
|
const tx = {
|
|
id: useLRC20Format ? data : data.id,
|
|
paymentStatus: 'completed',
|
|
paymentType: 'spark',
|
|
accountId: sparkInformation.identityPubKey,
|
|
details: {
|
|
sendingUUID,
|
|
fee: 0,
|
|
totalFee: 0 + supportFee,
|
|
supportFee: supportFee,
|
|
amount: amountSats,
|
|
address: address,
|
|
time: useLRC20Format
|
|
? new Date().getTime()
|
|
: new Date(data.updatedTime).getTime(),
|
|
direction: 'OUTGOING',
|
|
description: memo || '',
|
|
senderIdentityPublicKey: useLRC20Format
|
|
? ''
|
|
: data.receiverIdentityPublicKey,
|
|
isLRC20Payment: useLRC20Format,
|
|
LRC20Token: formattedToken,
|
|
...(paymentInfo.blitzContactInfo
|
|
? { remoteContactPayment: paymentInfo.blitzContactInfo }
|
|
: {}),
|
|
},
|
|
};
|
|
|
|
if (isSwap) {
|
|
if (usedUSDB) {
|
|
tx.details.isLRC20Payment = true;
|
|
tx.details.LRC20Token = USDB_TOKEN_ID;
|
|
tx.details.fee =
|
|
tx.details.fee +
|
|
dollarsToSats(
|
|
executionResponse.swap.feeAmount / Math.pow(10, 6),
|
|
poolInfoRef.currentPriceAInB,
|
|
);
|
|
tx.details.totalFee = tx.details.fee;
|
|
|
|
tx.details.amount = swapPaymentQuote.amountIn;
|
|
} else {
|
|
tx.details.isLRC20Payment = false;
|
|
tx.details.LRC20Token = '';
|
|
tx.details.fee =
|
|
tx.details.fee +
|
|
dollarsToSats(
|
|
executionResponse.swap.feeAmount / Math.pow(10, 6),
|
|
poolInfoRef.currentPriceAInB,
|
|
);
|
|
tx.details.totalFee = tx.details.fee;
|
|
tx.details.amount = amountSats;
|
|
}
|
|
} else if (
|
|
expectedReceiveType === 'tokens' &&
|
|
seletctedToken === 'Bitcoin'
|
|
) {
|
|
const usdbAmount = isSwap
|
|
? executionResponse.swap.amountOut
|
|
: fiatValueConvertedSendAmount;
|
|
tx.details.amount = usdbAmount;
|
|
}
|
|
|
|
// Mark only plain Bitcoin-funded sats sends (not token sends)
|
|
// as eligible for spend-and-replace.
|
|
if (
|
|
isSpendAndReplaceEnabled &&
|
|
usablePaymentMethod === 'BTC' &&
|
|
seletctedToken === 'Bitcoin'
|
|
) {
|
|
tx.details.isBitcoinFundedSend = true;
|
|
}
|
|
response = tx;
|
|
}
|
|
|
|
console.log(
|
|
response,
|
|
'resonse in send function',
|
|
sparkInformation.identityPubKey,
|
|
);
|
|
// Only save immediately if we have identityPubKey (otherwise the tx will not show up)
|
|
if (sparkInformation.identityPubKey) {
|
|
if (extraDetails && Object.keys(extraDetails).length > 0) {
|
|
response = {
|
|
...response,
|
|
details: { ...response.details, ...extraDetails },
|
|
};
|
|
}
|
|
await bulkUpdateSparkTransactions([response], 'paymentWrapperTx', 0);
|
|
}
|
|
|
|
if (!getFee) {
|
|
console.log('Scheduling post-payment optimization...');
|
|
scheduleOptimization(mnemonic, sparkInformation.identityPubKey, 2000);
|
|
}
|
|
return {
|
|
didWork: true,
|
|
response,
|
|
shouldSave: !sparkInformation.identityPubKey,
|
|
amountOutSats,
|
|
amountOutMicrodollars,
|
|
};
|
|
} catch (err) {
|
|
console.log('Send lightning payment error', err);
|
|
return { didWork: false, error: err.message };
|
|
} finally {
|
|
// Only emit if we processed a payment and have identityPubKey (will handle situation with no identiy pubkey on send screen)
|
|
if (
|
|
!getFee &&
|
|
(!fromMainSendScreen || // not the main send screen → fire always
|
|
sparkInformation?.identityPubKey) // on main send → only fire if identityPubKey exists
|
|
) {
|
|
isSendingPayingEventEmiiter.emit(SENDING_PAYMENT_EVENT_NAME, false);
|
|
}
|
|
}
|
|
};
|
|
|
|
export const sparkReceivePaymentWrapper = async ({
|
|
amountSats,
|
|
memo,
|
|
paymentType,
|
|
shouldNavigate,
|
|
mnemoinc,
|
|
sendWebViewRequest,
|
|
performSwaptoUSD = false,
|
|
includeSparkAddress = true,
|
|
expirySeconds,
|
|
isHoldInvoice = false,
|
|
paymentHash,
|
|
holdExpirySeconds,
|
|
encryptedPreimage,
|
|
extraDetails = {},
|
|
}) => {
|
|
try {
|
|
// if (!sparkWallet[sha256Hash(mnemoinc)])
|
|
// throw new Error('sparkWallet not initialized');
|
|
|
|
if (paymentType === 'lightning') {
|
|
if (isHoldInvoice) {
|
|
const invoiceResponse = await receiveSparkHodlLightningPayment({
|
|
amountSats,
|
|
paymentHash,
|
|
memo,
|
|
expirySeconds: holdExpirySeconds,
|
|
mnemonic: mnemoinc,
|
|
});
|
|
if (!invoiceResponse.didWork) throw new Error(invoiceResponse.error);
|
|
const invoice = invoiceResponse.response;
|
|
const tempTransaction = {
|
|
id: invoice.id,
|
|
amount: amountSats,
|
|
expiration: invoice.invoice.expiresAt,
|
|
description: memo || '',
|
|
shouldNavigate,
|
|
details: {
|
|
createdTime: new Date(invoice.createdAt).getTime(),
|
|
isLNURL: false,
|
|
shouldNavigate: true,
|
|
isBlitzContactPayment: false,
|
|
performSwaptoUSD: false,
|
|
isHoldInvoice: true,
|
|
encryptedPreimage,
|
|
paymentHash,
|
|
},
|
|
};
|
|
await addSingleUnpaidSparkLightningTransaction(tempTransaction);
|
|
return {
|
|
didWork: true,
|
|
data: invoice,
|
|
invoice: invoice.invoice.encodedInvoice,
|
|
};
|
|
}
|
|
|
|
const invoiceResponse = await receiveSparkLightningPayment({
|
|
amountSats,
|
|
memo,
|
|
mnemonic: mnemoinc,
|
|
includeSparkAddress,
|
|
expirySeconds,
|
|
});
|
|
|
|
if (!invoiceResponse.didWork) throw new Error(invoiceResponse.error);
|
|
const invoice = invoiceResponse.response;
|
|
|
|
const tempTransaction = {
|
|
id: invoice.id,
|
|
amount: amountSats,
|
|
expiration: invoice.invoice.expiresAt,
|
|
description: memo || '',
|
|
shouldNavigate,
|
|
details: {
|
|
createdTime: new Date(invoice.createdAt).getTime(),
|
|
isLNURL: false,
|
|
shouldNavigate: true,
|
|
isBlitzContactPayment: false,
|
|
performSwaptoUSD,
|
|
...extraDetails,
|
|
},
|
|
};
|
|
await addSingleUnpaidSparkLightningTransaction(tempTransaction);
|
|
return {
|
|
didWork: true,
|
|
data: invoice,
|
|
invoice: invoice.invoice.encodedInvoice,
|
|
};
|
|
} else if (paymentType === 'bitcoin') {
|
|
// Handle storage of tx when claiming in spark context
|
|
const depositAddress = await getSparkStaticBitcoinL1Address(mnemoinc);
|
|
if (!depositAddress)
|
|
throw new Error('Not able to generate bitcoin address');
|
|
return {
|
|
didWork: true,
|
|
invoice: depositAddress,
|
|
};
|
|
} else {
|
|
// No need to save address since it is constant
|
|
const sparkAddress = await getSparkAddress(mnemoinc);
|
|
if (!sparkAddress.didWork) throw new Error(sparkAddress.error);
|
|
|
|
const data = sparkAddress.response;
|
|
|
|
return {
|
|
didWork: true,
|
|
invoice: data,
|
|
};
|
|
}
|
|
} catch (err) {
|
|
console.log('Receive spark payment error', err);
|
|
return { didWork: false, error: err.message };
|
|
}
|
|
};
|
|
|
|
async function handleSupportPayment(masterInfoObject, supportFee, mnemonic) {
|
|
try {
|
|
if (!supportFee) return;
|
|
if (masterInfoObject?.enabledDeveloperSupport?.isEnabled) {
|
|
// const txPromise = sendSparkPayment({
|
|
// receiverSparkAddress: process.env.BLITZ_SPARK_SUPPORT_ADDRESSS,
|
|
// amountSats: supportFee,
|
|
// mnemonic,
|
|
// });
|
|
// await Promise.race([
|
|
// txPromise,
|
|
// new Promise(res => setTimeout(res, 30000)),
|
|
// ]);
|
|
// txPromise.catch(err =>
|
|
// console.log('Error sending support payment (late)', err),
|
|
// );
|
|
// await new Promise(res => setTimeout(res, 800)); // wait a bit
|
|
}
|
|
} catch (err) {
|
|
console.log('Error sending support payment', err);
|
|
}
|
|
}
|