Skip to content

Comments

Feat: dual event direction#41

Open
DNK90 wants to merge 5 commits intomainfrom
feat/dual-event-direction
Open

Feat: dual event direction#41
DNK90 wants to merge 5 commits intomainfrom
feat/dual-event-direction

Conversation

@DNK90
Copy link
Collaborator

@DNK90 DNK90 commented Feb 8, 2026

Add Direction Field for Dual Event Processing

Problem

When sweeping funds between two monitored wallets (e.g., deposit wallet → hot wallet), the indexer emits events for both the sender and receiver. However, both events contained identical transaction data without any way to distinguish which wallet should own each event.

This caused custody service to:

  • Receive two events with the same transaction hash
  • Attempt to create transaction records for both events
  • Both events pointed to the same wallet (receiver), causing incorrect balance updates
  • Unable to properly track dual-wallet balance changes during sweeps

Solution

This PR implements a comprehensive solution for dual event processing through 3 commits:

1. Emit Both Incoming and Outgoing Events

  • Detect when both sender and receiver are monitored addresses
  • Emit separate events for each wallet's perspective
  • Enables proper balance tracking for both sides of sweep transactions

2. Use Unique Idempotent Keys

  • Differentiate incoming vs outgoing events with distinct keys
  • Format: {txHash}-{event}-{in|out}
  • Prevents NATS from deduplicating legitimate dual events
  • Ensures both events are processed independently

3. Add Direction Field to Transaction

  • New field: Direction (values: "in" or "out")
  • Set Direction="in" for incoming events (toMonitored)
  • Set Direction="out" for outgoing events (fromMonitored)
  • Create transaction copy before setting direction to avoid shared data modification

Benefits

For Custody Service:

  • Can check Direction field to identify wallet ownership
  • For "in": Look up TO address wallet
  • For "out": Look up FROM address wallet
  • Creates transaction records with correct wallet_id

For Balance Updates:

  • Proper dual-wallet balance tracking during sweeps
  • Deposit wallet balance decreases (outgoing)
  • Hot wallet balance increases (incoming)
  • Both updates recorded in correct wallets

For System Integrity:

  • No more duplicate transaction attempts
  • Accurate balance synchronization
  • Proper audit trail for sweep operations

Technical Details

Changes to Transaction Model

type Transaction struct {
    Hash        string
    From        string
    To          string
    Value       string
    // ... other fields
    Direction   string  // NEW: "in" or "out"
}

Event Emission Logic

// When both from and to are monitored:
if fromMonitored && toMonitored {
    // Emit outgoing event for sender
    outgoingTx := *tx  // Copy to avoid shared state
    outgoingTx.Direction = "out"
    emit(outgoingTx, fromWallet)
    
    // Emit incoming event for receiver
    incomingTx := *tx  // Copy to avoid shared state
    incomingTx.Direction = "in"
    emit(incomingTx, toWallet)
}

Idempotent Key Format

  • Incoming: {txHash}-transfer-in
  • Outgoing: {txHash}-transfer-out

Testing Considerations

Scenarios to Test:

  1. ✅ Sweep from deposit wallet to hot wallet (both monitored)
  2. ✅ Regular deposit from external address to deposit wallet
  3. ✅ Regular withdrawal from hot wallet to external address
  4. ✅ Transfer between two monitored wallets
  5. ✅ Verify balance updates are correct for both wallets
  6. ✅ Confirm no duplicate transaction creation errors

Expected Behavior:

  • Single monitored wallet: 1 event, 1 transaction record
  • Both wallets monitored: 2 events, 2 transaction records (one per wallet)
  • Each wallet's balance updated correctly based on direction

Database Impact

Before: Transaction table had duplicate entries or errors
After: Clean transaction records with proper wallet ownership

Example:

wallet_id                              | hash     | direction | amount
---------------------------------------|----------|-----------|--------
deposit-wallet-uuid                    | 0xabc... | out       | -1.0 ETH
hot-wallet-uuid                        | 0xabc... | in        | +1.0 ETH

Migration Notes

Backward Compatibility:

  • New Direction field is additive
  • Existing events without direction will default to "in" (current behavior)
  • No breaking changes to event structure
  • Custody service can handle both old and new event formats

Deployment Order:

  1. Deploy multichain-indexer with this change
  2. Deploy custody service with direction field support
  3. Both can run simultaneously during transition

Related Issues

  • Fixes dual wallet balance synchronization for sweep transactions
  • Enables proper transaction ownership tracking
  • Resolves NATS event deduplication for legitimate dual events

Commits

  1. 2bec634 - feat: emit both incoming and outgoing events for monitored addresses
  2. dcd6dcb - fix: use unique idempotent keys for incoming vs outgoing events
  3. 14b0843 - feat: add direction field to Transaction for dual events

Ready for Review
All commits include clear messages with problem/solution descriptions.

DNK90 added 5 commits February 7, 2026 16:44
For wallet-to-wallet transfers (like sweeps), we need balance updates
for BOTH sender and receiver. This requires custody to create transaction
records for both wallets, which triggers finalization for both.

Changes:
- Emit incoming event when TO address is monitored (existing)
- Also emit outgoing event when FROM address is monitored (NEW)
- Both events processed independently by custody deposit_worker
- Results in 2 transaction records + 2 finalization tasks
- Finalization_worker updates both balances from on-chain state

This fixes sweep balance updates:
- Receiver balance increases (from incoming event)
- Sender balance decreases (from outgoing event)

Note: Withdrawals also emit outgoing events, but custody handles
them differently (withdrawal_worker vs deposit_worker).
Problem: When emitting both incoming and outgoing events for the same
transaction, NATS was deduplicating them because they had the same
idempotent key (tx hash). This caused custody to only receive one event
and create one transaction record, preventing proper balance updates
for both sender and receiver.

Solution:
- Added EmitTransactionWithKey() method to allow custom idempotent keys
- Use txHash+'-in' for incoming events
- Use txHash+'-out' for outgoing events
- This ensures both events are processed by custody, creating two
  transaction records and triggering finalization for both wallets

Fixes balance update issue where deposit wallet balance wasn't being
updated after sweeps.
Problem: When emitting both incoming and outgoing events for sweep
transactions, both events had identical transaction data. Custody
couldn't differentiate between them, so both events tried to create
a transaction record for the same wallet (receiver).

Solution:
- Add Direction field to Transaction struct ('in' or 'out')
- Set Direction='in' for incoming events (toMonitored)
- Set Direction='out' for outgoing events (fromMonitored)
- Create copy of transaction before setting direction to avoid
  modifying shared data

Now custody can:
- Check Direction field to know which wallet owns this event
- For 'in': lookup TO address wallet
- For 'out': lookup FROM address wallet
- Create transaction record with correct wallet_id

This enables proper dual-wallet balance updates for sweeps.
* 'main' of https://github.com/fystack/multichain-indexer:
  Update sample code
  Add notice for checking destination address
  Fix bloomfitler prefix
  Fix bug infinite catch up when confirmation blocks > 0
@DNK90 DNK90 requested a review from anhthii February 8, 2026 11:00
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