@relai-fi/subscriptions-react 0.1.0

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.
package/dist/index.js ADDED
@@ -0,0 +1,438 @@
1
+ import { createContext, useMemo, useContext, useState, useEffect, useCallback, useRef } from 'react';
2
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
+
4
+ // src/context.tsx
5
+
6
+ // src/client.ts
7
+ var DEFAULT_BASE_URL = "https://api.relai.fi";
8
+ var RelaiApiError = class extends Error {
9
+ constructor(status, message, body) {
10
+ super(message);
11
+ this.status = status;
12
+ this.body = body;
13
+ this.name = "RelaiApiError";
14
+ }
15
+ };
16
+ function createRelaiClient(opts = {}) {
17
+ const baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
18
+ const _fetch = opts.fetch ?? globalThis.fetch;
19
+ if (!_fetch) throw new Error("No fetch available \u2014 pass `fetch` in options.");
20
+ async function request(method, path, init = {}) {
21
+ const url = new URL(baseUrl + path);
22
+ for (const [k, v] of Object.entries(init.query ?? {})) url.searchParams.set(k, v);
23
+ const res = await _fetch(url.toString(), {
24
+ method,
25
+ headers: { "Content-Type": "application/json" },
26
+ body: init.body === void 0 ? void 0 : JSON.stringify(init.body)
27
+ });
28
+ const text = await res.text();
29
+ let json;
30
+ try {
31
+ json = text ? JSON.parse(text) : void 0;
32
+ } catch {
33
+ json = text;
34
+ }
35
+ if (!res.ok) {
36
+ const msg = json?.error ?? `RelAI ${method} ${path} failed (${res.status})`;
37
+ throw new RelaiApiError(res.status, msg, json ?? text);
38
+ }
39
+ return json;
40
+ }
41
+ const enc = encodeURIComponent;
42
+ return {
43
+ baseUrl,
44
+ meta: (planId) => request("GET", `/s/${enc(planId)}/meta`),
45
+ status: (planId, wallet) => request("GET", `/s/${enc(planId)}/status`, { query: { wallet } }),
46
+ prepareSubscribe: (planId, wallet) => request("POST", `/s/${enc(planId)}/subscribe`, {
47
+ body: { subscriberWallet: wallet }
48
+ }),
49
+ confirmSubscribe: async (planId, wallet, signature) => {
50
+ const { subscription } = await request(
51
+ "POST",
52
+ `/s/${enc(planId)}/confirm`,
53
+ { body: { subscriberWallet: wallet, signature } }
54
+ );
55
+ return subscription;
56
+ }
57
+ };
58
+ }
59
+ var RelaiContext = createContext(null);
60
+ function RelaiProvider({
61
+ client,
62
+ baseUrl,
63
+ wallet,
64
+ signAndSend,
65
+ theme,
66
+ children
67
+ }) {
68
+ const value = useMemo(
69
+ () => ({
70
+ client: client ?? createRelaiClient({ baseUrl }),
71
+ wallet,
72
+ signAndSend,
73
+ theme
74
+ }),
75
+ [client, baseUrl, wallet, signAndSend, theme]
76
+ );
77
+ return /* @__PURE__ */ jsx(RelaiContext.Provider, { value, children });
78
+ }
79
+ function useRelaiContext() {
80
+ return useContext(RelaiContext);
81
+ }
82
+ function useRelaiResolved(opts = {}) {
83
+ const ctx = useRelaiContext();
84
+ return useMemo(() => {
85
+ const client = opts.client ?? ctx?.client ?? createRelaiClient({ baseUrl: opts.baseUrl });
86
+ return {
87
+ client,
88
+ wallet: opts.wallet ?? ctx?.wallet,
89
+ signAndSend: opts.signAndSend ?? ctx?.signAndSend
90
+ };
91
+ }, [opts.client, opts.baseUrl, opts.wallet, opts.signAndSend, ctx]);
92
+ }
93
+ function usePlanMeta(planId, client) {
94
+ const [state, setState] = useState({ loading: true });
95
+ useEffect(() => {
96
+ let alive = true;
97
+ setState({ loading: true });
98
+ client.meta(planId).then((plan) => alive && setState({ plan, loading: false })).catch((error) => alive && setState({ loading: false, error }));
99
+ return () => {
100
+ alive = false;
101
+ };
102
+ }, [planId, client]);
103
+ return state;
104
+ }
105
+ function useSubscriptionStatus(planId, wallet, client) {
106
+ const [state, setState] = useState({
107
+ loading: false
108
+ });
109
+ const [nonce, setNonce] = useState(0);
110
+ const refetch = useCallback(() => setNonce((n) => n + 1), []);
111
+ useEffect(() => {
112
+ if (!wallet) {
113
+ setState({ loading: false, status: void 0 });
114
+ return;
115
+ }
116
+ let alive = true;
117
+ setState({ loading: true });
118
+ client.status(planId, wallet).then((status) => alive && setState({ status, loading: false })).catch((error) => alive && setState({ loading: false, error }));
119
+ return () => {
120
+ alive = false;
121
+ };
122
+ }, [planId, wallet, client, nonce]);
123
+ return { ...state, refetch };
124
+ }
125
+ function useSubscribe(opts) {
126
+ const { client, wallet, signAndSend } = useRelaiResolved(opts);
127
+ const [state, setState] = useState("idle");
128
+ const [error, setError] = useState();
129
+ const [subscription, setSubscription] = useState();
130
+ const running = useRef(false);
131
+ const reset = useCallback(() => {
132
+ setState("idle");
133
+ setError(void 0);
134
+ setSubscription(void 0);
135
+ }, []);
136
+ const subscribe = useCallback(async () => {
137
+ if (running.current) return void 0;
138
+ if (!wallet) {
139
+ const err = new Error("No wallet \u2014 connect a wallet before subscribing.");
140
+ setError(err);
141
+ setState("error");
142
+ opts.onError?.(err);
143
+ return void 0;
144
+ }
145
+ if (!signAndSend) {
146
+ const err = new Error("No signAndSend \u2014 pass one (see @relai-fi/subscriptions-react/wallet).");
147
+ setError(err);
148
+ setState("error");
149
+ opts.onError?.(err);
150
+ return void 0;
151
+ }
152
+ running.current = true;
153
+ setError(void 0);
154
+ try {
155
+ setState("preparing");
156
+ let prep = await client.prepareSubscribe(opts.planId, wallet);
157
+ if (prep.stage === "init-authority") {
158
+ setState("signing");
159
+ await signAndSend(prep.wireTransaction);
160
+ setState("preparing");
161
+ prep = await client.prepareSubscribe(opts.planId, wallet);
162
+ }
163
+ setState("signing");
164
+ const sig = await signAndSend(prep.wireTransaction);
165
+ setState("confirming");
166
+ const sub = await client.confirmSubscribe(opts.planId, wallet, sig);
167
+ setSubscription(sub);
168
+ setState("done");
169
+ opts.onSubscribed?.(sub);
170
+ return sub;
171
+ } catch (e) {
172
+ const err = e instanceof Error ? e : new Error(String(e));
173
+ setError(err);
174
+ setState("error");
175
+ opts.onError?.(err);
176
+ return void 0;
177
+ } finally {
178
+ running.current = false;
179
+ }
180
+ }, [client, wallet, signAndSend, opts]);
181
+ const busy = state === "preparing" || state === "signing" || state === "confirming";
182
+ return { subscribe, state, error, subscription, busy, reset };
183
+ }
184
+ var DEFAULT_LABELS = {
185
+ subscribe: "Subscribe",
186
+ connect: "Connect wallet",
187
+ preparing: "Preparing\u2026",
188
+ signing: "Confirm in wallet\u2026",
189
+ confirming: "Activating\u2026",
190
+ active: "Subscribed",
191
+ retry: "Try again"
192
+ };
193
+ function SubscribeButton(props) {
194
+ const { planId, onConnectWallet, hideStatus, className, style, children } = props;
195
+ const labels = { ...DEFAULT_LABELS, ...props.labels };
196
+ const { client, wallet } = useRelaiResolved(props);
197
+ const { status, refetch } = useSubscriptionStatus(
198
+ planId,
199
+ hideStatus ? void 0 : wallet,
200
+ client
201
+ );
202
+ const { subscribe, state, busy } = useSubscribe({
203
+ ...props,
204
+ onSubscribed: (sub) => {
205
+ refetch();
206
+ props.onSubscribed?.(sub);
207
+ }
208
+ });
209
+ const alreadyActive = !hideStatus && status?.active === true;
210
+ let label;
211
+ let disabled = false;
212
+ let dataState = state;
213
+ if (alreadyActive || state === "done") {
214
+ label = labels.active;
215
+ disabled = true;
216
+ dataState = "done";
217
+ } else if (!wallet) {
218
+ label = labels.connect;
219
+ disabled = !onConnectWallet;
220
+ } else if (state === "preparing") {
221
+ label = labels.preparing;
222
+ disabled = true;
223
+ } else if (state === "signing") {
224
+ label = labels.signing;
225
+ disabled = true;
226
+ } else if (state === "confirming") {
227
+ label = labels.confirming;
228
+ disabled = true;
229
+ } else if (state === "error") {
230
+ label = labels.retry;
231
+ } else {
232
+ label = labels.subscribe;
233
+ }
234
+ const onClick = () => {
235
+ if (busy || alreadyActive) return;
236
+ if (!wallet) {
237
+ onConnectWallet?.();
238
+ return;
239
+ }
240
+ void subscribe();
241
+ };
242
+ return /* @__PURE__ */ jsxs(
243
+ "button",
244
+ {
245
+ type: "button",
246
+ className: ["relai-btn", className].filter(Boolean).join(" "),
247
+ style,
248
+ "data-state": dataState,
249
+ "data-busy": busy ? "true" : void 0,
250
+ "aria-busy": busy || void 0,
251
+ disabled,
252
+ onClick,
253
+ children: [
254
+ busy && /* @__PURE__ */ jsx("span", { className: "relai-spinner", "aria-hidden": "true" }),
255
+ children ?? label
256
+ ]
257
+ }
258
+ );
259
+ }
260
+
261
+ // src/format.ts
262
+ var USDC_DECIMALS = 6;
263
+ function formatUsdc(amountBaseUnits, opts = {}) {
264
+ const decimals = opts.decimals ?? USDC_DECIMALS;
265
+ const n = Number(amountBaseUnits) / 10 ** decimals;
266
+ const dp = Number.isInteger(n * 100) ? 2 : Math.min(decimals, 6);
267
+ const s = n.toLocaleString(void 0, { minimumFractionDigits: 2, maximumFractionDigits: dp });
268
+ return opts.withTicker ? `${s} USDC` : s;
269
+ }
270
+ function formatPeriodSuffix(periodHours) {
271
+ switch (periodHours) {
272
+ case 1:
273
+ return "/hr";
274
+ case 24:
275
+ return "/day";
276
+ case 168:
277
+ return "/wk";
278
+ case 720:
279
+ return "/mo";
280
+ case 8760:
281
+ return "/yr";
282
+ default:
283
+ return periodHours % 24 === 0 ? `/${periodHours / 24}d` : `/${periodHours}h`;
284
+ }
285
+ }
286
+ function formatPeriodLabel(periodHours) {
287
+ const map = {
288
+ 1: "hourly",
289
+ 24: "daily",
290
+ 168: "weekly",
291
+ 720: "monthly",
292
+ 8760: "yearly"
293
+ };
294
+ const known = map[periodHours];
295
+ if (known) return `Billed ${known}`;
296
+ if (periodHours % 24 === 0) return `Billed every ${periodHours / 24} days`;
297
+ return `Billed every ${periodHours} hours`;
298
+ }
299
+ function formatNetwork(network) {
300
+ return network === "solana-devnet" ? "Solana devnet" : "Solana";
301
+ }
302
+ var Check = () => /* @__PURE__ */ jsx("svg", { className: "relai-check", viewBox: "0 0 20 20", width: "18", height: "18", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
303
+ "path",
304
+ {
305
+ d: "M16.7 5.3a1 1 0 0 1 0 1.4l-7.5 7.5a1 1 0 0 1-1.4 0L3.3 9.7a1 1 0 1 1 1.4-1.4l3.3 3.29 6.8-6.8a1 1 0 0 1 1.4 0Z",
306
+ fill: "currentColor"
307
+ }
308
+ ) });
309
+ function PricingCard(props) {
310
+ const { plan, features, description, highlight, hideNetwork, className, style } = props;
311
+ const badge = props.badge ?? (highlight ? "Popular" : void 0);
312
+ return /* @__PURE__ */ jsxs(
313
+ "div",
314
+ {
315
+ className: ["relai-card", highlight ? "relai-card--highlight" : "", className].filter(Boolean).join(" "),
316
+ style,
317
+ "data-plan": plan.planId,
318
+ children: [
319
+ badge && /* @__PURE__ */ jsx("span", { className: "relai-badge", children: badge }),
320
+ /* @__PURE__ */ jsxs("div", { className: "relai-card__head", children: [
321
+ /* @__PURE__ */ jsx("h3", { className: "relai-card__name", children: plan.name }),
322
+ description && /* @__PURE__ */ jsx("p", { className: "relai-card__desc", children: description })
323
+ ] }),
324
+ /* @__PURE__ */ jsxs("div", { className: "relai-price", children: [
325
+ /* @__PURE__ */ jsx("span", { className: "relai-price__amount", children: formatUsdc(plan.amountBaseUnits) }),
326
+ /* @__PURE__ */ jsxs("span", { className: "relai-price__unit", children: [
327
+ /* @__PURE__ */ jsx("span", { className: "relai-price__ticker", children: "USDC" }),
328
+ /* @__PURE__ */ jsx("span", { className: "relai-price__period", children: formatPeriodSuffix(plan.periodHours) })
329
+ ] })
330
+ ] }),
331
+ /* @__PURE__ */ jsxs("p", { className: "relai-price__caption", children: [
332
+ formatPeriodLabel(plan.periodHours),
333
+ !hideNetwork && /* @__PURE__ */ jsxs(Fragment, { children: [
334
+ " \xB7 ",
335
+ formatNetwork(plan.network)
336
+ ] })
337
+ ] }),
338
+ /* @__PURE__ */ jsx(
339
+ SubscribeButton,
340
+ {
341
+ planId: plan.planId,
342
+ client: props.client,
343
+ baseUrl: props.baseUrl,
344
+ wallet: props.wallet,
345
+ signAndSend: props.signAndSend,
346
+ onConnectWallet: props.onConnectWallet,
347
+ onSubscribed: props.onSubscribed,
348
+ onError: props.onError,
349
+ labels: props.labels
350
+ }
351
+ ),
352
+ features && features.length > 0 && /* @__PURE__ */ jsx("ul", { className: "relai-features", children: features.map((f, i) => /* @__PURE__ */ jsxs("li", { className: "relai-features__item", children: [
353
+ /* @__PURE__ */ jsx(Check, {}),
354
+ /* @__PURE__ */ jsx("span", { children: f })
355
+ ] }, i)) })
356
+ ]
357
+ }
358
+ );
359
+ }
360
+
361
+ // src/theme.ts
362
+ function themeToCssVars(theme) {
363
+ if (!theme) return {};
364
+ const v = {};
365
+ if (theme.primary) v["--relai-primary"] = theme.primary;
366
+ if (theme.primaryForeground) v["--relai-primary-fg"] = theme.primaryForeground;
367
+ if (theme.background) v["--relai-bg"] = theme.background;
368
+ if (theme.card) v["--relai-card"] = theme.card;
369
+ if (theme.foreground) v["--relai-fg"] = theme.foreground;
370
+ if (theme.mutedForeground) v["--relai-muted-fg"] = theme.mutedForeground;
371
+ if (theme.border) v["--relai-border"] = theme.border;
372
+ if (theme.radius) v["--relai-radius"] = theme.radius;
373
+ return v;
374
+ }
375
+ function useManyPlanMetas(ids, preloaded, client) {
376
+ const key = ids.join(",");
377
+ const [state, setState] = useState(
378
+ () => ({ plans: preloaded ?? [], loading: !preloaded })
379
+ );
380
+ useEffect(() => {
381
+ if (preloaded) {
382
+ setState({ plans: preloaded, loading: false });
383
+ return;
384
+ }
385
+ if (ids.length === 0) {
386
+ setState({ plans: [], loading: false });
387
+ return;
388
+ }
389
+ let alive = true;
390
+ setState({ plans: [], loading: true });
391
+ Promise.all(ids.map((id) => client.meta(id))).then((plans) => alive && setState({ plans, loading: false })).catch((error) => alive && setState({ plans: [], loading: false, error }));
392
+ return () => {
393
+ alive = false;
394
+ };
395
+ }, [key, preloaded, client]);
396
+ return state;
397
+ }
398
+ function PricingTable(props) {
399
+ const { client, wallet, signAndSend } = useRelaiResolved(props);
400
+ const ctx = useRelaiContext();
401
+ const theme = props.theme ?? ctx?.theme;
402
+ const ids = props.plans ? props.plans.map((p) => p.planId) : props.planIds ?? (props.planId ? [props.planId] : []);
403
+ const { plans, loading, error } = useManyPlanMetas(ids, props.plans, client);
404
+ const rootStyle = { ...themeToCssVars(theme), ...props.style };
405
+ const rootClass = ["relai-root", "relai-table", props.className].filter(Boolean).join(" ");
406
+ if (loading) {
407
+ return /* @__PURE__ */ jsx("div", { className: rootClass, style: rootStyle, children: /* @__PURE__ */ jsx("div", { className: "relai-state relai-state--loading", children: props.loadingText ?? "Loading plans\u2026" }) });
408
+ }
409
+ if (error) {
410
+ return /* @__PURE__ */ jsx("div", { className: rootClass, style: rootStyle, children: /* @__PURE__ */ jsx("div", { className: "relai-state relai-state--error", children: props.errorText ?? "Couldn't load plans." }) });
411
+ }
412
+ return /* @__PURE__ */ jsx("div", { className: rootClass, style: rootStyle, "data-count": plans.length, children: plans.map((plan) => {
413
+ const cfg = props.cards?.[plan.planId];
414
+ const highlight = cfg?.highlight ?? props.highlightPlanId === plan.planId;
415
+ return /* @__PURE__ */ jsx(
416
+ PricingCard,
417
+ {
418
+ plan,
419
+ features: cfg?.features,
420
+ description: cfg?.description,
421
+ highlight,
422
+ badge: cfg?.badge,
423
+ client,
424
+ wallet,
425
+ signAndSend,
426
+ onConnectWallet: props.onConnectWallet,
427
+ onSubscribed: props.onSubscribed,
428
+ onError: props.onError,
429
+ labels: props.labels
430
+ },
431
+ plan.planId
432
+ );
433
+ }) });
434
+ }
435
+
436
+ export { DEFAULT_BASE_URL, PricingCard, PricingTable, RelaiApiError, RelaiProvider, SubscribeButton, createRelaiClient, formatNetwork, formatPeriodLabel, formatPeriodSuffix, formatUsdc, themeToCssVars, usePlanMeta, useRelaiContext, useRelaiResolved, useSubscribe, useSubscriptionStatus };
437
+ //# sourceMappingURL=index.js.map
438
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client.ts","../src/context.tsx","../src/hooks.ts","../src/components/SubscribeButton.tsx","../src/format.ts","../src/components/PricingCard.tsx","../src/theme.ts","../src/components/PricingTable.tsx"],"names":["useMemo","jsx","jsxs","useState","useEffect"],"mappings":"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","file":"index.js","sourcesContent":["import type {\n PlanMeta,\n PrepareSubscribeResult,\n Subscription,\n SubscriptionStatusResult,\n} from \"./types.js\";\n\nexport const DEFAULT_BASE_URL = \"https://api.relai.fi\";\n\nexport class RelaiApiError extends Error {\n constructor(\n public readonly status: number,\n message: string,\n public readonly body?: unknown,\n ) {\n super(message);\n this.name = \"RelaiApiError\";\n }\n}\n\nexport interface RelaiClientOptions {\n /** API base URL. Defaults to https://api.relai.fi */\n baseUrl?: string;\n /** Custom fetch (defaults to global fetch). */\n fetch?: typeof fetch;\n}\n\n/** Browser-safe client for the public RelAI subscriptions endpoints. No API key. */\nexport interface RelaiClient {\n readonly baseUrl: string;\n /** Public plan terms for a pricing card. */\n meta(planId: string): Promise<PlanMeta>;\n /** Is `wallet` actively subscribed to `planId`? */\n status(planId: string, wallet: string): Promise<SubscriptionStatusResult>;\n /** Build the next unsigned subscribe transaction (two-stage). */\n prepareSubscribe(planId: string, wallet: string): Promise<PrepareSubscribeResult>;\n /** Confirm the subscription after the signed tx is broadcast. */\n confirmSubscribe(planId: string, wallet: string, signature: string): Promise<Subscription>;\n}\n\nexport function createRelaiClient(opts: RelaiClientOptions = {}): RelaiClient {\n const baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, \"\");\n const _fetch = opts.fetch ?? globalThis.fetch;\n if (!_fetch) throw new Error(\"No fetch available — pass `fetch` in options.\");\n\n async function request<T>(\n method: string,\n path: string,\n init: { body?: unknown; query?: Record<string, string> } = {},\n ): Promise<T> {\n const url = new URL(baseUrl + path);\n for (const [k, v] of Object.entries(init.query ?? {})) url.searchParams.set(k, v);\n const res = await _fetch(url.toString(), {\n method,\n headers: { \"Content-Type\": \"application/json\" },\n body: init.body === undefined ? undefined : JSON.stringify(init.body),\n });\n const text = await res.text();\n let json: unknown;\n try {\n json = text ? JSON.parse(text) : undefined;\n } catch {\n json = text;\n }\n if (!res.ok) {\n const msg = (json as { error?: string })?.error ?? `RelAI ${method} ${path} failed (${res.status})`;\n throw new RelaiApiError(res.status, msg, json ?? text);\n }\n return json as T;\n }\n\n const enc = encodeURIComponent;\n\n return {\n baseUrl,\n meta: (planId) => request<PlanMeta>(\"GET\", `/s/${enc(planId)}/meta`),\n status: (planId, wallet) =>\n request<SubscriptionStatusResult>(\"GET\", `/s/${enc(planId)}/status`, { query: { wallet } }),\n prepareSubscribe: (planId, wallet) =>\n request<PrepareSubscribeResult>(\"POST\", `/s/${enc(planId)}/subscribe`, {\n body: { subscriberWallet: wallet },\n }),\n confirmSubscribe: async (planId, wallet, signature) => {\n const { subscription } = await request<{ subscription: Subscription }>(\n \"POST\",\n `/s/${enc(planId)}/confirm`,\n { body: { subscriberWallet: wallet, signature } },\n );\n return subscription;\n },\n };\n}\n","import { createContext, useContext, useMemo } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { createRelaiClient } from \"./client.js\";\nimport type { RelaiClient } from \"./client.js\";\nimport type { RelaiTheme, SignAndSend } from \"./types.js\";\n\nexport interface RelaiContextValue {\n client: RelaiClient;\n /** The connected subscriber's wallet address (base58), if any. */\n wallet?: string;\n /** Signs + broadcasts a base64 wire tx → signature. */\n signAndSend?: SignAndSend;\n theme?: RelaiTheme;\n}\n\nconst RelaiContext = createContext<RelaiContextValue | null>(null);\n\nexport interface RelaiProviderProps {\n /** Provide a client, or `baseUrl` to build the default one. */\n client?: RelaiClient;\n baseUrl?: string;\n wallet?: string;\n signAndSend?: SignAndSend;\n theme?: RelaiTheme;\n children: ReactNode;\n}\n\n/**\n * Optional. Sets the RelAI client, wallet, signAndSend and theme for every\n * component below it. Individual components also accept these as props (props win).\n */\nexport function RelaiProvider({\n client,\n baseUrl,\n wallet,\n signAndSend,\n theme,\n children,\n}: RelaiProviderProps) {\n const value = useMemo<RelaiContextValue>(\n () => ({\n client: client ?? createRelaiClient({ baseUrl }),\n wallet,\n signAndSend,\n theme,\n }),\n [client, baseUrl, wallet, signAndSend, theme],\n );\n return <RelaiContext.Provider value={value}>{children}</RelaiContext.Provider>;\n}\n\n/** Read the RelAI context (returns null outside a provider). */\nexport function useRelaiContext(): RelaiContextValue | null {\n return useContext(RelaiContext);\n}\n","import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { createRelaiClient } from \"./client.js\";\nimport type { RelaiClient } from \"./client.js\";\nimport { useRelaiContext } from \"./context.js\";\nimport type {\n PlanMeta,\n SignAndSend,\n Subscription,\n SubscriptionStatusResult,\n} from \"./types.js\";\n\nexport interface RelaiResolvedOptions {\n client?: RelaiClient;\n baseUrl?: string;\n wallet?: string;\n signAndSend?: SignAndSend;\n}\n\nexport interface RelaiResolved {\n client: RelaiClient;\n wallet?: string;\n signAndSend?: SignAndSend;\n}\n\n/** Merge per-component props over the provider context (props win). */\nexport function useRelaiResolved(opts: RelaiResolvedOptions = {}): RelaiResolved {\n const ctx = useRelaiContext();\n return useMemo<RelaiResolved>(() => {\n const client =\n opts.client ??\n ctx?.client ??\n createRelaiClient({ baseUrl: opts.baseUrl });\n return {\n client,\n wallet: opts.wallet ?? ctx?.wallet,\n signAndSend: opts.signAndSend ?? ctx?.signAndSend,\n };\n }, [opts.client, opts.baseUrl, opts.wallet, opts.signAndSend, ctx]);\n}\n\nexport interface UsePlanMetaResult {\n plan?: PlanMeta;\n loading: boolean;\n error?: Error;\n}\n\n/** Fetch public plan terms for `planId`. */\nexport function usePlanMeta(planId: string, client: RelaiClient): UsePlanMetaResult {\n const [state, setState] = useState<UsePlanMetaResult>({ loading: true });\n useEffect(() => {\n let alive = true;\n setState({ loading: true });\n client\n .meta(planId)\n .then((plan) => alive && setState({ plan, loading: false }))\n .catch((error: Error) => alive && setState({ loading: false, error }));\n return () => {\n alive = false;\n };\n }, [planId, client]);\n return state;\n}\n\nexport interface UseSubscriptionStatusResult {\n status?: SubscriptionStatusResult;\n loading: boolean;\n error?: Error;\n refetch: () => void;\n}\n\n/** Track whether `wallet` is subscribed to `planId`. No-op until a wallet is set. */\nexport function useSubscriptionStatus(\n planId: string,\n wallet: string | undefined,\n client: RelaiClient,\n): UseSubscriptionStatusResult {\n const [state, setState] = useState<Omit<UseSubscriptionStatusResult, \"refetch\">>({\n loading: false,\n });\n const [nonce, setNonce] = useState(0);\n const refetch = useCallback(() => setNonce((n) => n + 1), []);\n\n useEffect(() => {\n if (!wallet) {\n setState({ loading: false, status: undefined });\n return;\n }\n let alive = true;\n setState({ loading: true });\n client\n .status(planId, wallet)\n .then((status) => alive && setState({ status, loading: false }))\n .catch((error: Error) => alive && setState({ loading: false, error }));\n return () => {\n alive = false;\n };\n }, [planId, wallet, client, nonce]);\n\n return { ...state, refetch };\n}\n\nexport type SubscribeState =\n | \"idle\"\n | \"preparing\"\n | \"signing\"\n | \"confirming\"\n | \"done\"\n | \"error\";\n\nexport interface UseSubscribeOptions extends RelaiResolvedOptions {\n planId: string;\n onSubscribed?: (sub: Subscription) => void;\n onError?: (err: Error) => void;\n}\n\nexport interface UseSubscribeResult {\n subscribe: () => Promise<Subscription | undefined>;\n state: SubscribeState;\n error?: Error;\n subscription?: Subscription;\n /** True while the flow is mid-run (preparing/signing/confirming). */\n busy: boolean;\n reset: () => void;\n}\n\n/**\n * Runs the full two-stage subscribe flow:\n * prepare → (init-authority sign → prepare again) → sign → confirm.\n */\nexport function useSubscribe(opts: UseSubscribeOptions): UseSubscribeResult {\n const { client, wallet, signAndSend } = useRelaiResolved(opts);\n const [state, setState] = useState<SubscribeState>(\"idle\");\n const [error, setError] = useState<Error | undefined>();\n const [subscription, setSubscription] = useState<Subscription | undefined>();\n const running = useRef(false);\n\n const reset = useCallback(() => {\n setState(\"idle\");\n setError(undefined);\n setSubscription(undefined);\n }, []);\n\n const subscribe = useCallback(async (): Promise<Subscription | undefined> => {\n if (running.current) return undefined;\n if (!wallet) {\n const err = new Error(\"No wallet — connect a wallet before subscribing.\");\n setError(err);\n setState(\"error\");\n opts.onError?.(err);\n return undefined;\n }\n if (!signAndSend) {\n const err = new Error(\"No signAndSend — pass one (see @relai-fi/subscriptions-react/wallet).\");\n setError(err);\n setState(\"error\");\n opts.onError?.(err);\n return undefined;\n }\n\n running.current = true;\n setError(undefined);\n try {\n setState(\"preparing\");\n let prep = await client.prepareSubscribe(opts.planId, wallet);\n\n // Stage 1: one-time delegate authority init for this wallet+mint.\n if (prep.stage === \"init-authority\") {\n setState(\"signing\");\n await signAndSend(prep.wireTransaction);\n setState(\"preparing\");\n prep = await client.prepareSubscribe(opts.planId, wallet);\n }\n\n // Stage 2: the subscribe transaction.\n setState(\"signing\");\n const sig = await signAndSend(prep.wireTransaction);\n\n setState(\"confirming\");\n const sub = await client.confirmSubscribe(opts.planId, wallet, sig);\n setSubscription(sub);\n setState(\"done\");\n opts.onSubscribed?.(sub);\n return sub;\n } catch (e) {\n const err = e instanceof Error ? e : new Error(String(e));\n setError(err);\n setState(\"error\");\n opts.onError?.(err);\n return undefined;\n } finally {\n running.current = false;\n }\n }, [client, wallet, signAndSend, opts]);\n\n const busy = state === \"preparing\" || state === \"signing\" || state === \"confirming\";\n return { subscribe, state, error, subscription, busy, reset };\n}\n","import type { CSSProperties, ReactNode } from \"react\";\nimport { useRelaiResolved, useSubscribe, useSubscriptionStatus } from \"../hooks.js\";\nimport type { RelaiResolvedOptions } from \"../hooks.js\";\nimport type { Subscription } from \"../types.js\";\n\nexport interface SubscribeButtonLabels {\n subscribe?: string;\n connect?: string;\n preparing?: string;\n signing?: string;\n confirming?: string;\n active?: string;\n retry?: string;\n}\n\nconst DEFAULT_LABELS: Required<SubscribeButtonLabels> = {\n subscribe: \"Subscribe\",\n connect: \"Connect wallet\",\n preparing: \"Preparing…\",\n signing: \"Confirm in wallet…\",\n confirming: \"Activating…\",\n active: \"Subscribed\",\n retry: \"Try again\",\n};\n\nexport interface SubscribeButtonProps extends RelaiResolvedOptions {\n planId: string;\n /** Called when the user has no connected wallet and clicks the button. */\n onConnectWallet?: () => void;\n onSubscribed?: (sub: Subscription) => void;\n onError?: (err: Error) => void;\n /** Hide the \"Subscribed\" current-plan state (skip the status check). */\n hideStatus?: boolean;\n labels?: SubscribeButtonLabels;\n className?: string;\n style?: CSSProperties;\n children?: ReactNode;\n}\n\n/**\n * A single subscribe button for `planId`. Resolves wallet / signAndSend / client\n * from props or <RelaiProvider>. Runs the two-stage subscribe flow on click and\n * reflects the live state. Shows \"Subscribed\" when the wallet already has the plan.\n */\nexport function SubscribeButton(props: SubscribeButtonProps) {\n const { planId, onConnectWallet, hideStatus, className, style, children } = props;\n const labels = { ...DEFAULT_LABELS, ...props.labels };\n const { client, wallet } = useRelaiResolved(props);\n\n const { status, refetch } = useSubscriptionStatus(\n planId,\n hideStatus ? undefined : wallet,\n client,\n );\n const { subscribe, state, busy } = useSubscribe({\n ...props,\n onSubscribed: (sub) => {\n refetch();\n props.onSubscribed?.(sub);\n },\n });\n\n const alreadyActive = !hideStatus && status?.active === true;\n\n let label: string;\n let disabled = false;\n let dataState = state;\n\n if (alreadyActive || state === \"done\") {\n label = labels.active;\n disabled = true;\n dataState = \"done\";\n } else if (!wallet) {\n label = labels.connect;\n disabled = !onConnectWallet;\n } else if (state === \"preparing\") {\n label = labels.preparing;\n disabled = true;\n } else if (state === \"signing\") {\n label = labels.signing;\n disabled = true;\n } else if (state === \"confirming\") {\n label = labels.confirming;\n disabled = true;\n } else if (state === \"error\") {\n label = labels.retry;\n } else {\n label = labels.subscribe;\n }\n\n const onClick = () => {\n if (busy || alreadyActive) return;\n if (!wallet) {\n onConnectWallet?.();\n return;\n }\n void subscribe();\n };\n\n return (\n <button\n type=\"button\"\n className={[\"relai-btn\", className].filter(Boolean).join(\" \")}\n style={style}\n data-state={dataState}\n data-busy={busy ? \"true\" : undefined}\n aria-busy={busy || undefined}\n disabled={disabled}\n onClick={onClick}\n >\n {busy && <span className=\"relai-spinner\" aria-hidden=\"true\" />}\n {children ?? label}\n </button>\n );\n}\n","import type { Network } from \"./types.js\";\n\nconst USDC_DECIMALS = 6;\n\n/** \"5000000\" → \"5.00\" (or \"5.00 USDC\" with the ticker). */\nexport function formatUsdc(\n amountBaseUnits: string | number,\n opts: { withTicker?: boolean; decimals?: number } = {},\n): string {\n const decimals = opts.decimals ?? USDC_DECIMALS;\n const n = Number(amountBaseUnits) / 10 ** decimals;\n // Trim to 2 dp for whole-cent USDC; keep more only if the value needs it.\n const dp = Number.isInteger(n * 100) ? 2 : Math.min(decimals, 6);\n const s = n.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: dp });\n return opts.withTicker ? `${s} USDC` : s;\n}\n\n/** Short price suffix for a pricing card: 720 → \"/mo\", 24 → \"/day\", 1 → \"/hr\". */\nexport function formatPeriodSuffix(periodHours: number): string {\n switch (periodHours) {\n case 1:\n return \"/hr\";\n case 24:\n return \"/day\";\n case 168:\n return \"/wk\";\n case 720:\n return \"/mo\";\n case 8760:\n return \"/yr\";\n default:\n return periodHours % 24 === 0 ? `/${periodHours / 24}d` : `/${periodHours}h`;\n }\n}\n\n/** Human label: 720 → \"Billed monthly\", 1 → \"Billed hourly\". */\nexport function formatPeriodLabel(periodHours: number): string {\n const map: Record<number, string> = {\n 1: \"hourly\",\n 24: \"daily\",\n 168: \"weekly\",\n 720: \"monthly\",\n 8760: \"yearly\",\n };\n const known = map[periodHours];\n if (known) return `Billed ${known}`;\n if (periodHours % 24 === 0) return `Billed every ${periodHours / 24} days`;\n return `Billed every ${periodHours} hours`;\n}\n\nexport function formatNetwork(network: Network): string {\n return network === \"solana-devnet\" ? \"Solana devnet\" : \"Solana\";\n}\n","import type { CSSProperties } from \"react\";\nimport { SubscribeButton } from \"./SubscribeButton.js\";\nimport type { SubscribeButtonLabels } from \"./SubscribeButton.js\";\nimport type { RelaiResolvedOptions } from \"../hooks.js\";\nimport { formatNetwork, formatPeriodLabel, formatPeriodSuffix, formatUsdc } from \"../format.js\";\nimport type { PlanMeta, Subscription } from \"../types.js\";\n\nexport interface PricingCardProps extends RelaiResolvedOptions {\n plan: PlanMeta;\n /** Bullet features shown under the price. */\n features?: string[];\n /** Short line under the plan name. */\n description?: string;\n /** Visually emphasize this card. */\n highlight?: boolean;\n /** Ribbon text (e.g. \"Popular\"). Defaults to \"Popular\" when highlight is set. */\n badge?: string;\n /** Hide the \"Solana / Solana devnet\" network line. */\n hideNetwork?: boolean;\n onConnectWallet?: () => void;\n onSubscribed?: (sub: Subscription) => void;\n onError?: (err: Error) => void;\n labels?: SubscribeButtonLabels;\n className?: string;\n style?: CSSProperties;\n}\n\nconst Check = () => (\n <svg className=\"relai-check\" viewBox=\"0 0 20 20\" width=\"18\" height=\"18\" aria-hidden=\"true\">\n <path\n d=\"M16.7 5.3a1 1 0 0 1 0 1.4l-7.5 7.5a1 1 0 0 1-1.4 0L3.3 9.7a1 1 0 1 1 1.4-1.4l3.3 3.29 6.8-6.8a1 1 0 0 1 1.4 0Z\"\n fill=\"currentColor\"\n />\n </svg>\n);\n\n/** A single Stripe-style pricing card for one plan. */\nexport function PricingCard(props: PricingCardProps) {\n const { plan, features, description, highlight, hideNetwork, className, style } = props;\n const badge = props.badge ?? (highlight ? \"Popular\" : undefined);\n\n return (\n <div\n className={[\"relai-card\", highlight ? \"relai-card--highlight\" : \"\", className]\n .filter(Boolean)\n .join(\" \")}\n style={style}\n data-plan={plan.planId}\n >\n {badge && <span className=\"relai-badge\">{badge}</span>}\n\n <div className=\"relai-card__head\">\n <h3 className=\"relai-card__name\">{plan.name}</h3>\n {description && <p className=\"relai-card__desc\">{description}</p>}\n </div>\n\n <div className=\"relai-price\">\n <span className=\"relai-price__amount\">{formatUsdc(plan.amountBaseUnits)}</span>\n <span className=\"relai-price__unit\">\n <span className=\"relai-price__ticker\">USDC</span>\n <span className=\"relai-price__period\">{formatPeriodSuffix(plan.periodHours)}</span>\n </span>\n </div>\n <p className=\"relai-price__caption\">\n {formatPeriodLabel(plan.periodHours)}\n {!hideNetwork && <> · {formatNetwork(plan.network)}</>}\n </p>\n\n <SubscribeButton\n planId={plan.planId}\n client={props.client}\n baseUrl={props.baseUrl}\n wallet={props.wallet}\n signAndSend={props.signAndSend}\n onConnectWallet={props.onConnectWallet}\n onSubscribed={props.onSubscribed}\n onError={props.onError}\n labels={props.labels}\n />\n\n {features && features.length > 0 && (\n <ul className=\"relai-features\">\n {features.map((f, i) => (\n <li key={i} className=\"relai-features__item\">\n <Check />\n <span>{f}</span>\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n}\n","import type { CSSProperties } from \"react\";\nimport type { RelaiTheme } from \"./types.js\";\n\n/** Map a RelaiTheme onto the `--relai-*` CSS variables consumed by styles.css. */\nexport function themeToCssVars(theme?: RelaiTheme): CSSProperties {\n if (!theme) return {};\n const v: Record<string, string> = {};\n if (theme.primary) v[\"--relai-primary\"] = theme.primary;\n if (theme.primaryForeground) v[\"--relai-primary-fg\"] = theme.primaryForeground;\n if (theme.background) v[\"--relai-bg\"] = theme.background;\n if (theme.card) v[\"--relai-card\"] = theme.card;\n if (theme.foreground) v[\"--relai-fg\"] = theme.foreground;\n if (theme.mutedForeground) v[\"--relai-muted-fg\"] = theme.mutedForeground;\n if (theme.border) v[\"--relai-border\"] = theme.border;\n if (theme.radius) v[\"--relai-radius\"] = theme.radius;\n return v as CSSProperties;\n}\n","import { useEffect, useState } from \"react\";\nimport type { CSSProperties } from \"react\";\nimport { PricingCard } from \"./PricingCard.js\";\nimport type { SubscribeButtonLabels } from \"./SubscribeButton.js\";\nimport { useRelaiResolved } from \"../hooks.js\";\nimport type { RelaiResolvedOptions } from \"../hooks.js\";\nimport { useRelaiContext } from \"../context.js\";\nimport { themeToCssVars } from \"../theme.js\";\nimport type { PlanMeta, RelaiTheme, Subscription } from \"../types.js\";\n\nexport interface PlanCardConfig {\n features?: string[];\n description?: string;\n highlight?: boolean;\n badge?: string;\n}\n\nexport interface PricingTableProps extends RelaiResolvedOptions {\n /** Plans to show, in order. Use this, or `plans`, or a single `planId`. */\n planIds?: string[];\n /** Preloaded plan metadata (skips fetching). */\n plans?: PlanMeta[];\n /** Shorthand for a one-plan table. */\n planId?: string;\n /** Per-plan presentation, keyed by planId. */\n cards?: Record<string, PlanCardConfig>;\n /** Emphasize one plan (adds the \"Popular\" ribbon unless overridden in `cards`). */\n highlightPlanId?: string;\n theme?: RelaiTheme;\n onConnectWallet?: () => void;\n onSubscribed?: (sub: Subscription) => void;\n onError?: (err: Error) => void;\n labels?: SubscribeButtonLabels;\n loadingText?: string;\n errorText?: string;\n className?: string;\n style?: CSSProperties;\n}\n\nfunction useManyPlanMetas(\n ids: string[],\n preloaded: PlanMeta[] | undefined,\n client: ReturnType<typeof useRelaiResolved>[\"client\"],\n) {\n const key = ids.join(\",\");\n const [state, setState] = useState<{ plans: PlanMeta[]; loading: boolean; error?: Error }>(\n () => ({ plans: preloaded ?? [], loading: !preloaded }),\n );\n\n useEffect(() => {\n if (preloaded) {\n setState({ plans: preloaded, loading: false });\n return;\n }\n if (ids.length === 0) {\n setState({ plans: [], loading: false });\n return;\n }\n let alive = true;\n setState({ plans: [], loading: true });\n Promise.all(ids.map((id) => client.meta(id)))\n .then((plans) => alive && setState({ plans, loading: false }))\n .catch((error: Error) => alive && setState({ plans: [], loading: false, error }));\n return () => {\n alive = false;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [key, preloaded, client]);\n\n return state;\n}\n\n/**\n * Drop-in Stripe-style pricing table for RelAI subscription plans.\n *\n * import \"@relai-fi/subscriptions-react/styles.css\";\n * <PricingTable planIds={[\"pl_basic\", \"pl_pro\"]} highlightPlanId=\"pl_pro\"\n * wallet={address} signAndSend={signAndSend} />\n */\nexport function PricingTable(props: PricingTableProps) {\n const { client, wallet, signAndSend } = useRelaiResolved(props);\n const ctx = useRelaiContext();\n const theme = props.theme ?? ctx?.theme;\n\n const ids = props.plans\n ? props.plans.map((p) => p.planId)\n : props.planIds ?? (props.planId ? [props.planId] : []);\n\n const { plans, loading, error } = useManyPlanMetas(ids, props.plans, client);\n\n const rootStyle: CSSProperties = { ...themeToCssVars(theme), ...props.style };\n const rootClass = [\"relai-root\", \"relai-table\", props.className].filter(Boolean).join(\" \");\n\n if (loading) {\n return (\n <div className={rootClass} style={rootStyle}>\n <div className=\"relai-state relai-state--loading\">{props.loadingText ?? \"Loading plans…\"}</div>\n </div>\n );\n }\n if (error) {\n return (\n <div className={rootClass} style={rootStyle}>\n <div className=\"relai-state relai-state--error\">\n {props.errorText ?? \"Couldn't load plans.\"}\n </div>\n </div>\n );\n }\n\n return (\n <div className={rootClass} style={rootStyle} data-count={plans.length}>\n {plans.map((plan) => {\n const cfg = props.cards?.[plan.planId];\n const highlight = cfg?.highlight ?? props.highlightPlanId === plan.planId;\n return (\n <PricingCard\n key={plan.planId}\n plan={plan}\n features={cfg?.features}\n description={cfg?.description}\n highlight={highlight}\n badge={cfg?.badge}\n client={client}\n wallet={wallet}\n signAndSend={signAndSend}\n onConnectWallet={props.onConnectWallet}\n onSubscribed={props.onSubscribed}\n onError={props.onError}\n labels={props.labels}\n />\n );\n })}\n </div>\n );\n}\n"]}