@ravnexchange/sdk 0.1.1

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,30 @@
1
+ import type { ChainDTO, ExecuteParams, ExecutionDTO, GetQuoteParams, QuoteDTO, StatusDTO, SubmitSignatureParams, TokenDTO } from "./types";
2
+ export interface RavnClientConfig {
3
+ /** Self-serve or enterprise key. Omit for the anonymous tier (works immediately, lower rate limit). */
4
+ apiKey?: string;
5
+ /** Default: https://ravn.exchange/api/v1 */
6
+ baseUrl?: string;
7
+ /** Swap in your own fetch (e.g. for React Native, or a test double). Defaults to global fetch. */
8
+ fetch?: typeof fetch;
9
+ }
10
+ export declare class RavnClient {
11
+ private readonly apiKey?;
12
+ private readonly baseUrl;
13
+ private readonly fetchImpl;
14
+ constructor(config?: RavnClientConfig);
15
+ private request;
16
+ /** POST /v1/quote — omitting destinationAddress/refundAddress returns a preview-only quote (see QuoteDTO.executable). */
17
+ getQuote(params: GetQuoteParams): Promise<QuoteDTO>;
18
+ /** POST /v1/execute — branch on the returned executionType (TRANSACTION / SIGNATURE / DEPOSIT). */
19
+ execute(params: ExecuteParams): Promise<ExecutionDTO>;
20
+ /** POST /v1/submit-signature — for SIGNATURE-type executions only. Returns the ref to poll getStatus with. */
21
+ submitSignature(params: SubmitSignatureParams): Promise<{
22
+ statusRef: string;
23
+ }>;
24
+ /** GET /v1/status — `ref` is the DEPOSIT address or the statusRef from submitSignature. */
25
+ getStatus(quoteToken: string, ref: string): Promise<StatusDTO>;
26
+ /** GET /v1/tokens — RAVN's listed token registry for one chain. Not a per-pair routability guarantee. */
27
+ getTokens(chainId: number): Promise<TokenDTO[]>;
28
+ /** GET /v1/chains — every chain RAVN lists tokens for. Static; safe to cache client-side. */
29
+ getChains(): Promise<ChainDTO[]>;
30
+ }
package/dist/client.js ADDED
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RavnClient = void 0;
4
+ const types_1 = require("./types");
5
+ const DEFAULT_BASE_URL = "https://ravn.exchange/api/v1";
6
+ class RavnClient {
7
+ constructor(config = {}) {
8
+ this.apiKey = config.apiKey;
9
+ this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
10
+ this.fetchImpl = config.fetch ?? globalThis.fetch;
11
+ }
12
+ async request(path, init) {
13
+ const res = await this.fetchImpl(`${this.baseUrl}${path}`, {
14
+ ...init,
15
+ headers: {
16
+ "content-type": "application/json",
17
+ ...(this.apiKey ? { "x-api-key": this.apiKey } : {}),
18
+ ...init.headers,
19
+ },
20
+ });
21
+ let body;
22
+ try {
23
+ body = (await res.json());
24
+ }
25
+ catch {
26
+ // A non-JSON body (an intermediary's HTML error page, an empty 204/502/504) must still
27
+ // surface as the one error type this client promises — never a raw SyntaxError.
28
+ throw new types_1.RavnApiError("INTERNAL", `HTTP ${res.status}: response was not valid JSON`, undefined, undefined);
29
+ }
30
+ if (!res.ok || body.error) {
31
+ const err = body.error ?? { code: "INTERNAL", message: `HTTP ${res.status}` };
32
+ throw new types_1.RavnApiError(err.code, err.message, err.details, body.meta);
33
+ }
34
+ return body.data;
35
+ }
36
+ /** POST /v1/quote — omitting destinationAddress/refundAddress returns a preview-only quote (see QuoteDTO.executable). */
37
+ getQuote(params) {
38
+ return this.request("/quote", { method: "POST", body: JSON.stringify(params) });
39
+ }
40
+ /** POST /v1/execute — branch on the returned executionType (TRANSACTION / SIGNATURE / DEPOSIT). */
41
+ execute(params) {
42
+ return this.request("/execute", { method: "POST", body: JSON.stringify(params) });
43
+ }
44
+ /** POST /v1/submit-signature — for SIGNATURE-type executions only. Returns the ref to poll getStatus with. */
45
+ submitSignature(params) {
46
+ return this.request("/submit-signature", {
47
+ method: "POST",
48
+ body: JSON.stringify(params),
49
+ });
50
+ }
51
+ /** GET /v1/status — `ref` is the DEPOSIT address or the statusRef from submitSignature. */
52
+ getStatus(quoteToken, ref) {
53
+ const qs = new URLSearchParams({ quoteToken, ref }).toString();
54
+ return this.request(`/status?${qs}`, { method: "GET" });
55
+ }
56
+ /** GET /v1/tokens — RAVN's listed token registry for one chain. Not a per-pair routability guarantee. */
57
+ getTokens(chainId) {
58
+ const qs = new URLSearchParams({ chainId: String(chainId) }).toString();
59
+ return this.request(`/tokens?${qs}`, { method: "GET" });
60
+ }
61
+ /** GET /v1/chains — every chain RAVN lists tokens for. Static; safe to cache client-side. */
62
+ getChains() {
63
+ return this.request("/chains", { method: "GET" });
64
+ }
65
+ }
66
+ exports.RavnClient = RavnClient;
@@ -0,0 +1,52 @@
1
+ import type { RavnClient } from "./client";
2
+ import type { ApprovalDTO, ExecuteParams, ExecutionDTO, StatusDTO } from "./types";
3
+ type TxLike = {
4
+ to?: string;
5
+ data?: string;
6
+ value?: string;
7
+ chainId?: number;
8
+ serialized?: string;
9
+ };
10
+ export interface ExecuteAndTrackHandlers {
11
+ /** Broadcast a transaction (an approval, or the main swap tx) and return its hash. */
12
+ sendTransaction(tx: TxLike): Promise<string>;
13
+ /**
14
+ * Wait for a transaction hash to confirm. Called on the approval before the main tx is ever
15
+ * sent — sending both back-to-back races the allowance and is the most common integration bug
16
+ * this helper exists to prevent.
17
+ */
18
+ waitForReceipt(hash: string, chainId?: number): Promise<void>;
19
+ /** Sign an EIP-712 typed-data payload. Only called for SIGNATURE executions. */
20
+ signTypedData?(typedData: Record<string, unknown>): Promise<`0x${string}`>;
21
+ /**
22
+ * The API sends `approval` whenever the venue generically needs an allowance — it deliberately
23
+ * never reads the chain to check whether the caller already has one (that's an RPC round-trip
24
+ * on every execute). Without this handler, that approval tx is sent unconditionally, even when
25
+ * unnecessary — the caller pays gas for a redundant approve on every swap of the same
26
+ * token/spender. Supply this (an allowance read against your own RPC) to skip it when already
27
+ * sufficient, same as the first-party app's own ensureErc20Allowance does.
28
+ */
29
+ hasAllowance?(approval: ApprovalDTO): Promise<boolean>;
30
+ }
31
+ export interface ExecuteAndTrackOptions {
32
+ /** Default true. Set false to get a ref back immediately and poll getStatus yourself. */
33
+ pollUntilTerminal?: boolean;
34
+ pollIntervalMs?: number;
35
+ }
36
+ export interface ExecuteAndTrackResult {
37
+ execution: ExecutionDTO;
38
+ approvalTxHash?: string;
39
+ txHash?: string;
40
+ /** Pass to client.getStatus(quoteToken, ref) — same value as txHash for TRANSACTION. */
41
+ statusRef?: string;
42
+ /** Set once pollUntilTerminal reaches a terminal status. Absent for DEPOSIT (see below) or when polling is turned off. */
43
+ finalStatus?: StatusDTO;
44
+ }
45
+ /**
46
+ * Sequences execute() → approval (if any) → wait for its receipt → main tx/signature → status
47
+ * polling, using caller-supplied signing/sending functions. The SDK stays wallet-agnostic (no
48
+ * RPC dependency) — this just gets the ORDER right, which quickstart's own docs flag as the
49
+ * most common integration failure: sending the main tx before the approval has confirmed.
50
+ */
51
+ export declare function executeAndTrack(client: RavnClient, params: ExecuteParams, handlers: ExecuteAndTrackHandlers, options?: ExecuteAndTrackOptions): Promise<ExecuteAndTrackResult>;
52
+ export {};
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.executeAndTrack = executeAndTrack;
4
+ const polling_1 = require("./polling");
5
+ /**
6
+ * Sequences execute() → approval (if any) → wait for its receipt → main tx/signature → status
7
+ * polling, using caller-supplied signing/sending functions. The SDK stays wallet-agnostic (no
8
+ * RPC dependency) — this just gets the ORDER right, which quickstart's own docs flag as the
9
+ * most common integration failure: sending the main tx before the approval has confirmed.
10
+ */
11
+ async function executeAndTrack(client, params, handlers, options = {}) {
12
+ const { pollUntilTerminal = true, pollIntervalMs = 4000 } = options;
13
+ const execution = await client.execute(params);
14
+ const pollStatus = async (ref) => {
15
+ if (!pollUntilTerminal)
16
+ return undefined;
17
+ for (;;) {
18
+ const status = await client.getStatus(params.quoteToken, ref);
19
+ if ((0, polling_1.isPollingTerminal)(status.status))
20
+ return status;
21
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
22
+ }
23
+ };
24
+ // Sends and confirms an approval unless the caller already knows it's covered — see
25
+ // ExecuteAndTrackHandlers.hasAllowance's doc comment for why the API can't decide this itself.
26
+ const maybeSendApproval = async (approval) => {
27
+ if (!approval)
28
+ return undefined;
29
+ if (await handlers.hasAllowance?.(approval))
30
+ return undefined;
31
+ const hash = await handlers.sendTransaction(approval);
32
+ await handlers.waitForReceipt(hash, approval.chainId);
33
+ return hash;
34
+ };
35
+ if (execution.executionType === "TRANSACTION") {
36
+ const approvalTxHash = await maybeSendApproval(execution.approval);
37
+ const txHash = await handlers.sendTransaction(execution.transaction);
38
+ return { execution, approvalTxHash, txHash, statusRef: txHash, finalStatus: await pollStatus(txHash) };
39
+ }
40
+ if (execution.executionType === "SIGNATURE") {
41
+ if (!handlers.signTypedData) {
42
+ throw new Error("executeAndTrack: this execution requires signTypedData, none was provided");
43
+ }
44
+ if (!execution.typedData) {
45
+ throw new Error("executeAndTrack: SIGNATURE execution is missing typedData");
46
+ }
47
+ // A signed order isn't allowance-free: CoW's vault relayer and Bebop/0x's Permit2 still pull
48
+ // the sell token with transferFrom, so this on-chain allowance (separate from approvalData's
49
+ // gasless per-trade signature) must be confirmed before signing — otherwise the order is
50
+ // accepted and simply never fills, with no error to explain why.
51
+ const approvalTxHash = await maybeSendApproval(execution.approval);
52
+ let approvalSignature;
53
+ if (execution.approvalData) {
54
+ approvalSignature = await handlers.signTypedData(execution.approvalData);
55
+ }
56
+ const signature = await handlers.signTypedData(execution.typedData);
57
+ const { statusRef } = await client.submitSignature({ quoteToken: params.quoteToken, signature, approvalSignature });
58
+ return { execution, approvalTxHash, statusRef, finalStatus: await pollStatus(statusRef) };
59
+ }
60
+ // DEPOSIT: nothing to sign or send from here — the user moves funds to execution.deposit.address
61
+ // out-of-band, and this helper has no way to know when that happens. Returned without polling;
62
+ // start polling yourself (getStatus / useRavnStatus) once the deposit is actually sent.
63
+ return { execution, statusRef: execution.statusRef };
64
+ }
@@ -0,0 +1,6 @@
1
+ export { RavnClient } from "./client";
2
+ export type { RavnClientConfig } from "./client";
3
+ export * from "./types";
4
+ export { isPollingTerminal } from "./polling";
5
+ export { executeAndTrack } from "./execute-and-track";
6
+ export type { ExecuteAndTrackHandlers, ExecuteAndTrackOptions, ExecuteAndTrackResult, } from "./execute-and-track";
package/dist/index.js ADDED
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.executeAndTrack = exports.isPollingTerminal = exports.RavnClient = void 0;
18
+ var client_1 = require("./client");
19
+ Object.defineProperty(exports, "RavnClient", { enumerable: true, get: function () { return client_1.RavnClient; } });
20
+ __exportStar(require("./types"), exports);
21
+ var polling_1 = require("./polling");
22
+ Object.defineProperty(exports, "isPollingTerminal", { enumerable: true, get: function () { return polling_1.isPollingTerminal; } });
23
+ var execute_and_track_1 = require("./execute-and-track");
24
+ Object.defineProperty(exports, "executeAndTrack", { enumerable: true, get: function () { return execute_and_track_1.executeAndTrack; } });
@@ -0,0 +1,7 @@
1
+ import type { SettlementStatus } from "./types";
2
+ /**
3
+ * Whether a status is done changing. "unknown" counts as terminal even though it isn't a real
4
+ * outcome — it means the venue has no live tracker (see StatusDTO.tracking), so it will never
5
+ * change and polling forever would just spin. Only "pending"/"processing" keep polling.
6
+ */
7
+ export declare function isPollingTerminal(status: SettlementStatus): boolean;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isPollingTerminal = isPollingTerminal;
4
+ /**
5
+ * Whether a status is done changing. "unknown" counts as terminal even though it isn't a real
6
+ * outcome — it means the venue has no live tracker (see StatusDTO.tracking), so it will never
7
+ * change and polling forever would just spin. Only "pending"/"processing" keep polling.
8
+ */
9
+ function isPollingTerminal(status) {
10
+ return status !== "pending" && status !== "processing";
11
+ }
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Wire types for RAVN's public /api/v1 surface. Deliberately a standalone mirror of
3
+ * src/lib/api/v1/dto.ts's public DTOs, not an import from it — this package ships to npm on
4
+ * its own and can't depend on the app's internal source tree. Keep these two in sync by hand
5
+ * when the API's public contract changes; that file's own comment already promises the shape
6
+ * is frozen for third parties, so drift should be rare.
7
+ */
8
+ export interface TokenDTO {
9
+ symbol: string;
10
+ name: string;
11
+ address: string;
12
+ decimals: number;
13
+ chainId: number;
14
+ isNative: boolean;
15
+ logoUrl?: string;
16
+ }
17
+ export interface QuoteDTO {
18
+ /** Opaque handle — pass back to execute() verbatim. Do not parse. */
19
+ quoteToken: string;
20
+ venue: {
21
+ id: string;
22
+ name: string;
23
+ };
24
+ routeType: string;
25
+ input: {
26
+ token: TokenDTO;
27
+ amount: string;
28
+ };
29
+ output: {
30
+ token: TokenDTO;
31
+ amount: string;
32
+ };
33
+ /** `supported: false` means the venue has no fee mechanism at all (bps will be 0 either
34
+ * way) — distinct from `supported: true, bps: 0`, a real, intentional zero fee. */
35
+ fee: {
36
+ bps: number;
37
+ amount: string;
38
+ token: TokenDTO;
39
+ supported: boolean;
40
+ };
41
+ /** False means this is a preview-only price — see RavnClient.getQuote's destinationAddress note. */
42
+ executable: boolean;
43
+ slippage: {
44
+ bps: number;
45
+ isFirm: boolean;
46
+ guaranteedMin: string | null;
47
+ } | null;
48
+ gas: {
49
+ native: string;
50
+ nativeSymbol: string;
51
+ usd: string | null;
52
+ estimated: boolean;
53
+ } | null;
54
+ /** Same price source as `gas.usd`. Null when the token can't be priced. */
55
+ inputUsd: string | null;
56
+ outputUsd: string | null;
57
+ estimatedTimeSeconds: number;
58
+ estimatedTimeIsGuess: boolean;
59
+ expiresAt: number;
60
+ /** Every other venue that raced and produced a usable quote, best output first. */
61
+ alternatives: AlternativeQuoteDTO[];
62
+ }
63
+ export interface AlternativeQuoteDTO {
64
+ venue: {
65
+ id: string;
66
+ name: string;
67
+ };
68
+ routeType: string;
69
+ /** Pass to execute() to run THIS route instead of the primary quote. */
70
+ quoteToken: string;
71
+ outputAmount: string;
72
+ fee: {
73
+ bps: number;
74
+ amount: string;
75
+ supported: boolean;
76
+ };
77
+ executable: boolean;
78
+ estimatedTimeSeconds: number;
79
+ estimatedTimeIsGuess: boolean;
80
+ }
81
+ export interface ChainDTO {
82
+ chainId: number;
83
+ name: string;
84
+ shortName: string;
85
+ nativeCurrency: {
86
+ name: string;
87
+ symbol: string;
88
+ decimals: number;
89
+ };
90
+ explorerUrl: string;
91
+ logoUrl?: string;
92
+ }
93
+ export interface ApprovalDTO {
94
+ to: string;
95
+ data: string;
96
+ value: string;
97
+ chainId?: number;
98
+ spender?: string;
99
+ amount: string;
100
+ unlimitedRecommended?: boolean;
101
+ }
102
+ export type ExecutionDTO = {
103
+ executionType: "TRANSACTION";
104
+ approval?: ApprovalDTO;
105
+ transaction: {
106
+ to?: string;
107
+ data?: string;
108
+ value?: string;
109
+ chainId?: number;
110
+ serialized?: string;
111
+ };
112
+ } | {
113
+ executionType: "SIGNATURE";
114
+ approval?: ApprovalDTO;
115
+ typedData?: Record<string, unknown>;
116
+ approvalData?: Record<string, unknown>;
117
+ submit: {
118
+ url: string;
119
+ payload?: Record<string, unknown>;
120
+ };
121
+ } | {
122
+ executionType: "DEPOSIT";
123
+ deposit: {
124
+ address: string;
125
+ amount: string;
126
+ chainId?: number;
127
+ };
128
+ statusRef: string;
129
+ };
130
+ export type SettlementStatus = "pending" | "processing" | "expired" | "success" | "refunded" | "failed" | "not_found" | "unknown";
131
+ export interface StatusDTO {
132
+ status: SettlementStatus;
133
+ venue: string;
134
+ venueStatus?: unknown;
135
+ tracking?: "unavailable";
136
+ /** Actual delivered output in the output token's base units, when the venue exposes it. */
137
+ deliveredAmount?: string | null;
138
+ /** The destination-chain tx that paid the user out, when the venue names one. */
139
+ txHash?: string | null;
140
+ }
141
+ export interface GetQuoteParams {
142
+ inputChainId: number;
143
+ outputChainId: number;
144
+ inputToken: string;
145
+ outputToken: string;
146
+ /** Positive integer string, in the token's smallest unit (no decimals). */
147
+ inputAmount: string;
148
+ userAddress: string;
149
+ /** Omitting this yields a preview-only quote — see QuoteDTO.executable. */
150
+ destinationAddress?: string;
151
+ refundAddress?: string;
152
+ slippageBps?: number;
153
+ rankingMode?: "best_output" | "fastest";
154
+ /** Drop specific venues from this race — e.g. a risk objection to one of them. */
155
+ excludeVenues?: string[];
156
+ }
157
+ export interface ExecuteParams {
158
+ quoteToken: string;
159
+ destinationAddress?: string;
160
+ refundAddress?: string;
161
+ }
162
+ export interface SubmitSignatureParams {
163
+ quoteToken: string;
164
+ signature: `0x${string}`;
165
+ approvalSignature?: `0x${string}`;
166
+ }
167
+ export interface ErrorMeta {
168
+ requestId: string;
169
+ version: string;
170
+ }
171
+ /** Thrown for every non-2xx response. `code` is the stable, machine-readable string from the API. */
172
+ export declare class RavnApiError extends Error {
173
+ readonly code: string;
174
+ readonly details: unknown;
175
+ readonly meta: ErrorMeta | undefined;
176
+ constructor(code: string, message: string, details: unknown, meta: ErrorMeta | undefined);
177
+ }
package/dist/types.js ADDED
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ /**
3
+ * Wire types for RAVN's public /api/v1 surface. Deliberately a standalone mirror of
4
+ * src/lib/api/v1/dto.ts's public DTOs, not an import from it — this package ships to npm on
5
+ * its own and can't depend on the app's internal source tree. Keep these two in sync by hand
6
+ * when the API's public contract changes; that file's own comment already promises the shape
7
+ * is frozen for third parties, so drift should be rare.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.RavnApiError = void 0;
11
+ /** Thrown for every non-2xx response. `code` is the stable, machine-readable string from the API. */
12
+ class RavnApiError extends Error {
13
+ constructor(code, message, details, meta) {
14
+ super(message);
15
+ this.code = code;
16
+ this.details = details;
17
+ this.meta = meta;
18
+ this.name = "RavnApiError";
19
+ }
20
+ }
21
+ exports.RavnApiError = RavnApiError;
package/package.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "@ravnexchange/sdk",
3
+ "version": "0.1.1",
4
+ "description": "Typed client for the RAVN integrator API (/api/v1) — quote, execute, submit-signature, status.",
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
+ "dependencies": {},
14
+ "devDependencies": {
15
+ "typescript": "^5.7.0"
16
+ }
17
+ }