Skip to content

feat: implement BLE swarm registry system with dual-chain support#99

Draft
aliXsed wants to merge 63 commits intomainfrom
aliX/swarm-manager
Draft

feat: implement BLE swarm registry system with dual-chain support#99
aliXsed wants to merge 63 commits intomainfrom
aliX/swarm-manager

Conversation

@aliXsed
Copy link
Collaborator

@aliXsed aliXsed commented Feb 6, 2026

  • Add FleetIdentity (ERC721) for BLE fleet ownership via Proximity UUID

  • Add ServiceProvider (ERC721) for service endpoint URL ownership

  • Add SwarmRegistryL1 with SSTORE2 for Ethereum L1 optimization

  • Add SwarmRegistryUniversal with native bytes storage for ZkSync Era compatibility

  • Implement XOR filter-based tag membership verification

  • Add deterministic swarm IDs derived from (fleetId, providerId, filter)

  • Support orphan detection and permissionless purging of burned NFT swarms

  • Include comprehensive test suites (157 tests total)

  • Add Solady dependency for SSTORE2 functionality

  • Give more thought to buildHighestBondedUUIDBundle for the case we have incomplete tiers vs full tiers in all levels

  • Give more thought to offering low open tier as the default for registering fleets

  • Update docs

  • Review tests

- Add FleetIdentity (ERC721) for BLE fleet ownership via Proximity UUID
- Add ServiceProvider (ERC721) for service endpoint URL ownership
- Add SwarmRegistryL1 with SSTORE2 for Ethereum L1 optimization
- Add SwarmRegistryUniversal with native bytes storage for ZkSync Era compatibility
- Implement XOR filter-based tag membership verification
- Add deterministic swarm IDs derived from (fleetId, providerId, filter)
- Support orphan detection and permissionless purging of burned NFT swarms
- Include comprehensive test suites (157 tests total)
- Add Solady dependency for SSTORE2 functionality
@aliXsed aliXsed force-pushed the aliX/swarm-manager branch from 52bb4af to 538047d Compare February 6, 2026 07:55
Algorithm: Greedy All-or-Nothing with Level Priority. When bonds
tie across admin/country/global levels, try each level in priority
order (admin → country → global). Include the entire tier only if
all members fit in the remaining room; otherwise skip that tier
entirely. Cursors always advance regardless, preventing starvation
of lower-priority levels when a larger tier is skipped.

This guarantees every included tier is complete (no partial tiers),
respects geographic priority, and avoids starving smaller levels.
Improved readability by replacing cryptic 'sc' abbreviation with
descriptive 'tierCount' name across all 10 occurrences in:
- highestActiveTier
- discoverHighestBondedTier (3 level checks)
- buildHighestBondedUUIDBundle (3 cursor inits)
- _openTier
- _findOpenTierView
- _trimTierCount
- Move regionKey to 3rd position (indexed)
- Move bondRefund to last position
- Update FleetPromoted and FleetDemoted event declarations
  to include indexed modifiers on fromTier and toTier

Event signature now:
FleetBurned(address indexed owner, uint256 indexed tokenId,
            uint32 indexed regionKey, uint256 tierIndex,
            uint256 bondRefund)

This groups indexed params first and places monetary values last.
…UUIDBundle\n\nReplace the greedy all-or-nothing algorithm with a shared-cursor\nfair-stop approach:\n\n- Single cursor descends from the highest active tier across all levels\n- At each cursor: try admin → country → global, include entire tier or skip\n- If ANY tier is skipped at a cursor position, STOP after finishing that cursor\n- Prevents cheaper tiers from being included when a same-priced peer was excluded\n\nTest suite rewritten: 36 tests (32 deterministic + 4 fuzz) covering\nbond priority, all-or-nothing, fair-stop, level filtering, lifecycle,\ncap enforcement, and integrity invariants."
…, globalCompetitiveHint, countryCompetitiveHint)
…oTier, DRY competitive hints, remove BOND_MULTIPLIER
FleetIdentity.sol:
- Remove auto-assign overloads for registerFleetGlobal (1-arg) and
  registerFleetCountry (2-arg); callers must now pass an explicit tier.
- Keep local auto-assign via _cheapestInclusionTierLocal which simulates
  the bundle algorithm to find the cheapest tier guaranteeing inclusion.
- Replace bestOpenTier with localInclusionHint (bounded O(MAX_TIERS)).
- Replace globalCompetitiveHint with globalInclusionHint (view-only,
  unbounded — iterates all active countries and admin areas).
- Replace countryCompetitiveHint with countryInclusionHint (view-only,
  unbounded — iterates all active admin areas for a country).
- Add _findCheapestInclusionTier: two-phase simulation that (1) builds
  the bundle without the candidate, tracking cutoff cursor and
  countBefore[], then (2) finds the cheapest tier T where the candidate
  region has room and the bundle would include it.
- Remove _openTier, _findOpenTierView, _scanHottestTier,
  _scanHottestCountry.
- Fix shadowed variable warnings in discoverHighestBondedTier.

Tests (158 pass):
- Rewrite auto-assign tests for local-only auto-assign.
- Replace bestOpenTier tests with localInclusionHint tests.
- Replace globalCompetitiveHint/countryCompetitiveHint tests with
  globalInclusionHint/countryInclusionHint tests covering pressure,
  burn updates, multiple locations, and registrant-can-act-on-hint.
- Update _registerNGlobal/_registerNCountry helpers to compute tiers
  dynamically (i / capacity).
- Fix all call sites for explicit tier arguments.
- Update fuzz tests for dynamic tier computation.
- Fix SwarmRegistryL1/Universal tests for 2-arg registerFleetGlobal."}
</invoke>
…dCheapestInclusionTier

Constants:
- MAX_COUNTRY_CODE (999), MAX_ADMIN_CODE (4095) replace bare literals
  in all validation guards and tierCapacity / region-index branches.
- LEVEL_ADMIN / LEVEL_COUNTRY / LEVEL_GLOBAL (0/1/2) name the
  bundle priority levels.
- ADMIN_SHIFT (12), ADMIN_CODE_MASK (0xFFF) centralise the
  bit-packing scheme.

Internal region-key helpers:
- _makeAdminRegion(cc, admin): replaces 7 inline `(uint32(cc) << 12) | uint32(admin)`.
- _countryFromRegion(rk), _adminFromRegion(rk): replace inline
  `uint16(rk >> 12)` / `uint16(rk & 0xFFF)`.

Bundle-level helpers (shared by buildHighestBondedUUIDBundle and
inclusion hints):
- _resolveBundleLevels(cc, admin): pure, returns keys[3] + active[3].
- _findMaxTierIndex(keys, active): view, returns (maxTI, anyActive).
- _simulateBundleCounts(keys, active, maxTI): view, Phase 1 of the
  inclusion simulation — returns countBefore[24] + cutoffCursor.

_findCheapestInclusionTier now takes (countryCode, adminCode,
candidateLevel) instead of a raw candidateRegion uint32.  The
region key is derived internally from keys[candidateLevel].
Dead `earlySkip` variable removed.

buildHighestBondedUUIDBundle delegates setup to _resolveBundleLevels
and _findMaxTierIndex, keeping only the UUID-collecting cursor loop.

All 158 FleetIdentity tests + 135 SwarmRegistry tests pass."
…ding

Replace all-or-nothing + fair-stop algorithm with partial inclusion:
- Members are included in array order (registration order) until bundle is full
- A tier that doesn't fully fit now includes as many members as possible
- Prevents economic attack where 1 high-tier fleet could displace 4 lower-tier fleets

Changes:
- buildHighestBondedUuidBundle: include partial tiers by array position
- _findCheapestInclusionTier: fix countBefore calculation, add _getCountFromTiersAbove helper
- _simulateBundleCounts: simplified to return count array only
- Updated tests to reflect partial inclusion behavior
Remove discoverHighestBondedTier, competitiveLandscape, and discoverAllLevels.

These functions are superseded by:
- buildHighestBondedUuidBundle: provides complete bundle for EdgeBeaconScanners
- localInclusionHint/countryInclusionHint/globalInclusionHint: provide actionable
  tier recommendations for registrants

The inclusion hints compute the exact tier needed for bundle inclusion,
making raw competitive data functions unnecessary.
… + Local)

- Removed registerFleetGlobal, globalInclusionHint, GLOBAL_REGION
- TIER_CAPACITY = 4 (unified for all levels)
- COUNTRY_BOND_MULTIPLIER = 8 (country pays 8× local bond)
- MAX_COUNTRY_IN_BUNDLE = 8 (cap country fleets in bundle)
- tierBond(tier, regionKey) determines multiplier based on region
- AdminAreaRequired error when buildHighestBondedUuidBundle called with adminCode=0
- Updated all tests to match new contract behavior
Make _validateExplicitTier a pure view function that only validates without modifying storage. Move regionTierCount update to _addToTier where the actual tier membership change occurs.

This follows better design principles:
- Validation functions should be pure checks without side effects
- State mutations happen at the logical point of change
- No additional gas cost, just cleaner separation of concerns

Remove redundant regionTierCount update in _promote since _addToTier now handles it.

All 184 tests pass.
- Define enum for UUID registration levels (None, Local, Country) with explicit values
- Replace magic numbers (0, 1, 2) with enum constants for better code clarity
- Add _isCountryRegion() internal helper to encapsulate region type detection
- Replace 8 occurrences of '<= MAX_COUNTRY_CODE' checks with semantic helper calls
- Improves intent visibility and maintainability throughout the contract
- All 184 tests passing
… parameter

Add FleetIdentityFairness.t.sol with 11 comprehensive tests validating the
fairness of the PREFERRED_COUNTRY_SLOTS = 8 parameter:

- Scenario tests: local-heavy, country-heavy, balanced, whale attack, multi-region
- Sensitivity analysis: validates 8 is mathematically optimal (100% fairness score)
- Bond escalation analysis: shows cost structure across tiers and competition levels
- Fairness invariants: regression guards ensuring local protection >= 60%, country access >= 20%
- Fuzz testing: validates fairness holds across random market conditions

Key findings:
- Value 8 (60% local / 40% country slots) is optimal
- Soft cap flexibility prevents slot waste when markets are imbalanced
- 8x bond multiplier approximates cost of registering in all admin areas
- Whale resistance: even with 64x bond, whale cannot exceed soft cap

All 11 tests pass. Original FleetIdentity tests (184 tests) still pass.
- Change tierBond(uint256 tier, uint32 regionKey) to tierBond(uint256 tier, bool isCountry)
- Simplifies API by directly accepting the only decision factor (country vs local)
- Update all 10 call sites in FleetIdentity.sol to pass _isCountryRegion(region)
- Update test calls in FleetIdentity.t.sol and FleetIdentityFairness.t.sol
- More explicit and gas-efficient than passing unnecessary regionKey parameter
…rameter

- Remove the 3-argument registerFleetLocal(uuid, countryCode, adminCode) overload
- Keep only the explicit tier version: registerFleetLocal(uuid, countryCode, adminCode, targetTier)
- Update all test calls (~100) to pass explicit tier values
- Rename auto-assign tests to use localInclusionHint() for finding cheapest tier
- Fix fuzz tests to spread registrations across tiers (i / 4) to avoid TierFull
- All 330 tests pass (184 FleetIdentity + 11 FleetIdentityFairness + 135 SwarmRegistry)
The region is already encoded in the token ID's upper 128 bits (bits 128-159).
Extracting via uint32(tokenId >> 128) saves gas on every registration and burn.

- Remove fleetRegion public mapping
- Replace all usages with inline region extraction from tokenId
- Update test helpers to use ownerOf() instead of relying on mapping defaults
- All 195 tests pass
- Create iso3166-2 subfolder with standardized country lookup tables
- Map ISO 3166-2 subdivision codes to dense numeric indices (0 to n-1)
- Includes 18 representative countries across all major regions
- Update doc structure for better browsability
- Files organized by ISO 3166-1 numeric code
- Reduce MAX_ADMIN_CODE from 4095 to 255 (sufficient for all real-world countries)
- Update ADMIN_SHIFT from 12 to 8 and ADMIN_CODE_MASK from 0xFFF to 0xFF
- Update documentation with ISO 3166-2 mapping details
- Minor code improvements: use ++i over i++, change tokenUuid/tokenRegion to public
- Update error message for consistency
- Implement RegistrationLevel.Owned for standalone UUID ownership without region registration
- Add claimUuid(), unregisterToOwned(), and releaseUuid() functions
- Remove migrateRegion functionality (replaced by unregisterToOwned + re-register)
- Change ADMIN_SHIFT from 8 to 10 to eliminate country/admin key overlap
- Update ADMIN_CODE_MASK to 0x3FF for 10-bit admin codes
- Update _register() to handle Owned→Registered transitions
- Update _update() to transfer uuidOwner on owned-only token transfers
- Update tests with owned-only mode tests and correct shift values
- All 191 tests passing (180 FleetIdentity + 11 FleetIdentityFairness)
Replace complex two-pass soft-cap system with simple tier-descent algorithm:
- Iterate from max tier down to 0
- At each tier: take locals first, then country
- Stop when bundle is full (20 slots)

This removes ~150 lines of complexity including:
- PREFERRED_COUNTRY_SLOTS constant (was 8)
- _appendCountryUuidsWithCap helper (~30 lines)
- _fillSkippedCountryUuids helper (~25 lines)
- Soft-cap tracking arrays (countryIncludedPerTier, countrySkippedPerTier)

**Fairness Model Change**: Shift from slot-guarantee (60/40 split) to economic-advantage model
- Country fleets pay 8× more than locals (COUNTRY_BOND_MULTIPLIER)
- Locals can reach tier 3 for the same cost country pays for tier 0
- This 8× multiplier is now the primary fairness mechanism
- Enable future adjustments to fairness via the multiplier

**Tests**:
- FleetIdentity.t.sol: 180 passed (removed soft-cap assertions, renamed tests)
- FleetIdentityFairness.t.sol: Rewritten with economic-advantage focus (13 tests)
- Total: 193 tests pass

**Lost Features** (documented):
- Hard 60/40 slot allocation guarantee
- Two-pass bundle filling system
- Explicit whale resistance via soft cap (now via cost difference)

See: https://github.com/nodl-ai/rollup/discussions/bundle-simplification
…UuidBundle and findCheapestInclusionTier

- Extract _buildHighestBondedUuidBundle to return highestTier and lowestTier for inclusion-tier logic
- Simplify _findCheapestInclusionTier to fully rely on bundle builder, unwinding count by subtracting both regions' contributions
- Remove _getCountFromTiersAbove and _resolveBundleLevels helpers (no longer needed)
- Remove LEVEL_ADMIN/LEVEL_COUNTRY constants, change isCountry parameter to bool
- Simplify _appendTierUuids by removing tierCount check (empty arrays are no-ops)
- Improve documentation for internal bundle builder and inclusion logic
- Tests: all 180 FleetIdentity tests pass
- Restore None (0) as the first enum value for unregistered UUIDs
- Reorder enum: None, Owned, Local, Country
- Simplify _register() logic to check existingLevel directly
- Remove unnecessary existingOwner check as primary condition
- Update all test assertions to match new enum values
- All 193 tests passing
- Extract bond transfer helpers: _pullBond() and _refundBond()
- Extract UUID cleanup: _clearUuidOwnership() and _decrementUuidCount()
- Extract tier cleanup: _cleanupFleetFromTier() consolidates removal sequence
- Add token minting helper: _mintFleetToken() for shared minting logic
- Refactor _register() into clearer if/else structure with three explicit paths
- Use tokenRegion() and tokenUuid() accessors throughout (replace inline shifts)
- Simplify burn(), unregisterToOwned(), releaseUuid(), _promote(), _demote()
- All 193 tests pass, no behavioral changes

Benefits:
- Reduced code duplication (3 cleanup patterns consolidated)
- Improved readability (dedicated helpers with clear intent)
- Easier maintenance (single point of change for common operations)
- Consistent token ID encoding/decoding (named accessors)
- Changed all 18 ISO 3166-2 country mapping files to use 1-indexed admin codes instead of 0-indexed
- Updated column header from "Dense Index" to "Admin Code"
- Updated README.md to clarify that admin codes are 1-indexed in table files
- Aligns with FleetIdentity contract requirement that adminCode > 0
- Removed confusing "dense index + 1" conceptual step for end users
- Remove countryRegionKey() external function (trivial uint32 cast)
- Rename _makeAdminRegion() to makeAdminRegion() for public use
- Remove adminRegionKey() wrapper; callers use makeAdminRegion directly
- Keep getActiveCountries/getActiveAdminAreas (required for full-array returns)
- Update all internal calls and tests to use makeAdminRegion()
- All 192 tests pass
…raphic registration

- Updated assistant-guide.md with geographic registration (country + admin area levels)
- Added UUID ownership lifecycle (Owned, Local, Country registration levels)
- Documented tier system, bond economics, and inclusion hints
- Updated sequence diagrams for registration, discovery, and lifecycle flows
- Added comprehensive FleetIdentity documentation (regions, tiers, bundles)
- Created new sequence-fleet-identity.md covering region encoding and bundle discovery
- Updated discovery flows to use geographic bundle priority
- Clarified multi-region registration and unregister-to-owned flows
- All changes verified with spellcheck (0 issues)
- Add buildCountryOnlyBundle() function for country-only fleet position verification
- Add comprehensive fleet maintenance guide with periodic inclusion checking
- Document country fleet maintenance when no active admin areas exist
- Add 6 new tests for buildCountryOnlyBundle functionality
- Update sequence-fleet-identity.md with country-only bundle info
- Update README with new maintenance documentation link
- Change Swarm struct from fleetId (uint256) to fleetUuid (bytes16)
- Update SwarmRegistryL1 and SwarmRegistryUniversal to use UUID ownership
- Change mappings from fleetSwarms to uuidSwarms and swarmIndexInFleet to swarmIndexInUuid
- Update all functions to verify ownership via FLEET_CONTRACT.uuidOwner(fleetUuid)
- Update tests to reflect UUID-based architecture
- Allows swarms to be defined for any registered UUID regardless of geographic tier
- Update copilot-instructions.md with L1-only contract build requirements
- Change fleetSwarms → uuidSwarms and swarmIndexInFleet → swarmIndexInUuid
- Update registerSwarm/computeSwarmId to use fleetUuid instead of fleetId
- Change ownership verification from ownerOf(fleetId) to uuidOwner(fleetUuid)
- Update Swarm struct field from uint256 fleetId to bytes16 fleetUuid
- Update sequence diagrams and code examples throughout docs
Reorganize documentation with professional top-down structure:

- README.md: Overview & architecture (entry page)
- data-model.md: Contract interfaces, enums, storage
- fleet-registration.md: Registration flows, tier economics
- swarm-operations.md: Swarm registration, filters, approval
- lifecycle.md: State machines, transitions
- discovery.md: Client discovery flows
- maintenance.md: Bundle monitoring, tier optimization
- iso3166-reference.md: Geographic codes reference

Removed redundant files:
- assistant-guide.md (content distributed to focused docs)
- graph-architecture.md (merged into data-model.md)
- sequence-*.md files (consolidated into topic-focused docs)
- Fix table column alignment and padding for consistency
- Add proper blank lines after headings and before lists
- Remove trailing whitespace
- Standardize TypeScript/Solidity code indentation
- Fix End-to-End Flow: Provider Owner now correctly shown for
  registerProvider() and acceptSwarm() instead of Fleet Owner
- Replace 'privacy-preserving' with 'non-enumerating' + 'membership proofs'
- Add Privacy Model section with data visibility table
- UUID=public (iOS CLBeaconRegion), Major/Minor=filter-protected, MAC=Android-only
* UUID owners can delegate tier maintenance to an operator
* Operator manages promote/demote; owner retains burn rights
* Bond split: owner pays BASE_BOND, operator pays/receives tier excess
setOperator() atomically transfers tier bonds between operators
* registerFleetLocalWithOperator/registerFleetCountryWithOperator for registration with operator
* Comprehensive tests covering all operator scenarios
* Documentation updated (README, data-model, lifecycle, maintenance, registration, assistant-guide)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant