{ description = "LEZ programs — AMM client FFI"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; flake-utils.url = "github:numtide/flake-utils"; crane.url = "github:ipetkov/crane"; rust-overlay = { url = "github:oxalica/rust-overlay"; inputs.nixpkgs.follows = "nixpkgs"; }; # The AMM QML UI module (apps/amm) is built from this same flake so it can # reference the amm_client_ffi crate package via `self` — no filesystem # path or git-remote reference to this repo is needed (see apps/amm/flake.nix # history: a `git+file://` URL pointing at a local checkout is # machine-specific and not portable). logos-module-builder.url = "github:logos-co/logos-module-builder"; # Core wallet module (the LEZ wallet FFI Qt plugin). The input name must # match the metadata.json `dependencies` entry so the builder can resolve it # as a module dependency. # # Fork of logos-blockchain/logos-execution-zone-module @ d70225ced with the # QtRO serialization fix: send_generic_public_transaction's `instruction` uses # a byte-string IPC type so the args survive the cross-process boundary (at # d70225ced the API already takes program_id_hex instead of program_elf/deps). # See docs/amm-swap-qtro-serialization-bug.md. logos_execution_zone = { url = "github:gravityblast/logos-execution-zone-module?ref=fix/generic-tx-instruction-bstr"; # Override the module's pinned LEZ monorepo (logos-execution-zone) to the # SAME rev the target sequencer runs (415964d7): the wallet client and the # sequencer must agree on the JSON-RPC API, or tx submission fails at # runtime with `MethodNotFound`. 415964d7's wallet_ffi takes a program id # for send_generic_public_transaction, matching the d70225ced module. inputs.logos-execution-zone.url = "github:logos-blockchain/logos-execution-zone?rev=415964d7f9043a1bfe28da8d0e8b3a6f64abb258"; }; }; outputs = inputs@{ self, nixpkgs, flake-utils, crane, rust-overlay, logos-module-builder, logos_execution_zone, ... }: let crateOutputs = flake-utils.lib.eachDefaultSystem ( system: let pkgs = import nixpkgs { inherit system; overlays = [ rust-overlay.overlays.default ]; }; # Honor the repo's pinned toolchain (rust-toolchain.toml -> 1.94.0). rustToolchain = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml; craneLib = (crane.mkLib pkgs).overrideToolchain rustToolchain; # Whole workspace: crane needs Cargo.lock + all path deps (amm_core, # twap_oracle_core, token_core, ...) to resolve `-p amm_client_ffi`. src = ./.; commonArgs = { inherit src; strictDeps = true; pname = "amm_client_ffi"; version = "0.1.0"; # CRITICAL: scope to ONLY this crate. The workspace also contains # `amm/methods` etc. whose build.rs compiles the risc0 guest (which # WOULD invoke Metal on darwin). `-p amm_client_ffi` never builds # those crates or their build scripts. cargoExtraArgs = "-p amm_client_ffi"; doCheck = false; # NOTE: cbindgen is used here as a Cargo *build-dependency* # (invoked from build.rs via its Rust library API), not as the # standalone CLI package — so no nativeBuildInputs entry is # needed; crane builds it as part of the normal cargo graph. }; cargoArtifacts = craneLib.buildDepsOnly commonArgs; ammClientFfi = craneLib.buildPackage ( commonArgs // { inherit cargoArtifacts; postInstall = '' mkdir -p $out/include cp programs/amm/client-ffi/amm_client_ffi.h $out/include/ '' + pkgs.lib.optionalString pkgs.stdenv.isDarwin '' # Set the dylib's install-name to its ABSOLUTE store path (NOT # @rpath): the logos module builder links this lib into the plugin # but never stages it into the plugin's runtime rpath, so an # @rpath id fails to dlopen at launch. An absolute /nix/store id # is recorded in the plugin's LC_LOAD_DYLIB, kept in the closure # by Nix, and resolved directly at runtime — no rpath needed. if [ -f $out/lib/libamm_client_ffi.dylib ]; then install_name_tool -id "$out/lib/libamm_client_ffi.dylib" $out/lib/libamm_client_ffi.dylib fi ''; } ); in { packages.default = ammClientFfi; packages.amm_client_ffi = ammClientFfi; } ); # The AMM QML UI module (apps/amm). Its external_libraries entry # (amm_client_ffi) is resolved to `self.packages.${system}.amm_client_ffi` # above — the module builder's resolveExtInput reads # `flakeInput.packages.${system}.${pkgName}`, and passing `self` here # works because `self` is this very flake, which already exposes that # package per crateOutputs above. appOutputs = logos-module-builder.lib.mkLogosQmlModule { src = ./apps/amm; configFile = ./apps/amm/metadata.json; flakeInputs = inputs; externalLibInputs = { amm_client_ffi = { input = self; packages.default = "amm_client_ffi"; }; }; }; # Expose the AMM QML UI as a NAMED app/package (`amm-ui`) rather than # `.default` — the repo is expected to grow more UIs over time, # each runnable via `nix run .#`. `appOutputs.apps..default` # and `appOutputs.packages..default` (the UI launcher and its # bundle, respectively) are renamed to `amm-ui`; no `default` survives # for either attribute set. appApps = appOutputs.apps or { }; appPkgs = appOutputs.packages or { }; # Wrap the app launcher to export DYLD_FALLBACK_LIBRARY_PATH pointing at the # amm_client_ffi lib. The logos module builder links the plugin against # @rpath/libamm_client_ffi.dylib but does NOT stage that dylib into the # plugin-dir it loads at runtime, so dlopen fails with "Failed to load UI # plugin". Adding the crate's store lib dir to DYLD's fallback search path # lets the loader find it (the store path stays in the closure). wrapWithDyld = system: app: let pkgs = import nixpkgs { inherit system; overlays = [ rust-overlay.overlays.default ]; }; ammFfi = crateOutputs.packages.${system}.amm_client_ffi; in app // { program = "${pkgs.writeShellScript "run-amm-ui" '' export DYLD_FALLBACK_LIBRARY_PATH="${ammFfi}/lib''${DYLD_FALLBACK_LIBRARY_PATH:+:$DYLD_FALLBACK_LIBRARY_PATH}" exec ${app.program} "$@" ''}"; }; renamedApps = builtins.mapAttrs ( system: attrs: (builtins.removeAttrs attrs [ "default" ]) // (if attrs ? default then { amm-ui = wrapWithDyld system attrs.default; } else { }) ) appApps; mergedPackages = builtins.mapAttrs ( system: cratePkgs: let appSysPkgs = appPkgs.${system} or { }; in (builtins.removeAttrs cratePkgs [ "default" ]) // (builtins.removeAttrs appSysPkgs [ "default" ]) // (if appSysPkgs ? default then { amm-ui = appSysPkgs.default; } else { }) ) crateOutputs.packages; in (builtins.removeAttrs appOutputs [ "apps" "packages" ]) // { apps = renamedApps; packages = mergedPackages; }; }