@ravnexchange/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.
@@ -0,0 +1,7 @@
1
+ export { useRavnQuote } from "./useRavnQuote";
2
+ export type { UseRavnQuoteResult } from "./useRavnQuote";
3
+ export { useRavnExecute } from "./useRavnExecute";
4
+ export type { UseRavnExecuteResult } from "./useRavnExecute";
5
+ export { useRavnStatus } from "./useRavnStatus";
6
+ export type { UseRavnStatusResult } from "./useRavnStatus";
7
+ export { isPollingTerminal, msUntilExpiry } from "./polling";
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.msUntilExpiry = exports.isPollingTerminal = exports.useRavnStatus = exports.useRavnExecute = exports.useRavnQuote = void 0;
4
+ var useRavnQuote_1 = require("./useRavnQuote");
5
+ Object.defineProperty(exports, "useRavnQuote", { enumerable: true, get: function () { return useRavnQuote_1.useRavnQuote; } });
6
+ var useRavnExecute_1 = require("./useRavnExecute");
7
+ Object.defineProperty(exports, "useRavnExecute", { enumerable: true, get: function () { return useRavnExecute_1.useRavnExecute; } });
8
+ var useRavnStatus_1 = require("./useRavnStatus");
9
+ Object.defineProperty(exports, "useRavnStatus", { enumerable: true, get: function () { return useRavnStatus_1.useRavnStatus; } });
10
+ var polling_1 = require("./polling");
11
+ Object.defineProperty(exports, "isPollingTerminal", { enumerable: true, get: function () { return polling_1.isPollingTerminal; } });
12
+ Object.defineProperty(exports, "msUntilExpiry", { enumerable: true, get: function () { return polling_1.msUntilExpiry; } });
@@ -0,0 +1,3 @@
1
+ export { isPollingTerminal } from "@ravnexchange/sdk";
2
+ /** Milliseconds until a quote's expiresAt, clamped to 0 (never negative). */
3
+ export declare function msUntilExpiry(expiresAt: number, now?: number): number;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isPollingTerminal = void 0;
4
+ exports.msUntilExpiry = msUntilExpiry;
5
+ // isPollingTerminal lives in @ravnexchange/sdk (core) — executeAndTrack needs it too and core
6
+ // can't depend on this React package. Re-exported here so existing @ravnexchange/react imports
7
+ // don't break.
8
+ var sdk_1 = require("@ravnexchange/sdk");
9
+ Object.defineProperty(exports, "isPollingTerminal", { enumerable: true, get: function () { return sdk_1.isPollingTerminal; } });
10
+ /** Milliseconds until a quote's expiresAt, clamped to 0 (never negative). */
11
+ function msUntilExpiry(expiresAt, now = Date.now()) {
12
+ return Math.max(0, expiresAt - now);
13
+ }
@@ -0,0 +1,13 @@
1
+ import type { ExecuteParams, ExecutionDTO, RavnClient } from "@ravnexchange/sdk";
2
+ export interface UseRavnExecuteResult {
3
+ execute: (params: ExecuteParams) => Promise<ExecutionDTO>;
4
+ data: ExecutionDTO | null;
5
+ isLoading: boolean;
6
+ error: unknown;
7
+ }
8
+ /**
9
+ * Thin state wrapper around client.execute(). Deliberately does NOT sign or send anything —
10
+ * this package has no opinion on wallets. Branch on the returned executionType and hand it to
11
+ * whatever signer the integrator already has (wagmi, ethers, a hardware wallet, ...).
12
+ */
13
+ export declare function useRavnExecute(client: RavnClient): UseRavnExecuteResult;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useRavnExecute = useRavnExecute;
4
+ const react_1 = require("react");
5
+ /**
6
+ * Thin state wrapper around client.execute(). Deliberately does NOT sign or send anything —
7
+ * this package has no opinion on wallets. Branch on the returned executionType and hand it to
8
+ * whatever signer the integrator already has (wagmi, ethers, a hardware wallet, ...).
9
+ */
10
+ function useRavnExecute(client) {
11
+ const [data, setData] = (0, react_1.useState)(null);
12
+ const [isLoading, setIsLoading] = (0, react_1.useState)(false);
13
+ const [error, setError] = (0, react_1.useState)(null);
14
+ const execute = (0, react_1.useCallback)(async (params) => {
15
+ setIsLoading(true);
16
+ setError(null);
17
+ try {
18
+ const result = await client.execute(params);
19
+ setData(result);
20
+ return result;
21
+ }
22
+ catch (err) {
23
+ setError(err);
24
+ throw err;
25
+ }
26
+ finally {
27
+ setIsLoading(false);
28
+ }
29
+ }, [client]);
30
+ return { execute, data, isLoading, error };
31
+ }
@@ -0,0 +1,16 @@
1
+ import type { GetQuoteParams, QuoteDTO, RavnClient } from "@ravnexchange/sdk";
2
+ export interface UseRavnQuoteResult {
3
+ quote: QuoteDTO | null;
4
+ isLoading: boolean;
5
+ error: unknown;
6
+ /** True once quote.expiresAt has passed — stop letting the user sign, call refetch(). */
7
+ isExpired: boolean;
8
+ refetch: () => void;
9
+ }
10
+ /**
11
+ * Fetches a quote and flips `isExpired` on its own timer when expiresAt passes — no polling,
12
+ * just one setTimeout per quote. Pass `params: null` to skip fetching (e.g. amount not entered
13
+ * yet). Does not auto-refetch on expiry: that's a UX choice (silently re-pricing behind a user's
14
+ * back is worse than telling them to ask again), left to the caller.
15
+ */
16
+ export declare function useRavnQuote(client: RavnClient, params: GetQuoteParams | null): UseRavnQuoteResult;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useRavnQuote = useRavnQuote;
4
+ const react_1 = require("react");
5
+ const polling_1 = require("./polling");
6
+ /**
7
+ * Fetches a quote and flips `isExpired` on its own timer when expiresAt passes — no polling,
8
+ * just one setTimeout per quote. Pass `params: null` to skip fetching (e.g. amount not entered
9
+ * yet). Does not auto-refetch on expiry: that's a UX choice (silently re-pricing behind a user's
10
+ * back is worse than telling them to ask again), left to the caller.
11
+ */
12
+ function useRavnQuote(client, params) {
13
+ const [quote, setQuote] = (0, react_1.useState)(null);
14
+ const [isLoading, setIsLoading] = (0, react_1.useState)(false);
15
+ const [error, setError] = (0, react_1.useState)(null);
16
+ const [isExpired, setIsExpired] = (0, react_1.useState)(false);
17
+ const [generation, setGeneration] = (0, react_1.useState)(0);
18
+ const paramsKey = params ? JSON.stringify(params) : null;
19
+ const refetch = (0, react_1.useCallback)(() => setGeneration((g) => g + 1), []);
20
+ (0, react_1.useEffect)(() => {
21
+ if (!params) {
22
+ setQuote(null);
23
+ setIsExpired(false);
24
+ return;
25
+ }
26
+ let cancelled = false;
27
+ setIsLoading(true);
28
+ setError(null);
29
+ setIsExpired(false);
30
+ client
31
+ .getQuote(params)
32
+ .then((q) => {
33
+ if (cancelled)
34
+ return;
35
+ setQuote(q);
36
+ })
37
+ .catch((err) => {
38
+ if (cancelled)
39
+ return;
40
+ setError(err);
41
+ })
42
+ .finally(() => {
43
+ if (!cancelled)
44
+ setIsLoading(false);
45
+ });
46
+ return () => {
47
+ cancelled = true;
48
+ };
49
+ // paramsKey (not params) drives refetch — a fresh object with the same fields shouldn't refire.
50
+ // eslint-disable-next-line react-hooks/exhaustive-deps
51
+ }, [client, paramsKey, generation]);
52
+ const expiresAt = quote?.expiresAt;
53
+ (0, react_1.useEffect)(() => {
54
+ if (!expiresAt)
55
+ return;
56
+ const timer = setTimeout(() => setIsExpired(true), (0, polling_1.msUntilExpiry)(expiresAt));
57
+ return () => clearTimeout(timer);
58
+ }, [expiresAt]);
59
+ return { quote, isLoading, error, isExpired, refetch };
60
+ }
@@ -0,0 +1,16 @@
1
+ import { type RavnClient, type StatusDTO } from "@ravnexchange/sdk";
2
+ export interface UseRavnStatusResult {
3
+ status: StatusDTO | null;
4
+ isLoading: boolean;
5
+ error: unknown;
6
+ }
7
+ export declare function isPermanentError(err: unknown): boolean;
8
+ /**
9
+ * Polls GET /v1/status until it reaches a terminal state (see isPollingTerminal — "unknown"
10
+ * counts as terminal, it means the venue has no live tracker and never will for this swap).
11
+ * Pass `params: null` to not poll at all (e.g. before execution has produced a ref).
12
+ */
13
+ export declare function useRavnStatus(client: RavnClient, params: {
14
+ quoteToken: string;
15
+ ref: string;
16
+ } | null, intervalMs?: number): UseRavnStatusResult;
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isPermanentError = isPermanentError;
4
+ exports.useRavnStatus = useRavnStatus;
5
+ const react_1 = require("react");
6
+ const sdk_1 = require("@ravnexchange/sdk");
7
+ const polling_1 = require("./polling");
8
+ // Codes the API itself defines as request-shaped, not transient — retrying with the exact same
9
+ // arguments can never turn one of these into success. Anything else (RATE_LIMITED, INTERNAL, a
10
+ // network failure) is worth retrying through.
11
+ const PERMANENT_ERROR_CODES = new Set(["UNAUTHORIZED", "INVALID_REQUEST", "QUOTE_INVALID", "NOT_FOUND"]);
12
+ function isPermanentError(err) {
13
+ return err instanceof sdk_1.RavnApiError && PERMANENT_ERROR_CODES.has(err.code);
14
+ }
15
+ /**
16
+ * Polls GET /v1/status until it reaches a terminal state (see isPollingTerminal — "unknown"
17
+ * counts as terminal, it means the venue has no live tracker and never will for this swap).
18
+ * Pass `params: null` to not poll at all (e.g. before execution has produced a ref).
19
+ */
20
+ function useRavnStatus(client, params, intervalMs = 4000) {
21
+ const [status, setStatus] = (0, react_1.useState)(null);
22
+ const [isLoading, setIsLoading] = (0, react_1.useState)(false);
23
+ const [error, setError] = (0, react_1.useState)(null);
24
+ (0, react_1.useEffect)(() => {
25
+ if (!params) {
26
+ setStatus(null);
27
+ return;
28
+ }
29
+ let cancelled = false;
30
+ let timer;
31
+ const poll = () => {
32
+ setIsLoading(true);
33
+ client
34
+ .getStatus(params.quoteToken, params.ref)
35
+ .then((s) => {
36
+ if (cancelled)
37
+ return;
38
+ setStatus(s);
39
+ setError(null);
40
+ if (!(0, polling_1.isPollingTerminal)(s.status)) {
41
+ timer = setTimeout(poll, intervalMs);
42
+ }
43
+ })
44
+ .catch((err) => {
45
+ if (cancelled)
46
+ return;
47
+ setError(err);
48
+ // Keep polling through a transient network/5xx error rather than giving up on the
49
+ // swap's status forever — but a permanent error (bad API key, invalid ref) can never
50
+ // resolve by asking again with the same arguments, so stop rather than spam the API
51
+ // for as long as the component happens to stay mounted.
52
+ if (!isPermanentError(err)) {
53
+ timer = setTimeout(poll, intervalMs);
54
+ }
55
+ })
56
+ .finally(() => {
57
+ if (!cancelled)
58
+ setIsLoading(false);
59
+ });
60
+ };
61
+ poll();
62
+ return () => {
63
+ cancelled = true;
64
+ if (timer)
65
+ clearTimeout(timer);
66
+ };
67
+ }, [client, params?.quoteToken, params?.ref, intervalMs]);
68
+ return { status, isLoading, error };
69
+ }
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@ravnexchange/react",
3
+ "version": "0.1.0",
4
+ "description": "React hooks for @ravnexchange/sdk — quote/execute/status state management. Bring your own wallet.",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "files": ["dist"],
8
+ "license": "MIT",
9
+ "publishConfig": { "access": "public" },
10
+ "scripts": {
11
+ "build": "tsc -p tsconfig.build.json"
12
+ },
13
+ "peerDependencies": {
14
+ "@ravnexchange/sdk": "*",
15
+ "react": ">=18"
16
+ },
17
+ "devDependencies": {
18
+ "@ravnexchange/sdk": "*",
19
+ "@types/react": "^18.3.0",
20
+ "typescript": "^5.7.0"
21
+ }
22
+ }