Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions examples/stellar/scan-payments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@ async function main() {
const signature = new Uint8Array(64); // wallet.sign(STEALTH_SIGNING_MESSAGE)
const keys = deriveStealthKeys(signature);

// 2. Fetch announcements from Soroban RPC
const announcements = await fetchAnnouncements('stellar');
// 2. Fetch announcements from Soroban RPC (v1 stream + optional v2 bucket filters)
const announcements = await fetchAnnouncements({
chain: 'stellar',
// viewTagBuckets: [42], // uncomment to fetch a single v2 bucket (~255/256 less traffic)
});
console.log(`Found ${announcements.length} total announcements`);

// 3. Scan for payments addressed to us
Expand Down
74 changes: 74 additions & 0 deletions src/chains/stellar/EVENT_FETCHING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Stellar announcement event fetching

This document describes how `@wraith-protocol/sdk/chains/stellar` ingests Soroban
announcer events and the privacy trade-offs of RPC topic filtering.

## Announcer versions

| Version | Contract field | Topic layout | RPC bucket filter |
| ------- | -------------- | ------------------------------------------------- | ----------------- |
| v1 | `announcer` | `("announce", scheme_id, stealth_address)` | No |
| v2 | `announcerV2` | `("announce", 2, view_tag_bucket, metadata_kind)` | Yes |

During the v1 → v2 migration window, `fetchAnnouncements()` reads from **both**
contracts when `announcerV2` is configured in deployments.

## Bucketed `getEvents` queries (v2)

For v2 announcements, the SDK builds Soroban RPC filters:

```
("announce", 2, view_tag_bucket, *)
```

Pass explicit buckets to avoid downloading the full v2 stream:

```typescript
import { fetchAnnouncements, viewTagToBucket } from '@wraith-protocol/sdk/chains/stellar';

const bucket = viewTagToBucket(0x2a);
const announcements = await fetchAnnouncements({
chain: 'stellar',
viewTagBuckets: [bucket],
});
```

When `viewTagBuckets` is omitted, the SDK uses `("announce", 2, *, *)` on the v2
contract. v1 events are always fetched without bucket filters because
`stealth_address` occupies topic slot 2 in the legacy layout.

## Client-side validation

RPC topic filters reduce bandwidth only. Recipients must still run
`scanAnnouncements()` to cryptographically verify each candidate event against
their viewing key. Never treat RPC-filtered events as trusted payments.

## Privacy trade-off

Indexing `view_tag_bucket` in a public Soroban topic leaks one byte of
correlation per payment. With 256 buckets, observers (including the RPC provider
when you pass bucket filters) can group announcements by approximate recipient
identity.

| Strategy | Bandwidth | Query privacy |
| --------------------------------- | --------- | ------------- |
| v1 full stream | Highest | Lowest |
| v2 single-bucket filter | Lowest | Lower |
| v2 all-bucket wildcard | Medium | Medium |
| Private indexer / self-hosted RPC | Varies | Highest |

For most users, 256 buckets is a reasonable balance. Choose single-bucket
filters when RPC egress cost dominates; prefer broader queries or a private
indexer when query pattern privacy dominates.

## Filter batching

Soroban RPC accepts at most five event filters per `getEvents` request. When
more than five buckets are requested, the SDK splits them into sequential
batches via `buildV2BucketEventFilterBatches()`.

## Related issues

- contracts [#23](https://github.com/wraith-protocol/contracts/issues/23) — topic design
- contracts [#24](https://github.com/wraith-protocol/contracts/issues/24) — v2 announcer contract
- contracts [#25](https://github.com/wraith-protocol/contracts/issues/25) — SDK bucketed fetch (this module)
Loading