Skip to content

Commit

Permalink
Remembers invoice (#521)
Browse files Browse the repository at this point in the history
* New update strategy for refundable swaps

* bug fixes on tx list

* new func findSwapWithInvoice on RefundableSwapsRepository

* remembers invoice and don't ask for swap again

* fix test
  • Loading branch information
bordalix authored Jan 3, 2024
1 parent 720617f commit 54259aa
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 42 deletions.
19 changes: 16 additions & 3 deletions src/application/updater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,14 @@ export class UpdaterService {
}
}

// check if something changed on submarine swaps made in the past
// these swaps can be in 2 different scenarios:
// - were broadcasted (have a txid)
// - remove them if they were swepts by Boltz (spent, not refundable)
// - user didn't went through and didn't broadcasted the tx
// - kept in cache cause we can't ask Boltz for another swap with
// the same invoice, so we will use this values if that happens
// - after invoice expiration date these swaps are removed from storage
async checkRefundableSwaps(network: NetworkString) {
this.processingCount += 1;
try {
Expand All @@ -97,9 +105,14 @@ export class UpdaterService {
if (!chainSource) throw new Error('Chain source not found for network ' + network);
for (const swap of swaps) {
if (!swap.redeemScript || swap.network !== network) return;
const fundingAddress = addressFromScript(swap.redeemScript);
const [utxo] = await chainSource.listUnspents(fundingAddress);
if (!utxo) await this.refundableSwapsRepository.removeSwap(swap);
if (swap.txid) {
const fundingAddress = addressFromScript(swap.redeemScript);
const [utxo] = await chainSource.listUnspents(fundingAddress);
if (!utxo) await this.refundableSwapsRepository.removeSwap(swap);
} else if (swap.expirationDate) {
const now = Date.now();
if (now > swap.expirationDate) await this.refundableSwapsRepository.removeSwap(swap);
}
}
} finally {
this.processingCount -= 1;
Expand Down
6 changes: 5 additions & 1 deletion src/domain/repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,9 +287,12 @@ export interface RefundableSwapParams {
blindingKey: string;
confidentialAddress?: string;
derivationPath?: string;
expectedAmount?: number;
expirationDate?: number;
fundingAddress?: string;
id?: string;
network?: NetworkString;
invoice?: string;
network: NetworkString;
redeemScript: string;
refundPublicKey?: string;
timeoutBlockHeight?: number;
Expand All @@ -300,6 +303,7 @@ export interface RefundableSwapParams {
export interface RefundableSwapsRepository {
addSwap(swap: RefundableSwapParams): Promise<void>;
findSwapWithAddress(address: string): Promise<RefundableSwapParams | undefined>;
findSwapWithInvoice(invoice: string): Promise<RefundableSwapParams | undefined>;
findSwapWithTxid(txid: string): Promise<RefundableSwapParams | undefined>;
getSwaps(): Promise<RefundableSwapParams[]>;
updateSwap(swap: RefundableSwapParams): Promise<void>;
Expand Down
4 changes: 2 additions & 2 deletions src/extension/components/button-transaction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ const ButtonTransaction: React.FC<Props> = ({ assetSelected, swap, txDetails }:
: '??'}{' '}
{assetSelected.ticker}
</span>
{swap && confirmed && (
{swap && Boolean(confirmed) && (
<span className="bg-smokeLight text-xxs px-1 py-0 font-semibold text-white rounded-full">
Refundable
</span>
Expand Down Expand Up @@ -150,7 +150,7 @@ const ButtonTransaction: React.FC<Props> = ({ assetSelected, swap, txDetails }:
<p className="wrap text-xs font-light break-all">{txDetails.txid}</p>
</div>
</div>
{swap ? (
{swap && confirmed ? (
<div className="flex justify-between">
<Button
isOutline={true}
Expand Down
98 changes: 62 additions & 36 deletions src/extension/wallet/send/lightning-enter-invoice.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { networks } from 'liquidjs-lib';
import { fromSatoshi, toSatoshi } from '../../utility';
import { AccountFactory, MainAccount, MainAccountTest } from '../../../application/account';
import { useStorageContext } from '../../context/storage-context';
import type { BoltzPair } from '../../../pkg/boltz';
import type { BoltzPair, SubmarineSwap } from '../../../pkg/boltz';
import { Boltz, boltzUrl } from '../../../pkg/boltz';
import zkp from '@vulpemventures/secp256k1-zkp';
import { Spinner } from '../../components/spinner';
Expand Down Expand Up @@ -43,6 +43,47 @@ const LightningInvoice: React.FC = () => {
fetchData().catch(console.error);
}, []);

const makeSwap = async (): Promise<SubmarineSwap | undefined> => {
try {
// get account
const accountFactory = await AccountFactory.create(walletRepository);
const accountName = network === 'liquid' ? MainAccount : MainAccountTest;
const mainAccount = await accountFactory.make(network, accountName);

// get refund pub key and change address
const refundAddress = await mainAccount.getNextAddress(false);
const refundPublicKey = refundAddress.publicKey;

// create submarine swap
const swap = await boltz.createSubmarineSwap(invoice, network, refundPublicKey);
const { address, blindingKey, expectedAmount, id, redeemScript } = swap;

// calculate funding address (used for refunding case the swap fails)
const fundingAddress = addressFromScript(redeemScript, network);

const expirationDate = boltz.getInvoiceExpireDate(invoice);

// save swap params to storage
await refundableSwapsRepository.addSwap({
blindingKey,
confidentialAddress: address,
expectedAmount,
expirationDate,
id,
invoice,
fundingAddress,
redeemScript,
refundPublicKey,
network,
});

return swap;
} catch (err: any) {
setError(err.message);
setIsSubmitting(false);
}
};

const handleBackBtn = () => history.goBack();

const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
Expand All @@ -69,12 +110,16 @@ const LightningInvoice: React.FC = () => {
const value = boltz.getInvoiceValue(invoice);
const valueInSats = toSatoshi(value);
const fees = boltz.calcBoltzFees(pair, valueInSats);
const expirationDate = boltz.getInvoiceExpireDate(invoice);

setSwapFees(fees);
setValue(value);

const { minimal, maximal } = pair.limits;

// validate date
if (Date.now() > expirationDate) return setError('Expired invoice');

// validate value
if (Number.isNaN(value)) return setError('Invalid value');
if (valueInSats <= 0) return setError('Value must be positive');
Expand All @@ -95,42 +140,23 @@ const LightningInvoice: React.FC = () => {
const handleProceed = async () => {
setIsSubmitting(true);

// get account
const accountFactory = await AccountFactory.create(walletRepository);
const accountName = network === 'liquid' ? MainAccount : MainAccountTest;
const mainAccount = await accountFactory.make(network, accountName);

// get refund pub key and change address
const refundAddress = await mainAccount.getNextAddress(false);
const refundPublicKey = refundAddress.publicKey;

try {
// create submarine swap
const { address, blindingKey, expectedAmount, id, redeemScript } =
await boltz.createSubmarineSwap(invoice, network, refundPublicKey);

const fundingAddress = addressFromScript(redeemScript, network);

// push to storage payment to be made
await sendFlowRepository.setReceiverAddressAmount(address, expectedAmount); // TODO -21

// save swap params to storage
await refundableSwapsRepository.addSwap({
blindingKey,
confidentialAddress: address,
id,
fundingAddress,
redeemScript,
network,
});

// go to choose fee route
history.push(SEND_CHOOSE_FEE_ROUTE);
} catch (err: any) {
setError(err.message);
setIsSubmitting(false);
return;
// check if we have a swap already made for this invoice
const swapOnCache = await refundableSwapsRepository.findSwapWithInvoice(invoice);

if (swapOnCache?.confidentialAddress && swapOnCache.expectedAmount) {
await sendFlowRepository.setReceiverAddressAmount(
swapOnCache?.confidentialAddress,
swapOnCache.expectedAmount
);
} else {
const swap = await makeSwap();
if (!swap) return;
const { address, expectedAmount } = swap;
await sendFlowRepository.setReceiverAddressAmount(address, expectedAmount);
}

// go to choose fee route
history.push(SEND_CHOOSE_FEE_ROUTE);
};

const isButtonDisabled = () => Boolean(!invoice || error || isSubmitting);
Expand Down
4 changes: 4 additions & 0 deletions src/infrastructure/storage/refundable-swaps-repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ export class RefundableSwapsStorageAPI implements RefundableSwapsRepository {
);
}

async findSwapWithInvoice(invoice: string): Promise<RefundableSwapParams | undefined> {
return (await this.getSwapData()).find((s) => s.invoice === invoice);
}

async findSwapWithTxid(txid: string): Promise<RefundableSwapParams | undefined> {
return (await this.getSwapData()).find((s) => s.txid === txid);
}
Expand Down

0 comments on commit 54259aa

Please sign in to comment.