refactor(apps/amm): move the amm_client crate into modules/amm/ffi as amm_ffi

After the amm_module refactor, this crate is linked only by the module (the
UI delegates to modules().amm_module and links nothing), so its home under
apps/amm/ and the name "client" were both misnomers: it's the AMM business
logic the module wraps, reached across an FFI boundary — the same relationship
logos_execution_zone has with wallet_ffi. Co-locate it with the module that
owns it and name it for that role.

The FFI surface is unchanged — the exported functions are already amm_* (not
amm_client_*) — so only the crate, directory, generated header, dylib, and
package names move. The module's call sites are untouched; it just includes the
renamed header.

- apps/amm/client → modules/amm/ffi (git-tracked rename; history preserved)
- crate amm_client → amm_ffi: package name, include/amm_ffi.h, libamm_ffi.dylib,
  AMM_FFI_H guard, build.rs output path, tests/public_api.rs import
- workspace member path + Cargo.lock
- flake.nix: pname, -p, header-copy path, dylib install_name, packages.amm_ffi,
  ammModuleOutputs externalLibInputs, DYLD wrapper
- modules/amm: metadata external_libraries, CMakeLists EXTERNAL_LIBS, impl
  #include + comments, README, flake note
- apps/amm/flake.nix: drop the now-dead amm_client external-lib input (the UI
  links no external lib of its own)

Resulting layout:
  modules/amm/
    src/   # C++ module  (amm_module_impl.{h,cpp})
    ffi/   # Rust crate  amm_ffi  (Cargo.toml, src/, include/amm_ffi.h)
This commit is contained in:
r4bbit
2026-08-03 23:11:06 +02:00
parent afeba568d8
commit f5ff9b829f
38 changed files with 83 additions and 94 deletions
+8 -8
View File
@@ -20,7 +20,7 @@
#include "logos_sdk.h"
extern "C" {
#include "amm_client.h"
#include "amm_ffi.h"
}
namespace {
@@ -193,20 +193,20 @@ std::vector<uint8_t> jsonWordsToLeBytes(const json& arr) {
return out;
}
// Result of an amm_client JSON op: the `{ ok, value }` envelope decoded.
// Result of an amm_ffi JSON op: the `{ ok, value }` envelope decoded.
struct FfiResult {
bool ok = false;
json value;
};
// Serialize `request`, hand it to an amm_client op, and decode its
// Serialize `request`, hand it to an amm_ffi op, and decode its
// `{ ok, value, error }` envelope. Mirrors apps/amm/src/AmmClient.cpp. `value`
// is only populated (and `ok` true) when the op reports success with an object.
FfiResult call(char* (*op)(const char*), const json& request) {
const std::string payload = request.dump();
char* raw = op(payload.c_str());
if (raw == nullptr) {
AMM_TRACE("amm_client op returned null");
AMM_TRACE("amm_ffi op returned null");
return {};
}
const std::string response(raw);
@@ -214,16 +214,16 @@ FfiResult call(char* (*op)(const char*), const json& request) {
const auto doc = json::parse(response, nullptr, /*allow_exceptions=*/false);
if (!doc.is_object()) {
AMM_TRACE("amm_client op returned invalid JSON");
AMM_TRACE("amm_ffi op returned invalid JSON");
return {};
}
if (!doc.value("ok", false)) {
AMM_TRACE("amm_client op failure: " << doc.value("error", std::string()));
AMM_TRACE("amm_ffi op failure: " << doc.value("error", std::string()));
return {};
}
const auto it = doc.find("value");
if (it == doc.end() || !it->is_object()) {
AMM_TRACE("amm_client op value is not an object");
AMM_TRACE("amm_ffi op value is not an object");
return {};
}
return {true, *it};
@@ -294,7 +294,7 @@ std::vector<uint8_t> AmmModuleImpl::loadAmmElf() {
std::string AmmModuleImpl::ammProgramId() {
const std::vector<uint8_t> elf = loadAmmElf();
if (elf.empty()) return {};
// Hand the deployed binary to the amm_client program_id op, which decodes it
// Hand the deployed binary to the amm_ffi program_id op, which decodes it
// and computes the Image ID — 64-char lowercase hex, little-endian per u32
// word (matches `spel program-id` and the on-chain *_program_id fields).
const FfiResult r = call(amm_program_id, json{{"elf", toHex(elf.data(), elf.size())}});
+4 -4
View File
@@ -11,7 +11,7 @@
//
// Orchestration only: the AMM domain math (PDA derivation, on-chain account
// decoding, quote/plan computation, and instruction encoding) lives in the Rust
// `amm_client` crate and is reached through its JSON FFI (amm_client.h — one
// `amm_ffi` crate and is reached through its JSON FFI (amm_ffi.h — one
// `char* op(const char*)` per operation, request and response both JSON). This
// module sequences those ops with chain I/O delegated to the
// `logos_execution_zone` wallet module (reached via modules().logos_execution_zone).
@@ -108,7 +108,7 @@ private:
// success, so a startup miss (bin not readable yet) retries.
Network network();
// 64-char lowercase-hex AMM program id via the amm_client `program_id` op
// 64-char lowercase-hex AMM program id via the amm_ffi `program_id` op
// over the AMM_PROGRAM_BIN bytes (empty if unset/unreadable/bad).
std::string ammProgramId();
@@ -120,13 +120,13 @@ private:
std::string normalizeAccountId(const std::string& id);
// Derives the config account id (amm_config_id) and reads it, returning the
// account-read shape the amm_client ops embed. Null json when the config_id
// account-read shape the amm_ffi ops embed. Null json when the config_id
// op itself fails (readPublicAccount always yields at least {id,status}).
nlohmann::json readConfig(const Network& net);
// Reads a public account through the wallet module and returns the
// { id, status, account:{ program_owner, balance, nonce, data } } shape the
// amm_client ops expect (see the app-side accountReadJson). `account` is
// amm_ffi ops expect (see the app-side accountReadJson). `account` is
// omitted when the read has no data (uninitialized/nonexistent).
nlohmann::json readPublicAccount(const std::string& account_id);