Skip to content

Commit 925510f

Browse files
committed
Added lightning to Android.
1 parent b2a6370 commit 925510f

File tree

9 files changed

+394
-3
lines changed

9 files changed

+394
-3
lines changed

android/Lndmobile/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
configurations.maybeCreate("default")
2+
artifacts.add("default", file('Lndmobile.aar'))

android/app/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ dependencies {
218218
debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") {
219219
exclude group:'com.facebook.flipper'
220220
}
221+
implementation project(path: ':Lndmobile')
221222

222223
if (enableHermes) {
223224
def hermesPath = "../../node_modules/hermes-engine/android/";

android/app/src/main/assets/lnd.conf

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[Application Options]
2+
debuglevel=info
3+
no-macaroons=1
4+
maxbackoff=2s
5+
nolisten=1
6+
7+
[Routing]
8+
routing.assumechanvalid=1
9+
10+
[Bitcoin]
11+
bitcoin.active=1
12+
bitcoin.testnet=1
13+
bitcoin.node=neutrino
14+
15+
[Neutrino]
16+
neutrino.addpeer=faucet.lightning.community
17+
neutrino.feeurl=https://nodes.lightning.computer/fees/v1/btc-fee-estimates.json
18+
19+
[autopilot]
20+
autopilot.active=0
21+
autopilot.private=0
22+
autopilot.minconfs=0
23+
autopilot.conftarget=30
24+
autopilot.allocation=1.0
25+
autopilot.heuristic=externalscore:0.95
26+
autopilot.heuristic=preferential:0.05
Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
package com.photon;
2+
3+
import android.content.res.AssetManager;
4+
import android.os.FileObserver;
5+
import android.util.Base64;
6+
import android.util.Log;
7+
8+
import com.facebook.react.bridge.Arguments;
9+
import com.facebook.react.bridge.Promise;
10+
import com.facebook.react.bridge.ReactApplicationContext;
11+
import com.facebook.react.bridge.ReactContextBaseJavaModule;
12+
import com.facebook.react.bridge.ReactMethod;
13+
import com.facebook.react.bridge.WritableMap;
14+
import com.facebook.react.modules.core.DeviceEventManagerModule;
15+
16+
import java.io.BufferedReader;
17+
import java.io.File;
18+
import java.io.FileInputStream;
19+
import java.io.FileNotFoundException;
20+
import java.io.FileOutputStream;
21+
import java.io.IOException;
22+
import java.io.InputStream;
23+
import java.io.InputStreamReader;
24+
import java.io.OutputStream;
25+
import java.lang.reflect.InvocationTargetException;
26+
import java.lang.reflect.Method;
27+
import java.util.HashMap;
28+
import java.util.Map;
29+
30+
import lndmobile.Callback;
31+
import lndmobile.Lndmobile;
32+
import lndmobile.RecvStream;
33+
import lndmobile.SendStream;
34+
35+
36+
public class LndNativeModule extends ReactContextBaseJavaModule {
37+
38+
private static final String streamEventName = "streamEvent";
39+
private static final String streamIdKey = "streamId";
40+
private static final String respB64DataKey = "data";
41+
private static final String respErrorKey = "error";
42+
private static final String respEventTypeKey = "event";
43+
private static final String respEventTypeData = "data";
44+
private static final String respEventTypeError = "error";
45+
private static final String logEventName = "logs";
46+
private static final String TAG = "lnd";
47+
48+
private Map<String, SendStream> activeStreams = new HashMap<>();
49+
private Map<String, Method> syncMethods = new HashMap<>();
50+
private Map<String, Method> streamMethods = new HashMap<>();
51+
52+
private FileObserver logObserver;
53+
54+
private static boolean isReceiveStream(Method m) {
55+
return m.toString().contains("RecvStream");
56+
}
57+
58+
private static boolean isSendStream(Method m) {
59+
return m.toString().contains("SendStream");
60+
}
61+
62+
private static boolean isStream(Method m) {
63+
return isReceiveStream(m) || isSendStream(m);
64+
}
65+
66+
public LndNativeModule(ReactApplicationContext reactContext) {
67+
super(reactContext);
68+
69+
Method[] methods = Lndmobile.class.getDeclaredMethods();
70+
for (Method m : methods) {
71+
String name = m.getName();
72+
name = name.substring(0, 1).toUpperCase() + name.substring(1);
73+
if (isStream(m)) {
74+
streamMethods.put(name, m);
75+
} else {
76+
syncMethods.put(name, m);
77+
}
78+
}
79+
}
80+
81+
@Override
82+
public String getName() {
83+
return "LndReactModule";
84+
}
85+
86+
@Override
87+
public Map<String, Object> getConstants() {
88+
final Map<String, Object> constants = new HashMap<>();
89+
return constants;
90+
}
91+
92+
class NativeCallback implements Callback {
93+
Promise promise;
94+
95+
NativeCallback(Promise promise) {
96+
this.promise = promise;
97+
}
98+
99+
@Override
100+
public void onError(Exception e) {
101+
promise.reject("LndNativeModule", e);
102+
}
103+
104+
@Override
105+
public void onResponse(byte[] bytes) {
106+
String b64 = "";
107+
if (bytes != null && bytes.length > 0) {
108+
b64 = Base64.encodeToString(bytes, Base64.NO_WRAP);
109+
}
110+
111+
WritableMap params = Arguments.createMap();
112+
params.putString(respB64DataKey, b64);
113+
114+
promise.resolve(params);
115+
}
116+
}
117+
118+
class ReceiveStream implements RecvStream {
119+
String streamID;
120+
121+
ReceiveStream(String id) {
122+
this.streamID = id;
123+
}
124+
125+
@Override
126+
public void onError(Exception e) {
127+
WritableMap params = Arguments.createMap();
128+
params.putString(streamIdKey, streamID);
129+
params.putString(respEventTypeKey, respEventTypeError);
130+
params.putString(respErrorKey, e.getLocalizedMessage());
131+
getReactApplicationContext()
132+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
133+
.emit(streamEventName, params);
134+
}
135+
136+
@Override
137+
public void onResponse(byte[] bytes) {
138+
String b64 = "";
139+
if (bytes != null && bytes.length > 0) {
140+
b64 = Base64.encodeToString(bytes, Base64.NO_WRAP);
141+
}
142+
143+
WritableMap params = Arguments.createMap();
144+
params.putString(streamIdKey, streamID);
145+
params.putString(respEventTypeKey, respEventTypeData);
146+
params.putString(respB64DataKey, b64);
147+
getReactApplicationContext()
148+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
149+
.emit(streamEventName, params);
150+
}
151+
}
152+
153+
@ReactMethod
154+
public void start(final Promise promise) {
155+
File appDir = getReactApplicationContext().getFilesDir();
156+
copyConfig(appDir);
157+
158+
final String logDir = appDir + "/logs/bitcoin/testnet";
159+
final String logFile = logDir + "/lnd.log";
160+
161+
FileInputStream stream = null;
162+
while (true) {
163+
try {
164+
stream = new FileInputStream(logFile);
165+
} catch (FileNotFoundException e) {
166+
File dir = new File(logDir);
167+
dir.mkdirs();
168+
File f = new File(logFile);
169+
try {
170+
f.createNewFile();
171+
continue;
172+
} catch (IOException e1) {
173+
e1.printStackTrace();
174+
return;
175+
}
176+
}
177+
break;
178+
}
179+
180+
final InputStreamReader istream = new InputStreamReader(stream);
181+
final BufferedReader buf = new BufferedReader(istream);
182+
try {
183+
readToEnd(buf, false);
184+
} catch (IOException e) {
185+
e.printStackTrace();
186+
return;
187+
}
188+
189+
logObserver = new FileObserver(logFile) {
190+
@Override
191+
public void onEvent(int event, String file) {
192+
if(event != FileObserver.MODIFY) {
193+
return;
194+
}
195+
try {
196+
readToEnd(buf, true);
197+
} catch (IOException e) {
198+
e.printStackTrace();
199+
}
200+
}
201+
};
202+
logObserver.startWatching();
203+
Log.i("LndNativeModule", "Started watching " + logFile);
204+
205+
final String args = "--lnddir=" + appDir;
206+
Log.i("LndNativeModule", "Starting LND with args " + args);
207+
208+
class UnlockCallback implements Callback {
209+
@Override
210+
public void onError(Exception e) {
211+
Log.i(TAG, "unlock err");
212+
Log.d(TAG, "unlock err");
213+
}
214+
@Override
215+
public void onResponse(byte[] bytes) {
216+
Log.i(TAG, "unlock started");
217+
Log.d(TAG, "unlock started");
218+
promise.resolve("unlocked");
219+
}
220+
}
221+
class RPCCallback implements Callback {
222+
@Override
223+
public void onError(Exception e) {
224+
Log.i(TAG, "rpc callback err");
225+
Log.d(TAG, "unlock started");
226+
}
227+
@Override
228+
public void onResponse(byte[] bytes) {
229+
Log.i(TAG, "rpc callback started");
230+
Log.d(TAG, "unlock started");
231+
promise.resolve("rpc started");
232+
}
233+
}
234+
235+
Runnable startLnd = new Runnable() {
236+
@Override
237+
public void run() {
238+
//Lndmobile.start(args, new NativeCallback(promise));
239+
Lndmobile.start(args, new UnlockCallback(), new RPCCallback());
240+
}
241+
};
242+
new Thread(startLnd).start();
243+
}
244+
245+
private void readToEnd(BufferedReader buf, boolean emit) throws IOException {
246+
String s = "";
247+
while ( (s = buf.readLine()) != null ) {
248+
if (!emit) {
249+
continue;
250+
}
251+
getReactApplicationContext()
252+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
253+
.emit(logEventName, s);
254+
}
255+
}
256+
257+
private void copyConfig(File appDir) {
258+
File conf = new File(appDir, "lnd.conf");
259+
AssetManager am = getCurrentActivity().getAssets();
260+
261+
try (InputStream in = am.open("lnd.conf")) {
262+
copy(in, conf);
263+
} catch (IOException e) {
264+
e.printStackTrace();
265+
}
266+
}
267+
268+
private static void copy(InputStream in, File dst) throws IOException {
269+
try (OutputStream out = new FileOutputStream(dst)) {
270+
byte[] buf = new byte[1024];
271+
int len;
272+
while ((len = in.read(buf)) > 0) {
273+
out.write(buf, 0, len);
274+
}
275+
}
276+
}
277+
278+
@ReactMethod
279+
public void sendCommand(String method, String msg, final Promise promise) {
280+
Method m = syncMethods.get(method);
281+
if (m == null) {
282+
promise.reject("LndNativeModule", "method not found");
283+
return;
284+
}
285+
286+
byte[] b = Base64.decode(msg, Base64.NO_WRAP);
287+
288+
try {
289+
m.invoke(null, b, new NativeCallback(promise));
290+
} catch (IllegalAccessException | InvocationTargetException e) {
291+
e.printStackTrace();
292+
promise.reject("LndNativeModule", e);
293+
}
294+
}
295+
296+
@ReactMethod
297+
public void sendStreamCommand(String method, String streamId, String msg) {
298+
Method m = streamMethods.get(method);
299+
if (m == null) {
300+
return;
301+
}
302+
ReceiveStream r = new ReceiveStream(streamId);
303+
304+
try {
305+
if (isSendStream(m)) {
306+
Object sendStream = m.invoke(null, r);
307+
this.activeStreams.put(streamId, (SendStream) sendStream);
308+
} else {
309+
byte[] b = Base64.decode(msg, Base64.NO_WRAP);
310+
m.invoke(null, b, r);
311+
}
312+
} catch (IllegalAccessException e) {
313+
e.printStackTrace();
314+
} catch (InvocationTargetException e) {
315+
e.printStackTrace();
316+
}
317+
}
318+
319+
@ReactMethod
320+
public void sendStreamWrite(String streamId, String msg) {
321+
SendStream stream = activeStreams.get(streamId);
322+
if (stream == null) {
323+
return;
324+
}
325+
326+
byte[] b = Base64.decode(msg, Base64.NO_WRAP);
327+
try {
328+
stream.send(b);
329+
} catch (Exception e) {
330+
e.printStackTrace();
331+
}
332+
}
333+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.photon;
2+
3+
import com.facebook.react.ReactPackage;
4+
import com.facebook.react.bridge.NativeModule;
5+
import com.facebook.react.bridge.ReactApplicationContext;
6+
import com.facebook.react.uimanager.ViewManager;
7+
8+
import java.util.ArrayList;
9+
import java.util.Collections;
10+
import java.util.List;
11+
12+
public class LndNativePackage implements ReactPackage {
13+
14+
@Override
15+
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
16+
return Collections.emptyList();
17+
}
18+
19+
@Override
20+
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
21+
List<NativeModule> modules = new ArrayList<>();
22+
modules.add(new LndNativeModule(reactContext));
23+
return modules;
24+
}
25+
}

0 commit comments

Comments
 (0)