Skip to content

Commit

Permalink
LND: onchain batching
Browse files Browse the repository at this point in the history
  • Loading branch information
kaloudis committed Apr 21, 2024
1 parent a948ade commit 1e2e050
Show file tree
Hide file tree
Showing 12 changed files with 208 additions and 18 deletions.
1 change: 1 addition & 0 deletions backends/CLightningREST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,5 +272,6 @@ export default class CLightningREST extends LND {
supportsSimpleTaprootChannels = () => false;
supportsCustomPreimages = () => false;
supportsSweep = () => true;
supportsOnchainBatching = () => false;
isLNDBased = () => false;
}
1 change: 1 addition & 0 deletions backends/Eclair.ts
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,7 @@ export default class Eclair {
supportsSimpleTaprootChannels = () => false;
supportsCustomPreimages = () => false;
supportsSweep = () => false;
supportsOnchainBatching = () => false;
isLNDBased = () => false;
}

Expand Down
1 change: 1 addition & 0 deletions backends/EmbeddedLND.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,5 +267,6 @@ export default class EmbeddedLND extends LND {
supportsSimpleTaprootChannels = () => this.supports('v0.17.0');
supportsCustomPreimages = () => true;
supportsSweep = () => true;
supportsOnchainBatching = () => true;
isLNDBased = () => true;
}
1 change: 1 addition & 0 deletions backends/LND.ts
Original file line number Diff line number Diff line change
Expand Up @@ -610,5 +610,6 @@ export default class LND {
supportsSimpleTaprootChannels = () => this.supports('v0.17.0');
supportsCustomPreimages = () => true;
supportsSweep = () => true;
supportsOnchainBatching = () => true;
isLNDBased = () => true;
}
1 change: 1 addition & 0 deletions backends/LightningNodeConnect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -451,5 +451,6 @@ export default class LightningNodeConnect {
supportsSimpleTaprootChannels = () => this.supports('v0.17.0');
supportsCustomPreimages = () => true;
supportsSweep = () => true;
supportsOnchainBatching = () => true;
isLNDBased = () => true;
}
1 change: 1 addition & 0 deletions backends/LndHub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,5 +152,6 @@ export default class LndHub extends LND {
supportsSimpleTaprootChannels = () => false;
supportsCustomPreimages = () => false;
supportsSweep = () => false;
supportsOnchainBatching = () => false;
isLNDBased = () => false;
}
1 change: 1 addition & 0 deletions backends/Spark.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,5 +378,6 @@ export default class Spark {
supportsSimpleTaprootChannels = () => false;
supportsCustomPreimages = () => false;
supportsSweep = () => false;
supportsOnchainBatching = () => false;
isLNDBased = () => false;
}
2 changes: 2 additions & 0 deletions locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,8 @@
"views.Send.noOnchainBalance": "No on-chain balance available. Close a channel or receive an on-chain transaction first.",
"views.Send.noLightningBalance": "No lightning balance available. Open a channel or receive a lightning payment first.",
"views.Send.zaplockerWarning": "This is a Zaplocker invoices that will hold payment up to 24 hours. Open ZEUS regularly after payment to help mitigate the risk of a force closed channel. Proceed at your own risk.",
"views.Send.addOutput": "Add output",
"views.Send.removeOutput": "Remove output",
"views.SendingLightning.sending": "Sending Transaction",
"views.SendingLightning.success": "Transaction successfully sent",
"views.SendingLightning.paymentHash": "Payment Hash",
Expand Down
7 changes: 7 additions & 0 deletions models/TransactionRequest.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
type FeeLimit = { percent: string } | { fixed: string };

export interface AdditionalOutput {
address: string;
amount: string;
satAmount: string | number;
}

export default interface TransactionRequest {
target_conf?: number | null; // optional
addr?: string;
Expand All @@ -11,6 +17,7 @@ export default interface TransactionRequest {
spend_unconfirmed?: boolean;
send_all?: boolean;
account?: string;
additional_outputs: Array<AdditionalOutput>;
}

export type SendPaymentRequest =
Expand Down
27 changes: 21 additions & 6 deletions stores/TransactionsStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,11 @@ export default class TransactionsStore {
this.loading = true;

// Decode the raw transaction hex string
const tx = bitcoin.Transaction.fromHex(raw_final_tx);
const tx = bitcoin.Transaction.fromHex(
raw_final_tx.includes('=')
? Base64Utils.base64ToHex(raw_final_tx)
: raw_final_tx
);
// Get the transaction ID (txid)
const txid = tx.getId();

Expand Down Expand Up @@ -261,8 +265,14 @@ export default class TransactionsStore {
public sendCoinsLNDCoinControl = (
transactionRequest: TransactionRequest
) => {
const { utxos, addr, amount, sat_per_vbyte, account } =
transactionRequest;
const {
utxos,
addr,
amount,
sat_per_vbyte,
account,
additional_outputs
} = transactionRequest;
const inputs: any = [];
const outputs: any = {};

Expand All @@ -277,6 +287,10 @@ export default class TransactionsStore {
outputs[addr] = Number(amount);
}

additional_outputs.map((output) => {
outputs[output.address] = Number(output.satAmount);
});

const fundPsbtRequest = {
raw: {
outputs,
Expand Down Expand Up @@ -321,9 +335,10 @@ export default class TransactionsStore {
this.loading = true;

if (
BackendUtils.isLNDBased() &&
transactionRequest.utxos &&
transactionRequest.utxos.length > 0
(BackendUtils.isLNDBased() &&
transactionRequest.utxos &&
transactionRequest.utxos.length > 0) ||
transactionRequest.additional_outputs.length > 0
) {
return this.sendCoinsLNDCoinControl(transactionRequest);
}
Expand Down
1 change: 1 addition & 0 deletions utils/BackendUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ class BackendUtils {
this.call('supportsSimpleTaprootChannels');
supportsCustomPreimages = () => this.call('supportsCustomPreimages');
supportsSweep = () => this.call('supportsSweep');
supportsOnchainBatching = () => this.call('supportsOnchainBatching');
isLNDBased = () => this.call('isLNDBased');

// LNC
Expand Down
182 changes: 170 additions & 12 deletions views/Send.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import Scan from '../assets/images/SVG/Scan.svg';
import Sweep from '../assets/images/SVG/Sweep.svg';

import Contact from '../models/Contact';
import { AdditionalOutput } from '../models/TransactionRequest';

interface SendProps {
exitSetup: any;
Expand Down Expand Up @@ -98,6 +99,7 @@ interface SendState {
contacts: Contact[];
clearOnBackPress: boolean;
account: string;
additionalOutputs: Array<AdditionalOutput>;
}

@inject(
Expand Down Expand Up @@ -153,7 +155,8 @@ export default class Send extends React.Component<SendProps, SendState> {
contactName,
contacts: [],
clearOnBackPress,
account: 'default'
account: 'default',
additionalOutputs: []
};
}

Expand Down Expand Up @@ -370,8 +373,14 @@ export default class Send extends React.Component<SendProps, SendState> {

sendCoins = (satAmount: string | number) => {
const { TransactionsStore, navigation } = this.props;
const { destination, fee, utxos, confirmationTarget, account } =
this.state;
const {
destination,
fee,
utxos,
confirmationTarget,
account,
additionalOutputs
} = this.state;

let request;
if (utxos && utxos.length > 0) {
Expand All @@ -382,6 +391,7 @@ export default class Send extends React.Component<SendProps, SendState> {
target_conf: Number(confirmationTarget),
utxos,
spend_unconfirmed: true,
additional_outputs: additionalOutputs,
account
};
} else {
Expand All @@ -390,7 +400,9 @@ export default class Send extends React.Component<SendProps, SendState> {
sat_per_vbyte: fee,
amount: satAmount.toString(),
target_conf: Number(confirmationTarget),
spend_unconfirmed: true
spend_unconfirmed: true,
additional_outputs: additionalOutputs,
account
};
}
TransactionsStore.sendCoins(request);
Expand Down Expand Up @@ -581,7 +593,8 @@ export default class Send extends React.Component<SendProps, SendState> {
clipboard,
loading,
contactName,
contacts
contacts,
additionalOutputs
} = this.state;
const {
confirmedBlockchainBalance,
Expand Down Expand Up @@ -625,7 +638,8 @@ export default class Send extends React.Component<SendProps, SendState> {
)}
{BackendUtils.supportsSweep() &&
isValid &&
transactionType === 'On-chain' && (
transactionType === 'On-chain' &&
additionalOutputs.length === 0 && (
<View
style={{
marginTop: 3,
Expand Down Expand Up @@ -911,6 +925,155 @@ export default class Send extends React.Component<SendProps, SendState> {
)}
</View>

{additionalOutputs.map((output, index) => {
return (
<View key={`additionalOutput-${index}`}>
<Text
style={{
...styles.text,
color: themeColor(
'secondaryText'
)
}}
>
{localeString(
'general.destination'
)}
</Text>
<TextInput
value={output?.address}
multiline={true}
textInputStyle={{
fontSize: 100,
marginTop: 10,
marginBottom: 10
}}
onChangeText={(
text: string
) => {
let newOutputs =
additionalOutputs;

newOutputs[index].address =
text;

this.setState({
additionalOutputs:
newOutputs
});
}}
/>
<AmountInput
amount={output?.amount.toString()}
title={localeString(
'views.Send.amount'
)}
onAmountChange={(
amount: string,
satAmount: string | number
) => {
let newOutputs =
additionalOutputs;

newOutputs[index].amount =
amount;
newOutputs[
index
].satAmount = satAmount;

this.setState({
additionalOutputs:
newOutputs
});
}}
/>
<View
style={{
marginTop: 10,
marginBottom: 20
}}
>
<Button
title={localeString(
'views.Send.removeOutput'
)}
icon={{
name: 'remove',
size: 25,
color: themeColor(
'background'
)
}}
onPress={() => {
let newOutputs =
additionalOutputs;

newOutputs =
newOutputs.filter(
(item) =>
item !==
output
);

this.setState({
additionalOutputs:
newOutputs
});
}}
tertiary
/>
</View>
</View>
);
})}

{transactionType === 'On-chain' &&
BackendUtils.supportsOnchainBatching() && (
<View
style={{
marginTop: 0,
marginBottom: 20
}}
>
<Button
title={localeString(
'views.Send.addOutput'
)}
icon={{
name: 'add',
size: 25,
color: themeColor(
'background'
)
}}
onPress={() => {
const additionalOutputs =
this.state
.additionalOutputs;

additionalOutputs.push({
address: '',
amount: '',
satAmount: ''
});

this.setState({
additionalOutputs
});
}}
/>
</View>
)}

{BackendUtils.supportsCoinControl() && (
<View style={{ marginBottom: 20 }}>
<UTXOPicker
onValueChange={this.selectUTXOs}
UTXOsStore={UTXOsStore}
/>
</View>
)}

<Text
style={{
...styles.text,
Expand All @@ -926,12 +1089,7 @@ export default class Send extends React.Component<SendProps, SendState> {
this.setState({ fee: text })
}
/>
{BackendUtils.supportsCoinControl() && (
<UTXOPicker
onValueChange={this.selectUTXOs}
UTXOsStore={UTXOsStore}
/>
)}

<View
style={{
...styles.button,
Expand Down

0 comments on commit 1e2e050

Please sign in to comment.