Skip to content

eth/eth_watch_cli.hpp

Namespaces

Name
eth
eth::cli

Classes

Name
struct eth::cli::WatchSpec
One watch subscription specified on the command line.
class eth::cli::EventRegistry
Registry mapping event signatures to their ABI parameter descriptors.

Functions

Name
std::optional< uint8_t > hex_nibble(char c)
Parse a hex nibble character. Returns nullopt for invalid chars.
template <size_t N>
bool
parse_hex_array(std::string_view hex, std::array< uint8_t, N > & out)
Parse a hex string into a fixed-size byte array.
std::optional< codec::Address > parse_address(std::string_view hex)
Parse a 0x-prefixed or bare 40-hex-char Ethereum address.
EventRegistry & event_registry()
Process-wide singleton registry, pre-populated with well-known events.
std::vector< abi::AbiParam > infer_params(const std::string & sig)
Convenience wrapper: look up sig in the global registry. Replaces the old infer_params() free function.

Functions Documentation

function hex_nibble

inline std::optional< uint8_t > hex_nibble(
    char c
)

Parse a hex nibble character. Returns nullopt for invalid chars.

function parse_hex_array

template <size_t N>
bool parse_hex_array(
    std::string_view hex,
    std::array< uint8_t, N > & out
)

Parse a hex string into a fixed-size byte array.

Parameters:

  • hex Must be exactly N*2 hex characters (no 0x prefix).

function parse_address

inline std::optional< codec::Address > parse_address(
    std::string_view hex
)

Parse a 0x-prefixed or bare 40-hex-char Ethereum address.

Return: Parsed address or nullopt if the format is invalid.

function event_registry

inline EventRegistry & event_registry()

Process-wide singleton registry, pre-populated with well-known events.

The registry is initialised on first access. All registrations persist for the lifetime of the process, so call register_event() once at startup before creating any watchers.

function infer_params

inline std::vector< abi::AbiParam > infer_params(
    const std::string & sig
)

Convenience wrapper: look up sig in the global registry. Replaces the old infer_params() free function.

Source code

// Copyright 2025 GeniusVentures
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include <eth/abi_decoder.hpp>
#include <array>
#include <optional>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>

namespace eth::cli {

struct WatchSpec
{
    std::string contract_hex;    
    std::string event_signature; 
};

[[nodiscard]] inline std::optional<uint8_t> hex_nibble(char c) noexcept
{
    if (c >= '0' && c <= '9') { return static_cast<uint8_t>(c - '0'); }
    if (c >= 'a' && c <= 'f') { return static_cast<uint8_t>(10 + (c - 'a')); }
    if (c >= 'A' && c <= 'F') { return static_cast<uint8_t>(10 + (c - 'A')); }
    return std::nullopt;
}

template <size_t N>
[[nodiscard]] bool parse_hex_array(std::string_view hex, std::array<uint8_t, N>& out) noexcept
{
    if (hex.size() != N * 2) { return false; }
    for (size_t i = 0; i < N; ++i)
    {
        auto hi = hex_nibble(hex[i * 2]);
        auto lo = hex_nibble(hex[i * 2 + 1]);
        if (!hi || !lo) { return false; }
        out[i] = static_cast<uint8_t>((*hi << 4) | *lo);
    }
    return true;
}

[[nodiscard]] inline std::optional<codec::Address> parse_address(std::string_view hex) noexcept
{
    if (hex.size() >= 2 && hex[0] == '0' && (hex[1] == 'x' || hex[1] == 'X'))
    {
        hex = hex.substr(2);
    }
    codec::Address addr{};
    if (!parse_hex_array(hex, addr)) { return std::nullopt; }
    return addr;
}

// ---------------------------------------------------------------------------
// Known-event registry
// ---------------------------------------------------------------------------

class EventRegistry
{
public:
    void register_event(std::string sig, std::vector<abi::AbiParam> params)
    {
        map_[std::move(sig)] = std::move(params);
    }

    [[nodiscard]] const std::vector<abi::AbiParam>* lookup(const std::string& sig) const noexcept
    {
        auto it = map_.find(sig);
        return (it != map_.end()) ? &it->second : nullptr;
    }

    [[nodiscard]] std::vector<abi::AbiParam> params_for(const std::string& sig) const
    {
        if (const auto* p = lookup(sig)) { return *p; }
        return {};
    }

private:
    std::unordered_map<std::string, std::vector<abi::AbiParam>> map_;
};

inline EventRegistry& event_registry()
{
    static EventRegistry reg = []()
    {
        EventRegistry r;

        // ── ERC-20 ────────────────────────────────────────────────────────────
        r.register_event("Transfer(address,address,uint256)", {
            {abi::AbiParamKind::kAddress, true,  "from"},
            {abi::AbiParamKind::kAddress, true,  "to"},
            {abi::AbiParamKind::kUint,    false, "value"},
        });
        r.register_event("Approval(address,address,uint256)", {
            {abi::AbiParamKind::kAddress, true,  "owner"},
            {abi::AbiParamKind::kAddress, true,  "spender"},
            {abi::AbiParamKind::kUint,    false, "value"},
        });

        // ── ERC-721 ───────────────────────────────────────────────────────────
        // Note: Transfer(address,address,uint256) is the same signature as ERC-20.
        // The third param is indexed `tokenId` in ERC-721 vs non-indexed `value`
        // in ERC-20.  Register it separately under a distinct key so callers can
        // opt in explicitly; the ERC-20 entry above is the default.
        r.register_event("Transfer(address,address,uint256 indexed)", {
            {abi::AbiParamKind::kAddress, true,  "from"},
            {abi::AbiParamKind::kAddress, true,  "to"},
            {abi::AbiParamKind::kUint,    true,  "tokenId"},
        });
        r.register_event("ApprovalForAll(address,address,bool)", {
            {abi::AbiParamKind::kAddress, true,  "owner"},
            {abi::AbiParamKind::kAddress, true,  "operator"},
            {abi::AbiParamKind::kBool,    false, "approved"},
        });

        // ── ERC-1155 ──────────────────────────────────────────────────────────
        r.register_event("TransferSingle(address,address,address,uint256,uint256)", {
            {abi::AbiParamKind::kAddress, true,  "operator"},
            {abi::AbiParamKind::kAddress, true,  "from"},
            {abi::AbiParamKind::kAddress, true,  "to"},
            {abi::AbiParamKind::kUint,    false, "id"},
            {abi::AbiParamKind::kUint,    false, "value"},
        });
        r.register_event("TransferBatch(address,address,address,uint256[],uint256[])", {
            {abi::AbiParamKind::kAddress, true,  "operator"},
            {abi::AbiParamKind::kAddress, true,  "from"},
            {abi::AbiParamKind::kAddress, true,  "to"},
            // uint256[] arrays decoded as raw bytes32 until dynamic array support is added
            {abi::AbiParamKind::kBytes32, false, "ids"},
            {abi::AbiParamKind::kBytes32, false, "values"},
        });

        // ── GNUS Bridge (GNUSBridge.sol) ──────────────────────────────────────
        // event BridgeSourceBurned(address indexed sender, uint256 id, uint256 amount,
        //                          uint256 srcChainID, uint256 destChainID)
        r.register_event("BridgeSourceBurned(address,uint256,uint256,uint256,uint256)", {
            {abi::AbiParamKind::kAddress, true,  "sender"},
            {abi::AbiParamKind::kUint,    false, "id"},
            {abi::AbiParamKind::kUint,    false, "amount"},
            {abi::AbiParamKind::kUint,    false, "srcChainID"},
            {abi::AbiParamKind::kUint,    false, "destChainID"},
        });

        return r;
    }();
    return reg;
}

[[nodiscard]] inline std::vector<abi::AbiParam> infer_params(const std::string& sig)
{
    return event_registry().params_for(sig);
}

} // namespace eth::cli

Updated on 2026-04-13 at 23:22:46 -0700