Commit bde8943
authored
feat(perps): Add market-level allowlist/blocklist filtering for HIP-3 (MetaMask#22086)
## **Description**
This PR implements market-level whitelisting and blacklisting for Perps
HIP-3 markets, providing granular control over which perpetual trading
markets are shown to users.
### What is the reason for the change?
Improvement from the initial remote feature flag implementation in PR
MetaMask#21823. While the initial PR established the remote feature flag
infrastructure for Perps, we needed more granular control beyond simple
DEX-level filtering. Specifically:
1. Users need the ability to enable/disable specific markets (e.g.,
"BTC", "xyz:XYZ100") rather than all markets from a DEX
2. Operators need both whitelist (enable only specific markets) and
blacklist (block specific markets) capabilities
3. The system must support wildcard patterns for efficient bulk
operations (e.g., "xyz:*" to enable/block all markets from a DEX)
### What is the improvement/solution?
**Migration from DEX-level to Market-level Filtering:**
- **Before**: Single `MM_PERPS_HIP3_ENABLED_DEXS` variable for
whitelisting entire DEXs
- **After**: Two variables for granular market control:
- `MM_PERPS_HIP3_ALLOWLIST_MARKETS` - Whitelist (empty = enable all,
non-empty = only show these)
- `MM_PERPS_HIP3_BLOCKLIST_MARKETS` - Blacklist (empty = block none,
non-empty = hide these)
**Key Features:**
1. **Wildcard Pattern Support**
- `"xyz:*"` or `"xyz"` - All markets from xyz DEX
- `"xyz:TSLA"` - Specific market
- `"BTC"` - Main DEX market
2. **Remote Feature Flag Integration**
- LaunchDarkly provides remote configuration
- Local env variables serve as fallback
- "Sticky remote" pattern: once remote config loads, never downgrade to
fallback
3. **Automatic Cache Invalidation & Reconnection**
- `hip3ConfigVersion` increments when config changes
- ConnectionManager monitors version via Redux
- Automatically triggers cache clearing and provider reconnection
- No manual intervention required
4. **Performance Optimizations**
- Pattern caching (pre-compiled regex stored in Map)
- Separate cache keys for filtered vs unfiltered data
- `skipFilters` parameter for administrative queries
**Architecture:**
```
LaunchDarkly � RemoteFeatureFlagController � PerpsController
� (hip3ConfigVersion++)
ConnectionManager
� (monitors version change)
Clear caches + Reconnect
�
HyperLiquidProvider
� (applies filters)
Market list with patterns applied
```
## **Changelog**
CHANGELOG entry: Added market-level whitelisting and blacklisting for
Perps HIP-3 markets with wildcard pattern support and automatic
reconnection on configuration changes
## **Related issues**
Related to: MetaMask#21823 (Initial remote feature flag implementation for
Perps)
This PR splits out the market filtering functionality (Task #2) from the
original feature flag work.
## **Manual testing steps**
```gherkin
Feature: Perps HIP-3 Market Filtering
Scenario: Whitelist enables all markets when empty
Given MM_PERPS_HIP3_ENABLED is true
And MM_PERPS_HIP3_ALLOWLIST_MARKETS is empty
And MM_PERPS_HIP3_BLOCKLIST_MARKETS is empty
When user opens the Perps tab
Then all available markets should be visible
And markets from both main DEX and HIP-3 DEXs should appear
Scenario: Whitelist restricts to specific markets
Given MM_PERPS_HIP3_ENABLED is true
And MM_PERPS_HIP3_ALLOWLIST_MARKETS is "BTC,ETH,xyz:TSLA"
And MM_PERPS_HIP3_BLOCKLIST_MARKETS is empty
When user opens the Perps tab
Then only BTC, ETH, and xyz:TSLA markets should be visible
And all other markets should be hidden
Scenario: Whitelist with wildcard enables all markets from DEX
Given MM_PERPS_HIP3_ENABLED is true
And MM_PERPS_HIP3_ALLOWLIST_MARKETS is "xyz:*,BTC"
And MM_PERPS_HIP3_BLOCKLIST_MARKETS is empty
When user opens the Perps tab
Then BTC market should be visible
And all markets from xyz DEX should be visible
And markets from other HIP-3 DEXs should be hidden
Scenario: Blacklist blocks specific markets
Given MM_PERPS_HIP3_ENABLED is true
And MM_PERPS_HIP3_ALLOWLIST_MARKETS is empty
And MM_PERPS_HIP3_BLOCKLIST_MARKETS is "xyz:TSLA,BTC"
When user opens the Perps tab
Then xyz:TSLA market should be hidden
And BTC market should be hidden
And all other markets should be visible
Scenario: Blacklist with wildcard blocks all markets from DEX
Given MM_PERPS_HIP3_ENABLED is true
And MM_PERPS_HIP3_ALLOWLIST_MARKETS is empty
And MM_PERPS_HIP3_BLOCKLIST_MARKETS is "xyz:*"
When user opens the Perps tab
Then all markets from xyz DEX should be hidden
And markets from main DEX and other HIP-3 DEXs should be visible
Scenario: Remote feature flag overrides local configuration
Given local env has MM_PERPS_HIP3_ALLOWLIST_MARKETS="BTC"
And LaunchDarkly is configured with perpsAllowlistMarkets="ETH,SOL"
When app initializes and fetches remote feature flags
Then only ETH and SOL markets should be visible
And BTC market should be hidden (remote overrides local)
Scenario: Automatic reconnection on configuration change
Given user has Perps tab open with markets loaded
And LaunchDarkly configuration is "BTC,ETH"
When LaunchDarkly configuration changes to "SOL,AVAX"
Then hip3ConfigVersion should increment
And ConnectionManager should detect the change
And app should automatically clear caches
And app should reconnect to providers
And only SOL and AVAX markets should be visible without manual refresh
Scenario: Fallback to local configuration when remote unavailable
Given LaunchDarkly is unreachable
And local env has MM_PERPS_HIP3_ALLOWLIST_MARKETS="BTC,ETH"
When user opens the Perps tab
Then local configuration should be used as fallback
And only BTC and ETH markets should be visible
Scenario: Shorthand wildcard notation
Given MM_PERPS_HIP3_ALLOWLIST_MARKETS is "xyz" (without :*)
When user opens the Perps tab
Then "xyz" should be interpreted as "xyz:*"
And all markets from xyz DEX should be visible
```
## **Screenshots/Recordings**
### **Before**
N/A - This is a configuration-based feature without UI changes
### **After**
N/A - This is a configuration-based feature without UI changes
## **Pre-merge author checklist**
- [ ] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile
Coding
Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [ ] I've completed the PR template to the best of my ability
- [ ] I've included tests if applicable
- [ ] I've documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [ ] I've applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.
## **Pre-merge reviewer checklist**
- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
---
## Technical Implementation Details
### Environment Variables
**New variables added:**
```bash
# HIP-3 Feature Flags (remote override with local fallback)
export MM_PERPS_HIP3_ENABLED="true"
export MM_PERPS_HIP3_ALLOWLIST_MARKETS="" # Whitelist: Empty = enable all markets
export MM_PERPS_HIP3_BLOCKLIST_MARKETS="" # Blacklist: Empty = no blocking
```
**Removed variables:**
```bash
# OLD - Removed
export MM_PERPS_HIP3_ENABLED_DEXS="" # Replaced by market-level filtering
export MM_PERPS_ENABLED_DEXS="" # Replaced by market-level filtering
```
### Pattern Matching Examples
| Pattern | Matches | Description |
|---------|---------|-------------|
| `"BTC"` | `BTC` (main DEX) | Exact match on main DEX market |
| `"xyz:TSLA"` | `xyz:TSLA` | Exact match on HIP-3 market |
| `"xyz:*"` | `xyz:TSLA`, `xyz:AAPL`, etc. | All markets from xyz DEX |
| `"xyz"` | `xyz:TSLA`, `xyz:AAPL`, etc. | Shorthand for `"xyz:*"` |
| `""` (empty) | All markets | Discovery mode (whitelist) or no blocking
(blacklist) |
### Pattern Matching Implementation
**Type Safety:**
- `CompiledPatternMatcher` - Type alias for pattern matchers (string for
exact match, RegExp for wildcards)
- `CompiledPattern` - Interface combining original pattern with compiled
matcher
**Compilation Strategy:**
```typescript
// Pattern → Compiled Matcher
"xyz:*" → /^xyz:/ (prefix regex)
"xyz" → /^xyz:/ (shorthand → prefix regex)
"xyz:TSLA" → "xyz:TSLA" (exact string match - fastest)
```
**Performance Optimization:**
1. All patterns pre-compiled at provider initialization via
`recompileAllPatterns()`
2. Stored in typed arrays (`CompiledPattern[]`) for direct iteration
3. Eliminates repeated `compilePattern()` function call overhead during
filtering
4. Better code clarity and type safety with pre-compiled matchers
5. Exact matches use string equality (fastest), wildcards use
RegExp.test()
**Filtering Logic in `shouldIncludeMarket()`:**
```typescript
// Main DEX markets always included
if (dex === null) return true;
// Apply whitelist (if non-empty)
if (compiledEnabledPatterns.length > 0) {
if (!compiledEnabledPatterns.some(p => matches(symbol, p.matcher))) {
return false; // Not whitelisted
}
}
// Apply blacklist (if non-empty)
if (compiledBlockedPatterns.length > 0) {
if (compiledBlockedPatterns.some(p => matches(symbol, p.matcher))) {
return false; // Blacklisted
}
}
return true;
```
### New Selectors
- `selectHip3ConfigVersion()` - Returns version number for cache
invalidation used by ConnectionManager to detect config changes
### Files Modified
**Core Logic (258 lines added):**
- `app/components/UI/Perps/controllers/PerpsController.ts`
- `refreshHip3ConfigFromRemote()` - Extracts and validates remote config
- Version tracking and increment on config change
- "Sticky remote" pattern implementation
**Provider Implementation (411 lines added):**
- `app/components/UI/Perps/controllers/providers/HyperLiquidProvider.ts`
- `CompiledPatternMatcher` type - Type alias for pattern matchers
- `CompiledPattern` interface - Type for pre-compiled patterns
- `recompileAllPatterns()` - Pre-compiles all patterns at initialization
- `shouldIncludeMarket()` - Pattern matching logic using pre-compiled
patterns
- `matchesCompiledPattern()` - Fast pattern matching without Map lookups
- `compilePattern()` - Regex compilation and caching
- `skipFilters` parameter support
- Separate cache keys for filtered/unfiltered data
**Selectors:**
- `app/components/UI/Perps/selectors/featureFlags/index.ts`
- `selectHip3ConfigVersion` - Version selector for monitoring config
changes
**Connection Management (22 lines added):**
- `app/components/UI/Perps/services/PerpsConnectionManager.ts`
- Monitors `hip3ConfigVersion` changes via Redux
- Triggers automatic cache clearing and reconnection
**Tests:**
- `app/components/UI/Perps/selectors/featureFlags/index.test.ts`
- `selectHip3ConfigVersion` (3 tests) - Validates version tracking
- Removed unused selector tests for unused allowlist/blocklist selectors
**Configuration:**
- `.js.env.example` - Environment variable documentation
- `bitrise.yml` - CI/CD environment configuration
- `app/core/Engine/controllers/perps-controller/index.ts` - Fallback
parsing
**Types:**
- `app/components/UI/Perps/controllers/types/index.ts`
- Updated `PerpsControllerConfig` interface
- Added `skipFilters` to `GetMarketsParams`
### Performance Considerations
1. **Pattern Pre-Compilation**: All filter patterns are compiled once at
initialization and stored in typed arrays (`CompiledPattern[]`),
eliminating repeated `compilePattern()` function calls during market
filtering
2. **Type-Safe Pattern Matchers**: Uses `CompiledPatternMatcher` type
alias and `CompiledPattern` interface for better documentation and type
safety
3. **Optimized Filtering**: `shouldIncludeMarket()` iterates
pre-compiled arrays directly with compiled matchers readily available,
improving code clarity and maintainability
4. **Separate Cache Keys**: Filtered and unfiltered data cached
separately (`"${dex}_raw"` vs `"${dex}_filtered"`)
5. **StreamManager Integration**: Market metadata cached for 5 minutes,
prices from WebSocket (real-time)
### Migration Guide
**From DEX-level to Market-level Filtering**
This PR replaces DEX-level filtering with more granular market-level
filtering:
**Removed Environment Variables:**
```bash
# OLD - No longer used
export MM_PERPS_HIP3_ENABLED_DEXS="xyz,abc" # Whitelist entire DEXs
export MM_PERPS_ENABLED_DEXS="xyz,abc" # Alternative naming (removed)
```
**New Environment Variables:**
```bash
# NEW - Market-level control
export MM_PERPS_HIP3_ALLOWLIST_MARKETS="" # Whitelist: empty = all markets (discovery mode)
export MM_PERPS_HIP3_BLOCKLIST_MARKETS="" # Blacklist: empty = block nothing
```
**Common Migration Scenarios:**
1. **Enable all markets from specific DEXs (most common):**
```bash
# Before: Enable all markets from xyz and abc DEXs
MM_PERPS_HIP3_ENABLED_DEXS="xyz,abc"
# After: Use wildcard patterns
MM_PERPS_HIP3_ALLOWLIST_MARKETS="xyz:*,abc:*"
# Or use shorthand (equivalent)
MM_PERPS_HIP3_ALLOWLIST_MARKETS="xyz,abc"
```
2. **Enable specific markets only:**
```bash
# Before: Not possible (DEX-level only)
# After: List specific markets
MM_PERPS_HIP3_ALLOWLIST_MARKETS="BTC,ETH,xyz:TSLA,xyz:AAPL"
```
3. **Enable all markets except specific ones:**
```bash
# Before: Not possible (no blacklist support)
# After: Use blacklist (whitelist empty = all markets)
MM_PERPS_HIP3_ALLOWLIST_MARKETS=""
MM_PERPS_HIP3_BLOCKLIST_MARKETS="xyz:SCAM,abc:RISKY"
```
4. **Block entire DEX:**
```bash
# Before: Omit from ENABLED_DEXS list
MM_PERPS_HIP3_ENABLED_DEXS="xyz" # abc implicitly blocked
# After: Use blacklist wildcard
MM_PERPS_HIP3_ALLOWLIST_MARKETS="" # Enable all
MM_PERPS_HIP3_BLOCKLIST_MARKETS="abc:*" # Block abc DEX
# Or use shorthand
MM_PERPS_HIP3_BLOCKLIST_MARKETS="abc"
```
5. **Enable all markets (discovery mode):**
```bash
# Before: Leave ENABLED_DEXS empty or set to all known DEXs
MM_PERPS_HIP3_ENABLED_DEXS=""
# After: Leave ALLOWLIST_MARKETS empty
MM_PERPS_HIP3_ALLOWLIST_MARKETS=""
MM_PERPS_HIP3_BLOCKLIST_MARKETS=""
```
**LaunchDarkly Remote Feature Flags:**
The same patterns apply to LaunchDarkly configuration:
- `perpsAllowlistMarkets` - Array of market patterns (empty = all
markets)
- `perpsBlocklistMarkets` - Array of market patterns to block (empty =
block nothing)
**Example LaunchDarkly Config:**
```json
{
"perpsHip3Enabled": true,
"perpsAllowlistMarkets": ["BTC", "ETH", "xyz:*"],
"perpsBlocklistMarkets": ["xyz:SCAM"]
}
```
### Breaking Changes
None - This is an additive change with backward-compatible fallback
behavior.
**Note:** While `MM_PERPS_HIP3_ENABLED_DEXS` is no longer used, removing
it from your configuration will not break anything. The new market-level
filtering is more flexible and supersedes DEX-level filtering.
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> Implements market-level allowlist/blocklist filtering for HIP-3 perps
with remote-flag overrides, caching, and automatic reconnection via
hip3ConfigVersion.
>
> - **Perps HIP-3 market filtering (allowlist/blocklist)**:
> - Add pattern-based filtering (exact, wildcard, DEX shorthand) via
`shouldIncludeMarket`, `compileMarketPattern`, etc. in
`utils/marketUtils`.
> - New parsing util `parseCommaSeparatedString` for LaunchDarkly/env
values.
> - New env vars in `.js.env.example` and CI (`bitrise.yml`):
`MM_PERPS_HIP3_ENABLED`, `MM_PERPS_HIP3_ALLOWLIST_MARKETS`,
`MM_PERPS_HIP3_BLOCKLIST_MARKETS` (replace old DEX-level flags).
> - **Controller & reconnection**:
> - `PerpsController`: ingest remote flags for HIP-3
(enabled/allowlist/blocklist), maintain `hip3ConfigVersion`, and
propagate config to provider.
> - `PerpsConnectionManager`: monitor `selectHip3ConfigVersion` to clear
caches and reconnect on config changes.
> - Add `selectHip3ConfigVersion` selector; default state updated to
include `hip3ConfigVersion`.
> - **Provider & subscriptions**:
> - `HyperLiquidProvider`: apply market filtering, add market metadata
caching (filtered/unfiltered), support `skipFilters`, and pass HIP-3
config to `HyperLiquidSubscriptionService`.
> - `HyperLiquidSubscriptionService`: support HIP-3 on/off, map webData2
(main DEX) vs webData3 (multi-DEX), update feature flags without
teardown; remove redundant clearinghouseState path.
> - **Types & tests**:
> - Extend `GetMarketsParams` with `skipFilters`; update
`PerpsControllerConfig` fallbacks.
> - Extensive unit tests added/updated for HIP-3 config parsing,
filtering, subscriptions, selectors, and connection manager.
> - **Misc**:
> - Remove unused debug styles in `PerpsTabView.styles.ts`.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
d93ca77. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->1 parent bbfd284 commit bde8943
21 files changed
Lines changed: 2589 additions & 921 deletions
File tree
- app
- components/UI/Perps
- Views/PerpsTabView
- controllers
- providers
- types
- selectors/featureFlags
- services
- utils
- core/Engine
- controllers/perps-controller
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
162 | 162 | | |
163 | 163 | | |
164 | 164 | | |
165 | | - | |
166 | | - | |
167 | | - | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
168 | 169 | | |
169 | 170 | | |
170 | 171 | | |
Lines changed: 0 additions & 11 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
90 | 90 | | |
91 | 91 | | |
92 | 92 | | |
93 | | - | |
94 | | - | |
95 | | - | |
96 | | - | |
97 | | - | |
98 | | - | |
99 | | - | |
100 | | - | |
101 | | - | |
102 | | - | |
103 | | - | |
104 | 93 | | |
105 | 94 | | |
106 | 95 | | |
| |||
Lines changed: 292 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
151 | 151 | | |
152 | 152 | | |
153 | 153 | | |
154 | | - | |
| 154 | + | |
155 | 155 | | |
156 | 156 | | |
157 | 157 | | |
| |||
161 | 161 | | |
162 | 162 | | |
163 | 163 | | |
164 | | - | |
| 164 | + | |
165 | 165 | | |
166 | 166 | | |
167 | 167 | | |
| |||
374 | 374 | | |
375 | 375 | | |
376 | 376 | | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
| 401 | + | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
| 427 | + | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
| 461 | + | |
| 462 | + | |
| 463 | + | |
| 464 | + | |
| 465 | + | |
| 466 | + | |
| 467 | + | |
| 468 | + | |
| 469 | + | |
| 470 | + | |
| 471 | + | |
| 472 | + | |
| 473 | + | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
| 558 | + | |
| 559 | + | |
| 560 | + | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
| 584 | + | |
| 585 | + | |
| 586 | + | |
| 587 | + | |
| 588 | + | |
| 589 | + | |
| 590 | + | |
| 591 | + | |
| 592 | + | |
| 593 | + | |
| 594 | + | |
| 595 | + | |
| 596 | + | |
| 597 | + | |
| 598 | + | |
| 599 | + | |
| 600 | + | |
| 601 | + | |
| 602 | + | |
| 603 | + | |
| 604 | + | |
| 605 | + | |
| 606 | + | |
| 607 | + | |
| 608 | + | |
| 609 | + | |
| 610 | + | |
| 611 | + | |
| 612 | + | |
| 613 | + | |
| 614 | + | |
| 615 | + | |
| 616 | + | |
| 617 | + | |
| 618 | + | |
| 619 | + | |
| 620 | + | |
| 621 | + | |
| 622 | + | |
| 623 | + | |
| 624 | + | |
| 625 | + | |
| 626 | + | |
| 627 | + | |
| 628 | + | |
| 629 | + | |
| 630 | + | |
| 631 | + | |
| 632 | + | |
| 633 | + | |
| 634 | + | |
| 635 | + | |
| 636 | + | |
| 637 | + | |
| 638 | + | |
| 639 | + | |
| 640 | + | |
| 641 | + | |
| 642 | + | |
| 643 | + | |
| 644 | + | |
| 645 | + | |
| 646 | + | |
| 647 | + | |
| 648 | + | |
| 649 | + | |
| 650 | + | |
| 651 | + | |
| 652 | + | |
| 653 | + | |
| 654 | + | |
| 655 | + | |
| 656 | + | |
| 657 | + | |
| 658 | + | |
| 659 | + | |
| 660 | + | |
| 661 | + | |
| 662 | + | |
| 663 | + | |
| 664 | + | |
| 665 | + | |
| 666 | + | |
377 | 667 | | |
378 | 668 | | |
379 | 669 | | |
| |||
0 commit comments