@mystars-tg/faas-sdk 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1038 @@
1
+ /**
2
+ * Wire types for the MyStars FaaS `/v1` API.
3
+ *
4
+ * These mirror the JSON the server actually returns, field-for-field, in
5
+ * `snake_case` — so they read identically to the REST docs. Money is always a
6
+ * decimal string (never a `number`); convert to smallest units only at the
7
+ * on-chain boundary.
8
+ */
9
+ /** On-chain payment currency. Both settle on the TON chain. */
10
+ type Currency = "ton" | "usdt_ton";
11
+ /** Orderable product type. */
12
+ type OrderType = "stars" | "premium";
13
+ /** Which numeric parameter a product is sized by. */
14
+ type Parameter = "quantity" | "months";
15
+ /** The full FaaS order status domain (15 values). */
16
+ type OrderStatus = "received" | "awaiting_payment" | "paid" | "reserved" | "swapping" | "funding" | "purchasing" | "fulfilling" | "completed" | "delivered" | "failed" | "reversed" | "expired" | "held" | "cancelled";
17
+ /** Statuses an order can never leave. */
18
+ type TerminalStatus = "delivered" | "failed" | "reversed" | "expired" | "cancelled";
19
+ /** The terminal status set — an order in one of these will not change again. */
20
+ declare const TERMINAL_STATUSES: ReadonlySet<OrderStatus>;
21
+ /** True when an order has reached a final state. */
22
+ declare function isTerminal(status: OrderStatus): status is TerminalStatus;
23
+ /** A reason a recipient cannot receive the item. */
24
+ type RecipientCheckReason = "already_subscribed" | "not_found" | "ineligible";
25
+ /** A reason an order failed. */
26
+ type FailureReason = "underpaid" | "overpaid" | "no_memo" | "wrong_memo" | "undeliverable" | "expired" | (string & {});
27
+ /** One supported payment currency, from `GET /v1/currencies`. */
28
+ interface CurrencyInfo {
29
+ code: Currency;
30
+ chain: string;
31
+ name: string;
32
+ }
33
+ /** One product from the catalog, from `GET /v1/products`. */
34
+ interface Product {
35
+ type: OrderType;
36
+ name: string;
37
+ parameter: Parameter;
38
+ min: number;
39
+ max: number;
40
+ /** A fixed allowed set (e.g. premium months `[3,6,12]`), or `null` for a continuous range. */
41
+ values: number[] | null;
42
+ }
43
+ /**
44
+ * The USDT processing-fee itemization (present only for `usdt_ton`).
45
+ *
46
+ * Invariant: `subtotal + processing_fee === total === amount`. This is an
47
+ * itemization of the fee already inside `amount` — never an additional charge.
48
+ * All fields are decimal USDT strings.
49
+ */
50
+ interface FeeBreakdown {
51
+ subtotal: string;
52
+ processing_fee: string;
53
+ total: string;
54
+ description: string;
55
+ currency: "usdt";
56
+ }
57
+ /** A price quote from `GET /v1/pricing`. */
58
+ interface PricingQuote {
59
+ type: OrderType;
60
+ quantity: number | null;
61
+ months: number | null;
62
+ /** All-in total to pay, in `currency`, as a decimal string. */
63
+ amount: string;
64
+ currency: Currency;
65
+ /** Itemized fee for `usdt_ton`; `null` for `ton`. */
66
+ fee: FeeBreakdown | null;
67
+ /** Informational market rate; `null` if the cache is cold. */
68
+ usdt_per_ton: string | null;
69
+ quoted_at: string;
70
+ /** A re-quote hint (`quoted_at + ttl`) — NOT a price lock. Price is fixed at order creation. */
71
+ valid_until: string;
72
+ }
73
+ /** The result of `POST /v1/recipients/check`. */
74
+ interface RecipientCheck {
75
+ resolved: boolean;
76
+ eligible: boolean;
77
+ recipient_name: string | null;
78
+ reason: RecipientCheckReason | null;
79
+ /** Verbatim Telegram/Fragment rejection text; `null` when eligible. */
80
+ telegram_message: string | null;
81
+ }
82
+ /** The on-chain payment instruction attached to a created order. */
83
+ interface PaymentInstruction {
84
+ currency: Currency;
85
+ chain: "ton";
86
+ /** The treasury OWNER address — the same for both `ton` and `usdt_ton`. */
87
+ pay_to_address: string | null;
88
+ /** The bare order UUID, attached as the on-chain text comment (no `STARS:` prefix). */
89
+ memo: string | null;
90
+ /** The exact amount to send, as a decimal string. */
91
+ amount: string;
92
+ amount_units: "ton" | "usdt";
93
+ /** Itemized fee for `usdt_ton`; `null` for `ton`. */
94
+ fee: FeeBreakdown | null;
95
+ }
96
+ /** The full order resource, from `GET /v1/orders` and `GET /v1/orders/:id`. */
97
+ interface Order {
98
+ order_id: string;
99
+ status: OrderStatus;
100
+ type: OrderType;
101
+ recipient_username: string | null;
102
+ quantity: number | null;
103
+ months: number | null;
104
+ amount_ton: string | null;
105
+ payment_tx: string | null;
106
+ purchase_tx: string | null;
107
+ failure_reason: FailureReason | null;
108
+ reversal_tx: string | null;
109
+ telegram_message: string | null;
110
+ created_at: string;
111
+ updated_at: string;
112
+ /** Non-null only while `status === "awaiting_payment"`. */
113
+ expires_at: string | null;
114
+ }
115
+ /** The result of `POST /v1/orders` (201 new, or 200 idempotent replay). */
116
+ interface CreateOrderResult {
117
+ order_id: string;
118
+ /**
119
+ * A fresh 201 create is always `awaiting_payment`. A 200 idempotent replay
120
+ * (same `Idempotency-Key`, same body) echoes the order's CURRENT status, which
121
+ * may already have advanced (e.g. `paid`, `delivered`) — so narrow against the
122
+ * full status domain, not the literal.
123
+ */
124
+ status: OrderStatus;
125
+ type: OrderType;
126
+ quantity: number | null;
127
+ months: number | null;
128
+ payment: PaymentInstruction;
129
+ expires_at: string;
130
+ /** `true` when the server returned 200 (idempotent replay) rather than 201 (fresh create). */
131
+ replayed: boolean;
132
+ }
133
+ /** One page of orders from `GET /v1/orders`. */
134
+ interface OrdersPage {
135
+ orders: Order[];
136
+ /** Opaque keyset cursor for the next page, or `null` on the final page. */
137
+ next_cursor: string | null;
138
+ }
139
+ /** The terminal statuses that fire a webhook (NOT `cancelled`, which is a manual API action). */
140
+ type WebhookStatus = "delivered" | "failed" | "reversed" | "expired";
141
+ /**
142
+ * The terminal statuses that fire an order webhook — `delivered`/`failed`/
143
+ * `reversed`/`expired` (NOT `cancelled`, which is a manual API action and never
144
+ * webhooked). Mirrors `webhook_terminal` in the contract status-machine fixture.
145
+ */
146
+ declare const WEBHOOK_TERMINAL: ReadonlySet<WebhookStatus>;
147
+ /** The status an order is in immediately after a fresh `POST /v1/orders` (201). */
148
+ declare const INITIAL_ORDER_STATUS: OrderStatus;
149
+ /** Statuses an order can be cancelled from via `POST /v1/orders/:id/cancel`. */
150
+ declare const CANCELLABLE_STATUSES: ReadonlySet<OrderStatus>;
151
+ /**
152
+ * The body of an order webhook (POSTed to your `callback_url`). Deliberately
153
+ * minimal — fetch `GET /v1/orders/:id` for full detail. Treat every field beyond
154
+ * `order_id`/`status` as optional.
155
+ */
156
+ interface WebhookEvent {
157
+ order_id: string;
158
+ status: WebhookStatus;
159
+ failure_reason?: FailureReason;
160
+ purchase_tx?: string;
161
+ }
162
+
163
+ /**
164
+ * Typed error taxonomy for the MyStars FaaS SDK.
165
+ *
166
+ * The server returns an envelope `{ error: { code, message, telegram_message? } }`
167
+ * for handled errors, and a bare `{ "error": "not_found" }` string for unmatched
168
+ * routes. `errorFromResponse` maps both forms — plus network/timeout failures —
169
+ * to the right class, keyed on the envelope `code` (the code, not the HTTP
170
+ * status, is authoritative; an unknown future code falls back to the base class
171
+ * so the SDK never crashes on a new code).
172
+ */
173
+
174
+ /** Base class for every error this SDK throws. */
175
+ declare abstract class MyStarsError extends Error {
176
+ constructor(message: string);
177
+ }
178
+ interface ApiErrorInit {
179
+ code: string;
180
+ status: number;
181
+ message: string;
182
+ telegramMessage?: string | undefined;
183
+ requestId?: string | undefined;
184
+ retryable?: boolean;
185
+ raw?: unknown;
186
+ /** The `Idempotency-Key` that was sent with the failed request (set by `createOrder`). */
187
+ idempotencyKey?: string | undefined;
188
+ }
189
+ /** Any error originating from an HTTP response (or a failed attempt to make one). */
190
+ declare class MyStarsApiError extends MyStarsError {
191
+ /** The envelope `error.code`, or `"unknown"` / `"network"`. */
192
+ readonly code: string;
193
+ /** HTTP status code (`0` for a network/timeout failure). */
194
+ readonly status: number;
195
+ /** Buyer-facing Telegram/Fragment message, when the server supplied one. */
196
+ readonly telegramMessage?: string;
197
+ /** The `x-request-id` response header, when present. */
198
+ readonly requestId?: string;
199
+ /** Coarse hint that the failure is potentially transient. The retry policy decides for real. */
200
+ readonly retryable: boolean;
201
+ /** The parsed response body (or the thrown error), for debugging. */
202
+ readonly raw?: unknown;
203
+ /**
204
+ * The `Idempotency-Key` that was sent with the request that failed, when known.
205
+ *
206
+ * `createOrder` stamps this on a thrown error so you can SAFELY retry the
207
+ * create with the SAME key (`{ idempotencyKey: err.idempotencyKey }`) instead
208
+ * of minting a duplicate deliverable when you can't tell whether the order was
209
+ * created server-side. `undefined` for errors not raised by a keyed request.
210
+ */
211
+ readonly idempotencyKey?: string;
212
+ constructor(init: ApiErrorInit);
213
+ }
214
+ /** 400 — malformed request, validation failure, or missing `Idempotency-Key`. */
215
+ declare class BadRequestError extends MyStarsApiError {
216
+ }
217
+ /** 401 — missing or invalid `X-Api-Key`. */
218
+ declare class UnauthorizedError extends MyStarsApiError {
219
+ }
220
+ /** 403 — the API key is valid but the tenant is suspended or banned. */
221
+ declare class ForbiddenError extends MyStarsApiError {
222
+ }
223
+ /** 404 — order not found (or an unmatched route). */
224
+ declare class NotFoundError extends MyStarsApiError {
225
+ }
226
+ /** 409 — a generic conflict. */
227
+ declare class ConflictError extends MyStarsApiError {
228
+ }
229
+ /** 409 — the same `Idempotency-Key` was reused with a different request body. */
230
+ declare class IdempotencyConflictError extends ConflictError {
231
+ }
232
+ /** 409 — the order is not in `awaiting_payment` and cannot be cancelled. */
233
+ declare class OrderNotCancellableError extends ConflictError {
234
+ }
235
+ /**
236
+ * 422 — the recipient cannot receive the item; no order was created.
237
+ *
238
+ * The server's 422 body carries only `telegram_message` (the buyer-facing reason
239
+ * to show your user) — NOT a structured `reason` code. For the structured
240
+ * `reason` (`already_subscribed` | `not_found` | `ineligible`), call
241
+ * `client.checkRecipient(...)` first and read its `reason` field.
242
+ */
243
+ declare class RecipientIneligibleError extends MyStarsApiError {
244
+ readonly telegramMessage: string;
245
+ constructor(init: ApiErrorInit);
246
+ }
247
+ /** Which limiter a {@link RateLimitError} came from: the per-minute limiter or the daily order-cap/flood guard. */
248
+ type RateLimitKind = "general" | "order_cap";
249
+ /** 429 — rate limited. `kind` distinguishes the per-minute limiter from the daily order cap / flood guard. */
250
+ declare class RateLimitError extends MyStarsApiError {
251
+ /** Milliseconds to wait before retrying, from `Retry-After`; `null` for the order-cap/flood limiter. */
252
+ readonly retryAfterMs: number | null;
253
+ readonly limit: number | null;
254
+ readonly remaining: number | null;
255
+ readonly reset: number | null;
256
+ /** `"general"` when RFC-9110 `RateLimit-*` headers are present; `"order_cap"` otherwise. */
257
+ readonly kind: RateLimitKind;
258
+ constructor(init: ApiErrorInit & {
259
+ retryAfterMs?: number | null;
260
+ limit?: number | null;
261
+ remaining?: number | null;
262
+ reset?: number | null;
263
+ kind: RateLimitKind;
264
+ });
265
+ }
266
+ /** 503 — a price source or upstream dependency is temporarily unavailable. Retryable. */
267
+ declare class ServiceUnavailableError extends MyStarsApiError {
268
+ }
269
+ /** 500 — an unhandled server error. */
270
+ declare class InternalServerError extends MyStarsApiError {
271
+ }
272
+ /** The request never produced an HTTP response (DNS, connection reset, aborted, etc.). */
273
+ declare class NetworkError extends MyStarsApiError {
274
+ }
275
+ /** The request exceeded the configured timeout. */
276
+ declare class TimeoutError extends NetworkError {
277
+ }
278
+ /** A webhook payload failed signature verification (or the header was missing/malformed). */
279
+ declare class WebhookSignatureError extends MyStarsError {
280
+ }
281
+ /** `waitForOrder` gave up before the order reached a terminal state. */
282
+ declare class OrderWaitTimeoutError extends MyStarsError {
283
+ /** The most recent order snapshot observed before timing out. */
284
+ readonly lastOrder: Order;
285
+ constructor(lastOrder: Order, message?: string);
286
+ }
287
+ /** Parse a `Retry-After` header (delta-seconds, or an HTTP-date) into milliseconds. */
288
+ declare function parseRetryAfterMs(value: string | undefined, now?: number): number | null;
289
+ /** Map an HTTP response (status + parsed body + headers) to the appropriate typed error. */
290
+ declare function errorFromResponse(status: number, body: unknown, headers: Headers): MyStarsApiError;
291
+
292
+ /**
293
+ * Retry policy + backoff for transient failures.
294
+ *
295
+ * Only retries when the request is idempotency-safe AND the failure is
296
+ * transient. Crucially, a `createOrder` retry reuses the SAME `Idempotency-Key`
297
+ * (the transport guarantees this), so a retried create returns the server's
298
+ * idempotent replay instead of minting a duplicate deliverable.
299
+ */
300
+
301
+ /** The decision context handed to {@link RetryPolicy.retryOn} / {@link defaultShouldRetry} for one failed attempt. */
302
+ interface RetryContext {
303
+ method: string;
304
+ path: string;
305
+ /** 0-based index of the attempt that just failed. */
306
+ attempt: number;
307
+ /** Whether this request is safe to replay (GET, or any request carrying an Idempotency-Key). */
308
+ idempotent: boolean;
309
+ /** The classified error from the failed attempt. */
310
+ error: MyStarsApiError;
311
+ }
312
+ /**
313
+ * Tunable retry policy passed to `MyStarsClient` (`retry`). Pass `false` instead
314
+ * to disable retries; omit a field to keep its default.
315
+ */
316
+ interface RetryPolicy {
317
+ /** Max retries AFTER the first attempt. Default 3. */
318
+ maxRetries?: number;
319
+ /** Base backoff delay in ms. Default 500. */
320
+ baseDelayMs?: number;
321
+ /** Backoff cap in ms. Default 30_000. */
322
+ maxDelayMs?: number;
323
+ /** Jitter strategy. Default "full". */
324
+ jitter?: "full" | "none";
325
+ /** Honor a 429 `Retry-After` header as a lower bound on the delay. Default true. */
326
+ respectRetryAfter?: boolean;
327
+ /** Override the default retry classifier entirely. */
328
+ retryOn?: (ctx: RetryContext) => boolean;
329
+ }
330
+ /**
331
+ * The built-in classifier: retry idempotent requests on network/timeout/503/500,
332
+ * the general 429, and any other error flagged transient (e.g. a 502/504 gateway
333
+ * error, which `errorFromResponse` maps to a base error with `retryable: true`).
334
+ */
335
+ declare function defaultShouldRetry(ctx: RetryContext): boolean;
336
+
337
+ /**
338
+ * The HTTP transport: URL building, auth/idempotency headers, timeout, a bounded
339
+ * response read, JSON parsing, and the retry loop.
340
+ *
341
+ * Conventions: trailing-slash strip, AbortController timeout, bounded read,
342
+ * `X-Api-Key` + `Idempotency-Key` headers, and the key is NEVER placed into any
343
+ * logged/intercepted object.
344
+ */
345
+
346
+ /** Info passed to {@link Interceptors.onRequest} before an attempt is sent. Never contains the API key. */
347
+ interface RequestLogInfo {
348
+ method: string;
349
+ url: string;
350
+ idempotencyKey?: string;
351
+ }
352
+ /** Info passed to {@link Interceptors.onResponse} after a response is received. */
353
+ interface ResponseLogInfo {
354
+ method: string;
355
+ url: string;
356
+ status: number;
357
+ /** Wall-clock duration of the attempt, in ms. */
358
+ durationMs: number;
359
+ requestId?: string;
360
+ }
361
+ /** Info passed to {@link Interceptors.onRetry} just before the SDK sleeps and retries. */
362
+ interface RetryLogInfo {
363
+ method: string;
364
+ url: string;
365
+ /** 1-based index of the retry about to be made. */
366
+ attempt: number;
367
+ /** Backoff the SDK will wait before the retry, in ms. */
368
+ delayMs: number;
369
+ /** Why the retry fired, e.g. `"timeout (HTTP 0)"`. */
370
+ reason: string;
371
+ }
372
+ /**
373
+ * Observability hooks invoked around each request. All are optional, may be async
374
+ * (awaited inline), and NEVER receive the API key. Use them for logging/metrics —
375
+ * not for mutating the request.
376
+ */
377
+ interface Interceptors {
378
+ onRequest?: (info: RequestLogInfo) => void | Promise<void>;
379
+ onResponse?: (info: ResponseLogInfo) => void | Promise<void>;
380
+ onRetry?: (info: RetryLogInfo) => void | Promise<void>;
381
+ }
382
+
383
+ /**
384
+ * Keyset (cursor) pagination over `GET /v1/orders`.
385
+ *
386
+ * `for await (const order of pager)` flattens every page; `pager.pages()` yields
387
+ * a page at a time; `pager.page(cursor?)` fetches a single page. The cursor is
388
+ * an opaque base64url token — never construct it by hand.
389
+ */
390
+
391
+ /** Fetches one page given the previous page's `next_cursor` (`undefined` for the first page). */
392
+ type FetchOrdersPage = (cursor: string | undefined) => Promise<OrdersPage>;
393
+ /**
394
+ * Lazy, auto-advancing view over `GET /v1/orders`. Iterate it directly to stream
395
+ * every order, or use `pages()` / `page()` for page-at-a-time control. Returned by
396
+ * `client.listOrders(...)` — you rarely construct it yourself.
397
+ *
398
+ * @example
399
+ * ```ts
400
+ * for await (const order of client.listOrders({ status: "delivered" })) {
401
+ * console.log(order.order_id);
402
+ * }
403
+ * // or collect everything: const all = await client.listOrders().all();
404
+ * ```
405
+ */
406
+ declare class OrdersPager implements AsyncIterable<Order> {
407
+ private readonly fetchPage;
408
+ private readonly startCursor;
409
+ /**
410
+ * @param fetchPage - fetches a page given a cursor (the client wires this to the transport)
411
+ * @param startCursor - an optional cursor to begin from (resumes mid-stream)
412
+ */
413
+ constructor(fetchPage: FetchOrdersPage, startCursor?: string);
414
+ /** Fetch a single page. Pass the previous page's `next_cursor` to advance. */
415
+ page(cursor?: string): Promise<OrdersPage>;
416
+ /** Yield one page at a time until `next_cursor` is null. */
417
+ pages(): AsyncIterableIterator<OrdersPage>;
418
+ /** Yield every order across all pages. */
419
+ [Symbol.asyncIterator](): AsyncIterator<Order>;
420
+ /** Collect every order across all pages into a single array. */
421
+ all(): Promise<Order[]>;
422
+ }
423
+
424
+ /**
425
+ * Reconciliation — catch terminal transitions a dropped or dead-lettered webhook
426
+ * missed. Webhook delivery is at-least-once and unordered, so a safety net that
427
+ * diffs the server's order list against your local store closes the gap.
428
+ *
429
+ * Walks `GET /v1/orders` newest-first; for each TERMINAL order your store hasn't
430
+ * recorded (`isKnown` returns false), it's collected (and `onMissed` fired).
431
+ * Stops paginating once it passes `since` (orders are newest-first).
432
+ */
433
+
434
+ /** The slice of the client {@link reconcile} needs (structurally typed to avoid a cycle). */
435
+ interface ReconcileClient {
436
+ listOrders(params?: {
437
+ status?: OrderStatus;
438
+ limit?: number;
439
+ }): OrdersPager;
440
+ }
441
+ /** Options for {@link reconcile} — your store check plus optional status/since/page-size filters. */
442
+ interface ReconcileOptions {
443
+ /** Only reconcile a specific terminal status (else all terminal orders). */
444
+ status?: OrderStatus;
445
+ /** Stop once orders are older than this (orders are newest-first). */
446
+ since?: Date | string;
447
+ /** Page size for the underlying list. */
448
+ limit?: number;
449
+ /** Your store's check: has this terminal order already been processed? */
450
+ isKnown: (order: Order) => boolean | Promise<boolean>;
451
+ /** Fired for each missed terminal order, in newest-first order. */
452
+ onMissed?: (order: Order) => void | Promise<void>;
453
+ }
454
+ /**
455
+ * Walk the server's orders newest-first and collect TERMINAL orders your store
456
+ * hasn't recorded — the safety net for webhooks that were dropped or dead-lettered.
457
+ *
458
+ * @param client - anything with a `listOrders` returning an {@link OrdersPager} (a `MyStarsClient` fits)
459
+ * @param opts - the {@link ReconcileOptions}; `isKnown` is required, `since` bounds the scan
460
+ * @returns the missed terminal orders, newest-first (also delivered one-by-one to `onMissed`)
461
+ * @throws `MyStarsApiError` if a page fetch fails
462
+ * @example
463
+ * ```ts
464
+ * const missed = await client.reconcile({
465
+ * since: new Date(Date.now() - 24 * 3600_000),
466
+ * isKnown: (o) => myStore.has(o.order_id),
467
+ * onMissed: (o) => myStore.markTerminal(o),
468
+ * });
469
+ * ```
470
+ */
471
+ declare function reconcile(client: ReconcileClient, opts: ReconcileOptions): Promise<Order[]>;
472
+
473
+ /**
474
+ * Poll an order until it reaches a terminal state (or a custom predicate / the
475
+ * deadline). Polling a still-`awaiting_payment` order also nudges the server's
476
+ * on-demand payment detection, so this doubles as a payment tracker.
477
+ */
478
+
479
+ /** Polling/backoff knobs and observation hooks for {@link waitForOrder} / `client.waitForOrder`. */
480
+ interface WaitForOrderOptions {
481
+ /** First poll interval (ms). Default 2000. */
482
+ pollIntervalMs?: number;
483
+ /** Upper bound on the backed-off poll interval (ms). Default 15_000. */
484
+ maxPollIntervalMs?: number;
485
+ /** Multiplier applied to the interval after each poll. Default 1.5. */
486
+ backoffFactor?: number;
487
+ /** Total time to wait before throwing `OrderWaitTimeoutError` (ms). Default 30 min. */
488
+ maxWaitMs?: number;
489
+ /** Apply jitter to the poll interval. Default "full". */
490
+ jitter?: "full" | "none";
491
+ /** Resolve when this returns true. Default: the order is terminal. */
492
+ until?: (order: Order) => boolean;
493
+ /** Called whenever the observed status changes (including the first observation). */
494
+ onUpdate?: (order: Order) => void;
495
+ signal?: AbortSignal;
496
+ }
497
+
498
+ /**
499
+ * `MyStarsClient` — the typed entry point to the MyStars FaaS API.
500
+ *
501
+ * Wraps the 8 public `/v1` endpoints with typed requests/responses, automatic
502
+ * retries (honoring `Retry-After`), automatic `Idempotency-Key` generation +
503
+ * safe reuse on retry, keyset pagination, and order tracking.
504
+ */
505
+
506
+ /** Base URL of the production B2B edge (`api.mystars.tg`). */
507
+ declare const PRODUCTION_BASE_URL = "https://api.mystars.tg/v1";
508
+ /** Configuration for {@link MyStarsClient}. Only `apiKey` is required. */
509
+ interface MyStarsClientOptions {
510
+ /** Your tenant API key (`faas_…`). Sent as the `X-Api-Key` header. */
511
+ apiKey: string;
512
+ /** Full base URL incl. `/v1`. Defaults to production (`api.mystars.tg`). */
513
+ baseUrl?: string;
514
+ /** Injected fetch. Defaults to the global `fetch`. */
515
+ fetch?: typeof fetch;
516
+ /** Per-request timeout in ms. Default 30_000. */
517
+ timeoutMs?: number;
518
+ /** Retry policy, or `false` to disable retries. */
519
+ retry?: RetryPolicy | false;
520
+ /** Generates the `Idempotency-Key` for `createOrder` when the caller omits one. Default uuid v4. */
521
+ idempotencyKeyFactory?: () => string;
522
+ /** Sent as `User-Agent` (Node only; browsers ignore it). */
523
+ userAgent?: string;
524
+ /** Observability hooks. Never receive the API key. */
525
+ interceptors?: Interceptors;
526
+ /** Test injectables. */
527
+ now?: () => number;
528
+ sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
529
+ random?: () => number;
530
+ }
531
+ /** Per-call options common to every request — currently just an `AbortSignal`. */
532
+ interface RequestOptions {
533
+ signal?: AbortSignal;
534
+ }
535
+ /** Options for {@link MyStarsClient.createOrder} — a {@link RequestOptions} plus an optional stable key. */
536
+ interface CreateOrderOptions extends RequestOptions {
537
+ /** Reuse a specific idempotency key (e.g. derived from your own order id). */
538
+ idempotencyKey?: string;
539
+ }
540
+ /** Discriminated input for {@link MyStarsClient.getPricing} — sized by `quantity` (stars) or `months` (premium). */
541
+ type PricingParams = {
542
+ type: "stars";
543
+ quantity: number;
544
+ payment_currency?: Currency;
545
+ } | {
546
+ type: "premium";
547
+ months: number;
548
+ payment_currency?: Currency;
549
+ };
550
+ /** Discriminated input for {@link MyStarsClient.checkRecipient} (premium may include the intended `months`). */
551
+ type CheckRecipientParams = {
552
+ type: "stars";
553
+ recipient: {
554
+ username: string;
555
+ };
556
+ } | {
557
+ type: "premium";
558
+ recipient: {
559
+ username: string;
560
+ };
561
+ months?: number;
562
+ };
563
+ /** Discriminated input for {@link MyStarsClient.createOrder} — recipient + sizing + optional currency/callback. */
564
+ type CreateOrderParams = {
565
+ type: "stars";
566
+ recipient: {
567
+ username: string;
568
+ };
569
+ quantity: number;
570
+ payment_currency?: Currency;
571
+ callback_url?: string;
572
+ } | {
573
+ type: "premium";
574
+ recipient: {
575
+ username: string;
576
+ };
577
+ months: number;
578
+ payment_currency?: Currency;
579
+ callback_url?: string;
580
+ };
581
+ /** Filters for {@link MyStarsClient.listOrders} — status, page size, and a starting cursor. */
582
+ interface ListOrdersParams {
583
+ status?: OrderStatus;
584
+ /** 1-100; the server caps at 100 and defaults to 50. */
585
+ limit?: number;
586
+ cursor?: string;
587
+ }
588
+ /** The typed entry point to the MyStars FaaS `/v1` API. See the module overview for the full flow. */
589
+ declare class MyStarsClient {
590
+ private readonly transport;
591
+ private readonly idempotencyKeyFactory;
592
+ private readonly now;
593
+ private readonly sleep;
594
+ private readonly random;
595
+ /**
596
+ * @param opts - the {@link MyStarsClientOptions} (at minimum `apiKey`). Prefer the
597
+ * {@link MyStarsClient.production} factory.
598
+ * @throws `Error` if `apiKey` is missing, or no `fetch` is available (Node <18 — pass `fetch` explicitly)
599
+ */
600
+ constructor(opts: MyStarsClientOptions);
601
+ /**
602
+ * Build a client pointed at production (`api.mystars.tg`).
603
+ *
604
+ * @param apiKey - your tenant `faas_…` API key
605
+ * @param opts - any other {@link MyStarsClientOptions} except `apiKey`/`baseUrl`
606
+ * @returns a configured client
607
+ * @example
608
+ * ```ts
609
+ * const client = MyStarsClient.production(process.env.MYSTARS_API_KEY!);
610
+ * ```
611
+ */
612
+ static production(apiKey: string, opts?: Omit<MyStarsClientOptions, "apiKey" | "baseUrl">): MyStarsClient;
613
+ /**
614
+ * `GET /v1/currencies` — the supported on-chain payment currencies.
615
+ *
616
+ * @param opts - optional `signal` to abort the request
617
+ * @returns the supported {@link CurrencyInfo} list
618
+ * @throws `MyStarsApiError` on an API/network failure
619
+ */
620
+ listCurrencies(opts?: RequestOptions): Promise<CurrencyInfo[]>;
621
+ /**
622
+ * `GET /v1/products` — the orderable product catalog (price-free).
623
+ *
624
+ * @param opts - optional `signal` to abort the request
625
+ * @returns the {@link Product} catalog (buyable shapes + limits)
626
+ * @throws `MyStarsApiError` on an API/network failure
627
+ */
628
+ listProducts(opts?: RequestOptions): Promise<Product[]>;
629
+ /**
630
+ * `GET /v1/pricing` — quote the all-in price for an item. Probe-rate-limited (30/min).
631
+ *
632
+ * @param params - `{ type, quantity | months, payment_currency? }`; `payment_currency` defaults server-side
633
+ * @param opts - optional `signal` to abort the request
634
+ * @returns the {@link PricingQuote} — `amount` is the all-in total in `currency`; `fee` is itemized for `usdt_ton`
635
+ * @throws `MyStarsValidationError` if `quantity`/`months` is out of range
636
+ * @throws `RateLimitError` (429) if the probe limit is exceeded
637
+ * @throws `ServiceUnavailableError` (503) if a price source is temporarily down
638
+ * @example
639
+ * ```ts
640
+ * const q = await client.getPricing({ type: "stars", quantity: 500, payment_currency: "ton" });
641
+ * console.log(`pay ${q.amount} ${q.currency}`);
642
+ * ```
643
+ */
644
+ getPricing(params: PricingParams, opts?: RequestOptions): Promise<PricingQuote>;
645
+ /**
646
+ * `POST /v1/recipients/check` — resolve a `@username` and check eligibility before ordering.
647
+ *
648
+ * @param params - `{ type, recipient: { username }, months? }`; the username is canonicalized (strip `@`, lowercase)
649
+ * @param opts - optional `signal` to abort the request
650
+ * @returns the {@link RecipientCheck} — `resolved`/`eligible`, the display `recipient_name`, and a `reason` when ineligible
651
+ * @throws `MyStarsValidationError` if the username is malformed
652
+ * @throws `MyStarsApiError` on an API/network failure
653
+ */
654
+ checkRecipient(params: CheckRecipientParams, opts?: RequestOptions): Promise<RecipientCheck>;
655
+ /**
656
+ * `POST /v1/orders` — create an order. An `Idempotency-Key` is sent once and
657
+ * reused across this call's retries, so a retried create returns the idempotent
658
+ * replay (`replayed: true`) instead of a duplicate.
659
+ *
660
+ * MONEY SAFETY — supply a STABLE key derived from YOUR OWN order id
661
+ * (`{ idempotencyKey: \`order-\${myOrderId}\` }`). The auto-generated uuid only
662
+ * dedupes WITHIN a single `createOrder` call's internal retries; a brand-new
663
+ * call (e.g. your process crashed and re-ran) mints a fresh uuid and can create
664
+ * a SECOND order for the same intent. A caller-stable key makes the create
665
+ * idempotent across process restarts and at-least-once job runners.
666
+ *
667
+ * On failure the thrown {@link MyStarsApiError} carries the `idempotencyKey`
668
+ * that was used — when you can't tell whether the order was created
669
+ * server-side, retry with that exact key (`{ idempotencyKey: err.idempotencyKey }`)
670
+ * to get the idempotent replay rather than a duplicate.
671
+ *
672
+ * @param params - `{ type, recipient: { username }, quantity | months, payment_currency?, callback_url? }`
673
+ * @param opts - optional caller-stable `idempotencyKey` (recommended) and `signal`
674
+ * @returns the {@link CreateOrderResult} — `payment` instruction + `expires_at`; `replayed` is `true` on a 200 idempotent replay
675
+ * @throws `MyStarsValidationError` if inputs are invalid
676
+ * @throws `RecipientIneligibleError` (422) if the recipient cannot receive the item (no order created)
677
+ * @throws `IdempotencyConflictError` (409) if the same key is reused with a different body
678
+ * @throws `MyStarsApiError` on any other API failure (carries the `idempotencyKey` for safe retry)
679
+ * @example
680
+ * ```ts
681
+ * const order = await client.createOrder(
682
+ * { type: "stars", recipient: { username: "durov" }, quantity: 100 },
683
+ * { idempotencyKey: `order-${myOrderId}` },
684
+ * );
685
+ * // pay order.payment.amount → order.payment.pay_to_address, comment = order.payment.memo
686
+ * ```
687
+ */
688
+ createOrder(params: CreateOrderParams, opts?: CreateOrderOptions): Promise<CreateOrderResult>;
689
+ /**
690
+ * `GET /v1/orders/:id` — fetch one order (tenant-scoped). Polling an
691
+ * `awaiting_payment` order also nudges the server's on-demand payment detection.
692
+ *
693
+ * @param orderId - the order UUID
694
+ * @param opts - optional `signal` to abort the request
695
+ * @returns the {@link Order}
696
+ * @throws `NotFoundError` (404) if no such order exists for this tenant
697
+ * @throws `MyStarsApiError` on any other API failure
698
+ */
699
+ getOrder(orderId: string, opts?: RequestOptions): Promise<Order>;
700
+ /**
701
+ * `POST /v1/orders/:id/cancel` — cancel an `awaiting_payment` order.
702
+ *
703
+ * @param orderId - the order UUID
704
+ * @param opts - optional `signal` to abort the request
705
+ * @returns `{ order_id, status: "cancelled" }`
706
+ * @throws `OrderNotCancellableError` (409) if the order is no longer `awaiting_payment` (a retry after a lost success can also surface this even though the cancel committed — re-check with `getOrder`)
707
+ * @throws `MyStarsApiError` on any other API failure
708
+ */
709
+ cancelOrder(orderId: string, opts?: RequestOptions): Promise<{
710
+ order_id: string;
711
+ status: "cancelled";
712
+ }>;
713
+ private listOrdersPage;
714
+ /**
715
+ * `GET /v1/orders` — an auto-paginating view: `for await (const order of client.listOrders())`.
716
+ * The pager owns the cursor; `params.cursor` (if given) is the starting page.
717
+ *
718
+ * @param params - optional `status` filter, `limit` (1-100), and a starting `cursor`
719
+ * @param opts - optional `signal` to abort the underlying page fetches
720
+ * @returns an {@link OrdersPager} (async-iterable; `.pages()` / `.page()` / `.all()`)
721
+ * @example
722
+ * ```ts
723
+ * for await (const order of client.listOrders({ status: "delivered" })) {
724
+ * console.log(order.order_id);
725
+ * }
726
+ * ```
727
+ */
728
+ listOrders(params?: ListOrdersParams, opts?: RequestOptions): OrdersPager;
729
+ /**
730
+ * Poll `GET /v1/orders/:id` until the order is terminal (or the deadline).
731
+ *
732
+ * @param orderId - the order UUID
733
+ * @param options - the {@link WaitForOrderOptions} (intervals, deadline, `until`, `onUpdate`, `signal`)
734
+ * @returns the final {@link Order} once terminal
735
+ * @throws `OrderWaitTimeoutError` if `maxWaitMs` elapses first (carries the last snapshot)
736
+ * @throws `MyStarsApiError` if a poll fails non-transiently
737
+ * @example
738
+ * ```ts
739
+ * const final = await client.waitForOrder(order.order_id, { onUpdate: (o) => console.log(o.status) });
740
+ * ```
741
+ */
742
+ waitForOrder(orderId: string, options?: WaitForOrderOptions): Promise<Order>;
743
+ /**
744
+ * Diff the server's orders against your local store to catch webhook-missed terminal transitions.
745
+ *
746
+ * @param options - the {@link ReconcileOptions}; `isKnown` is required, `since` bounds the scan
747
+ * @returns the missed terminal {@link Order}s, newest-first
748
+ * @throws `MyStarsApiError` if a page fetch fails
749
+ */
750
+ reconcile(options: ReconcileOptions): Promise<Order[]>;
751
+ }
752
+
753
+ /**
754
+ * Webhook signature verification.
755
+ *
756
+ * Reimplements the server's webhook signing byte-for-byte: the `X-Faas-Signature`
757
+ * header is the lowercase-hex HMAC-SHA256 of the RAW request body under the tenant's
758
+ * webhook secret — no timestamp. During a 24h secret rotation the header is two
759
+ * comma-joined signatures (`"<current>,<previous>"`); we split on `,` and
760
+ * constant-time-compare each, so verification holds with EITHER secret.
761
+ *
762
+ * Universal: prefers Web Crypto (`globalThis.crypto.subtle`) so it runs in Deno,
763
+ * Bun, Cloudflare Workers, and browsers; on default Node 18 (where the global is
764
+ * absent) it falls back to `node:crypto`'s `webcrypto`. The verifier is therefore async.
765
+ */
766
+
767
+ /**
768
+ * Verify an `X-Faas-Signature` header against the raw webhook body.
769
+ * Handles the single-signature and the `"current,previous"` rotation forms.
770
+ */
771
+ declare function verifyWebhookSignature(rawBody: string | Uint8Array, signatureHeader: string | null | undefined, secret: string): Promise<boolean>;
772
+ /**
773
+ * Verify the signature, then JSON-parse the body into a typed {@link WebhookEvent}.
774
+ * Throws {@link WebhookSignatureError} on a bad/missing signature or unparseable body.
775
+ *
776
+ * IMPORTANT: pass the RAW request bytes/string (verify before any framework
777
+ * re-serializes the JSON), and dedup on `event.order_id` — delivery is
778
+ * at-least-once and unordered.
779
+ */
780
+ declare function constructEvent(rawBody: string | Uint8Array, signatureHeader: string | null | undefined, secret: string): Promise<WebhookEvent>;
781
+
782
+ /**
783
+ * Drop-in webhook handlers for Express and Fastify.
784
+ *
785
+ * Both verify the `X-Faas-Signature` over the RAW body, parse the event, hand it
786
+ * to your `onEvent`, and reply `2xx` fast (the server needs a 2xx within 5s).
787
+ * They are structurally typed (no `express`/`fastify` runtime dependency), so the
788
+ * SDK stays dependency-free.
789
+ *
790
+ * Dedup is YOUR job — delivery is at-least-once and unordered; key on
791
+ * `event.order_id` (+ `status`).
792
+ */
793
+
794
+ interface ExpressLikeReq {
795
+ headers: Record<string, string | string[] | undefined>;
796
+ body?: unknown;
797
+ rawBody?: unknown;
798
+ }
799
+ interface ExpressLikeRes {
800
+ status(code: number): ExpressLikeRes;
801
+ send(body?: unknown): unknown;
802
+ }
803
+ type ExpressLikeHandler = (req: ExpressLikeReq, res: ExpressLikeRes) => Promise<void>;
804
+ /** Options for {@link expressWebhook} / {@link fastifyWebhook} — the secret, the event handler, and an error hook. */
805
+ interface WebhookMiddlewareOptions<Req = unknown> {
806
+ /** The tenant webhook secret, or a function resolving it per-request (e.g. multi-tenant routing). */
807
+ secret: string | ((req: Req) => string | Promise<string>);
808
+ /** Called with the verified, parsed event. Keep it fast; offload heavy work to a queue. */
809
+ onEvent: (event: WebhookEvent, ctx: {
810
+ rawBody: string;
811
+ req: Req;
812
+ }) => void | Promise<void>;
813
+ /** Optional hook for signature/handler errors (after the response is sent). */
814
+ onError?: (err: unknown, req: Req) => void;
815
+ }
816
+ /**
817
+ * Express handler. REQUIRES the raw body — mount with `express.raw({ type: "*\/*" })`
818
+ * (or any raw-body parser) on the webhook route so `req.body`/`req.rawBody` is a
819
+ * Buffer/string, NOT a pre-parsed object.
820
+ */
821
+ declare function expressWebhook(opts: WebhookMiddlewareOptions<ExpressLikeReq>): ExpressLikeHandler;
822
+ interface FastifyLikeReq {
823
+ headers: Record<string, string | string[] | undefined>;
824
+ body?: unknown;
825
+ rawBody?: unknown;
826
+ }
827
+ interface FastifyLikeReply {
828
+ code(statusCode: number): FastifyLikeReply;
829
+ send(payload?: unknown): unknown;
830
+ }
831
+ type FastifyLikeHandler = (req: FastifyLikeReq, reply: FastifyLikeReply) => Promise<void>;
832
+ /**
833
+ * Fastify handler. REQUIRES the raw body — register a content-type parser that
834
+ * keeps the Buffer (e.g. `addContentTypeParser("application/json", { parseAs: "buffer" }, (req, body, done) => done(null, body))`)
835
+ * so `req.body` is a Buffer/string, not a pre-parsed object.
836
+ */
837
+ declare function fastifyWebhook(opts: WebhookMiddlewareOptions<FastifyLikeReq>): FastifyLikeHandler;
838
+
839
+ /**
840
+ * Retail-markup calculator.
841
+ *
842
+ * Takes our WHOLESALE quote (what you pay us) and computes the price to charge
843
+ * your end-customer after adding your OWN retail margin — and, optionally,
844
+ * passing our processing fee straight through to the customer.
845
+ *
846
+ * Money is handled to the same grid the server uses: USDT to whole cents via the
847
+ * exact two-stage cent-ceil (`ceilUsdToCents`), and TON to the 0.0001-GRAM grid.
848
+ * The cross-language `markup-vectors.json` fixture pins this so the TS and Python
849
+ * SDKs produce identical retail prices.
850
+ *
851
+ * NOTE: our wholesale markup is set server-side and is redacted — this module
852
+ * never sees it. The margin here is purely YOUR retail margin on top of the
853
+ * quoted wholesale amount.
854
+ */
855
+
856
+ /**
857
+ * Ceil a USD(T) amount to whole cents WITHOUT IEEE-754 drift — snap to integer
858
+ * micro-USDT first, then ceil to the cent grid. Byte-identical to the server's
859
+ * `ceilUsdToCents` (web checkout + FaaS `/v1/pricing` land on the same cent).
860
+ *
861
+ * @example ceilUsdToCents(0.06) // 0.06 (naive Math.ceil(x*100)/100 returns 0.07)
862
+ * @example ceilUsdToCents(3.3461) // 3.35
863
+ */
864
+ declare function ceilUsdToCents(usd: number): number;
865
+ /** Ceil a TON amount to the 0.0001-GRAM grid (snap to integer nanoTON first). */
866
+ declare function ceilTonTo4dp(ton: number): number;
867
+ /** The wholesale quote a retail markup is applied to (a `PricingQuote` or a `PaymentInstruction` both fit). */
868
+ interface MarkupInput {
869
+ amount: string;
870
+ currency: Currency;
871
+ fee: FeeBreakdown | null;
872
+ }
873
+ /** Your retail-margin configuration for {@link applyRetailMarkup}. */
874
+ interface RetailMarkupConfig {
875
+ /** Your retail margin, in percent, applied to the goods value (e.g. 12.5 for +12.5%). */
876
+ marginPct: number;
877
+ /**
878
+ * When true (default), our processing fee (`usdt_ton` only) is added to the
879
+ * customer total as a separate line — the customer pays it, you remit it to us,
880
+ * and it doesn't eat your margin. When false, you absorb the fee out of your margin.
881
+ */
882
+ passThroughProcessingFee?: boolean;
883
+ }
884
+ /** One labelled line of a {@link RetailQuote} breakdown (label + decimal-string amount). */
885
+ interface RetailLineItem {
886
+ label: string;
887
+ amount: string;
888
+ }
889
+ /** The customer-facing breakdown returned by {@link applyRetailMarkup}. All amounts are decimal strings. */
890
+ interface RetailQuote {
891
+ currency: Currency;
892
+ /** What you pay us (the wholesale quote amount). */
893
+ wholesaleAmount: string;
894
+ marginPct: number;
895
+ /** The base goods value before your margin (the fee's `subtotal` for usdt_ton, else `amount`). */
896
+ goods: string;
897
+ /** Your added margin (subtotal − goods). */
898
+ markup: string;
899
+ /** Marked-up goods (goods + markup). */
900
+ subtotal: string;
901
+ /** Passed-through processing fee (usdt_ton + passThrough), else "0". */
902
+ processingFee: string;
903
+ /** What to charge your customer (subtotal + processingFee). */
904
+ total: string;
905
+ /** Your gross margin on the sale (total − wholesaleAmount). */
906
+ profit: string;
907
+ lineItems: RetailLineItem[];
908
+ }
909
+ /**
910
+ * Apply your retail margin to a wholesale quote and return an itemized
911
+ * customer-facing breakdown.
912
+ */
913
+ declare function applyRetailMarkup(input: MarkupInput, config: RetailMarkupConfig): RetailQuote;
914
+
915
+ /**
916
+ * Non-custodial invoice builder.
917
+ *
918
+ * Turns an order's `payment` block into things you can pay with: smallest-unit
919
+ * amounts, a `ton://transfer` deeplink, a Tonkeeper link, a QR payload, and TON
920
+ * Connect message(s). This module holds NO keys and signs nothing — feed the
921
+ * output to a wallet / TON Connect, or to `@mystars-tg/faas-wallet` to broadcast.
922
+ */
923
+
924
+ /** Message value (nanoTON) attached to a USDT jetton transfer to cover its gas. */
925
+ declare const JETTON_TRANSFER_GAS_NANO = "50000000";
926
+ /**
927
+ * Convert a non-negative decimal string to integer smallest units (half-up),
928
+ * without IEEE-754 drift. A leading `-` is rejected — a payment amount is never
929
+ * negative, and a signed value would build a nonsensical (or zero) transfer.
930
+ */
931
+ declare function decimalToUnits(amount: string, decimals: number): bigint;
932
+ /** Decimal TON string → nanoTON. */
933
+ declare function toNano(amount: string): bigint;
934
+ /** Decimal USDT string → micro-USDT. */
935
+ declare function toMicro(amount: string): bigint;
936
+ /** One TON Connect `messages[]` entry — feed to `tonConnectUI.sendTransaction({ messages })`. */
937
+ interface TonConnectMessage {
938
+ address: string;
939
+ /** nanoTON, as a string (TON Connect's expected form). */
940
+ amount: string;
941
+ /** base64 BoC payload. */
942
+ payload?: string;
943
+ }
944
+ /** Options for the invoice builders — only needed to produce a signable USDT (jetton) message. */
945
+ interface BuildInvoiceOptions {
946
+ /** The payer's wallet address (raw or friendly) — required to build a USDT jetton message. */
947
+ senderAddress?: string;
948
+ /** The payer's OWN USDT jetton wallet address — required to build a USDT TON Connect message. */
949
+ jettonWalletAddress?: string;
950
+ /** Validity window for the TON Connect transaction, in seconds. Default 600. */
951
+ validForSeconds?: number;
952
+ /** Stamp for `valid_until` (epoch ms). Pass `Date.now()` — kept injectable for determinism. */
953
+ now?: number;
954
+ }
955
+ /** The aggregated payable artifacts for an order, returned by {@link buildPaymentRequest}. */
956
+ interface PaymentRequest {
957
+ currency: PaymentInstruction["currency"];
958
+ payToAddress: string;
959
+ memo: string;
960
+ amountUnits: "ton" | "usdt";
961
+ /** nanoTON (ton) or micro-USDT (usdt_ton), as a string. */
962
+ amountSmallestUnit: string;
963
+ /** `ton://transfer/...` — TON only (a USDT jetton transfer has no plain deeplink). */
964
+ tonDeeplink?: string;
965
+ /** `https://app.tonkeeper.com/transfer/...` — TON only. */
966
+ tonkeeperLink?: string;
967
+ /** A URI to render as a QR code — TON only. */
968
+ qrPayload?: string;
969
+ /** TON Connect `messages` array (USDT requires `senderAddress` + `jettonWalletAddress`). */
970
+ tonConnect: TonConnectMessage[];
971
+ /** Set when a field couldn't be produced (e.g. USDT without a resolved jetton wallet). */
972
+ note?: string;
973
+ }
974
+ /**
975
+ * Build TON Connect message(s) for the payment. Throws for a USDT payment unless
976
+ * `senderAddress` + `jettonWalletAddress` are supplied (a jetton message must be
977
+ * sent to the payer's own jetton wallet).
978
+ */
979
+ declare function buildTonConnectMessages(payment: PaymentInstruction, opts?: BuildInvoiceOptions): TonConnectMessage[];
980
+ /** Build a `ton://transfer` deeplink. TON only — throws for USDT. */
981
+ declare function buildTonDeeplink(payment: PaymentInstruction): string;
982
+ /**
983
+ * Aggregate everything you can pay the order with. Best-effort: TON yields the
984
+ * full set; USDT yields the smallest-unit amount + TON Connect message only when
985
+ * `senderAddress` + `jettonWalletAddress` are supplied (otherwise `note` explains).
986
+ */
987
+ declare function buildPaymentRequest(payment: PaymentInstruction, opts?: BuildInvoiceOptions): PaymentRequest;
988
+
989
+ /** Build the op-0 text-comment payload for `comment`, as a base64 BoC. */
990
+ declare function buildCommentPayload(comment: string): string;
991
+
992
+ /** `forward_ton_amount` carried inside the transfer (0 — the memo still survives in internal_transfer). */
993
+ declare const FORWARD_TON_AMOUNT_NANO: bigint;
994
+ /** Jetton transfer opcode (TEP-74). */
995
+ declare const JETTON_TRANSFER_OP = 260734629;
996
+ /** Parse a TON address (friendly base64url or raw `wc:hex`) into workchain + 32-byte hash. Throws on a bad checksum/shape. */
997
+ declare function parseTonAddress(address: string): {
998
+ workchain: number;
999
+ hash: Uint8Array;
1000
+ };
1001
+ /**
1002
+ * Build the TEP-74 jetton transfer BoC payload (base64).
1003
+ *
1004
+ * @param amountMicro - jetton amount in micro-units (e.g. "4990000" for 4.99 USDT)
1005
+ * @param destination - the recipient OWNER address (the FaaS `pay_to_address`), raw or friendly
1006
+ * @param sender - the payer's wallet address (`response_destination`), raw or friendly
1007
+ * @param memo - the bare order UUID (op-0 comment in the forward payload)
1008
+ */
1009
+ declare function buildJettonTransferPayload(amountMicro: string | bigint, destination: string, sender: string, memo: string): string;
1010
+
1011
+ /**
1012
+ * Lightweight client-side validation that mirrors the server's documented
1013
+ * constraints (and the `GET /v1/products` catalog), so common mistakes fail
1014
+ * fast without a round trip. These constants track the pinned CONTRACT_VERSION.
1015
+ */
1016
+
1017
+ /** Minimum Stars quantity a single order can buy (inclusive). */
1018
+ declare const STARS_MIN_QUANTITY = 50;
1019
+ /** Maximum Stars quantity a single order can buy (inclusive). */
1020
+ declare const STARS_MAX_QUANTITY = 1000000;
1021
+ /** The allowed Telegram Premium subscription lengths, in months. */
1022
+ declare const PREMIUM_MONTHS: readonly number[];
1023
+ /** Thrown for invalid input caught before any HTTP request is made. */
1024
+ declare class MyStarsValidationError extends MyStarsError {
1025
+ }
1026
+ /** Canonicalize a Telegram username the same way the server does: strip a leading `@`, lowercase. */
1027
+ declare function canonicalUsername(input: string): string;
1028
+
1029
+ /**
1030
+ * The MyStars FaaS API contract version this SDK was built and verified against.
1031
+ *
1032
+ * Bumped in lockstep with the SDK's contract fixtures; CI fails the build on drift.
1033
+ */
1034
+ declare const CONTRACT_VERSION = "1.9.0";
1035
+ /** This SDK's own semantic version (decoupled from the API contract version). */
1036
+ declare const SDK_VERSION = "0.1.2";
1037
+
1038
+ export { BadRequestError, type BuildInvoiceOptions, CANCELLABLE_STATUSES, CONTRACT_VERSION, type CheckRecipientParams, ConflictError, type CreateOrderOptions, type CreateOrderParams, type CreateOrderResult, type Currency, type CurrencyInfo, FORWARD_TON_AMOUNT_NANO, type FailureReason, type FeeBreakdown, type FetchOrdersPage, ForbiddenError, INITIAL_ORDER_STATUS, IdempotencyConflictError, type Interceptors, InternalServerError, JETTON_TRANSFER_GAS_NANO, JETTON_TRANSFER_OP, type ListOrdersParams, type MarkupInput, MyStarsApiError, MyStarsClient, type MyStarsClientOptions, MyStarsError, MyStarsValidationError, NetworkError, NotFoundError, type Order, OrderNotCancellableError, type OrderStatus, type OrderType, OrderWaitTimeoutError, type OrdersPage, OrdersPager, PREMIUM_MONTHS, PRODUCTION_BASE_URL, type Parameter, type PaymentInstruction, type PaymentRequest, type PricingParams, type PricingQuote, type Product, RateLimitError, type RateLimitKind, type RecipientCheck, type RecipientCheckReason, RecipientIneligibleError, type ReconcileClient, type ReconcileOptions, type RequestLogInfo, type RequestOptions, type ResponseLogInfo, type RetailLineItem, type RetailMarkupConfig, type RetailQuote, type RetryContext, type RetryLogInfo, type RetryPolicy, SDK_VERSION, STARS_MAX_QUANTITY, STARS_MIN_QUANTITY, ServiceUnavailableError, TERMINAL_STATUSES, type TerminalStatus, TimeoutError, type TonConnectMessage, UnauthorizedError, WEBHOOK_TERMINAL, type WaitForOrderOptions, type WebhookEvent, type WebhookMiddlewareOptions, WebhookSignatureError, type WebhookStatus, applyRetailMarkup, buildCommentPayload, buildJettonTransferPayload, buildPaymentRequest, buildTonConnectMessages, buildTonDeeplink, canonicalUsername, ceilTonTo4dp, ceilUsdToCents, constructEvent, decimalToUnits, defaultShouldRetry, errorFromResponse, expressWebhook, fastifyWebhook, isTerminal, parseRetryAfterMs, parseTonAddress, reconcile, toMicro, toNano, verifyWebhookSignature };