Skip to main content

Use the Logos Delivery module API from an app

Get started integrating Logos messaging into a C++ module.

This procedure covers building a Logos module that calls the Logos Delivery API to subscribe to content topics, send messages, and react to delivery events. It gives application developers a working pattern for integrating Logos messaging into their C++ modules. A complete, runnable reference implementation is available in logos-delivery-demo (tag v0.1.0).

The two repositories used in this tutorial are logos-delivery-module (pinned to v0.1.3) and logos-delivery, which is a transitive dependency resolved and linked statically by Nix.

Before you start, make sure you have the following:

  • macOS (aarch64 or x86_64) or Linux (aarch64 or x86_64)
  • ~1 GB of RAM
  • Nix with flakes enabled

What to expect

  • You can subscribe to a content topic and receive messages.
  • You can send a message and confirm delivery by tracking messageSent and messagePropagated events.
  • You can integrate the full Logos Delivery lifecycle — create, start, subscribe, send, stop — into your C++ module.

Step 1: Create a Logos module

Scaffold a new module using logos-module-builder. For a full walkthrough, see the Build a Logos C++ UI module tutorial.

  1. Create and enter the project directory:

    mkdir your-module-name && cd your-module-name
  2. Initialise from the template:

    nix flake init -t github:logos-co/logos-module-builder/0.2.0#ui-qml-backend
  3. Initialise a Git repository and stage all generated files:

    git init && git add -A
  4. Remove the template's example sources. The scaffolded template includes ui_example files with mismatched class names and IIDs; leaving them causes build errors or plugin-load failures at runtime:

    rm -f src/ui_example.rep src/ui_example_interface.h src/ui_example_plugin.h src/ui_example_plugin.cpp

Step 2: Declare delivery_module as a dependency

Add delivery_module to both metadata.json and flake.nix, pinning to the released tag so your app stays stable as the module API evolves.

info

The flake input name (delivery_module) must exactly match the dependency name in metadata.json. logos-module-builder uses this name to generate the typed wrapper at build time.

  1. In metadata.json, add delivery_module to the dependencies array:

    {
    "name": "my_app",
    "dependencies": ["delivery_module"],
    ...
    }
  2. In flake.nix, add a matching pinned input:

    inputs = {
    logos-module-builder.url = "github:logos-co/logos-module-builder";
    delivery_module.url = "github:logos-co/logos-delivery-module/v0.1.3";
    };

Step 3: Call the delivery module API

In your module's initLogos() function, construct LogosModules with the provided LogosAPI*. The initLogos() function can be found in the .cpp file generated by the scaffolding — in the demo, this is src/delivery_module_plugin.cpp. LogosModules is generated at build time by logos-module-builder; include it via the umbrella header and keep it as a member on the plugin. The full API reference is in src/delivery_module_plugin.h and the module README.

info

Register event handlers before calling start() so you don't miss the first connectionStateChanged event.

  1. Initialise LogosModules in initLogos():

    #include "logos_sdk.h" // generated umbrella — exposes LogosModules

    // In your plugin class:
    // LogosModules* m_logos = nullptr;

    void MyPlugin::initLogos(LogosAPI* api) {
    m_logos = new LogosModules(api);
    // m_logos->delivery_module is now the typed wrapper for the Logos Delivery module.
    }
  2. Register event handlers. All lifecycle calls are synchronous; events arrive off-thread via the Qt event loop:

    m_logos->delivery_module.on("connectionStateChanged", [](const QVariantList& data) {
    // data[0]: QString — connection status
    // data[1]: QString — timestamp (ns since epoch)
    });

    m_logos->delivery_module.on("messageReceived", [](const QVariantList& data) {
    // data[0]: QString — messageHash
    // data[1]: QString — contentTopic
    // data[2]: QByteArray — payload (raw bytes)
    // data[3]: QString — timestamp (ns since epoch)
    });

    m_logos->delivery_module.on("messageSent", [](const QVariantList& data) { /* requestId, hash, ts */ });
    m_logos->delivery_module.on("messagePropagated", [](const QVariantList& data) { /* requestId, hash, ts */ });
    m_logos->delivery_module.on("messageError", [](const QVariantList& data) { /* requestId, hash, error, ts */ });
  3. Initialise the node with createNode, which returns a LogosResult. Always check success before continuing and surface getError() on failure. For a complete list of node configuration keys, see the Module Interface section of the README.

    const QString cfg = R"({"logLevel":"INFO","mode":"Core","preset":"logos.test"})";

    LogosResult r = m_logos->delivery_module.createNode(cfg);
    if (!r.success) {
    qWarning() << "createNode failed:" << r.getError();
    return;
    }
  4. Connect to the network with start(). The connectionStateChanged event fires on the Qt event loop once the node connects to peers:

    LogosResult r = m_logos->delivery_module.start();
    if (!r.success) {
    qWarning() << "start failed:" << r.getError();
    return;
    }
  5. Subscribe to a content topic using a LIP-23 content-topic string. Call subscribe() before any messages are sent on that topic:

    LogosResult r = m_logos->delivery_module.subscribe(contentTopic);
    if (!r.success) {
    qWarning() << "subscribe failed:" << r.getError();
    }
  6. Send a message. On success, getString() returns the request ID; track it through messageSentmessagePropagated events (or messageError):

    // payload is a QByteArray of raw bytes (same type messageReceived delivers in data[2]).
    LogosResult r = m_logos->delivery_module.send(contentTopic, payload);
    if (!r.success) {
    qWarning() << "send failed:" << r.getError();
    return;
    }
    const QString requestId = r.getString();
  7. Shut down cleanly with stop(). This tears down the underlying node and drops every active subscription and event listener:

    LogosResult s = m_logos->delivery_module.stop();
    if (!s.success) qWarning() << "stop failed:" << s.getError();

Step 4: Build and run

  1. Build the module:

    nix build
  2. Preview the module using logos-standalone-app (for ui_qml modules):

    nix run
  3. Package as .lgx for installation into logos-basecamp:

    nix build .#lgx

Troubleshooting the Logos Delivery module

createNode returns an unsuccessful LogosResult?

The JSON may be malformed, or an internal initialisation error occurred. Validate that the JSON is well-formed and that key names are camelCase matching WakuNodeConf fields. Set "logLevel": "DEBUG" for verbose output and inspect result.getError() for details.

send() returns an unsuccessful LogosResult?

The node was not started, or contentTopic is empty or invalid. Call start() first and verify it returned success. Confirm the content topic follows the LIP-23 format.

messageSent never fires after a successful send()?

The node may not be connected to peers yet, or the network layer rejected the message (for example, an RLN proof failure). Wait for connectionStateChanged before sending. If messageError fires, inspect data[2] for the error message.

messageReceived never fires?

subscribe() was not called before messages were sent, or the payload was sent on a different content topic. Call subscribe(topic) before any messages are sent on that topic.

Two instances of the same app on one host fail to start?

You pinned the same fixed port (for example, tcpPort) for both instances. Omit the port keys from the createNode config so each instance binds an OS-assigned ephemeral port — this is the default behaviour in v0.1.3. Alternatively, assign distinct explicit port values per instance.

createNode succeeds but a second call without stop() causes undefined behaviour?

createNode must be called exactly once per context. Call stop() and destroy the context before calling createNode again.