feat(token): add Logos token API module

This commit is contained in:
Ricardo Guilherme Schmidt
2026-08-20 12:12:57 +02:00
committed by r4bbit
parent 72a3e741a0
commit 741e72add9
27 changed files with 3627 additions and 81 deletions
@@ -0,0 +1,34 @@
#include "token_instruction_words.h"
#include <cstdint>
#include <limits>
#include <nlohmann/json.hpp>
namespace token_module::detail {
std::vector<std::uint8_t> jsonInstructionLeBytes(const nlohmann::json& input) {
std::vector<std::uint8_t> result;
if (!input.is_array()) return result;
result.reserve(input.size() * sizeof(std::uint32_t));
for (const auto& item : input) {
if (!item.is_number_unsigned() && !item.is_number_integer()) return {};
std::uint64_t raw = 0;
if (item.is_number_unsigned()) {
raw = item.get<std::uint64_t>();
} else {
const std::int64_t signed_raw = item.get<std::int64_t>();
if (signed_raw < 0) return {};
raw = static_cast<std::uint64_t>(signed_raw);
}
if (raw > std::numeric_limits<std::uint32_t>::max()) return {};
const auto word = static_cast<std::uint32_t>(raw);
result.push_back(static_cast<std::uint8_t>(word & 0xff));
result.push_back(static_cast<std::uint8_t>((word >> 8) & 0xff));
result.push_back(static_cast<std::uint8_t>((word >> 16) & 0xff));
result.push_back(static_cast<std::uint8_t>((word >> 24) & 0xff));
}
return result;
}
} // namespace token_module::detail
@@ -0,0 +1,16 @@
#pragma once
#include <cstdint>
#include <vector>
#include <nlohmann/json_fwd.hpp>
namespace token_module::detail {
// Decodes a plan's `instruction` word array (u32 values) into the little-endian
// byte string the wallet module's send_generic_public_transaction expects (its
// `instruction` param is a byte-string IPC type). Returns {} on any non-array
// input or a word that is negative, fractional, or exceeds u32.
std::vector<std::uint8_t> jsonInstructionLeBytes(const nlohmann::json& input);
} // namespace token_module::detail
+808
View File
@@ -0,0 +1,808 @@
#include "token_module_impl.h"
#include "token_instruction_words.h"
#include <algorithm>
#include <cctype>
#include <cstdint>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <iterator>
#include <memory>
#include <mutex>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include <nlohmann/json.hpp>
// Generated by logos-module-builder. Kept out of the universal API header.
#include "logos_sdk.h"
extern "C" {
#include "token_ffi.h"
}
namespace {
using json = nlohmann::json;
constexpr char TOKEN_PROGRAM_BIN_ENV[] = "TOKEN_PROGRAM_BIN";
constexpr char TOKEN_PROGRAM_ID_ENV[] = "TOKEN_PROGRAM_ID";
std::mutex programInfoMutex;
bool tokenDebug() {
static const bool enabled = std::getenv("TOKEN_DEBUG") != nullptr;
return enabled;
}
#define TOKEN_TRACE(message) \
do { \
if (tokenDebug()) std::cerr << "[token-debug] " << message << '\n'; \
} while (false)
int hexValue(char value) {
if (value >= '0' && value <= '9') return value - '0';
if (value >= 'a' && value <= 'f') return value - 'a' + 10;
if (value >= 'A' && value <= 'F') return value - 'A' + 10;
return -1;
}
bool isHexLength(const std::string& value, std::size_t length) {
if (value.size() != length) return false;
return std::all_of(value.begin(), value.end(), [](char c) { return hexValue(c) >= 0; });
}
bool isEvenHex(const std::string& value) {
return value.size() % 2 == 0
&& std::all_of(value.begin(), value.end(), [](char c) { return hexValue(c) >= 0; });
}
std::string lowercase(std::string value) {
std::transform(value.begin(), value.end(), value.begin(), [](unsigned char c) {
return static_cast<char>(std::tolower(c));
});
return value;
}
std::string trim(const std::string& value) {
std::size_t begin = 0;
std::size_t end = value.size();
while (begin < end && std::isspace(static_cast<unsigned char>(value[begin]))) ++begin;
while (end > begin && std::isspace(static_cast<unsigned char>(value[end - 1]))) --end;
return value.substr(begin, end - begin);
}
bool isZeroId(const std::string& value) {
return value.size() == 64
&& std::all_of(value.begin(), value.end(), [](char c) { return c == '0'; });
}
bool isAllZero(const std::string& value) {
return !value.empty()
&& std::all_of(value.begin(), value.end(), [](char c) { return c == '0'; });
}
bool isDefaultPublicAccount(const std::string& owner,
const std::string& balance,
const std::string& nonce,
const std::string& data) {
// The execution-zone read API represents an absent public account with
// Account::default(): zero owner, balance, and nonce, with no data.
return isZeroId(owner) && isAllZero(balance) && isAllZero(nonce) && data.empty();
}
std::string jsonString(const json& object, const char* key) {
const auto it = object.find(key);
return it != object.end() && it->is_string() ? it->get<std::string>() : std::string();
}
std::string bytesToHex(const std::uint8_t* bytes, std::size_t length) {
static constexpr char digits[] = "0123456789abcdef";
std::string result;
result.reserve(length * 2);
for (std::size_t i = 0; i < length; ++i) {
result.push_back(digits[bytes[i] >> 4]);
result.push_back(digits[bytes[i] & 0x0f]);
}
return result;
}
LogosMap publicOk() {
return {{"status", "ok"}, {"error", ""}};
}
LogosMap publicError(const std::string& error) {
return {{"status", "error"}, {"error", error}};
}
template <typename Operation>
LogosMap guarded(Operation&& operation) noexcept {
try {
return operation();
} catch (const std::exception& error) {
TOKEN_TRACE("caught exception: " << error.what());
} catch (...) {
TOKEN_TRACE("caught non-standard exception");
}
return publicError("backend_error");
}
struct FfiResult {
bool ok = false;
json value;
std::string error;
};
FfiResult callToken(char* (*operation)(const char*), const json& request) {
const std::string payload = request.dump();
std::unique_ptr<char, decltype(&token_free)> response(
operation(payload.c_str()),
&token_free);
if (!response) {
TOKEN_TRACE("token_ffi returned null");
return {};
}
const json document = json::parse(response.get(), nullptr, false);
if (!document.is_object()) {
TOKEN_TRACE("token_ffi returned malformed JSON");
return {};
}
const auto ok = document.find("ok");
if (ok == document.end() || !ok->is_boolean()) return {};
if (!ok->get<bool>()) {
const std::string error = jsonString(document, "error");
TOKEN_TRACE("token_ffi failure: " << error);
return {false, json(), error};
}
const auto value = document.find("value");
if (value == document.end() || !value->is_object()) return {};
return {true, *value, {}};
}
bool isStableError(const std::string& error) {
static const std::set<std::string> stable = {
"bad_request",
"bad_amount",
"invalid_account_id",
"invalid_authority",
"invalid_metadata_standard",
"account_read_failed",
"token_program_mismatch",
"invalid_definition_data",
"invalid_holding_data",
"invalid_metadata_data",
"backend_error",
};
return stable.find(error) != stable.end();
}
std::string ffiError(const FfiResult& result, const std::string& fallback = "backend_error") {
return isStableError(result.error) ? result.error : fallback;
}
} // namespace
std::vector<std::uint8_t> TokenModuleImpl::loadTokenBinary() const {
const char* path = std::getenv(TOKEN_PROGRAM_BIN_ENV);
if (path == nullptr || *path == '\0') return {};
std::ifstream file(path, std::ios::binary);
if (!file) return {};
return std::vector<std::uint8_t>(std::istreambuf_iterator<char>(file),
std::istreambuf_iterator<char>());
}
std::string TokenModuleImpl::loadTokenProgramId() const {
const char* value = std::getenv(TOKEN_PROGRAM_ID_ENV);
return value == nullptr ? std::string() : trim(value);
}
nlohmann::json TokenModuleImpl::tokenProgramInfo() {
const std::lock_guard<std::mutex> lock(programInfoMutex);
if (programInfoResolved_) {
return {{"programId", programId_}, {"programIdHex", programIdHex_}};
}
const std::string configured_program = loadTokenProgramId();
const bool program_id_configured = !configured_program.empty();
const char* binary_path = std::getenv(TOKEN_PROGRAM_BIN_ENV);
const bool binary_configured = binary_path != nullptr && *binary_path != '\0';
const std::string configured_program_hex = program_id_configured
? normalizeAccountId(configured_program)
: std::string();
if (program_id_configured && configured_program_hex.empty()) {
TOKEN_TRACE("TOKEN_PROGRAM_ID is not a valid account ID");
return json();
}
const std::vector<std::uint8_t> binary = loadTokenBinary();
if (binary_configured && binary.empty()) {
TOKEN_TRACE("TOKEN_PROGRAM_BIN is configured but unreadable");
return json();
}
std::string derived_program;
std::string derived_program_hex;
if (!binary.empty()) {
const FfiResult result = callToken(
token_program_id,
{{"elf", bytesToHex(binary.data(), binary.size())}});
if (!result.ok) return json();
derived_program_hex = lowercase(jsonString(result.value, "programId"));
derived_program = jsonString(result.value, "programIdBase58");
if (derived_program.empty() || !isHexLength(derived_program_hex, 64)
|| isZeroId(derived_program_hex)) {
return json();
}
}
if (!program_id_configured && !binary_configured) return json();
if (program_id_configured && binary_configured
&& configured_program_hex != derived_program_hex) {
TOKEN_TRACE("TOKEN_PROGRAM_ID does not match TOKEN_PROGRAM_BIN");
return json();
}
const std::string program_id_hex = program_id_configured
? configured_program_hex
: derived_program_hex;
const std::string program_id = binary_configured
? derived_program
: modules().logos_execution_zone.account_id_to_base58(program_id_hex);
if (program_id.empty() || !isHexLength(program_id_hex, 64) || isZeroId(program_id_hex)) {
return json();
}
// Cache successful resolution only: an early missing/unreadable
// configuration may become available later during process startup.
programId_ = program_id;
programIdHex_ = program_id_hex;
programInfoResolved_ = true;
return {{"programId", programId_}, {"programIdHex", programIdHex_}};
}
std::string TokenModuleImpl::normalizeAccountId(const std::string& id) {
std::string normalized = trim(id);
if (isHexLength(normalized, 64)) {
normalized = lowercase(std::move(normalized));
return isZeroId(normalized) ? std::string() : normalized;
}
if (normalized.empty()) return {};
normalized = lowercase(
modules().logos_execution_zone.account_id_from_base58(normalized));
return isHexLength(normalized, 64) && !isZeroId(normalized)
? normalized
: std::string();
}
nlohmann::json TokenModuleImpl::readPublicAccount(const std::string& account_id) {
json read = {{"id", account_id}, {"status", "not_found"}};
logos::CallError call_error;
const std::string raw =
modules().logos_execution_zone.get_account_public(account_id, &call_error);
if (!call_error.ok()) {
TOKEN_TRACE("logos_execution_zone account read transport failure: " << call_error.code);
read["status"] = "backend_error";
return read;
}
if (raw.empty()) return read;
const json account = json::parse(raw, nullptr, false);
if (!account.is_object()) {
read["status"] = "backend_error";
return read;
}
std::string owner = lowercase(jsonString(account, "program_owner"));
std::string balance = lowercase(jsonString(account, "balance"));
std::string nonce = lowercase(jsonString(account, "nonce"));
std::string data = lowercase(jsonString(account, "data"));
if (!isHexLength(owner, 64) || !isHexLength(balance, 32)
|| !isHexLength(nonce, 32) || !isEvenHex(data)) {
read["status"] = "backend_error";
return read;
}
if (isDefaultPublicAccount(owner, balance, nonce, data)) return read;
read["status"] = "ok";
read["account"] = {
{"program_owner", std::move(owner)},
{"balance", std::move(balance)},
{"nonce", std::move(nonce)},
{"data", std::move(data)},
};
return read;
}
nlohmann::json TokenModuleImpl::walletAccountIds() {
logos::CallError call_error;
const json accounts = modules().logos_execution_zone.list_accounts(&call_error);
if (!call_error.ok()) {
TOKEN_TRACE("logos_execution_zone account list transport failure: " << call_error.code);
return json();
}
if (!accounts.is_array()) return json();
std::set<std::string> unique;
for (const auto& account : accounts) {
if (!account.is_object()) continue;
const auto is_public = account.find("is_public");
if (is_public == account.end() || !is_public->is_boolean() || !is_public->get<bool>()) {
continue;
}
const std::string id = normalizeAccountId(jsonString(account, "account_id"));
if (!id.empty()) unique.insert(id);
}
return json(unique);
}
bool TokenModuleImpl::isFreshOwnedAccount(const std::string& account_id,
bool& fresh,
std::string& error) {
const json read = readPublicAccount(account_id);
if (jsonString(read, "status") == "ok") {
const json info = tokenProgramInfo();
if (!info.is_object()) {
error = "config_missing";
return false;
}
const FfiResult decoded = callToken(token_decode_holding, {
{"tokenProgramId", info["programIdHex"]},
{"holding", read},
});
if (!decoded.ok) {
error = ffiError(decoded);
return false;
}
fresh = false;
return true;
}
if (jsonString(read, "status") != "not_found") {
error = "backend_error";
return false;
}
const json wallet_ids = walletAccountIds();
if (!wallet_ids.is_array()) {
error = "backend_error";
return false;
}
if (std::find(wallet_ids.begin(), wallet_ids.end(), json(account_id)) == wallet_ids.end()) {
error = "account_read_failed";
return false;
}
fresh = true;
return true;
}
bool TokenModuleImpl::requireFreshOwnedAccount(const std::string& account_id,
std::string& error) {
bool fresh = false;
if (!isFreshOwnedAccount(account_id, fresh, error)) return false;
if (!fresh) {
error = "account_read_failed";
return false;
}
return true;
}
LogosMap TokenModuleImpl::programInfo() {
return guarded([&]() -> LogosMap {
const json info = tokenProgramInfo();
if (!info.is_object()) return publicError("config_missing");
LogosMap result = publicOk();
result["programId"] = info["programId"];
result["programIdHex"] = info["programIdHex"];
result["networkFingerprint"] = info["programIdHex"];
return result;
});
}
LogosMap TokenModuleImpl::inspectAccount(const std::string& account_id,
TokenOperation operation,
const char* payload_key) {
const json info = tokenProgramInfo();
if (!info.is_object()) return publicError("config_missing");
const std::string normalized = normalizeAccountId(account_id);
if (normalized.empty()) return publicError("invalid_account_id");
const json read = readPublicAccount(normalized);
const std::string read_status = jsonString(read, "status");
if (read_status == "backend_error") return publicError("backend_error");
if (read_status != "ok") return publicError("account_read_failed");
json request = {{"tokenProgramId", info["programIdHex"]}};
request[payload_key] = read;
const FfiResult decoded = callToken(operation, request);
if (!decoded.ok) return publicError(ffiError(decoded));
LogosMap result = publicOk();
result[payload_key] = decoded.value;
return result;
}
LogosMap TokenModuleImpl::inspectDefinition(const std::string& definition_id) {
return guarded([&]() {
return inspectAccount(definition_id, token_decode_definition, "definition");
});
}
LogosMap TokenModuleImpl::inspectHolding(const std::string& holding_id) {
return guarded([&]() {
return inspectAccount(holding_id, token_decode_holding, "holding");
});
}
LogosMap TokenModuleImpl::inspectMetadata(const std::string& metadata_id) {
return guarded([&]() {
return inspectAccount(metadata_id, token_decode_metadata, "metadata");
});
}
LogosMap TokenModuleImpl::walletTokenAccounts() {
return guarded([&]() -> LogosMap {
const json info = tokenProgramInfo();
if (!info.is_object()) return publicError("config_missing");
const std::string program_id = jsonString(info, "programIdHex");
const json ids = walletAccountIds();
if (!ids.is_array()) return publicError("backend_error");
json accounts = json::array();
for (const auto& id_value : ids) {
if (!id_value.is_string()) continue;
const std::string id = id_value.get<std::string>();
const json read = readPublicAccount(id);
if (jsonString(read, "status") != "ok") continue;
const auto account = read.find("account");
if (account == read.end() || !account->is_object()
|| lowercase(jsonString(*account, "program_owner")) != program_id) {
continue;
}
const FfiResult decoded = callToken(token_decode_account, {
{"tokenProgramId", program_id},
{"account", read},
});
if (!decoded.ok || !isHexLength(jsonString(decoded.value, "accountIdHex"), 64)) {
continue;
}
accounts.push_back(decoded.value);
}
std::sort(accounts.begin(), accounts.end(), [](const json& left, const json& right) {
return lowercase(jsonString(left, "accountIdHex"))
< lowercase(jsonString(right, "accountIdHex"));
});
LogosMap result = publicOk();
result["accounts"] = std::move(accounts);
return result;
});
}
LogosMap TokenModuleImpl::submitPlan(const nlohmann::json& plan) {
const auto account_ids_it = plan.find("accountIds");
const auto signers_it = plan.find("signingRequirements");
const auto instruction_it = plan.find("instruction");
const std::string program_id = lowercase(jsonString(plan, "programId"));
if (account_ids_it == plan.end() || signers_it == plan.end()
|| instruction_it == plan.end() || !isHexLength(program_id, 64)) {
return publicError("backend_error");
}
const std::vector<std::string> account_ids =
account_ids_it->get<std::vector<std::string>>();
const std::vector<bool> signers = signers_it->get<std::vector<bool>>();
const std::vector<std::uint8_t> instruction =
token_module::detail::jsonInstructionLeBytes(*instruction_it);
if (account_ids.empty() || account_ids.size() != signers.size()
|| instruction.empty()
|| std::any_of(account_ids.begin(), account_ids.end(), [](const std::string& id) {
return !isHexLength(id, 64) || isZeroId(id);
})) {
return publicError("backend_error");
}
logos::CallError call_error;
const std::string raw = modules().logos_execution_zone.send_generic_public_transaction(
account_ids,
signers,
instruction,
program_id,
&call_error);
if (!call_error.ok()) {
TOKEN_TRACE("logos_execution_zone submission transport failure: " << call_error.code);
return publicError("wallet_submission_failed");
}
const json reply = json::parse(raw, nullptr, false);
if (!reply.is_object()) return publicError("wallet_submission_failed");
const auto success = reply.find("success");
const std::string transaction_id = jsonString(reply, "tx_hash");
if (success == reply.end() || !success->is_boolean() || !success->get<bool>()
|| transaction_id.empty()) {
return publicError("wallet_submission_failed");
}
LogosMap result = publicOk();
result["transactionId"] = transaction_id;
return result;
}
bool TokenModuleImpl::normalizeAuthority(const std::string& authority,
std::string& normalized) {
const std::string sentinel = lowercase(trim(authority));
if (sentinel == "none" || sentinel == "self") {
normalized = sentinel;
return true;
}
normalized = normalizeAccountId(authority);
return !normalized.empty();
}
LogosMap TokenModuleImpl::planAndSubmit(TokenOperation planner, nlohmann::json request) {
const json info = tokenProgramInfo();
if (!info.is_object()) return publicError("config_missing");
const std::string configured_program = jsonString(info, "programIdHex");
request["tokenProgramId"] = configured_program;
const FfiResult result = callToken(planner, request);
if (!result.ok) return publicError(ffiError(result));
if (lowercase(jsonString(result.value, "programId")) != configured_program) {
return publicError("backend_error");
}
return submitPlan(result.value);
}
LogosMap TokenModuleImpl::createFungible(const std::string& definition_target_id,
const std::string& holding_target_id,
const std::string& name,
const nlohmann::json& total_supply_raw,
const std::string& mint_authority) {
return guarded([&]() -> LogosMap {
const std::string definition = normalizeAccountId(definition_target_id);
const std::string holding = normalizeAccountId(holding_target_id);
if (definition.empty() || holding.empty()) return publicError("invalid_account_id");
std::string error;
if (!requireFreshOwnedAccount(definition, error)
|| !requireFreshOwnedAccount(holding, error)) {
return publicError(error);
}
std::string authority;
if (!normalizeAuthority(mint_authority, authority)) {
return publicError("invalid_authority");
}
return planAndSubmit(token_create_fungible_plan, {
{"definitionTargetId", definition},
{"holdingTargetId", holding},
{"name", name},
{"totalSupplyRaw", total_supply_raw},
{"mintAuthority", authority},
});
});
}
LogosMap TokenModuleImpl::createFungibleWithMetadata(
const std::string& definition_target_id,
const std::string& holding_target_id,
const std::string& metadata_target_id,
const std::string& name,
const nlohmann::json& total_supply_raw,
const std::string& mint_authority,
const std::string& metadata_standard,
const std::string& uri,
const std::string& creators) {
return guarded([&]() -> LogosMap {
const std::string definition = normalizeAccountId(definition_target_id);
const std::string holding = normalizeAccountId(holding_target_id);
const std::string metadata = normalizeAccountId(metadata_target_id);
if (definition.empty() || holding.empty() || metadata.empty()) {
return publicError("invalid_account_id");
}
std::string error;
if (!requireFreshOwnedAccount(definition, error)
|| !requireFreshOwnedAccount(holding, error)
|| !requireFreshOwnedAccount(metadata, error)) {
return publicError(error);
}
std::string authority;
if (!normalizeAuthority(mint_authority, authority)) {
return publicError("invalid_authority");
}
return planAndSubmit(token_create_fungible_with_metadata_plan, {
{"definitionTargetId", definition},
{"holdingTargetId", holding},
{"metadataTargetId", metadata},
{"name", name},
{"totalSupplyRaw", total_supply_raw},
{"mintAuthority", authority},
{"metadataStandard", metadata_standard},
{"uri", uri},
{"creators", creators},
});
});
}
LogosMap TokenModuleImpl::createNonFungible(
const std::string& definition_target_id,
const std::string& master_holding_target_id,
const std::string& metadata_target_id,
const std::string& name,
const nlohmann::json& printable_supply_raw,
const std::string& metadata_standard,
const std::string& uri,
const std::string& creators) {
return guarded([&]() -> LogosMap {
const std::string definition = normalizeAccountId(definition_target_id);
const std::string master = normalizeAccountId(master_holding_target_id);
const std::string metadata = normalizeAccountId(metadata_target_id);
if (definition.empty() || master.empty() || metadata.empty()) {
return publicError("invalid_account_id");
}
std::string error;
if (!requireFreshOwnedAccount(definition, error)
|| !requireFreshOwnedAccount(master, error)
|| !requireFreshOwnedAccount(metadata, error)) {
return publicError(error);
}
return planAndSubmit(token_create_non_fungible_plan, {
{"definitionTargetId", definition},
{"masterHoldingTargetId", master},
{"metadataTargetId", metadata},
{"name", name},
{"printableSupplyRaw", printable_supply_raw},
{"metadataStandard", metadata_standard},
{"uri", uri},
{"creators", creators},
});
});
}
LogosMap TokenModuleImpl::initializeHolding(const std::string& definition_id,
const std::string& holding_target_id) {
return guarded([&]() -> LogosMap {
const std::string definition = normalizeAccountId(definition_id);
const std::string holding = normalizeAccountId(holding_target_id);
if (definition.empty() || holding.empty()) return publicError("invalid_account_id");
std::string error;
if (!requireFreshOwnedAccount(holding, error)) return publicError(error);
return planAndSubmit(token_initialize_holding_plan, {
{"definitionId", definition},
{"holdingTargetId", holding},
});
});
}
LogosMap TokenModuleImpl::transfer(const std::string& sender_holding_id,
const std::string& recipient_holding_id,
const nlohmann::json& amount_raw) {
return guarded([&]() -> LogosMap {
const std::string sender = normalizeAccountId(sender_holding_id);
const std::string recipient = normalizeAccountId(recipient_holding_id);
if (sender.empty() || recipient.empty()) return publicError("invalid_account_id");
bool recipient_fresh = false;
std::string error;
if (!isFreshOwnedAccount(recipient, recipient_fresh, error)) {
return publicError(error);
}
return planAndSubmit(token_transfer_plan, {
{"senderHoldingId", sender},
{"recipientHoldingId", recipient},
{"amountRaw", amount_raw},
{"recipientIsFresh", recipient_fresh},
});
});
}
LogosMap TokenModuleImpl::burn(const std::string& definition_id,
const std::string& holding_id,
const nlohmann::json& amount_raw) {
return guarded([&]() -> LogosMap {
const std::string definition = normalizeAccountId(definition_id);
const std::string holding = normalizeAccountId(holding_id);
if (definition.empty() || holding.empty()) return publicError("invalid_account_id");
return planAndSubmit(token_burn_plan, {
{"definitionId", definition},
{"holdingId", holding},
{"amountRaw", amount_raw},
});
});
}
LogosMap TokenModuleImpl::mint(const std::string& definition_id,
const std::string& holding_id,
const nlohmann::json& amount_raw) {
return guarded([&]() -> LogosMap {
const std::string definition = normalizeAccountId(definition_id);
const std::string holding = normalizeAccountId(holding_id);
if (definition.empty() || holding.empty()) return publicError("invalid_account_id");
bool holding_fresh = false;
std::string error;
if (!isFreshOwnedAccount(holding, holding_fresh, error)) return publicError(error);
return planAndSubmit(token_mint_plan, {
{"definitionId", definition},
{"holdingId", holding},
{"amountRaw", amount_raw},
{"holdingIsFresh", holding_fresh},
});
});
}
LogosMap TokenModuleImpl::mintWithAuthority(const std::string& definition_id,
const std::string& holding_id,
const std::string& authority_id,
const nlohmann::json& amount_raw) {
return guarded([&]() -> LogosMap {
const std::string definition = normalizeAccountId(definition_id);
const std::string holding = normalizeAccountId(holding_id);
const std::string authority = normalizeAccountId(authority_id);
if (definition.empty() || holding.empty()) return publicError("invalid_account_id");
if (authority.empty()) return publicError("invalid_authority");
bool holding_fresh = false;
std::string error;
if (!isFreshOwnedAccount(holding, holding_fresh, error)) return publicError(error);
return planAndSubmit(token_mint_with_authority_plan, {
{"definitionId", definition},
{"holdingId", holding},
{"authorityId", authority},
{"amountRaw", amount_raw},
{"holdingIsFresh", holding_fresh},
});
});
}
LogosMap TokenModuleImpl::setAuthority(const std::string& definition_id,
const std::string& new_authority) {
return guarded([&]() -> LogosMap {
const std::string definition = normalizeAccountId(definition_id);
if (definition.empty()) return publicError("invalid_account_id");
std::string authority;
if (!normalizeAuthority(new_authority, authority)) {
return publicError("invalid_authority");
}
return planAndSubmit(token_set_authority_plan, {
{"definitionId", definition},
{"newAuthority", authority},
});
});
}
LogosMap TokenModuleImpl::setAuthorityWithAuthority(
const std::string& definition_id,
const std::string& authority_id,
const std::string& new_authority) {
return guarded([&]() -> LogosMap {
const std::string definition = normalizeAccountId(definition_id);
const std::string current_authority = normalizeAccountId(authority_id);
if (definition.empty()) return publicError("invalid_account_id");
if (current_authority.empty()) return publicError("invalid_authority");
std::string next_authority;
if (!normalizeAuthority(new_authority, next_authority)) {
return publicError("invalid_authority");
}
return planAndSubmit(token_set_authority_with_authority_plan, {
{"definitionId", definition},
{"authorityId", current_authority},
{"newAuthority", next_authority},
});
});
}
LogosMap TokenModuleImpl::printNft(const std::string& master_holding_id,
const std::string& printed_holding_target_id) {
return guarded([&]() -> LogosMap {
const std::string master = normalizeAccountId(master_holding_id);
const std::string printed = normalizeAccountId(printed_holding_target_id);
if (master.empty() || printed.empty()) return publicError("invalid_account_id");
std::string error;
if (!requireFreshOwnedAccount(printed, error)) return publicError(error);
return planAndSubmit(token_print_nft_plan, {
{"masterHoldingId", master},
{"printedHoldingTargetId", printed},
});
});
}
+142
View File
@@ -0,0 +1,142 @@
#pragma once
#include <cstdint>
#include <string>
#include <vector>
#include <logos_json.h>
#include <logos_module_context.h>
// Universal Logos core module for the LEZ Token program. Public methods form
// the generated API, so this header deliberately uses only standard C++ and
// Logos JSON types. Rust token_ffi owns token codecs and instruction encoding;
// logos_execution_zone owns wallet/account I/O and transaction submission.
class TokenModuleImpl : public LogosModuleContext {
public:
TokenModuleImpl() = default;
~TokenModuleImpl() = default;
/// Returns `{status,error,programId,programIdHex,networkFingerprint}`.
/// Configure TOKEN_PROGRAM_ID or TOKEN_PROGRAM_BIN. If both are set, they
/// must identify the same program.
LogosMap programInfo();
/// Reads and decodes any public token definition account. `definition_id`
/// accepts 32-byte hex or base58. Success adds `definition`; failures use
/// invalid_account_id, account_read_failed, token_program_mismatch,
/// invalid_definition_data, config_missing, or backend_error.
LogosMap inspectDefinition(const std::string& definition_id);
/// Reads and decodes any public token holding account. Success adds
/// `holding`; failures follow the same stable read envelope.
LogosMap inspectHolding(const std::string& holding_id);
/// Reads and decodes any public token metadata account. Success adds
/// `metadata`; failures follow the same stable read envelope.
LogosMap inspectMetadata(const std::string& metadata_id);
/// Discovers wallet-owned public token accounts from live reads. Invalid,
/// foreign, unreadable, and ambiguously decoded rows are skipped. Success
/// adds `accounts`, sorted by lowercase accountIdHex.
LogosMap walletTokenAccounts();
/// Creates a fungible definition and initial holding. Both target IDs must
/// be fresh public wallet accounts and sign. `total_supply_raw` is an exact
/// non-negative u128 decimal. `mint_authority`: none, self, or account ID.
LogosMap createFungible(const std::string& definition_target_id,
const std::string& holding_target_id,
const std::string& name,
const nlohmann::json& total_supply_raw,
const std::string& mint_authority);
/// Creates a metadata-backed fungible definition. All three target IDs are
/// fresh public wallet accounts and sign. Standard is simple or expanded.
LogosMap createFungibleWithMetadata(const std::string& definition_target_id,
const std::string& holding_target_id,
const std::string& metadata_target_id,
const std::string& name,
const nlohmann::json& total_supply_raw,
const std::string& mint_authority,
const std::string& metadata_standard,
const std::string& uri,
const std::string& creators);
/// Creates a non-fungible definition, master holding, and metadata. All
/// targets must be fresh public wallet accounts and sign.
LogosMap createNonFungible(const std::string& definition_target_id,
const std::string& master_holding_target_id,
const std::string& metadata_target_id,
const std::string& name,
const nlohmann::json& printable_supply_raw,
const std::string& metadata_standard,
const std::string& uri,
const std::string& creators);
/// Initializes a fresh wallet-owned holding; holding target signs.
LogosMap initializeHolding(const std::string& definition_id,
const std::string& holding_target_id);
/// Transfers exact raw amount. Sender signs. Recipient signs only when its
/// live account is empty and wallet ownership proves it is fresh.
LogosMap transfer(const std::string& sender_holding_id,
const std::string& recipient_holding_id,
const nlohmann::json& amount_raw);
/// Burns exact raw amount. Holding signs.
LogosMap burn(const std::string& definition_id,
const std::string& holding_id,
const nlohmann::json& amount_raw);
/// Mints through self-authority. Definition signs; holding signs only when
/// live state and wallet ownership prove it is fresh.
LogosMap mint(const std::string& definition_id,
const std::string& holding_id,
const nlohmann::json& amount_raw);
/// Mints through explicit external authority. Authority signs; holding
/// signs only when live state and wallet ownership prove it is fresh.
LogosMap mintWithAuthority(const std::string& definition_id,
const std::string& holding_id,
const std::string& authority_id,
const nlohmann::json& amount_raw);
/// Rotates/revokes self authority. `new_authority`: none, self, or account
/// ID. Definition signs.
LogosMap setAuthority(const std::string& definition_id,
const std::string& new_authority);
/// Rotates/revokes explicit external authority. Current authority signs.
LogosMap setAuthorityWithAuthority(const std::string& definition_id,
const std::string& authority_id,
const std::string& new_authority);
/// Prints an NFT copy. Master and fully fresh printed target both sign.
LogosMap printNft(const std::string& master_holding_id,
const std::string& printed_holding_target_id);
private:
using TokenOperation = char* (*)(const char*);
std::vector<std::uint8_t> loadTokenBinary() const;
std::string loadTokenProgramId() const;
nlohmann::json tokenProgramInfo();
std::string normalizeAccountId(const std::string& id);
nlohmann::json readPublicAccount(const std::string& account_id);
nlohmann::json walletAccountIds();
bool isFreshOwnedAccount(const std::string& account_id,
bool& fresh,
std::string& error);
bool requireFreshOwnedAccount(const std::string& account_id,
std::string& error);
LogosMap inspectAccount(const std::string& account_id,
TokenOperation operation,
const char* payload_key);
bool normalizeAuthority(const std::string& authority,
std::string& normalized);
LogosMap planAndSubmit(TokenOperation planner, nlohmann::json request);
LogosMap submitPlan(const nlohmann::json& plan);
bool programInfoResolved_ = false;
std::string programId_;
std::string programIdHex_;
};