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.