Skip to content
Merged
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
64 changes: 59 additions & 5 deletions src/types/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
5 changes: 5 additions & 0 deletions src/types/receipts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}. */
Expand Down
29 changes: 26 additions & 3 deletions src/validators/splitRatioValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}. */
Expand All @@ -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.
Expand Down Expand Up @@ -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<string>();
for (const share of config.shares) {
if (seen.has(share.address)) {
Expand Down