diff --git a/src/types/events.ts b/src/types/events.ts index c20f5fb..77f9f42 100644 --- a/src/types/events.ts +++ b/src/types/events.ts @@ -16,12 +16,66 @@ export interface EventCursor { updatedAt: number; } +// --------------------------------------------------------------------------- +// Discriminated event payload interfaces +// --------------------------------------------------------------------------- + +/** Subscription successfully established. */ +export interface ConnectedEvent { + kind: "Connected"; + type: "connected"; + invoiceId: string; +} + +/** Subscription lost; consumers may wish to show an error or auto-retry. */ +export interface DisconnectedEvent { + kind: "Disconnected"; + type: "disconnected"; + invoiceId: string; + error: Error; +} + +/** Reconnection attempt in progress. */ +export interface ReconnectingEvent { + kind: "Reconnecting"; + type: "reconnecting"; + invoiceId: string; + attempt: number; + delayMs: number; +} + +/** Event cursor persisted to storage for crash recovery. */ +export interface CursorPersistedEvent { + kind: "CursorPersisted"; + type: "cursor_persisted"; + invoiceId: string; + cursor: EventCursor; +} + +/** + * Top-level discriminated union of every SDK event payload. + * + * Use the `kind` field for exhaustive pattern-matching: + * + * ```ts + * function handle(event: SdkEvent) { + * switch (event.kind) { + * case "Connected": // ... + * case "Disconnected": // ... + * case "Reconnecting": // ... + * case "CursorPersisted": // ... + * } + * } + * ``` + */ +export type SdkEvent = + | ConnectedEvent + | DisconnectedEvent + | ReconnectingEvent + | CursorPersistedEvent; + /** Lifecycle events emitted by SubscriptionManager for observability. */ -export type SubscriptionManagerLifecycleEvent = - | { type: "connected"; invoiceId: string } - | { type: "disconnected"; invoiceId: string; error: Error } - | { type: "reconnecting"; invoiceId: string; attempt: number; delayMs: number } - | { type: "cursor_persisted"; invoiceId: string; cursor: EventCursor }; +export type SubscriptionManagerLifecycleEvent = SdkEvent; /** Options accepted by SubscriptionManager and its per-invoice subscribe() calls. */ export interface SubscriptionOptions { diff --git a/src/types/receipts.ts b/src/types/receipts.ts index 6a7fe90..8e59bc4 100644 --- a/src/types/receipts.ts +++ b/src/types/receipts.ts @@ -16,6 +16,11 @@ export interface PaymentReceipt { ledger: number; /** Unix timestamp (milliseconds). */ timestamp: number; + /** + * Network fee paid for this transaction, in stroops (1 XLM = 10,000,000 stroops). + * Optional for backward compatibility with existing serialised receipts. + */ + networkFeeStroops?: number; } /** One SHA-256-linked entry in a {@link ReceiptChain}. */ diff --git a/src/validators/splitRatioValidator.ts b/src/validators/splitRatioValidator.ts index b2bcbf0..d47b7f7 100644 --- a/src/validators/splitRatioValidator.ts +++ b/src/validators/splitRatioValidator.ts @@ -44,6 +44,15 @@ export interface SplitConfig { * dealing with imprecise user input). */ tolerance?: number; + /** + * Minimum share ratio per recipient, expressed as a percentage (e.g. 0.01 + * means 0.01 %). Any recipient whose ratio falls below this threshold + * fails validation because the resulting dust payment would be rejected + * by Stellar's minimum balance check. + * + * Defaults to `0.01` (0.01 %). Set to `0` to disable the minimum check. + */ + minRatioPercent?: number; } /** Structured validation result returned by {@link validateSplitRatios}. */ @@ -66,8 +75,9 @@ export interface SplitRatioValidationResult { * 1. At least one share is provided. * 2. No share is negative. * 3. No share is zero. - * 4. No duplicate recipient addresses. - * 5. Shares sum to 1.0 (± tolerance). + * 4. No recipient ratio is below `minRatioPercent` (default 0.01 %). + * 5. No duplicate recipient addresses. + * 6. Shares sum to 1.0 (± tolerance). * * @param config - The split configuration to validate. * @returns A structured result with `valid` and `errors` fields. @@ -100,7 +110,20 @@ export function validateSplitRatios( } } - // 3. Duplicate address check + // 3. Minimum ratio check + const minRatioPercent = config.minRatioPercent ?? 0.01; + if (minRatioPercent > 0) { + const minShareFraction = minRatioPercent / 100; + for (const share of config.shares) { + if (share.share > 0 && share.share < minShareFraction) { + errors.push( + `Recipient ${share.address} has a ratio of ${(share.share * 100).toPrecision(4)}% which is below the minimum ${minRatioPercent}%. Increase the recipient's share or set minRatioPercent to 0 to disable this check.`, + ); + } + } + } + + // 4. Duplicate address check const seen = new Set(); for (const share of config.shares) { if (seen.has(share.address)) {