developer

Events

On-chain event system and indexing

Overview#

Mini Veil emits events that enable off-chain components (relayer, frontend) to track protocol state changes. Events are parsed from program logs using discriminators.

Event Types#

DepositEvent#

Emitted by shield and shield_token instructions.

Discriminator: SHA-256("event:DepositEvent")[0:8]
interface DepositEvent {
  commitment: number[];     // [u8; 32] — Poseidon commitment
  amount: number[];         // u64 — deposit amount
  denomination: number[];   // u64 — pool denomination
}

StealthAnnouncement#

Emitted by announce_stealth instruction.

Discriminator: [197, 85, 83, 203, 142, 88, 5, 176]
(SHA-256("event:StealthAnnouncement")[0:8])
interface StealthAnnouncement {
  ephemeral_pubkey: number[];         // [u8; 32]
  view_tag: number;                   // u16
  encrypted_ephemeral_key: number[];  // [u8; 80]
  sender: PublicKey;
  timestamp: number;                  // i64
}

UnshieldCompressedEvent#

Emitted by unshield_compressed instruction.

Discriminator: Standard Anchor 8-byte discriminator
interface UnshieldCompressedEvent {
  nullifier_hash: number[];  // [u8; 32]
  amount: number[];          // u64
}

Event Parsing#

Events are parsed from raw program logs:

function parseStealthEvents(logs: string[]): StealthAnnouncement[] {
  const results: StealthAnnouncement[] = [];
  for (const line of logs) {
    if (!line.startsWith("Program data: ")) continue;
    const raw = Buffer.from(line.slice("Program data: ".length), "base64");
    if (raw.length < 8) continue;
    const disc = raw.subarray(0, 8);
    // Match discriminator
    if (discriminatorsMatch(disc, STEALTH_DISCRIMINATOR)) {
      // Parse fields
    }
  }
  return results;
}

Event Subscription#

Relayer#

The relayer uses connection.onLogs for real-time event subscription:

connection.onLogs(PROGRAM_ID, (logs) => {
  for (const event of relayer.parseDepositEventsFromLogs(logs)) {
    relayer.processDepositEvent(event);
  }
}, "confirmed");

Frontend (ScanClaimCard)#

The frontend uses connection.getSignaturesForAddress for historical event retrieval:

const signatures = await connection.getSignaturesForAddress(
  PROGRAM_ID,
  { limit: 50 },
);

for (const sig of signatures) {
  const tx = await connection.getTransaction(sig.signature);
  if (tx?.meta?.logMessages) {
    const events = parseStealthEvents(tx.meta.logMessages);
    // Process events
  }
}

Event Flow#

1. Shield transaction confirms
2. onLogs callback fires with program logs
3. Relayer parses DepositEvent, inserts into tree
4. After root update, the new root is available for withdrawals

5. Unshield transaction confirms
6. StealthAnnouncement event is logged
7. Recipient's ScanClaimCard fetches recent signatures
8. Parses StealthAnnouncement from transaction logs
9. Derives stealth key, checks balance, enables claim