{"id":18928834,"url":"https://github.com/lightninglabs/falafel","last_synced_at":"2026-03-16T08:34:13.744Z","repository":{"id":48292826,"uuid":"205000318","full_name":"lightninglabs/falafel","owner":"lightninglabs","description":"Go tool to generate go APIs for gRPC services for use on mobile/WASM platforms.","archived":false,"fork":false,"pushed_at":"2024-08-15T22:54:40.000Z","size":4424,"stargazers_count":34,"open_issues_count":0,"forks_count":11,"subscribers_count":27,"default_branch":"master","last_synced_at":"2025-04-15T06:54:07.340Z","etag":null,"topics":["bitcoin","golang","grpc","lightning-network","lnd","protobuf","wasm"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lightninglabs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-08-28T18:43:49.000Z","updated_at":"2024-08-15T22:54:44.000Z","dependencies_parsed_at":"2024-08-15T23:52:37.301Z","dependency_job_id":"32f43d8e-7f10-4eae-a7a2-442ae4ac6da2","html_url":"https://github.com/lightninglabs/falafel","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lightninglabs%2Ffalafel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lightninglabs%2Ffalafel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lightninglabs%2Ffalafel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lightninglabs%2Ffalafel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lightninglabs","download_url":"https://codeload.github.com/lightninglabs/falafel/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249023710,"owners_count":21199958,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bitcoin","golang","grpc","lightning-network","lnd","protobuf","wasm"],"created_at":"2024-11-08T11:28:12.771Z","updated_at":"2026-03-16T08:34:13.710Z","avatar_url":"https://github.com/lightninglabs.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# falafel\nfalafel is a `protoc` plugin written in go that is used to generate\n[`gomobile`](https://godoc.org/golang.org/x/mobile/cmd/gomobile) compatible\nAPIs for gRPC services for use on mobile platforms.\n\nCurrently being used with\n[lnd](https://github.com/lightningnetwork/lnd/tree/master/mobile).\n\n## Description\nfalafel translates protobuf definitions to `gomobile` compatible APIs. Behind\nthis API we directly talk to the gRPC server using an in-memory gRPC client,\nensuring all communication happens in-process using serialized protocol\nbuffers, without needing to expose the gRPC server on an open port. To support\nstreaming RPCs, like subscribing to real-time updates, callbacks are provided\nfor all APIs.\n\nThe gRPC server must support using custom listeners.\n\n## Getting started\n\n### Pass the falafel plugin to `protoc` with custom options.\nHere is an example how `falafel` is used with `lnd`:\n\n```bash\nfalafel=$(which falafel)\n\n# Name of the package for the generated APIs.\npkg=\"lndmobile\"\n\n# The package where the protobuf definitions originally are found.\ntarget_pkg=\"github.com/lightningnetwork/lnd/lnrpc\"\n\n# A mapping from grpc service to name of the custom listeners. The grpc server\n# must be configured to listen on these.\nlisteners=\"lightning=lightningLis walletunlocker=walletUnlockerLis\"\n\n# Set to 1 to create boiler plate grpc client code and listeners. If more than\n# one proto file is being parsed, it should only be done once.\nmem_rpc=1\n\nopts=\"package_name=$pkg,target_package=$target_pkg,listeners=$listeners,mem_rpc=$mem_rpc\"\nprotoc -I/usr/local/include -I. \\\n       -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \\\n       --plugin=protoc-gen-custom=$falafel\\\n       --custom_out=./build \\\n       --custom_opt=\"$opts\" \\\n       --proto_path=../lnrpc \\\n       rpc.proto\n```\n\nWith the go bindings generated, define an entry point for the application to\nstart the gRPC service:\n\n```go\nfunc Start() {\n\t// We call the main method with the custom in-memory listeners called\n\t// by the mobile APIs, such that the grpc server will use these.\n\tcfg := lnd.ListenerCfg{\n\t\tWalletUnlocker: walletUnlockerLis,\n\t\tRPCListener:    lightningLis,\n\t}\n\n\tgo func() {\n\t\tif err := lnd.Main(cfg); err != nil {\n\t\t\tif e, ok := err.(*flags.Error); ok \u0026\u0026\n\t\t\t\te.Type == flags.ErrHelp {\n\t\t\t} else {\n\t\t\t\tfmt.Fprintln(os.Stderr, err)\n\t\t\t}\n\t\t\tos.Exit(1)\n\t\t}\n\t}()\n}\n```\n\nThe gRPC server should be started by listening on the passed listeners.\n\n### Compiling with gomobile\nPackage `lndmobile` is now ready to be cross-compiled using `gomobile`:\n```bash\ngomobile bind -target=ios github.com/lightningnetwork/lnd/mobile\n```\n\n## Generating JSON/WASM stubs\n\nfalafel was initially built as a code generator specifically for generating\n`gomobile` compatible RPC stubs for `lnd`. But because generating stub code from\nprotobuf files is a very useful task, falafel also has a secondary operating\nmode in which it generates stubs for interacting with a gRPC interface from a\nJSON/WASM context.\n\n### What are JSON/WASM stubs?\n\nIn short, the JSON stubs generated by falafel is helper code that allows a\ndynamic language environment that uses JSON as its main data structure (e.g.\nJavaScript code running in a browser) to interact with a gRPC client that is\nrunning in the same browser but for example in a WASM context.\n\nOr in other words: The stubs convert a JSON request into a proper gRPC request,\nsend it to the gRPC server and translate the response back into JSON.\n\nThe stubs could also be described as doing the reverse of what `grpc-gateway`\ndoes, on the client side, translating between JSON and native gRPC.\n\n### Example of generated code\n\nFor our main example, we assume the following `stateservice.proto` file:\n\n```protobuf\nsyntax = \"proto3\";\npackage lnrpc;\noption go_package = \"github.com/lightningnetwork/lnd/lnrpc\";\n\nservice State {\n  rpc SubscribeState (SubscribeStateRequest)\n      returns (stream SubscribeStateResponse);\n  rpc GetState (GetStateRequest) returns (GetStateResponse);\n}\n\nenum WalletState {\n  NON_EXISTING = 0;\n  LOCKED = 1;\n  UNLOCKED = 2;\n  RPC_ACTIVE = 3;\n\n  WAITING_TO_START = 255;\n}\nmessage SubscribeStateRequest {\n}\nmessage SubscribeStateResponse {\n  WalletState state = 1;\n}\nmessage GetStateRequest {\n}\nmessage GetStateResponse {\n  WalletState state = 1;\n}\n```\n\nRunning falafel with:\n\n```shell\nFALAFEL_BIN=$(which falafel)\nopts=\"package_name=lnrpc,js_stubs=1,build_tags=// +build js\"\nprotoc -I/usr/local/include -I. -I.. \\\n    --plugin=protoc-gen-custom=$FALAFEL_BIN\\\n    --custom_out=. \\\n    --custom_opt=\"$opts\" \\\n    stateservice.proto\n```\n\nwill then generate the following stub file:\n\n```go\n// Code generated by falafel 0.9.1. DO NOT EDIT.\n// source: stateservice.proto\n\n// +build js\n\npackage main\n\nimport (\n\t\"context\"\n\n\tgateway \"github.com/grpc-ecosystem/grpc-gateway/v2/runtime\"\n\t\"github.com/lightningnetwork/lnd/lnrpc\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/protobuf/encoding/protojson\"\n)\n\nfunc RegisterStateJSONCallbacks(registry map[string]func(ctx context.Context,\n\tconn *grpc.ClientConn, reqJSON string, callback func(string, error))) {\n\n\tmarshaler := \u0026gateway.JSONPb{\n\t    MarshalOptions: protojson.MarshalOptions{\n\t\t    UseProtoNames:   true,\n\t\t    EmitUnpopulated: true,\n\t    },\n\t}\n\n\tregistry[\"lnrpc.State.SubscribeState\"] = func(ctx context.Context,\n\t\tconn *grpc.ClientConn, reqJSON string, callback func(string, error)) {\n\n\t\treq := \u0026lnrpc.SubscribeStateRequest{}\n\t\terr := marshaler.Unmarshal([]byte(reqJSON), req)\n\t\tif err != nil {\n\t\t\tcallback(\"\", err)\n\t\t\treturn\n\t\t}\n\n\t\tclient := lnrpc.NewStateClient(conn)\n\t\tstream, err := client.SubscribeState(ctx, req)\n\t\tif err != nil {\n\t\t\tcallback(\"\", err)\n\t\t\treturn\n\t\t}\n\n\t\tgo func() {\n\t\t\tfor {\n\t\t\t\tselect {\n\t\t\t\tcase \u003c-stream.Context().Done():\n\t\t\t\t\tcallback(\"\", stream.Context().Err())\n\t\t\t\t\treturn\n\t\t\t\tdefault:\n\t\t\t\t}\n\n\t\t\t\tresp, err := stream.Recv()\n\t\t\t\tif err != nil {\n\t\t\t\t\tcallback(\"\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\trespBytes, err := marshaler.Marshal(resp)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcallback(\"\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tcallback(string(respBytes), nil)\n\t\t\t}\n\t\t}()\n\t}\n\n\tregistry[\"lnrpc.State.GetState\"] = func(ctx context.Context,\n\t\tconn *grpc.ClientConn, reqJSON string, callback func(string, error)) {\n\n\t\treq := \u0026lnrpc.GetStateRequest{}\n\t\terr := marshaler.Unmarshal([]byte(reqJSON), req)\n\t\tif err != nil {\n\t\t\tcallback(\"\", err)\n\t\t\treturn\n\t\t}\n\n\t\tclient := lnrpc.NewStateClient(conn)\n\t\tresp, err := client.GetState(ctx, req)\n\t\tif err != nil {\n\t\t\tcallback(\"\", err)\n\t\t\treturn\n\t\t}\n\n\t\trespBytes, err := marshaler.Marshal(resp)\n\t\tif err != nil {\n\t\t\tcallback(\"\", err)\n\t\t\treturn\n\t\t}\n\t\tcallback(string(respBytes), nil)\n\t}\n}\n```\n\nAn example WASM client can then be built to bridge the gap between JavaScript\nand the native gRPC client.\n\n```go\n// +build js\n\npackage main\n\nimport (\n\t\"context\"\n\t\"runtime/debug\"\n\t\"syscall/js\"\n\n\t\"google.golang.org/grpc\"\n\t\n\t// Import the generated JSON stubs from the lnrpc package where we generated\n\t// them before\n\t_ \"github.com/lightningnetwork/lnd/lnrpc\"\n)\n\nvar (\n\tlndConn *grpc.ClientConn\n\n\tregistry = make(map[string]func(ctx context.Context,\n\t\tconn *grpc.ClientConn, reqJSON string,\n\t\tcallback func(string, error)))\n)\n\nfunc main() {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tlog.Debugf(\"Recovered in f: %v\", r)\n\t\t\tdebug.PrintStack()\n\t\t}\n\t}()\n\n\t// Setup JS callbacks.\n\tjs.Global().Set(\"wasmClientInvokeRPC\", js.FuncOf(wasmClientInvokeRPC))\n\tlnrpc.RegisterStateJSONCallbacks(registry)\n\n\t// Setup native gRPC connection to lnd, the stubs will translate calls to\n\t// this connection.\n\tlndConn = connectToLnd()\n\n\t// Wait for interrupt signal here or do other stuff...\n}\n\nfunc wasmClientInvokeRPC(_ js.Value, args []js.Value) interface{} {\n\tif len(args) != 3 {\n\t\treturn js.ValueOf(\"invalid use of wasmClientInvokeRPC, \" +\n\t\t\t\"need 3 parameters: rpcName, request, callback\")\n\t}\n\n\tif lndConn == nil {\n\t\treturn js.ValueOf(\"RPC connection not ready\")\n\t}\n\n\trpcName := args[0].String()\n\trequestJSON := args[1].String()\n\tjsCallback := args[len(args)-1:][0]\n\n\tmethod, ok := registry[rpcName]\n\tif !ok {\n\t\treturn js.ValueOf(\"rpc with name \" + rpcName + \" not found\")\n\t}\n\n\tgo func() {\n\t\tlog.Infof(\"Calling '%s' on RPC with request %s\",\n\t\t\trpcName, requestJSON)\n\t\tcb := func(resultJSON string, err error) {\n\t\t\tif err != nil {\n\t\t\t\tjsCallback.Invoke(js.ValueOf(err.Error()))\n\t\t\t} else {\n\t\t\t\tjsCallback.Invoke(js.ValueOf(resultJSON))\n\t\t\t}\n\t\t}\n\t\tctx := context.Background()\n\t\tmethod(ctx, lndConn, requestJSON, cb)\n\t\t\u003c-ctx.Done()\n\t}()\n\treturn nil\n}\n```\n\nA website can then call into those functions with a little bit of JavaScript\n(we assume here that the WASM binary was already loaded and initialized\ncorrectly for this example):\n\n```html\n\u003chtml\u003e\n\u003cbody\u003e\n\n\u003cbutton onClick=\"callWASM('GetState', '{}');\"\u003eGetState\u003c/button\u003e\n\n\u003cscript\u003e\n    async function callWASM(rpcName, req) {\n        wasmClientInvokeRPC('lnrpc.Lightning.'+rpcName, req, setResult);\n    }\n    \n    function setResult(result) {\n        console.log(\"Got result from RPC: \" + JSON.stringify(result));\n    }\n\u003c/script\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flightninglabs%2Ffalafel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flightninglabs%2Ffalafel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flightninglabs%2Ffalafel/lists"}