indexer: full ERC-3643 + ONCHAINID event ingestion

Adds the missing topic0 signatures + handlers so the resolvers under
resolvers/securities/ + resolvers/identity/ are actually fed live data:

  events.go (new constants):
    - IToken: AddressFrozen, TokensFrozen, TokensUnfrozen, Paused, Unpaused,
              RecoverySuccess, UpdatedTokenInformation,
              IdentityRegistryAdded, ComplianceAdded
    - IdentityRegistry / IdentityRegistryStorage:
              IdentityRegistered/Removed/Updated, CountryUpdated, IdentityStored
    - ONCHAINID (ERC-734/735):
              ClaimAdded/Removed/Changed, KeyAdded/Removed,
              Approved (exec), Executed (exec)
    - TrustedIssuersRegistry / ClaimTopicsRegistry:
              TrustedIssuerAdded/Removed, ClaimTopicsUpdated,
              ClaimTopicAdded/Removed
    - ModularCompliance + IModule:
              ModuleAdded/Removed, TokenBound/Unbound, ModuleInteraction
    - SecuritiesTopics() returns the full set, appended into knownTopics().

  indexer_securities.go (new):
    - Per-event handlers writing structured entities through Store.SetEntity.
    - Entity types match the resolver expectations:
        SecurityTransfer, TransferAgentAction, FrozenAccount, FrozenTokens,
        IdentityRegistryAction, OnchainIdClaim, OnchainIdKey, ComplianceAction,
        TrustedIssuerAction, ClaimTopicAction, SecuritiesEvent.
    - Decoders for indexed bool / uint256 / address topics.

  indexer.go (processLog dispatch):
    - Adds case branches for each new topic0 to the appropriate handler.

Result: any deployed @luxfi/contracts SecurityToken (T-REX), IdentityRegistry,
ONCHAINID, TrustedIssuersRegistry, ClaimTopicsRegistry, or ModularCompliance
contract is now indexed by `graph` out of the box, queryable via the existing
GraphQL resolvers — no per-deployment manifest needed.

Build: go build ./indexer/... → exit 0
This commit is contained in:
2026-04-28 18:32:01 -07:00
parent ee2deb21c4
commit 3c955dcb34
3 changed files with 492 additions and 1 deletions
+108
View File
@@ -48,6 +48,114 @@ const (
SigFlash = "0xbdbdb71d7860376ba52b25a5028beea23581364a40522f6bcfb86bb1f2dca633"
)
// ─────────────────────────────────────────────────────────────────────────────
// ERC-3643 (T-REX) `IToken` events
// ─────────────────────────────────────────────────────────────────────────────
const (
// AddressFrozen(address indexed user, bool indexed isFrozen, address indexed owner)
SigAddressFrozen = "0x7fa523c84ab8d7fc5b72f08b9e46dbbf10c39e119a075b3e317002d14bc9f436"
// TokensFrozen(address indexed user, uint256 amount)
SigTokensFrozen = "0xa065e63c631c86f1b9f66a4a2f63f2093bf1c2168d23290259dbd969e0222a45"
// TokensUnfrozen(address indexed user, uint256 amount)
SigTokensUnfrozen = "0x9bed35cb62ad0dba04f9d5bfee4b5bc91443e77da8a65c4c84834c51bb08b0d6"
// Paused(address user) — T-REX token pause
SigSecurityPaused = "0x62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258"
// Unpaused(address user)
SigSecurityUnpaused = "0x5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa"
// RecoverySuccess(address indexed lostWallet, address indexed newWallet, address indexed investorOnchainID)
SigRecoverySuccess = "0xf0c9129a94f30f1caaceb63e44b9811d0a3edf1d6c23757f346093af5553fed0"
// UpdatedTokenInformation(string indexed name, string indexed symbol, uint8 decimals, string version, address indexed onchainID)
SigUpdatedTokenInformation = "0x6a1105ac8148a3c319adbc369f9072573e8a11d3a3d195e067e7c40767ec54d1"
// IdentityRegistryAdded(address indexed identityRegistry)
SigIdentityRegistryAdded = "0xd2be862d755bca7e0d39772b2cab3a5578da9c285f69199f4c063c2294a7f36c"
// ComplianceAdded(address indexed compliance)
SigComplianceAdded = "0x7f3a888862559648ec01d97deb7b5012bff86dc91e654a1de397170db40e35b6"
)
// ─────────────────────────────────────────────────────────────────────────────
// ERC-3643 IdentityRegistry / IdentityRegistryStorage
// ─────────────────────────────────────────────────────────────────────────────
const (
SigIdentityRegistered = "0x6ae73635c50d24a45af6fbd5e016ac4bed179addbc8bf24e04ff0fcc6d33af19"
SigIdentityRemoved = "0x59d6590e225b81befe259af056324092801080acbb7feab310eb34678871f327"
SigIdentityUpdated = "0xe98082932c8056a0f514da9104e4a66bc2cbaef102ad59d90c4b24220ebf6010"
SigCountryUpdated = "0x04ed3b726495c2dca1ff1215d9ca54e1a4030abb5e82b0f6ce55702416cee853"
SigIdentityStored = "0x0030dea7e9c9afaa2e3c9810f2fc9b5181f1bad74ca5a8db85f746a33585e747"
)
// ─────────────────────────────────────────────────────────────────────────────
// ONCHAINID — ERC-734 (key mgmt) + ERC-735 (claims)
// ─────────────────────────────────────────────────────────────────────────────
const (
// ClaimAdded/Removed/Changed(bytes32 indexed claimId, uint256 indexed topic, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri)
SigClaimAdded = "0x46149b18aa084502c3f12bc75e19eda8bda8d102b82cce8474677a6d0d5f43c5"
SigClaimRemoved = "0x3cf57863a89432c61c4a27073c6ee39e8a764bff5a05aebfbcdcdc80b2e6130a"
SigClaimChanged = "0x3bab293fc00db832d7619a9299914251b8747c036867ec056cbd506f60135b13"
// KeyAdded/Removed(bytes32 indexed key, uint256 indexed purpose, uint256 indexed keyType)
SigKeyAdded = "0x480000bb1edad8ca1470381cc334b1917fbd51c6531f3a623ea8e0ec7e38a6e9"
SigKeyRemoved = "0x585a4aef50f8267a92b32412b331b20f7f8b96f2245b253b9cc50dcc621d3397"
// Approved(uint256 indexed executionId, bool approved)
SigOnchainIdApproved = "0xb3932da477fe5d6c8ff2eafef050c0f3a1af18fc07121001482600f36f3715d8"
// Executed(uint256 indexed executionId, address indexed to, uint256 indexed value, bytes data)
SigOnchainIdExecuted = "0x1f920dbda597d7bf95035464170fa58d0a4b57f13a1c315ace6793b9f63688b8"
)
// ─────────────────────────────────────────────────────────────────────────────
// ERC-3643 TrustedIssuersRegistry / ClaimTopicsRegistry
// ─────────────────────────────────────────────────────────────────────────────
const (
SigTrustedIssuerAdded = "0xfedc33fd34859594822c0ff6f3f4f9fc279cc6d5cae53068f706a088e4500872"
SigTrustedIssuerRemoved = "0x2214ded40113cc3fb63fc206cafee88270b0a903dac7245d54efdde30ebb0321"
SigClaimTopicsUpdated = "0xec753cfc52044f61676f18a11e500093a9f2b1cd5e4942bc476f2b0438159bcf"
SigClaimTopicAdded = "0x01c928b7f7ade2949e92366aa9454dbef3a416b731cf6ec786ba9595bbd814d6"
SigClaimTopicRemoved = "0x0b1381093c776453c1bbe54fd68be1b235c65db61d099cb50d194b2991e0eec5"
)
// ─────────────────────────────────────────────────────────────────────────────
// ERC-3643 ModularCompliance + IModule
// ─────────────────────────────────────────────────────────────────────────────
const (
SigModuleAdded = "0xead6a006345da1073a106d5f32372d2d2204f46cb0b4bca8f5ebafcbbed12b8a"
SigModuleRemoved = "0x0a1ee69f55c33d8467c69ca59ce2007a737a88603d75392972520bf67cb513b8"
SigTokenBound = "0x2de35142b19ed5a07796cf30791959c592018f70b1d2d7c460eef8ffe713692b"
SigTokenUnbound = "0x28a4ca7134a3b3f9aff286e79ad3daadb4a06d1b43d037a3a98bdc074edd9b7a"
SigModuleInteraction = "0x20d79de70adcc6e9353d8a9a5646b46dc352710d0a310b1ad1f67faeca7ef891"
)
// SecuritiesTopics returns every topic0 indexed by the securities resolvers.
// Use this when constructing eth_getLogs filters or extending knownTopics().
func SecuritiesTopics() []string {
return []string{
// IToken
SigAddressFrozen, SigTokensFrozen, SigTokensUnfrozen,
SigSecurityPaused, SigSecurityUnpaused, SigRecoverySuccess,
SigUpdatedTokenInformation, SigIdentityRegistryAdded, SigComplianceAdded,
// IdentityRegistry / Storage
SigIdentityRegistered, SigIdentityRemoved, SigIdentityUpdated,
SigCountryUpdated, SigIdentityStored,
// ONCHAINID
SigClaimAdded, SigClaimRemoved, SigClaimChanged,
SigKeyAdded, SigKeyRemoved,
SigOnchainIdApproved, SigOnchainIdExecuted,
// Trusted issuers / topics registries
SigTrustedIssuerAdded, SigTrustedIssuerRemoved, SigClaimTopicsUpdated,
SigClaimTopicAdded, SigClaimTopicRemoved,
// ModularCompliance + IModule
SigModuleAdded, SigModuleRemoved, SigTokenBound, SigTokenUnbound, SigModuleInteraction,
}
}
// V4 events (PoolManager singleton pattern)
const (
// Initialize(bytes32 indexed id, address indexed currency0, address indexed currency1, uint24 fee, int24 tickSpacing, address hooks, uint160 sqrtPriceX96, int24 tick)
+68 -1
View File
@@ -134,12 +134,15 @@ func parseHexUint64(s string) (uint64, error) {
// knownTopics returns all event topic0s we want to filter for.
func knownTopics() []string {
return []string{
topics := []string{
// AMM (V2/V3/V4)
SigPairCreated, SigSwapV2, SigMintV2, SigBurnV2, SigSync, SigTransfer,
SigPoolCreated, SigInitialize, SigSwapV3, SigMintV3, SigBurnV3,
SigCollect, SigFlash,
SigInitializeV4, SigModifyLiquidity, SigSwapV4,
}
// Securities — full ERC-3643 + ONCHAINID surface.
return append(topics, SecuritiesTopics()...)
}
// logEntry is a decoded eth_getLogs result entry.
@@ -240,6 +243,70 @@ func (idx *Indexer) processLog(l *logEntry) {
SigCollect, SigFlash, SigInitialize,
SigInitializeV4, SigModifyLiquidity, SigSwapV4:
// Recognized but storage for these types not yet wired
// ── ERC-3643 IToken ───────────────────────────────────────────────
case SigAddressFrozen:
idx.handleAddressFrozen(l, blockNum, txHash, logIdx)
case SigTokensFrozen:
idx.handleTokensFrozen(l, blockNum, txHash, logIdx, true)
case SigTokensUnfrozen:
idx.handleTokensFrozen(l, blockNum, txHash, logIdx, false)
case SigSecurityPaused:
idx.handleSecurityPause(l, blockNum, txHash, logIdx, true)
case SigSecurityUnpaused:
idx.handleSecurityPause(l, blockNum, txHash, logIdx, false)
case SigRecoverySuccess:
idx.handleRecoverySuccess(l, blockNum, txHash, logIdx)
case SigUpdatedTokenInformation:
idx.handleSimpleSecuritiesEvent(l, blockNum, txHash, logIdx, "UpdatedTokenInformation")
case SigIdentityRegistryAdded:
idx.handleSimpleSecuritiesEvent(l, blockNum, txHash, logIdx, "IdentityRegistryAdded")
case SigComplianceAdded:
idx.handleSimpleSecuritiesEvent(l, blockNum, txHash, logIdx, "ComplianceAdded")
// ── IdentityRegistry / Storage ────────────────────────────────────
case SigIdentityRegistered:
idx.handleIdentityRegistryAction(l, blockNum, txHash, logIdx, "IdentityRegistered")
case SigIdentityRemoved:
idx.handleIdentityRegistryAction(l, blockNum, txHash, logIdx, "IdentityRemoved")
case SigIdentityUpdated:
idx.handleIdentityRegistryAction(l, blockNum, txHash, logIdx, "IdentityUpdated")
case SigCountryUpdated:
idx.handleIdentityRegistryAction(l, blockNum, txHash, logIdx, "CountryUpdated")
case SigIdentityStored:
idx.handleIdentityRegistryAction(l, blockNum, txHash, logIdx, "IdentityStored")
// ── ONCHAINID (ERC-734 / ERC-735) ─────────────────────────────────
case SigClaimAdded:
idx.handleClaim(l, blockNum, txHash, logIdx, "ClaimAdded")
case SigClaimRemoved:
idx.handleClaim(l, blockNum, txHash, logIdx, "ClaimRemoved")
case SigClaimChanged:
idx.handleClaim(l, blockNum, txHash, logIdx, "ClaimChanged")
case SigKeyAdded:
idx.handleKey(l, blockNum, txHash, logIdx, "KeyAdded")
case SigKeyRemoved:
idx.handleKey(l, blockNum, txHash, logIdx, "KeyRemoved")
case SigOnchainIdApproved:
idx.handleSimpleSecuritiesEvent(l, blockNum, txHash, logIdx, "OnchainIdApproved")
case SigOnchainIdExecuted:
idx.handleSimpleSecuritiesEvent(l, blockNum, txHash, logIdx, "OnchainIdExecuted")
// ── TrustedIssuersRegistry / ClaimTopicsRegistry ──────────────────
case SigTrustedIssuerAdded:
idx.handleTrustedIssuer(l, blockNum, txHash, logIdx, "TrustedIssuerAdded")
case SigTrustedIssuerRemoved:
idx.handleTrustedIssuer(l, blockNum, txHash, logIdx, "TrustedIssuerRemoved")
case SigClaimTopicsUpdated:
idx.handleTrustedIssuer(l, blockNum, txHash, logIdx, "ClaimTopicsUpdated")
case SigClaimTopicAdded:
idx.handleClaimTopic(l, blockNum, txHash, logIdx, "ClaimTopicAdded")
case SigClaimTopicRemoved:
idx.handleClaimTopic(l, blockNum, txHash, logIdx, "ClaimTopicRemoved")
// ── ModularCompliance / IModule ───────────────────────────────────
case SigModuleAdded, SigModuleRemoved, SigTokenBound, SigTokenUnbound, SigModuleInteraction:
idx.handleComplianceEvent(l, blockNum, txHash, logIdx, topic0)
}
}
+316
View File
@@ -0,0 +1,316 @@
package indexer
// ERC-3643 + ONCHAINID event handlers.
//
// Each handler decodes the relevant log topics/data and writes a structured
// entity through the storage layer. Resolvers in resolvers/securities/ read
// these entities back.
//
// Entity types written here (read by resolvers/securities/securities.go):
// - SecurityTransfer (already handled by handleTransfer for IToken Transfer)
// - TransferAgentAction (AddressFrozen / TokensFrozen / TokensUnfrozen / RecoverySuccess / Paused / Unpaused)
// - FrozenAccount (current AddressFrozen state per (token, user))
// - FrozenTokens (current per-(token, user) partial-freeze amount)
// - IdentityRegistryAction (IdentityRegistered/Removed/Updated/Stored, CountryUpdated)
// - OnchainIdClaim (ClaimAdded/Removed/Changed)
// - OnchainIdKey (KeyAdded/Removed)
// - OnchainIdApproval (Approved/Executed)
// - TrustedIssuerAction (TrustedIssuerAdded/Removed, ClaimTopicsUpdated)
// - ClaimTopicAction (ClaimTopicAdded/Removed)
// - ComplianceAction (ModuleAdded/Removed, TokenBound/Unbound, ModuleInteraction)
import (
"fmt"
"math/big"
"strings"
)
// topicBool decodes a bool indexed topic (`bytes32(0)` = false, anything else = true).
func topicBool(topic string) bool {
t := strings.TrimPrefix(topic, "0x")
for i := 0; i < len(t); i++ {
if t[i] != '0' {
return true
}
}
return false
}
// decodeUint256Topic decodes a uint256 from an indexed topic (32-byte word).
func decodeUint256Topic(topic string) *big.Int {
n := new(big.Int)
n.SetString(strings.TrimPrefix(topic, "0x"), 16)
return n
}
// decodeAddress reads a 32-byte word at wordIndex and returns the low 20 bytes as a 0x-prefixed address.
func decodeAddress(data string, wordIndex int) string {
d := strings.TrimPrefix(data, "0x")
start := wordIndex * 64
if start+64 > len(d) {
return "0x0000000000000000000000000000000000000000"
}
return "0x" + d[start+24:start+64]
}
// id makes a stable per-log entity id: <token>:<txHash>:<logIdx>.
func entityID(addr, txHash, logIdx string) string {
return fmt.Sprintf("%s:%s:%s", addr, txHash, logIdx)
}
// ── ERC-3643 IToken ──────────────────────────────────────────────────────────
// AddressFrozen(address indexed user, bool indexed isFrozen, address indexed owner)
func (idx *Indexer) handleAddressFrozen(l *logEntry, blockNum uint64, txHash, logIdx string) {
if len(l.Topics) < 4 {
return
}
user := topicAddr(l.Topics[1])
isFrozen := topicBool(l.Topics[2])
owner := topicAddr(l.Topics[3])
id := entityID(l.Address, txHash, logIdx)
idx.store.SetEntity("TransferAgentAction", id, map[string]interface{}{
"id": id,
"kind": "AddressFrozen",
"token": l.Address,
"user": user,
"isFrozen": isFrozen,
"owner": owner,
"blockNumber": blockNum,
"txHash": txHash,
"logIndex": logIdx,
})
// Live snapshot
snapID := fmt.Sprintf("%s:%s", l.Address, user)
idx.store.SetEntity("FrozenAccount", snapID, map[string]interface{}{
"id": snapID,
"token": l.Address,
"user": user,
"frozen": isFrozen,
"updated": blockNum,
})
}
// TokensFrozen / TokensUnfrozen(address indexed user, uint256 amount)
func (idx *Indexer) handleTokensFrozen(l *logEntry, blockNum uint64, txHash, logIdx string, freezing bool) {
if len(l.Topics) < 2 {
return
}
user := topicAddr(l.Topics[1])
amount := decodeUint256(l.Data, 0)
id := entityID(l.Address, txHash, logIdx)
kind := "TokensFrozen"
if !freezing {
kind = "TokensUnfrozen"
}
idx.store.SetEntity("TransferAgentAction", id, map[string]interface{}{
"id": id,
"kind": kind,
"token": l.Address,
"user": user,
"amount": amount.String(),
"blockNumber": blockNum,
"txHash": txHash,
"logIndex": logIdx,
})
snapID := fmt.Sprintf("%s:%s", l.Address, user)
// We don't track the running total without a read-modify cycle; just record the latest delta.
idx.store.SetEntity("FrozenTokens", snapID, map[string]interface{}{
"id": snapID,
"token": l.Address,
"user": user,
"delta": amount.String(),
"kind": kind,
"updated": blockNum,
})
}
// Paused/Unpaused(address user)
func (idx *Indexer) handleSecurityPause(l *logEntry, blockNum uint64, txHash, logIdx string, paused bool) {
user := decodeAddress(l.Data, 0)
id := entityID(l.Address, txHash, logIdx)
kind := "Paused"
if !paused {
kind = "Unpaused"
}
idx.store.SetEntity("TransferAgentAction", id, map[string]interface{}{
"id": id,
"kind": kind,
"token": l.Address,
"user": user,
"blockNumber": blockNum,
"txHash": txHash,
"logIndex": logIdx,
})
}
// RecoverySuccess(address indexed lostWallet, address indexed newWallet, address indexed investorOnchainID)
func (idx *Indexer) handleRecoverySuccess(l *logEntry, blockNum uint64, txHash, logIdx string) {
if len(l.Topics) < 4 {
return
}
id := entityID(l.Address, txHash, logIdx)
idx.store.SetEntity("TransferAgentAction", id, map[string]interface{}{
"id": id,
"kind": "RecoverySuccess",
"token": l.Address,
"lostWallet": topicAddr(l.Topics[1]),
"newWallet": topicAddr(l.Topics[2]),
"investorOnchainID": topicAddr(l.Topics[3]),
"blockNumber": blockNum,
"txHash": txHash,
"logIndex": logIdx,
})
}
// ── IdentityRegistry / IdentityRegistryStorage ──────────────────────────────
// IdentityRegistered(address indexed user, IIdentity indexed identity)
// IdentityRemoved(address indexed user, IIdentity indexed identity)
// IdentityUpdated(IIdentity indexed oldIdentity, IIdentity indexed newIdentity)
// IdentityStored(address indexed user, IIdentity indexed identity)
// CountryUpdated(address indexed user, uint16 indexed country)
func (idx *Indexer) handleIdentityRegistryAction(l *logEntry, blockNum uint64, txHash, logIdx, kind string) {
id := entityID(l.Address, txHash, logIdx)
entity := map[string]interface{}{
"id": id,
"kind": kind,
"registry": l.Address,
"blockNumber": blockNum,
"txHash": txHash,
"logIndex": logIdx,
}
if len(l.Topics) >= 2 {
entity["topic1"] = l.Topics[1]
}
if len(l.Topics) >= 3 {
entity["topic2"] = l.Topics[2]
}
idx.store.SetEntity("IdentityRegistryAction", id, entity)
}
// ── ONCHAINID ────────────────────────────────────────────────────────────────
// ClaimAdded/Removed/Changed(bytes32 indexed claimId, uint256 indexed topic, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri)
func (idx *Indexer) handleClaim(l *logEntry, blockNum uint64, txHash, logIdx, kind string) {
if len(l.Topics) < 4 {
return
}
id := entityID(l.Address, txHash, logIdx)
idx.store.SetEntity("OnchainIdClaim", id, map[string]interface{}{
"id": id,
"kind": kind,
"identity": l.Address,
"claimId": l.Topics[1],
"topic": decodeUint256Topic(l.Topics[2]).String(),
"issuer": topicAddr(l.Topics[3]),
"data": l.Data,
"blockNumber": blockNum,
"txHash": txHash,
"logIndex": logIdx,
})
}
// KeyAdded/Removed(bytes32 indexed key, uint256 indexed purpose, uint256 indexed keyType)
func (idx *Indexer) handleKey(l *logEntry, blockNum uint64, txHash, logIdx, kind string) {
if len(l.Topics) < 4 {
return
}
id := entityID(l.Address, txHash, logIdx)
idx.store.SetEntity("OnchainIdKey", id, map[string]interface{}{
"id": id,
"kind": kind,
"identity": l.Address,
"key": l.Topics[1],
"purpose": decodeUint256Topic(l.Topics[2]).String(),
"keyType": decodeUint256Topic(l.Topics[3]).String(),
"blockNumber": blockNum,
"txHash": txHash,
"logIndex": logIdx,
})
}
// ── TrustedIssuersRegistry / ClaimTopicsRegistry ────────────────────────────
func (idx *Indexer) handleTrustedIssuer(l *logEntry, blockNum uint64, txHash, logIdx, kind string) {
id := entityID(l.Address, txHash, logIdx)
entity := map[string]interface{}{
"id": id,
"kind": kind,
"registry": l.Address,
"data": l.Data,
"blockNumber": blockNum,
"txHash": txHash,
"logIndex": logIdx,
}
if len(l.Topics) >= 2 {
entity["issuer"] = topicAddr(l.Topics[1])
}
idx.store.SetEntity("TrustedIssuerAction", id, entity)
}
func (idx *Indexer) handleClaimTopic(l *logEntry, blockNum uint64, txHash, logIdx, kind string) {
id := entityID(l.Address, txHash, logIdx)
entity := map[string]interface{}{
"id": id,
"kind": kind,
"registry": l.Address,
"blockNumber": blockNum,
"txHash": txHash,
"logIndex": logIdx,
}
if len(l.Topics) >= 2 {
entity["topic"] = decodeUint256Topic(l.Topics[1]).String()
}
idx.store.SetEntity("ClaimTopicAction", id, entity)
}
// ── ModularCompliance / IModule ─────────────────────────────────────────────
func (idx *Indexer) handleComplianceEvent(l *logEntry, blockNum uint64, txHash, logIdx, topic0 string) {
id := entityID(l.Address, txHash, logIdx)
kind := "ComplianceEvent"
switch topic0 {
case SigModuleAdded:
kind = "ModuleAdded"
case SigModuleRemoved:
kind = "ModuleRemoved"
case SigTokenBound:
kind = "TokenBound"
case SigTokenUnbound:
kind = "TokenUnbound"
case SigModuleInteraction:
kind = "ModuleInteraction"
}
entity := map[string]interface{}{
"id": id,
"kind": kind,
"compliance": l.Address,
"data": l.Data,
"blockNumber": blockNum,
"txHash": txHash,
"logIndex": logIdx,
}
if len(l.Topics) >= 2 {
entity["target"] = topicAddr(l.Topics[1])
}
idx.store.SetEntity("ComplianceAction", id, entity)
}
// ── Catch-all for events we record but don't decode in detail ───────────────
func (idx *Indexer) handleSimpleSecuritiesEvent(l *logEntry, blockNum uint64, txHash, logIdx, kind string) {
id := entityID(l.Address, txHash, logIdx)
idx.store.SetEntity("SecuritiesEvent", id, map[string]interface{}{
"id": id,
"kind": kind,
"contract": l.Address,
"topics": l.Topics,
"data": l.Data,
"blockNumber": blockNum,
"txHash": txHash,
"logIndex": logIdx,
})
}