diff --git a/Makefile b/Makefile index 13882253e..ceebfe3c2 100644 --- a/Makefile +++ b/Makefile @@ -433,10 +433,11 @@ docker-liteprotocoltester-push: ################ ## C Bindings ## ################ -.PHONY: cbindings cwaku_example libwaku +.PHONY: cbindings cwaku_example libwaku liblmapi STATIC ?= 0 -BUILD_COMMAND ?= libwakuDynamic +LIBWAKU_BUILD_COMMAND ?= libwakuDynamic +LMAPI_BUILD_COMMAND ?= liblmapiDynamic ifeq ($(detected_OS),Windows) LIB_EXT_DYNAMIC = dll @@ -452,11 +453,15 @@ endif LIB_EXT := $(LIB_EXT_DYNAMIC) ifeq ($(STATIC), 1) LIB_EXT = $(LIB_EXT_STATIC) - BUILD_COMMAND = libwakuStatic + LIBWAKU_BUILD_COMMAND = libwakuStatic + LMAPI_BUILD_COMMAND = liblmapiStatic endif libwaku: | build deps librln - echo -e $(BUILD_MSG) "build/$@.$(LIB_EXT)" && $(ENV_SCRIPT) nim $(BUILD_COMMAND) $(NIM_PARAMS) waku.nims $@.$(LIB_EXT) + echo -e $(BUILD_MSG) "build/$@.$(LIB_EXT)" && $(ENV_SCRIPT) nim $(LIBWAKU_BUILD_COMMAND) $(NIM_PARAMS) waku.nims $@.$(LIB_EXT) + +liblmapi: | build deps librln + echo -e $(BUILD_MSG) "build/$@.$(LIB_EXT)" && $(ENV_SCRIPT) nim $(LMAPI_BUILD_COMMAND) $(NIM_PARAMS) waku.nims $@.$(LIB_EXT) ##################### ## Mobile Bindings ## diff --git a/flake.nix b/flake.nix index 88229a826..0263615c0 100644 --- a/flake.nix +++ b/flake.nix @@ -71,6 +71,13 @@ zerokitRln = zerokit.packages.${system}.rln; }; + liblmapi = pkgs.callPackage ./nix/default.nix { + inherit stableSystems; + src = self; + targets = ["liblmapi"]; + zerokitRln = zerokit.packages.${system}.rln; + }; + default = libwaku; }); diff --git a/liblmapi/declare_lib.nim b/liblmapi/declare_lib.nim new file mode 100644 index 000000000..30493a4b4 --- /dev/null +++ b/liblmapi/declare_lib.nim @@ -0,0 +1,10 @@ +import ffi +import waku/factory/waku + +declareLibrary("lmapi") + +proc lmapi_set_event_callback( + ctx: ptr FFIContext[Waku], callback: FFICallBack, userData: pointer +) {.dynlib, exportc, cdecl.} = + ctx[].eventCallback = cast[pointer](callback) + ctx[].eventUserData = userData diff --git a/liblmapi/examples/simple_example.c b/liblmapi/examples/simple_example.c new file mode 100644 index 000000000..654c009ef --- /dev/null +++ b/liblmapi/examples/simple_example.c @@ -0,0 +1,173 @@ +#include "../liblmapi.h" +#include +#include +#include +#include + +// Helper function to extract a JSON string field value +// Very basic parser - for production use a proper JSON library +const char* extract_json_field(const char *json, const char *field, char *buffer, size_t bufSize) { + char searchStr[256]; + snprintf(searchStr, sizeof(searchStr), "\"%s\":\"", field); + + const char *start = strstr(json, searchStr); + if (!start) { + return NULL; + } + + start += strlen(searchStr); + const char *end = strchr(start, '"'); + if (!end) { + return NULL; + } + + size_t len = end - start; + if (len >= bufSize) { + len = bufSize - 1; + } + + memcpy(buffer, start, len); + buffer[len] = '\0'; + + return buffer; +} + +// Event callback that handles message events +void event_callback(int ret, const char *msg, size_t len, void *userData) { + if (ret != RET_OK || msg == NULL || len == 0) { + return; + } + + // Create null-terminated string for easier parsing + char *eventJson = malloc(len + 1); + if (!eventJson) { + return; + } + memcpy(eventJson, msg, len); + eventJson[len] = '\0'; + + // Extract eventType + char eventType[64]; + if (!extract_json_field(eventJson, "eventType", eventType, sizeof(eventType))) { + free(eventJson); + return; + } + + // Handle different event types + if (strcmp(eventType, "message_sent") == 0) { + char requestId[128]; + char messageHash[128]; + extract_json_field(eventJson, "requestId", requestId, sizeof(requestId)); + extract_json_field(eventJson, "messageHash", messageHash, sizeof(messageHash)); + printf("📤 [EVENT] Message sent - RequestID: %s, Hash: %s\n", requestId, messageHash); + + } else if (strcmp(eventType, "message_error") == 0) { + char requestId[128]; + char messageHash[128]; + char error[256]; + extract_json_field(eventJson, "requestId", requestId, sizeof(requestId)); + extract_json_field(eventJson, "messageHash", messageHash, sizeof(messageHash)); + extract_json_field(eventJson, "error", error, sizeof(error)); + printf("❌ [EVENT] Message error - RequestID: %s, Hash: %s, Error: %s\n", + requestId, messageHash, error); + + } else if (strcmp(eventType, "message_propagated") == 0) { + char requestId[128]; + char messageHash[128]; + extract_json_field(eventJson, "requestId", requestId, sizeof(requestId)); + extract_json_field(eventJson, "messageHash", messageHash, sizeof(messageHash)); + printf("✅ [EVENT] Message propagated - RequestID: %s, Hash: %s\n", requestId, messageHash); + + } else { + printf("â„šī¸ [EVENT] Unknown event type: %s\n", eventType); + } + + free(eventJson); +} + +// Simple callback that prints results +void simple_callback(int ret, const char *msg, size_t len, void *userData) { + const char *operation = (const char *)userData; + if (ret == RET_OK) { + if (len > 0) { + printf("[%s] Success: %.*s\n", operation, (int)len, msg); + } else { + printf("[%s] Success\n", operation); + } + } else { + printf("[%s] Error: %.*s\n", operation, (int)len, msg); + } +} + +int main() { + printf("=== Logos Messaging API (LMAPI) Example ===\n\n"); + + // Configuration JSON for creating a node + const char *config = "{" + "\"mode\": \"Core\"," + "\"clusterId\": 1," + "\"entryNodes\": []," + "\"networkingConfig\": {" + "\"listenIpv4\": \"0.0.0.0\"," + "\"p2pTcpPort\": 60000," + "\"discv5UdpPort\": 9000" + "}" + "}"; + + printf("1. Creating node...\n"); + void *ctx = lmapi_create_node(config, simple_callback, (void *)"create_node"); + if (ctx == NULL) { + printf("Failed to create node\n"); + return 1; + } + + // Wait a bit for the callback + sleep(1); + + printf("\n2. Setting up event callback...\n"); + lmapi_set_event_callback(ctx, event_callback, NULL); + printf("Event callback registered for message events\n"); + + printf("\n3. Starting node...\n"); + lmapi_start_node(ctx, simple_callback, (void *)"start_node"); + + // Wait for node to start + sleep(2); + + printf("\n4. Subscribing to content topic...\n"); + const char *contentTopic = "/example/1/chat/proto"; + lmapi_subscribe(ctx, simple_callback, (void *)"subscribe", contentTopic); + + // Wait for subscription + sleep(1); + + printf("\n5. Sending a message...\n"); + printf("Watch for message events (sent, propagated, or error):\n"); + // Create base64-encoded payload: "Hello, Logos Messaging!" + const char *message = "{" + "\"contentTopic\": \"/example/1/chat/proto\"," + "\"payload\": \"SGVsbG8sIExvZ29zIE1lc3NhZ2luZyE=\"," + "\"ephemeral\": false" + "}"; + lmapi_send(ctx, simple_callback, (void *)"send", message); + + // Wait for message events to arrive + printf("Waiting for message delivery events...\n"); + sleep(3); + + printf("\n6. Unsubscribing from content topic...\n"); + lmapi_unsubscribe(ctx, simple_callback, (void *)"unsubscribe", contentTopic); + + sleep(1); + + printf("\n7. Stopping node...\n"); + lmapi_stop_node(ctx, simple_callback, (void *)"stop_node"); + + sleep(1); + + printf("\n8. Destroying context...\n"); + lmapi_destroy(ctx, simple_callback, (void *)"destroy"); + + printf("\n=== Example completed ===\n"); + return 0; +} diff --git a/liblmapi/json_event.nim b/liblmapi/json_event.nim new file mode 100644 index 000000000..389e29120 --- /dev/null +++ b/liblmapi/json_event.nim @@ -0,0 +1,27 @@ +import std/[json, macros] + +type JsonEvent*[T] = ref object + eventType*: string + payload*: T + +macro toFlatJson*(event: JsonEvent): JsonNode = + ## Serializes JsonEvent[T] to flat JSON with eventType first, + ## followed by all fields from T's payload + result = quote: + var jsonObj = newJObject() + jsonObj["eventType"] = %`event`.eventType + + # Serialize payload fields into the same object (flattening) + let payloadJson = %`event`.payload + for key, val in payloadJson.pairs: + jsonObj[key] = val + + jsonObj + +proc `$`*[T](event: JsonEvent[T]): string = + $toFlatJson(event) + +proc newJsonEvent*[T](eventType: string, payload: T): JsonEvent[T] = + ## Creates a new JsonEvent with the given eventType and payload. + ## The payload's fields will be flattened into the JSON output. + JsonEvent[T](eventType: eventType, payload: payload) diff --git a/liblmapi/liblmapi.h b/liblmapi/liblmapi.h new file mode 100644 index 000000000..55766f409 --- /dev/null +++ b/liblmapi/liblmapi.h @@ -0,0 +1,81 @@ + +// Generated manually and inspired by libwaku.h +// Header file for Logos Messaging API (LMAPI) library +#ifndef __liblmapi__ +#define __liblmapi__ + +#include +#include + +// The possible returned values for the functions that return int +#define RET_OK 0 +#define RET_ERR 1 +#define RET_MISSING_CALLBACK 2 + +#ifdef __cplusplus +extern "C" +{ +#endif + + typedef void (*FFICallBack)(int callerRet, const char *msg, size_t len, void *userData); + + // Creates a new instance of the node from the given configuration JSON. + // Returns a pointer to the Context needed by the rest of the API functions. + // Configuration should be in JSON format following the NodeConfig structure. + void *lmapi_create_node( + const char *configJson, + FFICallBack callback, + void *userData); + + // Starts the node. + int lmapi_start_node(void *ctx, + FFICallBack callback, + void *userData); + + // Stops the node. + int lmapi_stop_node(void *ctx, + FFICallBack callback, + void *userData); + + // Destroys an instance of a node created with lmapi_create_node + int lmapi_destroy(void *ctx, + FFICallBack callback, + void *userData); + + // Subscribe to a content topic. + // contentTopic: string representing the content topic (e.g., "/myapp/1/chat/proto") + int lmapi_subscribe(void *ctx, + FFICallBack callback, + void *userData, + const char *contentTopic); + + // Unsubscribe from a content topic. + int lmapi_unsubscribe(void *ctx, + FFICallBack callback, + void *userData, + const char *contentTopic); + + // Send a message. + // messageJson: JSON string with the following structure: + // { + // "contentTopic": "/myapp/1/chat/proto", + // "payload": "base64-encoded-payload", + // "ephemeral": false + // } + // Returns a request ID that can be used to track the message delivery. + int lmapi_send(void *ctx, + FFICallBack callback, + void *userData, + const char *messageJson); + + // Sets a callback that will be invoked whenever an event occurs. + // It is crucial that the passed callback is fast, non-blocking and potentially thread-safe. + void lmapi_set_event_callback(void *ctx, + FFICallBack callback, + void *userData); + +#ifdef __cplusplus +} +#endif + +#endif /* __liblmapi__ */ diff --git a/liblmapi/liblmapi.nim b/liblmapi/liblmapi.nim new file mode 100644 index 000000000..39f308994 --- /dev/null +++ b/liblmapi/liblmapi.nim @@ -0,0 +1,29 @@ +import std/[atomics, options] +import chronicles, chronos, chronos/threadsync, ffi +import waku/factory/waku, waku/node/waku_node, ./declare_lib + +################################################################################ +## Include different APIs, i.e. all procs with {.ffi.} pragma +include ./lmapi/node_api, ./lmapi/messaging_api + +################################################################################ +### Exported procs + +proc lmapi_destroy( + ctx: ptr FFIContext[Waku], callback: FFICallBack, userData: pointer +): cint {.dynlib, exportc, cdecl.} = + initializeLibrary() + checkParams(ctx, callback, userData) + + ffi.destroyFFIContext(ctx).isOkOr: + let msg = "liblmapi error: " & $error + callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), userData) + return RET_ERR + + ## always need to invoke the callback although we don't retrieve value to the caller + callback(RET_OK, nil, 0, userData) + + return RET_OK + +# ### End of exported procs +# ################################################################################ diff --git a/liblmapi/lmapi/messaging_api.nim b/liblmapi/lmapi/messaging_api.nim new file mode 100644 index 000000000..450804cb6 --- /dev/null +++ b/liblmapi/lmapi/messaging_api.nim @@ -0,0 +1,86 @@ +import std/[json, base64] +import chronos, results, ffi +import stew/byteutils +import + waku/factory/waku, + waku/waku_core/topics/content_topic, + waku/api/[api, types], + ../declare_lib + +proc lmapi_subscribe( + ctx: ptr FFIContext[Waku], + callback: FFICallBack, + userData: pointer, + contentTopicStr: cstring, +) {.ffi.} = + # ContentTopic is just a string type alias + let contentTopic = ContentTopic($contentTopicStr) + + (await api.subscribe(ctx.myLib[], contentTopic)).isOkOr: + let errMsg = $error + return err("Subscribe failed: " & errMsg) + + return ok("") + +proc lmapi_unsubscribe( + ctx: ptr FFIContext[Waku], + callback: FFICallBack, + userData: pointer, + contentTopicStr: cstring, +) {.ffi.} = + # ContentTopic is just a string type alias + let contentTopic = ContentTopic($contentTopicStr) + + api.unsubscribe(ctx.myLib[], contentTopic).isOkOr: + let errMsg = $error + return err("Unsubscribe failed: " & errMsg) + + return ok("") + +proc lmapi_send( + ctx: ptr FFIContext[Waku], + callback: FFICallBack, + userData: pointer, + messageJson: cstring, +) {.ffi.} = + ## Parse the message JSON and send the message + var jsonNode: JsonNode + try: + jsonNode = parseJson($messageJson) + except Exception as e: + return err("Failed to parse message JSON: " & e.msg) + + # Extract content topic + if not jsonNode.hasKey("contentTopic"): + return err("Missing contentTopic field") + + # ContentTopic is just a string type alias + let contentTopic = ContentTopic(jsonNode["contentTopic"].getStr()) + + # Extract payload (expect base64 encoded string) + if not jsonNode.hasKey("payload"): + return err("Missing payload field") + + var payload: seq[byte] + try: + let payloadStr = jsonNode["payload"].getStr() + # base64.decode returns string, convert to seq[byte] + let decodedStr = base64.decode(payloadStr) + payload = cast[seq[byte]](decodedStr) + except Exception as e: + return err("Failed to decode payload: " & e.msg) + + # Extract ephemeral flag + let ephemeral = jsonNode.getOrDefault("ephemeral").getBool(false) + + # Create message envelope + let envelope = MessageEnvelope.init( + contentTopic = contentTopic, payload = payload, ephemeral = ephemeral + ) + + # Send the message + let requestId = (await api.send(ctx.myLib[], envelope)).valueOr: + let errMsg = $error + return err("Send failed: " & errMsg) + + return ok($requestId) diff --git a/liblmapi/lmapi/node_api.nim b/liblmapi/lmapi/node_api.nim new file mode 100644 index 000000000..a27a176e4 --- /dev/null +++ b/liblmapi/lmapi/node_api.nim @@ -0,0 +1,155 @@ +import std/[json, options] +import chronos, results, ffi +import + waku/factory/waku, + waku/node/waku_node, + waku/api/[api, api_conf, types], + waku/events/message_events, + ../declare_lib, + ../json_event + +# Add JSON serialization for RequestId +proc `%`*(id: RequestId): JsonNode = + %($id) + +registerReqFFI(CreateNodeRequest, ctx: ptr FFIContext[Waku]): + proc(configJson: cstring): Future[Result[string, string]] {.async.} = + ## Parse the JSON configuration and create a node + var jsonNode: JsonNode + try: + jsonNode = parseJson($configJson) + except Exception as e: + return err("Failed to parse config JSON: " & e.msg) + + # Extract basic configuration + let mode = + if jsonNode.hasKey("mode") and jsonNode["mode"].getStr() == "Edge": + WakuMode.Edge + else: + WakuMode.Core + + # Build protocols config + var entryNodes: seq[string] = @[] + if jsonNode.hasKey("entryNodes"): + for node in jsonNode["entryNodes"]: + entryNodes.add(node.getStr()) + + var staticStoreNodes: seq[string] = @[] + if jsonNode.hasKey("staticStoreNodes"): + for node in jsonNode["staticStoreNodes"]: + staticStoreNodes.add(node.getStr()) + + let clusterId = + if jsonNode.hasKey("clusterId"): + uint16(jsonNode["clusterId"].getInt()) + else: + 1u16 # Default cluster ID + + # Build networking config + let networkingConfig = + if jsonNode.hasKey("networkingConfig"): + let netJson = jsonNode["networkingConfig"] + NetworkingConfig( + listenIpv4: netJson.getOrDefault("listenIpv4").getStr("0.0.0.0"), + p2pTcpPort: uint16(netJson.getOrDefault("p2pTcpPort").getInt(60000)), + discv5UdpPort: uint16(netJson.getOrDefault("discv5UdpPort").getInt(9000)), + ) + else: + DefaultNetworkingConfig + + # Build protocols config + let protocolsConfig = ProtocolsConfig.init( + entryNodes = entryNodes, + staticStoreNodes = staticStoreNodes, + clusterId = clusterId, + ) + + # Build node config + let nodeConfig = NodeConfig.init( + mode = mode, + protocolsConfig = protocolsConfig, + networkingConfig = networkingConfig, + ) + + # Create the node + ctx.myLib[] = (await api.createNode(nodeConfig)).valueOr: + let errMsg = $error + chronicles.error "CreateNodeRequest failed", err = errMsg + return err(errMsg) + + return ok("") + +proc lmapi_create_node( + configJson: cstring, callback: FFICallback, userData: pointer +): pointer {.dynlib, exportc, cdecl.} = + initializeLibrary() + + if isNil(callback): + echo "error: missing callback in lmapi_create_node" + return nil + + var ctx = ffi.createFFIContext[Waku]().valueOr: + let msg = "Error in createFFIContext: " & $error + callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), userData) + return nil + + ctx.userData = userData + + ffi.sendRequestToFFIThread( + ctx, CreateNodeRequest.ffiNewReq(callback, userData, configJson) + ).isOkOr: + let msg = "error in sendRequestToFFIThread: " & $error + callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), userData) + return nil + + return ctx + +proc lmapi_start_node( + ctx: ptr FFIContext[Waku], callback: FFICallBack, userData: pointer +) {.ffi.} = + # setting up outgoing event listeners + let sentListener = MessageSentEvent.listen( + ctx.myLib[].brokerCtx, + proc(event: MessageSentEvent) {.async: (raises: []).} = + callEventCallback(ctx, "onMessageSent"): + $newJsonEvent("message_sent", event), + ).valueOr: + chronicles.error "MessageSentEvent.listen failed", err = $error + return err("MessageSentEvent.listen failed: " & $error) + + let errorListener = MessageErrorEvent.listen( + ctx.myLib[].brokerCtx, + proc(event: MessageErrorEvent) {.async: (raises: []).} = + callEventCallback(ctx, "onMessageError"): + $newJsonEvent("message_error", event), + ).valueOr: + chronicles.error "MessageErrorEvent.listen failed", err = $error + return err("MessageErrorEvent.listen failed: " & $error) + + let propagatedListener = MessagePropagatedEvent.listen( + ctx.myLib[].brokerCtx, + proc(event: MessagePropagatedEvent) {.async: (raises: []).} = + callEventCallback(ctx, "onMessagePropagated"): + $newJsonEvent("message_propagated", event), + ).valueOr: + chronicles.error "MessagePropagatedEvent.listen failed", err = $error + return err("MessagePropagatedEvent.listen failed: " & $error) + + (await startWaku(addr ctx.myLib[])).isOkOr: + let errMsg = $error + chronicles.error "START_NODE failed", err = errMsg + return err("failed to start: " & errMsg) + return ok("") + +proc lmapi_stop_node( + ctx: ptr FFIContext[Waku], callback: FFICallBack, userData: pointer +) {.ffi.} = + MessageErrorEvent.dropAllListeners(ctx.myLib[].brokerCtx) + MessageSentEvent.dropAllListeners(ctx.myLib[].brokerCtx) + MessagePropagatedEvent.dropAllListeners(ctx.myLib[].brokerCtx) + + (await ctx.myLib[].stop()).isOkOr: + let errMsg = $error + chronicles.error "STOP_NODE failed", err = errMsg + return err("failed to stop: " & errMsg) + return ok("") diff --git a/liblmapi/nim.cfg b/liblmapi/nim.cfg new file mode 100644 index 000000000..b98c1b381 --- /dev/null +++ b/liblmapi/nim.cfg @@ -0,0 +1,27 @@ +# Nim configuration for liblmapi + +# Ensure correct compiler configuration +--gc: + refc +--threads: + on + +# Include paths +--path: + "../vendor/nim-ffi" +--path: + "../" + +# Optimization and debugging +--opt: + speed +--debugger: + native + +# Export symbols for dynamic library +--app: + lib +--noMain + +# Enable FFI macro features when needed for debugging +# --define:ffiDumpMacros diff --git a/nix/default.nix b/nix/default.nix index d77862e8f..6c853f4c3 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -94,8 +94,9 @@ in stdenv.mkDerivation { # Copy library files cp build/* $out/bin/ 2>/dev/null || true - # Copy the header file - cp library/libwaku.h $out/include/ + # Copy header files + cp library/libwaku.h $out/include/ 2>/dev/null || true + cp liblmapi/liblmapi.h $out/include/ 2>/dev/null || true ''; meta = with pkgs.lib; { diff --git a/waku.nimble b/waku.nimble index 7368ba74b..6ffbf834c 100644 --- a/waku.nimble +++ b/waku.nimble @@ -64,7 +64,7 @@ proc buildBinary(name: string, srcDir = "./", params = "", lang = "c") = exec "nim " & lang & " --out:build/" & name & " --mm:refc " & extra_params & " " & srcDir & name & ".nim" -proc buildLibrary(lib_name: string, srcDir = "./", params = "", `type` = "static") = +proc buildLibrary(lib_name: string, srcDir = "./", params = "", `type` = "static", srcFile = "libwaku.nim", mainPrefix = "libwaku") = if not dirExists "build": mkDir "build" # allow something like "nim nimbus --verbosity:0 --hints:off nimbus.nims" @@ -73,12 +73,12 @@ proc buildLibrary(lib_name: string, srcDir = "./", params = "", `type` = "static extra_params &= " " & paramStr(i) if `type` == "static": exec "nim c" & " --out:build/" & lib_name & - " --threads:on --app:staticlib --opt:size --noMain --mm:refc --header -d:metrics --nimMainPrefix:libwaku --skipParentCfg:on -d:discv5_protocol_id=d5waku " & - extra_params & " " & srcDir & "libwaku.nim" + " --threads:on --app:staticlib --opt:size --noMain --mm:refc --header -d:metrics --nimMainPrefix:" & mainPrefix & " --skipParentCfg:on -d:discv5_protocol_id=d5waku " & + extra_params & " " & srcDir & srcFile else: exec "nim c" & " --out:build/" & lib_name & - " --threads:on --app:lib --opt:size --noMain --mm:refc --header -d:metrics --nimMainPrefix:libwaku --skipParentCfg:off -d:discv5_protocol_id=d5waku " & - extra_params & " " & srcDir & "libwaku.nim" + " --threads:on --app:lib --opt:size --noMain --mm:refc --header -d:metrics --nimMainPrefix:" & mainPrefix & " --skipParentCfg:off -d:discv5_protocol_id=d5waku " & + extra_params & " " & srcDir & srcFile proc buildMobileAndroid(srcDir = ".", params = "") = let cpu = getEnv("CPU") @@ -400,3 +400,11 @@ task libWakuIOS, "Build the mobile bindings for iOS": let srcDir = "./library" let extraParams = "-d:chronicles_log_level=ERROR" buildMobileIOS srcDir, extraParams + +task liblmapiStatic, "Build the liblmapi (Logos Messaging API) static library": + let lib_name = paramStr(paramCount()) + buildLibrary lib_name, "liblmapi/", chroniclesParams, "static", "liblmapi.nim", "liblmapi" + +task liblmapiDynamic, "Build the liblmapi (Logos Messaging API) dynamic library": + let lib_name = paramStr(paramCount()) + buildLibrary lib_name, "liblmapi/", chroniclesParams, "dynamic", "liblmapi.nim", "liblmapi"