Lightning on Mobile: Neutrino in the Palm of your Hand
by Johan T. Halseth
Now that lnd 0.8 has been released, we wanted to highlight one of the new features that can be particularly interesting for some developers, namely full support for integrating lnd within iOS and Android apps! It is still an experimental feature enabled by our falafel tool, but we encourage developers to start testing lnd on mobile platforms, and report any issues encountered. If you just want to run Lightning on your phone, check out some of the wallets already using lnd 0.8, such as Breez, Zap and tippin.me.
What we wanted from lnd on mobile
When we set out to make a mobile SDK for lnd we wanted to make sure mobile platforms wouldn’t get a lesser version of the almighty lnd. It was a goal to be able to run fully fledged lnd on your phone, with all the features of its desktop counterpart intact.
To achieve this we compile a special version of lnd for the target mobile platform using gomobile
, then generate mobile APIs from the protobuf definitions used for lnd’s gRPC interface. This gives mobile apps access to the full lnd API, and you can run a lightning node with all available features directly on your handheld!
Neutrino in a box
Even though you can run full lnd on your phone, we recommend running with the Neutrino bitcoin backend enabled. Neutrino comes bundled with lnd, so it will already be included in the compiled mobile library you can add to your application. It is technically possible to run a full node on your phone and connect lnd to it, but this is obviously not as practical on battery constrained devices.
Neutrino is fast, lightweight and private. Check out this blog post for more information.
Technical details
The lnd codebase is cross compiled to iOS and Android using the official golang tool gomobile
, which creates native platform libraries from Go code. To expose the lnd RPC as mobile APIs, we use falafel to translate the lnrpc protobuf definitions to gomobile
compatible APIs at build time. Behind this API we directly talk to lnd using an in-memory gRPC client, ensuring all communication happens in-process using serialized protocol buffers, without needing to expose the gRPC server on an open port. To support the powerful streaming RPCs lnd provide, like subscribing to real-time updates, we provide callbacks for all APIs, to make them feel like home on mobile.
Generating APIs and cross compiling in one step ensures the mobile version of lnd will always stay up to date with the lnd revision you are building from, and makes updates as seamless as dropping a new lndmobile
library into your app.
Building
To build lnd for your mobile platform of choice, we recommend having the latest version of Go installed (v1.13.4 as of this being written), and the latest version of gomobile
. In addition you’ll need to have falafel installed. When you have all that run:
gomobile init -v
make <platform>
where <platform>
is either ios
or android
. This will create native libraries that can be added to your app project. More information about the build process can be found here: lnd/mobile
Integrating with iOS
Now let’s take a look at how we would use this in a simple iOS project. We’ll use the beautiful language Swift in our examples, but the library is also perfectly compatible with the slightly less beautiful Objective-C.
First we’ll generate proto definitions for Swift my adding this line to lnrpc/gen_protos.sh
:
--swift_out=.
Now run make rpc
and copy the generated rpc.pb.swift
file into your project. Note that protoc-gen-swift
must be in your path for the code generation to succeed, and that SwiftProtobuf
is a required dependency of the generated code.
After adding the generated swift code and Lndmobile.framework
to your project, import Lndmobile
:
import Lndmobile
The first thing that needs to be done is to start lnd, and as always a few configuration options must be set in order for lnd to start up:
class StartCallback: NSObject, LndmobileCallbackProtocol {
func onError(_ p0: Error?) { }
func onResponse(_ p0: Data?) {
print("lnd started")
}
}
LndmobileStart("--bitcoin.active --bitcoin.testnet --no-macaroons --bitcoin.node=neutrino", StartCallback())
You could also bundle a config file with your app and give it to lnd using --configfile=
. A sample config file for mobile can be found here.
Notice that the callback class we define conforms to the LndmobileCallbackProtocol
. This is a key requirement for all calls to Lndmobile
; they must provide a callback object that will handle the result from the API call. For simplicity, we just ignore the result here, as we expect lnd to successfully start up.
Now that lnd has been started, we initialize the wallet. The first time lnd starts the wallet has to be created, which involves generating and storing the seed:
class GenSeedCallback: NSObject, LndmobileCallbackProtocol {
func onError(_ p0: Error?) {}
func onResponse(_ p0: Data?) {
let resp = try! Lnrpc_GenSeedResponse(serializedData: p0!)
let cipherSeed = resp.cipherSeedMnemonic
}
}
let genSeedMsg = Lnrpc_GenSeedRequest()
let msg = try! genSeedMsg.serializedData()
LndmobileGenSeed(msg, GenSeedCallback())
With the seed in hand, use it to initialize the wallet:
class InitWalletCallback: NSObject, LndmobileCallbackProtocol {
func onError(_ p0: Error?) {}
func onResponse(_ p0: Data?) {
print("wallet initialized!")
}
}
let password = "somesuperstrongpw"
var initMsg = Lnrpc_InitWalletRequest()
initMsg.walletPassword = password.data(using: .utf8)!
initMsg.cipherSeedMnemonic = cipherSeed
let msg = try! initMsg.serializedData()
LndmobileInitWallet(msg, InitWalletCallback())
To make sure the wallet is unlocked, and lnd is syncing and ready to accept more RPC calls, GetInfo
can be polled until we get a response. A single GetInfo
call is done like this:
class GetInfoCallback: NSObject, LndmobileCallbackProtocol {
func onError(_ p0: Error?) {
print("lnd not ready...")
}
func onResponse(_ p0: Data?) {
let resp = try! Lnrpc_GetInfoResponse(serializedData: p0!)
print("lnd ready! getinfo: \(resp)")
}
}
let getInfo = Lnrpc_GetInfoRequest()
let msg = try! getInfo.serializedData()
LndmobileGetInfo(msg, GetInfoCallback())
Integration with Android
In the Android world, Java is still king, and the lnd APIs will be Java compatible. This also means that they are available from Kotlin.
Generate proto definitions by adding this line to lnrpc/gen_protos.sh
:
--java_out=.
This will create a new file lnrpc/Rpc.java
that can be added to your Android Studio project. Note that you will also need to add these dependencies for the generated file to compile:
dependencies {
implementation 'com.google.protobuf:protobuf-java:3.10.0'
implementation 'com.google.api.grpc:proto-google-common-protos:1.17.0'
}
Now add the generated Lndmobile.aar
to your project and import it:
import lndmobile.Callback;
import lndmobile.Lndmobile;
And start the daemon:
class StartCallback implements Callback {
@Override
public void onError(Exception e) {}
@Override
public void onResponse(byte[] bytes) {
Log.i("", "lnd started")
}
}
Lndmobile.start("--bitcoin.active --bitcoin.node=neutrino --bitcoin.testnet --no-macaroons", new StartCallback());
From here on the flow is exactly as on iOS, generating a seed an initializing the wallet. For instance, calling lnd
to initialize the wallet after getting the seed can be done like this:
class InitWalletCallback implements Callback {
@Override
public void onError(Exception e) {}
@Override
public void onResponse(byte[] bytes) {
try {
Lnrpc.InitWalletResponse resp = Lnrpc.InitWalletResponse.parseFrom(bytes);
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
}
}
}
ByteString pw = ByteString.copyFromUtf8("somesuperstrongpw");
String[] cipherSeed = new String[]{"<24 words>"};
Lnrpc.InitWalletRequest req = Lnrpc.InitWalletRequest
.newBuilder()
.setWalletPassword(pw)
.addAllCipherSeedMnemonic(Arrays.asList(cipherSeed))
.build();
Lndmobile.initWallet(req.toByteArray(), new InitWalletCallback());
Developers, developers, developers, developers
We hope developers find the mobile lnd APIs easy to work with, without compromising on the features available. Talented developers are already building impressive mobile apps utilizing lnd, and we appreciate all feedback and contributions we have gotten.
Using simple bindings the APIs are also avaialble from other languages and frameworks, like React Native. Check out the source for Lightning App for an example how to connect lnd to to your React app.
If you encounter any issues with the APIs or the mobile version of lnd, feel free to file an issue on the lnd or falafel repositories, or reach out in the #mobile
channel on the LND Slack community