Specialized services for Circles protocol trust management:
- Scans Circles BackingCompleted/BackingInitiated events
- Filters out blacklisted backers
- Trusts valid new backers into a group in batches of 50
- Reconciles initiated-but-not-completed processes (resets CowSwap order or creates LBP)
- Optionally notifies Slack when something looks stuck
- Runs once a minute
- Supports dry-run mode to exercise the logic without submitting Safe transactions
- Scans Circles RegisterHuman events for newly registered avatars
- Cross-checks avatars against the blacklist service and Metri Safe GraphQL data
- Trusts eligible avatars into the configured group in batches (default 10)
- Supports dry-run mode and Slack notifications
- Runs every 10 minutes
- Scans on-chain RegisterHuman logs from the configured contract
- Filters events where
originInviteris one of a configured allowlist - Trusts the matching
humanavatars into a configured group - Supports chunked block scanning, dry-run mode, and Slack notifications
- Runs continuously with configurable poll interval
- Monitors affiliate group changes and trust relationships
- Reconciles trust between MetaOrg trustees and affiliates
- Supports dry-run mode for testing
- Incremental scanning with configurable refresh intervals
- Batch processing for efficient trust operations
- Monitors
AffiliateGroupChangedevents from the Circles affiliate registry over WSS - Trusts humans into one of five managed groups when they set that group as their affiliate
- Untrusts humans from a managed group when they switch away from it
- Replays historical logs from a persisted cursor on startup before listening live
- Supports dry-run mode with optional Safe transaction simulation
- Reads multi-affiliate community intent from
circles_getAffiliateGroupMembersWishlist - Loads each managed group's
minRepScoreand checks every intended member's reputation - Enforces the aggregate community membership-fee cap (
totalFeePercentage <= 100) - Trusts eligible intended members and untrusts members that become ineligible
- Untrusts every current trustee removed from the wishlist, treating removal as intent to leave
- Supports dry-run Safe simulation
- Calls
setApprovalForCRC(address[])for every address trusted by the configured truster and every Gnosis App user withavatarType=RegisterHuman - Executes transactions directly from the configured EOA signer
- Keeps a process-local in-memory approval cache to avoid repeating
setApprovalForCRCfor the same address after a successful batch - Supports dry-run mode and signer-backed simulation
- Node.js ≥ 24
- A Circles RPC endpoint (e.g. https://rpc.aboutcircles.com/)
- A private key that is the group service for the backers group
# 1) Install
npm install
# 2) Configure
touch .env # Check the values below and fill in your .env file
# 3) Run
# Option A: build then run with .env
npx tsc && node --env-file=.env dist/src/main.js
# Option B: use scripts (also load .env)
npm run start:crc-backers
npm run start:gp-crc
npm run start:dublin-tms
npm run start:oic
npm run start:all
npm run start:group-affiliates
npm run start:community-new
npm run start:router2RPC_URL remains the default read RPC for all apps. Set TX_RPC_URL when you want transaction submission and confirmation to use a different RPC; if unset, writes keep using RPC_URL.
When DRY_RUN=1, the apps now attempt gas estimation plus transaction simulation for write batches if the relevant signer/Safe credentials are configured. If those credentials are omitted in dry-run mode, the app still logs planned actions and explicitly skips the simulation step.
# RPC & addresses
RPC_URL=https://rpc.aboutcircles.com/
TX_RPC_URL=https://your-write-rpc.example/ # optional; overrides only transaction execution
BACKING_FACTORY_ADDRESS=0xeced91232c609a42f6016860e8223b8aecaa7bd0
BACKERS_GROUP_ADDRESS=0x1ACA75e38263c79d9D4F10dF0635cc6FCfe6F026
# Safe execution
CRC_BACKERS_SAFE_ADDRESS= # Safe set as the group's service
CRC_BACKERS_SAFE_SIGNER_PRIVATE_KEY= # Private key for one Safe signer
# Blacklist service
BLACKLISTING_SERVICE_URL=https://squid-app-3gxnl.ondigitalocean.app/aboutcircles-advanced-analytics2/bot-analytics/blacklist
# Scan window / timing
START_AT_BLOCK=39743285
CONFIRMATION_BLOCKS=2
EXPECTED_SECONDS_TILL_COMPLETION=60
# Notifications
SLACK_WEBHOOK_URL=
SLACK_WEBHOOK_URL_INFO= # Secondary webhook for informational messages
# Logging
VERBOSE_LOGGING=1 # any truthy value enables debug/table
# Operation mode
DRY_RUN=0 # Set to "1" to skip Safe transactions and only log actions# RPC & addresses
RPC_URL=https://rpc.aboutcircles.com/
TX_RPC_URL=https://your-write-rpc.example/ # optional; overrides only transaction execution
GP_CRC_GROUP_ADDRESS=0xb629a1e86f3efada0f87c83494da8cc34c3f84ef
# Safe execution (required unless dry run)
GP_CRC_SAFE_ADDRESS= # Safe that controls the group's service role
GP_CRC_SAFE_SIGNER_PRIVATE_KEY= # Private key for a Safe signer
# Metri Safe GraphQL
METRI_SAFE_GRAPHQL_URL=https://gnosis-e702590.dedicated.hyperindex.xyz/v1/graphql
METRI_SAFE_API_KEY=
# Blacklist service
BLACKLISTING_SERVICE_URL=https://squid-app-3gxnl.ondigitalocean.app/aboutcircles-advanced-analytics2/bot-analytics/blacklist
# Scan window / timing
START_AT_BLOCK=31734312
CONFIRMATION_BLOCKS=10
DRY_RUN=0 # Set to "1" to skip transactions
# Notifications
SLACK_WEBHOOK_URL=
SLACK_WEBHOOK_URL_INFO= # Secondary webhook for informational messages
# Logging
VERBOSE_LOGGING=1# RPC & addresses
RPC_URL=https://rpc.aboutcircles.com/
TX_RPC_URL=https://your-write-rpc.example/ # optional; overrides only transaction execution
OIC_GROUP_ADDRESS=0x4E2564e5df6C1Fb10C1A018538de36E4D5844DE5
OIC_META_ORG_ADDRESS= # REQUIRED - Meta organization address
AFFILIATE_REGISTRY_ADDRESS=0xca8222e780d046707083f51377b5fd85e2866014
# EOA execution (required unless dry run)
OIC_SERVICE_EOA= # Optional: expected service EOA address for key validation
OIC_SERVICE_PRIVATE_KEY= # Private key for the group service EOA
# Scan window / timing
# Start block is hardcoded in OIC code to 41734312
CONFIRMATION_BLOCKS=10
REFRESH_INTERVAL_SEC=60
# Operation mode
DRY_RUN=0 # Set to "1" for dry run mode (no actual transactions)
# Notifications
SLACK_WEBHOOK_URL=
SLACK_WEBHOOK_URL_INFO= # Secondary webhook for informational messages
# Logging
VERBOSE_LOGGING=1 # any truthy value enables debug/table# RPC
RPC_URL=https://rpc.aboutcircles.com/
TX_RPC_URL=https://your-write-rpc.example/ # optional; overrides only transaction execution
# RegisterHuman source + target group
DUBLIN_TMS_ADDRESS=0xAeCda439CC8Ac2a2da32bE871E0C2D7155350f80
# Scan window / timing
DUBLIN_TMS_START_BLOCK=44560106
DUBLIN_TMS_TO_BLOCK= # optional; if unset follows head-confirmations
DUBLIN_TMS_CHUNK_SIZE=2000
DUBLIN_TMS_CONFIRMATION_BLOCKS=2
DUBLIN_TMS_POLL_INTERVAL_MS=600000
DUBLIN_TMS_BATCH_SIZE=50
# EOA execution (required unless dry run)
DUBLIN_TMS_SERVICE_EOA=0x20a3C619De4C15E360d30F329DBCfe5bb618654f
DUBLIN_TMS_SERVICE_PRIVATE_KEY=
# Operation mode
DRY_RUN=0
# Notifications
DUBLIN_TMS_SLACK_WEBHOOK_URL= # optional override; falls back to SLACK_WEBHOOK_URL
SLACK_WEBHOOK_URL=
SLACK_WEBHOOK_URL_INFO= # Secondary webhook for informational messages
# Logging
VERBOSE_LOGGING=1# RPC & addresses
RPC_URL=https://rpc.aboutcircles.com/
TX_RPC_URL=https://your-write-rpc.example/ # optional; overrides only transaction execution
ROUTER_WSS_URL=wss://rpc.aboutcircles.com/ws/chain
ROUTER_ADDRESS=0xdc287474114cc0551a81ddc2eb51783fbf34802f
ROUTER_BASE_GROUP_ADDRESS=0x1ACA75e38263c79d9D4F10dF0635cc6FCfe6F026
# Safe execution (required unless dry run)
ROUTER_SAFE_ADDRESS= # Safe that controls the router's trusted executor
ROUTER_SAFE_SIGNER_PRIVATE_KEY= # Private key for one Safe signer
# Scan window / timing
ROUTER_POLL_INTERVAL_MS=1800000
ROUTER_ENABLE_BATCH_SIZE=50
ROUTER_FETCH_PAGE_SIZE=2000
# Operation mode
DRY_RUN=0 # Set to "1" to log actions without enabling routing
# Notifications
SLACK_WEBHOOK_URL=
SLACK_WEBHOOK_URL_INFO= # Secondary webhook for informational messages
# Logging
VERBOSE_LOGGING=1# RPC & addresses
RPC_URL=https://rpc.aboutcircles.com/
TX_RPC_URL=https://your-write-rpc.example/ # optional; overrides only transaction execution
ROUTER2_ADDRESS=0xE171a76De6B645A28b3767f84B177a4f6659a3D7
ROUTER2_TRUSTED_BY_ADDRESS=0x93eD5A96347927ff6fF6b790F8Cf5258240c321f
ROUTER2_GNOSIS_APP_INDEXER_URL=https://gnosis-e702590.dedicated.hyperindex.xyz/v1/graphql
ROUTER2_GNOSIS_APP_FROM_BLOCK= # optional; only fetches GnosisAppUser rows with createdAtBlock > this value
# EOA execution (required unless dry run)
ROUTER2_SIGNER_PRIVATE_KEY= # Private key for the EOA that can call router2
# Scan window / timing
ROUTER2_POLL_INTERVAL_MS=600000
ROUTER2_BATCH_SIZE=20
ROUTER2_FETCH_PAGE_SIZE=1000
ROUTER2_FETCH_TIMEOUT_MS=60000
ROUTER2_ERRORS_BEFORE_CRASH=5
ROUTER2_DRY_RUN_SIMULATION=1 # Set to "0" to skip signer-backed gas simulations in dry-run
ROUTER2_LOG_BATCH_ADDRESSES=0 # Set to "1" to log every address in every batch
# Operation mode
DRY_RUN=0 # Set to "1" to log/simulate without sending EOA transactions
# Notifications
ROUTER2_SLACK_WEBHOOK_URL= # optional override; falls back to SLACK_WEBHOOK_URL
SLACK_WEBHOOK_URL=
SLACK_WEBHOOK_URL_INFO=
# Logging
VERBOSE_LOGGING=1# RPC & affiliate registry
RPC_URL=https://rpc.aboutcircles.com/
TX_RPC_URL=https://your-write-rpc.example/ # optional; overrides only transaction execution
GROUP_AFFILIATES_WSS_URL=wss://rpc.aboutcircles.com/ws/chain
GROUP_AFFILIATES_REGISTRY_ADDRESS=0xca8222e780d046707083f51377b5fd85e2866014
# Safe execution (required unless dry run)
GROUP_AFFILIATES_SAFE_ADDRESS= # Safe that can execute trust/untrust for all managed groups
GROUP_AFFILIATES_SAFE_SIGNER_PRIVATE_KEY= # Private key for one Safe signer
GROUP_AFFILIATES_SIGNER_ADDRESS= # Optional expected signer address for key validation
# Scan window / batching
GROUP_AFFILIATES_START_BLOCK=46282003
GROUP_AFFILIATES_BATCH_SIZE=20
CONFIRMATION_BLOCKS=2
# Reputation gate
GROUP_AFFILIATES_REPUTATION_BASE_URL=https://rpc.aboutcircles.com/analytics/rep_score/groups/score_group/avatars
GROUP_AFFILIATES_REPUTATION_SCORES_URL=https://rpc.aboutcircles.com/analytics/rep_score/groups/score_group/scores
GROUP_AFFILIATES_REPUTATION_BULK=1
GROUP_AFFILIATES_REPUTATION_CONCURRENCY=8
GROUP_AFFILIATES_REPUTATION_TIMEOUT_MS=30000
GROUP_AFFILIATES_REPUTATION_REFRESH_MS=1800000
GROUP_AFFILIATES_REPUTATION_SNAPSHOT_TTL_MS=1800000
GROUP_AFFILIATES_PROFILE_BASE_URL=https://staging.circlesubi.network/profiles/profile
GROUP_AFFILIATES_PROFILE_TIMEOUT_MS=30000
# Operation mode
DRY_RUN=0 # Set to "1" to log actions without sending Safe transactions
# Notifications / coordination
SLACK_WEBHOOK_URL=
SLACK_WEBHOOK_URL_INFO=
LEADER_DB_URL= # Optional: PG connection for cursor persistence (group_tms_state) and router-tms enablement state (historical name)
INSTANCE_ID= # Used by Slack messages to tag the source host
# Logging
VERBOSE_LOGGING=1# Chain reads (getLogs, eth_blockNumber) and Safe writes.
RPC_URL=https://rpc.aboutcircles.com/
TX_RPC_URL=https://your-write-rpc.example/ # optional
# Only used by MEMBERSHIP_SOURCE=rpc (staging-only wishlist methods).
COMMUNITY_NEW_RPC_URL=https://rpc.staging.aboutcircles.com
# Comma-separated groups managed by this worker (BaseGroups). Defaults to the
# three featured community groups when omitted. Do NOT list a ScoreGroup here —
# ScoreGroups are append-only and untrust would revert.
COMMUNITY_NEW_GROUP_ADDRESSES=0x4E2564e5df6C1Fb10C1A018538de36E4D5844DE5,0x2709757a543CF1BF4d92586b73d3891438b2589d,0xEEcAe593589a6eE4a12AE64F19420B47F3112Fa9
# --- Membership source: where the worker reads group intent from ---
# rpc — staging-only wishlist RPC (dev/staging only).
# old — OLD single-slot registry (0xca8222) via AffiliateGroupChanged;
# byte-identical to group-affiliates (prod parity baseline).
# hybrid — OLD registry for everyone EXCEPT COMMUNITY_NEW_TEST_ADDRESSES,
# who are governed by the NEW multi registry (multi-group testing).
# new — NEW multi registry (0x4a25a7cf) for everyone (final GA state).
COMMUNITY_NEW_MEMBERSHIP_SOURCE=old
# Test-dev allowlist (hybrid only): these avatars read from the NEW registry.
COMMUNITY_NEW_TEST_ADDRESSES=
# Cold-start dev addresses have reputation 0; opt-in to bypass the rep gate for
# the allowlist so they can be trusted during testing. NEVER set for real users.
COMMUNITY_NEW_TEST_BYPASS_REPUTATION=0
# OLD registry (old/hybrid). Reconciliation is poll-gated (each poll refreshes the
# map before reading it), so WSS only keeps the map warm between polls — for lower
# trust latency, lower COMMUNITY_NEW_POLL_INTERVAL_MS rather than relying on WSS.
COMMUNITY_NEW_OLD_REGISTRY_ADDRESS=0xca8222e780d046707083f51377b5fd85e2866014
COMMUNITY_NEW_OLD_REGISTRY_START_BLOCK=46282003
COMMUNITY_NEW_OLD_ENABLE_WSS=0
COMMUNITY_NEW_OLD_WSS_URL= # optional; else derived from RPC_URL
# NEW registry (new/hybrid).
COMMUNITY_NEW_REGISTRY_ADDRESS=0x4a25a7cf216351963f1637ad965d77b3ae277ef3
COMMUNITY_NEW_REGISTRY_DEPLOY_BLOCK=46891940
COMMUNITY_NEW_ENABLE_WSS=0
COMMUNITY_NEW_WSS_URL= # optional; else derived from RPC_URL
# Persistence for the on-chain maps (block cursor + membership). Historical name;
# it is just the shared state-DB DSN. Required for old/hybrid/new persistence.
LEADER_DB_URL=
# Untrust policy + safety. Default is `wishlist` (authoritative) in old/hybrid to
# match group-affiliates, else `add-only`. The circuit breaker aborts a wet run
# whose untrust set is implausibly large (guards a bad rescan).
COMMUNITY_NEW_UNTRUST_MODE= # add-only|union|wishlist (per-mode default)
COMMUNITY_NEW_MAX_UNTRUST_TOTAL=20
COMMUNITY_NEW_MAX_UNTRUST_RATIO=0.5 # (0,1]
# Cutover grandfather list (union mode ONLY). JSON mapping each managed group to
# avatars that are trusted on-chain but absent from the registry feeding the
# wishlist (orphans). They are added to the union protected set so an
# authoritative sweep spares exactly these addresses, while every OTHER member
# still gets full join/leave/reputation enforcement. Frozen by config on purpose
# (a recomputed set would re-protect post-cutover leavers). Errors if set while
# UNTRUST_MODE is not union, or if it names an unmanaged group.
# {"0x<group>":["0x<avatar>", ...]}
COMMUNITY_NEW_GRANDFATHER_ADDRESSES=
# Cutover gate: community-new shares the Safe/signer with group-affiliates. Must
# retire group-affiliates for these groups first, then set this to run wet.
COMMUNITY_NEW_ACK_GROUP_AFFILIATES_RETIRED=0
# One Safe must be the service for every configured group.
COMMUNITY_NEW_SAFE_ADDRESS=
COMMUNITY_NEW_SAFE_SIGNER_PRIVATE_KEY=
COMMUNITY_NEW_SIGNER_ADDRESS= # optional key/address validation
# Reconciliation and RPC pagination
COMMUNITY_NEW_POLL_INTERVAL_MS=600000 # lower (~60000) on prod for near-realtime
COMMUNITY_NEW_PAGE_SIZE=500 # 1..1000
COMMUNITY_NEW_BATCH_SIZE=20
COMMUNITY_NEW_FEE_FETCH_CONCURRENCY=2
COMMUNITY_NEW_RPC_TIMEOUT_MS=30000
COMMUNITY_NEW_RPC_MAX_PAGES=500
COMMUNITY_NEW_ERRORS_BEFORE_CRASH=5
# Group criteria and reputation lookups. The fee cap uses a staging-only RPC and
# is only enforced in `rpc` mode; old/hybrid/new run with no fee cap (parity with
# group-affiliates). In prod the reputation base URL is set to the in-network,
# address-indexed endpoint (…/groups/0x93ed5a96…/avatars) so a slug rename cannot
# 404 it; the public `score_group_v2` slug below is the code default fallback.
COMMUNITY_NEW_PROFILE_TIMEOUT_MS=30000
COMMUNITY_NEW_REPUTATION_BASE_URL= # required only when bulk mode is disabled
COMMUNITY_NEW_REPUTATION_SCORES_URL=https://rpc.aboutcircles.com/analytics/rep_score/groups/score_group_v2/scores
COMMUNITY_NEW_REPUTATION_BULK=1
COMMUNITY_NEW_REPUTATION_TIMEOUT_MS=30000
COMMUNITY_NEW_REPUTATION_SNAPSHOT_TTL_MS=600000
# Operation and notifications
DRY_RUN=0
COMMUNITY_NEW_SLACK_WEBHOOK_URL= # falls back to SLACK_WEBHOOK_URL
SLACK_WEBHOOK_URL=
SLACK_WEBHOOK_URL_INFO=
VERBOSE_LOGGING=1prod1 cutover (replaces group-affiliates — same Safe, so only one runs wet):
- Deploy
MEMBERSHIP_SOURCE=hybrid,UNTRUST_MODE=union,DRY_RUN=1; runnpm run diff:community-new. The report prints both the strictwishlistblast radius and theunion+grandfatherset the worker applies — confirm the latter is 0 (or intentional). PopulateCOMMUNITY_NEW_GRANDFATHER_ADDRESSESwith the orphans thewishlistcolumn lists so day-one removals are zero while leave/rep enforcement stays live for everyone else. - Stop group-affiliates → set
COMMUNITY_NEW_ACK_GROUP_AFFILIATES_RETIRED=1,DRY_RUN=0. Verify trust/untrust matches the prior worker (grandfathered orphans retained; genuine leavers still untrusted). - Switch to
hybridwithCOMMUNITY_NEW_TEST_ADDRESSES=…(+COMMUNITY_NEW_TEST_BYPASS_REPUTATION=1for cold-start dev addresses) to exercise multi-group. - GA: switch to
newfor full multi-membership.
# RPC & addresses
RPC_URL=https://rpc.aboutcircles.com/
TX_RPC_URL=https://your-write-rpc.example/ # optional; overrides only transaction execution
GNOSIS_GROUP_ADDRESS=0xC19BC204eb1c1D5B3FE500E5E5dfaBaB625F286c
# Safe execution
GNOSIS_GROUP_SAFE_ADDRESS= # Safe that owns the group service role
GNOSIS_GROUP_SAFE_SIGNER_PRIVATE_KEY= # Private key of a 1/n Safe signer
# External services
BLACKLISTING_SERVICE_URL=https://squid-app-3gxnl.ondigitalocean.app/aboutcircles-advanced-analytics2/bot-analytics/blacklist
GNOSIS_GROUP_SCORING_URL=https://safe-watch-api-prod.ai.gnosisdev.com/validate-trustees
# Scan window / timing
GNOSIS_GROUP_RUN_INTERVAL_MINUTES=30 # Run interval in minutes
GNOSIS_GROUP_FETCH_PAGE_SIZE= # Fetch page size
GNOSIS_GROUP_SCORE_BATCH_SIZE= # Score batch size
GNOSIS_GROUP_SCORE_THRESHOLD= # Gnosis trust-score threshold for membership (default: 80, strictly greater than threshold)
GNOSIS_GROUP_BATCH_SIZE= # Transaction batch size
GNOSIS_GROUP_SCORE_CACHE_TTL_MINUTES=240 # Score cache TTL in minutes
GNOSIS_GROUP_SCORE_FETCH_TIMEOUT_MS= # Timeout per scoring request (default 90000)
# Operation mode
DRY_RUN=0 # Set to "1" to skip on-chain trust/untrust transactions
# Notifications
GNOSIS_GROUP_SLACK_WEBHOOK_URL= # Optional override; falls back to SLACK_WEBHOOK_URL
SLACK_WEBHOOK_URL=
SLACK_WEBHOOK_URL_INFO= # Secondary webhook for informational messages
# Logging
VERBOSE_LOGGING=1- Uses a reorg buffer (
CONFIRMATION_BLOCKS) from chain head - From
START_AT_BLOCK→ head:- Pulls BackingCompleted, filters via blacklist service, dedupes already-trusted backers, and calls
trustBatchWithConditions(group, addresses, expiry=max uint96)in batches of 50 - Finds BackingInitiated without matching completion:
- If past deadline (initiated timestamp + 24h): try
createLBP(); notify Slack on inconsistent/insufficient states - If before deadline: try
resetCowswapOrder()when valid; if already settled, attempt LBP creation
- If past deadline (initiated timestamp + 24h): try
- Pulls BackingCompleted, filters via blacklist service, dedupes already-trusted backers, and calls
- Logs summaries by default;
VERBOSE_LOGGINGprints debug and tables DRY_RUN=1skips Safe transactions and only logs what would have been executed- Keeps running: initial run, then every minute
- Walks block ranges from
START_AT_BLOCKto the safe head (head - CONFIRMATION_BLOCKS) and fetchesRegisterHumanevents viacircles_events - Deduplicates avatars, checks them against the blacklist in chunks, and verifies a configured Metri Pay safe exists
- Skips avatars without safes or already trusted in
GP_CRC_GROUP_ADDRESS; trusts the rest in batches (default 10) with retry logic - Supports dry-run logging, verbose logging, and optional Slack notifications for start, shutdown, and errors
- Runs every 10 minutes
- Monitors AffiliateGroupChanged events for trust relationship updates
- Calculates desired trustees by intersecting:
- Addresses trusted by any MetaOrg trustee
- Current affiliates in the registry
- Performs batch trust/untrust operations to reconcile differences
- Supports incremental scanning to avoid re-processing old events
- Can run in dry-run mode for testing without making transactions
- Configurable refresh intervals and batch sizes
- Walks block ranges from
DUBLIN_TMS_START_BLOCKtohead - DUBLIN_TMS_CONFIRMATION_BLOCKS(orDUBLIN_TMS_TO_BLOCKwhen set) - Fetches
RegisterHuman(human, originInviter, proxyInviter)logs from the fixed invitation module0x00738aca013B7B2e6cfE1690F0021C3182Fa40B5 - Filters to events where
originInviteris in the fixed Dublin inviter allowlist - Deduplicates
humanaddresses, skips already-trusted avatars inDUBLIN_TMS_ADDRESS, and trusts remaining avatars in batches - Supports dry-run logging and startup/shutdown/run-error Slack notifications
- Make sure the configured Safe owns the appropriate service roles and that the signer key belongs to a Safe owner
- Slack notifications are best-effort; failures may throw and crash the process
- Services are designed to fail loudly on errors and require a supervisor to monitor and restart
- Replace placeholder values (
your_private_key_here, etc.) with actual values in Docker commands
CRC_BACKERS_SAFE_ADDRESSmust be the backers group's service and the signer key must belong to that Safe- If you change the factory address, bump
START_AT_BLOCKaccordingly - Service is stateless and does not store data between runs
GP_CRC_GROUP_ADDRESSmust match the group you intend to automatically trust new avatars into- Provide
GP_CRC_SAFE_ADDRESSplusGP_CRC_SAFE_SIGNER_PRIVATE_KEYunlessDRY_RUN=1 METRI_SAFE_GRAPHQL_URLis required; addMETRI_SAFE_API_KEYif the endpoint is restrictedDRY_RUN=1will log intended trust batches without submitting transactions
OIC_SERVICE_PRIVATE_KEYis used for direct EOA execution whenDRY_RUN=0- If set,
OIC_SERVICE_EOAmust match the address derived fromOIC_SERVICE_PRIVATE_KEY - OIC start block is hardcoded to
41734312 OIC_META_ORG_ADDRESSis required - this is the MetaOrg whose trustees will be monitored- Use
DRY_RUN=1for testing without making actual blockchain transactions
- Uses one Safe for all managed group contracts; set
GROUP_AFFILIATES_SAFE_ADDRESSplusGROUP_AFFILIATES_SAFE_SIGNER_PRIVATE_KEYunlessDRY_RUN=1 GROUP_AFFILIATES_WSS_URLdefaults to the/ws/chainvariant derived fromRPC_URL- Fetches each managed group's profile and uses its
minRepScoreas the reputation threshold - Only trusts affiliates whose reputation endpoint returns
reputation_score > minRepScorefor that managed group - Periodically rechecks current trustees and untrusts addresses whose reputation score falls to that group's
minRepScoreor below - Cursor persistence uses
LEADER_DB_URLand app namegroup-affiliates DRY_RUN=1logs planned trust/untrust batches and simulates them when Safe credentials are configured- Service maintains incremental state to avoid re-processing old events
- The wishlist RPC is the source of membership intent; a normal group trust relation alone is not community intent
- Eligibility requires
reputation_score > minRepScoreand an aggregate wishlist fee no greater than 100% - Missing or invalid group criteria and failed RPC/reputation reads fail the run before any transactions are submitted
- A current trustee absent from the wishlist is treated as having left and is untrusted regardless of reputation or fee criteria
- Current trustees still on the wishlist are untrusted when they fail reputation or fee eligibility
- The RPC methods read chain head and do not support block pinning, so reconciliation is intentionally repeatable and eventually consistent
- Use
DRY_RUN=1to inspect and optionally simulate the exact trust/untrust plan before enabling writes
DUBLIN_TMS_SERVICE_EOAshould be the group service EOA (defaults to0x20a3C619De4C15E360d30F329DBCfe5bb618654f)DUBLIN_TMS_SERVICE_PRIVATE_KEYmust matchDUBLIN_TMS_SERVICE_EOAwhenDRY_RUN=0- Set
DUBLIN_TMS_TO_BLOCKto run a bounded historical backfill - If
DUBLIN_TMS_TO_BLOCKis unset, the app follows the chain head with the configured confirmation buffer
- Fetches registered human avatars and filters out blacklisted addresses
- Calls the Safe Watch trustee validation API and uses
gnosis_trust_scorefor membership decisions - Uses a Safe for execution; set
GNOSIS_GROUP_SAFE_ADDRESSplusGNOSIS_GROUP_SAFE_SIGNER_PRIVATE_KEYfor a 1/n Safe owner DRY_RUN=1skips on-chain trust/untrust transactions while still evaluating blacklist and trust scores
ROUTER_SAFE_ADDRESSmust control the router's executor role; signer key must belong to that SafeROUTER_BASE_GROUP_ADDRESSdetermines which base group memberships are required before enabling routing- Use
DRY_RUN=1to log planned enablements without sending transactions
- The Dockerfile uses
APP_NAMEbuild argument to determine which app to run - Environment variables can be passed directly with
-eflags instead of using.envfiles - The container runs as the
nodeuser for security