chantools: sweepremoteclosed

This commit is contained in:
Evan Kaloudis
2025-02-07 14:07:04 -05:00
parent f78d231282
commit 11beebdff6
9 changed files with 535 additions and 10 deletions
+14
View File
@@ -168,6 +168,10 @@ import LSPS1Order from './views/LSPS1/Order';
import LSPS7 from './views/LSPS7/index';
import LSPS7Order from './views/LSPS7/Order';
// Chantools
import Chantools from './views/Settings/EmbeddedNode/Chantools';
import Sweepremoteclosed from './views/Settings/EmbeddedNode/Chantools/Sweepremoteclosed';
import RawTxHex from './views/RawTxHex';
import CustodialWalletWarning from './views/Settings/CustodialWalletWarning';
@@ -924,6 +928,16 @@ export default class App extends React.PureComponent {
DeveloperTools
}
/>
<Stack.Screen
name="Chantools" // @ts-ignore:next-line
component={Chantools}
/>
<Stack.Screen
name="Sweepremoteclosed" // @ts-ignore:next-line
component={
Sweepremoteclosed
}
/>
</Stack.Navigator>
</NavigationContainer>
</>
@@ -145,4 +145,16 @@ class LncModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMod
gocb.setCallback(::sendEvent)
Lndmobile.invokeRPC(namespace, eventName, request, gocb)
}
// chantools
@ReactMethod
fun sweepRemoteClosed(seedPhrase: String, apiURL: String, sweepAddr: String, recoveryWindow: Int, feeRate: Int, sleepSeconds: Int, publish: Boolean = false, isTestnet: Boolean = false, promise: Promise) {
try {
var response = Lndmobile.sweepRemoteClosed(seedPhrase, apiURL, sweepAddr, recoveryWindow, feeRate, sleepSeconds, publish, isTestnet)
promise.resolve(response);
} catch(e: Throwable) {
promise.reject("request Error", e);
}
}
}
+3 -3
View File
@@ -1,10 +1,10 @@
VERSION=v0.18.4-beta-zeus
VERSION=v0.18.4-beta-zeus.chantools-alpha1
ANDROID_FILE=Lndmobile.aar
IOS_FILE=Lndmobile.xcframework
ANDROID_SHA256='8f3f569fd603fb101d7de08a7f9e017cb7267fd24da549908b7733b40e54393f'
IOS_SHA256='159e63de8b4a14a402f885adb88c9b34a66c7f6c8a15cbb3c5cc2ba44a5c1bd6'
ANDROID_SHA256='db6a1953ed993ef13a843c22040560631c5870f294ec2256031fb5e73b113542'
IOS_SHA256='ceb9956853bad47391d068718364fd786767100e45d41b1660a0e421a19c4182'
FILE_PATH=https://github.com/ZeusLN/lnd/releases/download/$VERSION/
+26
View File
@@ -216,6 +216,32 @@ RCT_EXPORT_METHOD(initListener:(NSString *)nameSpace
];
}
// chantools
RCT_EXPORT_METHOD(sweepRemoteClosed:(NSString *)seedPhrase
apiURL:(NSString *)apiURL
sweepAddr:(NSString *)sweepAddr
recoveryWindow:(NSInteger)recoveryWindow
feeRate:(NSInteger)feeRate
sleepSeconds:(NSInteger)sleepSeconds
publish:(BOOL)publish
isTestnet:(BOOL)isTestnet
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
NSError *error;
NSString *response = LndmobileSweepRemoteClosed(seedPhrase, apiURL, sweepAddr, recoveryWindow, feeRate, sleepSeconds, publish, isTestnet, &error);
if (error) {
// Create a string representation of the error
NSString *errorString = [NSString stringWithFormat:@"%@",
error.localizedDescription];
reject(@"sweepRemoteClosed_failure", errorString, error);
} else {
resolve(response);
}
}
// Don't compile this code when we build for the old architecture.
#ifdef RCT_NEW_ARCH_ENABLED
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
+9
View File
@@ -1267,6 +1267,15 @@
"view.Settings.LSPServicesList.title": "LSP Services",
"view.Settings.LSPServicesList.flow2": "Just-in-time channels",
"view.Settings.LSPServicesList.lsps1": "Request channels in advance",
"views.Settings.EmbeddedNode.Chantools.Sweepremoteclosed.seed": "Seed phrase",
"views.Settings.EmbeddedNode.Chantools.Sweepremoteclosed.recoveryWindow": "Recovery window",
"views.Settings.EmbeddedNode.Chantools.Sweepremoteclosed.recoveryWindow.explainer": "The number of keys to scan per derivation path.",
"views.Settings.EmbeddedNode.Chantools.Sweepremoteclosed.sleepSeconds": "Seconds to wait between queries",
"views.Settings.EmbeddedNode.Chantools.Sweepremoteclosed.sleepSeconds.explainer": "Use this to add a pause between queries if you get rate limited by the API.",
"views.Settings.EmbeddedNode.Chantools.Sweepremoteclosed.sweepAddr": "Sweep address",
"views.Settings.EmbeddedNode.Chantools.Sweepremoteclosed.apiUrl": "API URL",
"views.Settings.EmbeddedNode.Chantools.Sweepremoteclosed.customApiUrl": "Custom API URL",
"views.Settings.EmbeddedNode.Chantools.Sweepremoteclosed.start": "Start sweep",
"views.LspExplanation.text1": "Zeus is a self-custodial lightning wallet. In order to send or receive a lightning payment, you must open a lightning payment channel, which has a setup fee.",
"views.LspExplanation.text2": "Once the channel is set up, you'll only have to pay normal network fees until your channel exhausts its capacity.",
"views.LspExplanation.buttonText": "Learn more about liquidity",
+23
View File
@@ -457,6 +457,29 @@ export default class EmbeddedNodeAdvancedSettings extends React.Component<
</Text>
</View>
</>
<>
<ListItem
containerStyle={{
backgroundColor: 'transparent'
}}
onPress={() => navigation.navigate('Chantools')}
>
<ListItem.Content>
<ListItem.Title
style={{
color: themeColor('text'),
fontFamily: 'PPNeueMontreal-Book'
}}
>
chantools
</ListItem.Title>
</ListItem.Content>
<Icon
name="keyboard-arrow-right"
color={themeColor('secondaryText')}
/>
</ListItem>
</>
<>
<View style={{ marginTop: 20 }}>
<Button
@@ -0,0 +1,374 @@
import * as React from 'react';
import {
NativeModules,
ScrollView,
StyleSheet,
TouchableOpacity,
View
} from 'react-native';
import { StackNavigationProp } from '@react-navigation/stack';
import { inject, observer } from 'mobx-react';
import Button from '../../../../components/Button';
import DropdownSetting from '../../../../components/DropdownSetting';
import Header from '../../../../components/Header';
import KeyValue from '../../../../components/KeyValue';
import LoadingIndicator from '../../../../components/LoadingIndicator';
import OnchainFeeInput from '../../../../components/OnchainFeeInput';
import { Row } from '../../../../components/layout/Row';
import Screen from '../../../../components/Screen';
import { ErrorMessage } from '../../../../components/SuccessErrorMessage';
import Text from '../../../../components/Text';
import TextInput from '../../../../components/TextInput';
import { localeString } from '../../../../utils/LocaleUtils';
import { themeColor } from '../../../../utils/ThemeUtils';
import NodeInfoStore from '../../../../stores/NodeInfoStore';
import CaretDown from '../../../../assets/images/SVG/Caret Down.svg';
import CaretRight from '../../../../assets/images/SVG/Caret Right.svg';
export const API_URL_KEYS = [
{
key: 'https://api.node-recovery.com',
value: 'https://api.node-recovery.com'
},
{
key: 'https://blockstream.info/api',
value: 'https://blockstream.info/api'
},
{ key: 'https://mempool.space/api', value: 'https://mempool.space/api' },
{ key: 'Custom', value: 'Custom' }
];
interface SweepremoteclosedProps {
navigation: StackNavigationProp<any, any>;
NodeInfoStore: NodeInfoStore;
}
interface SweepremoteclosedState {
seed: string;
sweepAddr: string;
apiUrl: string;
customApiUrl: string;
feeRate: string;
sleepSeconds: string;
recoveryWindow: string;
loading: boolean;
settingsToggle: boolean;
error: string;
}
@inject('NodeInfoStore')
@observer
export default class Sweepremoteclosed extends React.Component<
SweepremoteclosedProps,
SweepremoteclosedState
> {
state = {
seed: '',
sweepAddr: '',
apiUrl: 'https://api.node-recovery.com',
customApiUrl: '',
feeRate: '21',
recoveryWindow: '200',
sleepSeconds: '0',
loading: false,
settingsToggle: false,
error: ''
};
render() {
const { navigation } = this.props;
const {
seed,
sweepAddr,
apiUrl,
customApiUrl,
feeRate,
recoveryWindow,
sleepSeconds,
loading,
settingsToggle,
error
} = this.state;
return (
<Screen>
<View style={{ flex: 1 }}>
<Header
leftComponent="Back"
centerComponent={{
text: 'sweepremoteclosed',
style: {
color: themeColor('text'),
fontFamily: 'PPNeueMontreal-Book'
}
}}
navigation={navigation}
/>
{error && <ErrorMessage message={error} dismissable />}
{loading && <LoadingIndicator />}
<ScrollView style={{ margin: 10 }}>
<>
<Text
style={{
...styles.text,
color: themeColor('secondaryText')
}}
>
{localeString(
'views.Settings.EmbeddedNode.Chantools.Sweepremoteclosed.seed'
)}
</Text>
<TextInput
placeholder={
'cherry truth mask employ box silver mass bunker fiscal vote'
}
value={seed}
onChangeText={(text: string) =>
this.setState({
seed: text
})
}
locked={loading}
/>
</>
<>
<Text
style={{
...styles.text,
color: themeColor('secondaryText')
}}
>
{localeString(
'views.Settings.EmbeddedNode.Chantools.Sweepremoteclosed.sweepAddr'
)}
</Text>
<TextInput
value={sweepAddr}
onChangeText={(text: string) =>
this.setState({
sweepAddr: text
})
}
locked={loading}
/>
</>
<>
<Text
style={{
...styles.text,
color: themeColor('secondaryText')
}}
>
{localeString('views.Send.feeSatsVbyte')}
</Text>
<OnchainFeeInput
fee={feeRate}
onChangeFee={(text: string) => {
this.setState({
feeRate: text
});
}}
navigation={navigation}
/>
</>
<TouchableOpacity
onPress={() => {
this.setState({
settingsToggle: !settingsToggle
});
}}
>
<View
style={{
marginBottom: 10
}}
>
<Row justify="space-between">
<View style={{ width: '95%' }}>
<KeyValue
keyValue={localeString(
'general.advancedSettings'
)}
/>
</View>
{settingsToggle ? (
<CaretDown
fill={themeColor('text')}
width="20"
height="20"
/>
) : (
<CaretRight
fill={themeColor('text')}
width="20"
height="20"
/>
)}
</Row>
</View>
</TouchableOpacity>
{settingsToggle && (
<>
<>
<Text
style={{
...styles.text,
color: themeColor('secondaryText')
}}
infoModalText={localeString(
'views.Settings.EmbeddedNode.Chantools.Sweepremoteclosed.recoveryWindow.explainer'
)}
>
{localeString(
'views.Settings.EmbeddedNode.Chantools.Sweepremoteclosed.recoveryWindow'
)}
</Text>
<TextInput
placeholder={'200'}
value={recoveryWindow}
onChangeText={(text: string) =>
this.setState({
recoveryWindow: text
})
}
locked={loading}
keyboardType="numeric"
/>
</>
<>
<Text
style={{
...styles.text,
color: themeColor('secondaryText')
}}
infoModalText={localeString(
'views.Settings.EmbeddedNode.Chantools.Sweepremoteclosed.sleepSeconds.explainer'
)}
>
{localeString(
'views.Settings.EmbeddedNode.Chantools.Sweepremoteclosed.sleepSeconds'
)}
</Text>
<TextInput
placeholder={'0'}
value={sleepSeconds}
onChangeText={(text: string) =>
this.setState({
sleepSeconds: text
})
}
locked={loading}
keyboardType="numeric"
/>
</>
<View style={{ marginTop: 10 }}>
<DropdownSetting
title={localeString(
'views.Settings.EmbeddedNode.Chantools.Sweepremoteclosed.apiUrl'
)}
selectedValue={apiUrl}
onValueChange={(value: string) => {
this.setState({
apiUrl: value
});
}}
values={API_URL_KEYS}
/>
</View>
{apiUrl === 'Custom' && (
<>
<Text
style={{
...styles.text,
color: themeColor(
'secondaryText'
)
}}
>
{localeString(
'views.Settings.EmbeddedNode.Chantools.Sweepremoteclosed.customApiUrl'
)}
</Text>
<TextInput
value={customApiUrl}
onChangeText={(text: string) =>
this.setState({
sweepAddr: text
})
}
locked={!!customApiUrl}
/>
</>
)}
</>
)}
</ScrollView>
<View style={{ bottom: 10 }}>
<View
style={{
paddingTop: 30,
paddingBottom: 15,
paddingLeft: 10,
paddingRight: 10
}}
>
<Button
title={localeString(
'views.Settings.EmbeddedNode.Chantools.Sweepremoteclosed.start'
)}
onPress={async () => {
this.setState({
error: '',
loading: true
});
console.log('start sweepremoteclosed', {
seed,
apiUrl,
customApiUrl,
recoveryWindow,
feeRate,
sleepSeconds
});
try {
const response =
await NativeModules.LncModule.sweepRemoteClosed(
seed,
apiUrl === 'Custom'
? customApiUrl
: apiUrl,
sweepAddr,
Number(recoveryWindow || 200),
Number(feeRate || 2),
Number(sleepSeconds || 0),
false, // publish
this.props.NodeInfoStore
?.nodeInfo?.isTestNet // isTestNet
);
navigation.navigate('TxHex', {
txHex: response,
hideWarning: true
});
} catch (e: any) {
this.setState({
error: e.toString(),
loading: false
});
}
}}
/>
</View>
</View>
</View>
</Screen>
);
}
}
const styles = StyleSheet.create({
text: {
fontFamily: 'PPNeueMontreal-Book'
}
});
@@ -0,0 +1,63 @@
import * as React from 'react';
import { ScrollView, View } from 'react-native';
import { Icon, ListItem } from 'react-native-elements';
import { StackNavigationProp } from '@react-navigation/stack';
import Header from '../../../../components/Header';
import Screen from '../../../../components/Screen';
import { themeColor } from '../../../../utils/ThemeUtils';
interface ChantoolsProps {
navigation: StackNavigationProp<any, any>;
}
export default class Chantools extends React.Component<ChantoolsProps, {}> {
render() {
const { navigation } = this.props;
return (
<Screen>
<View style={{ flex: 1 }}>
<Header
leftComponent="Back"
centerComponent={{
text: 'chantools',
style: {
color: themeColor('text'),
fontFamily: 'PPNeueMontreal-Book'
}
}}
navigation={navigation}
/>
<ScrollView>
<>
<ListItem
containerStyle={{
backgroundColor: 'transparent'
}}
onPress={() =>
navigation.navigate('Sweepremoteclosed')
}
>
<ListItem.Content>
<ListItem.Title
style={{
color: themeColor('text'),
fontFamily: 'PPNeueMontreal-Book'
}}
>
sweepremoteclosed
</ListItem.Title>
</ListItem.Content>
<Icon
name="keyboard-arrow-right"
color={themeColor('secondaryText')}
/>
</ListItem>
</>
</ScrollView>
</View>
</Screen>
);
}
}
+11 -7
View File
@@ -40,7 +40,7 @@ interface TxHexProps {
ChannelsStore: ChannelsStore;
NodeInfoStore: NodeInfoStore;
TransactionsStore: TransactionsStore;
route: Route<'TxHex', { txHex: string }>;
route: Route<'TxHex', { txHex: string; hideWarning?: boolean }>;
}
interface TxHexState {
@@ -301,12 +301,16 @@ export default class TxHex extends React.Component<TxHexProps, TxHexState> {
</Text>
</>
)}
{!loading && pending_chan_ids.length === 0 && (
<WarningMessage
message={localeString('views.TxHex.channelWarning')}
fontSize={13}
/>
)}
{!loading &&
pending_chan_ids.length === 0 &&
!this.props.route.params?.hideWarning && (
<WarningMessage
message={localeString(
'views.TxHex.channelWarning'
)}
fontSize={13}
/>
)}
{loading && (
<View style={{ margin: 15 }}>
<LoadingIndicator />