@ravnexchange/sdk 0.2.0 → 0.2.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.
- package/dist/client.d.ts +1 -1
- package/dist/client.js +10 -2
- package/dist/execute-and-track.d.ts +3 -2
- package/dist/execute-and-track.js +19 -8
- package/dist/types.d.ts +13 -2
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export declare class RavnClient {
|
|
|
17
17
|
/** POST /v1/quote: omitting destinationAddress/refundAddress returns a preview-only quote (see QuoteDTO.executable). */
|
|
18
18
|
getQuote(params: GetQuoteParams): Promise<QuoteDTO>;
|
|
19
19
|
/** POST /v1/execute: branch on the returned executionType (TRANSACTION / SIGNATURE / DEPOSIT). */
|
|
20
|
-
execute(params: ExecuteParams): Promise<ExecutionDTO>;
|
|
20
|
+
execute({ idempotencyKey, ...params }: ExecuteParams): Promise<ExecutionDTO>;
|
|
21
21
|
/** POST /v1/submit-signature: for SIGNATURE-type executions only. Returns the ref to poll getStatus with. */
|
|
22
22
|
submitSignature(params: SubmitSignatureParams): Promise<{
|
|
23
23
|
statusRef: string;
|
package/dist/client.js
CHANGED
|
@@ -57,8 +57,16 @@ class RavnClient {
|
|
|
57
57
|
return this.request("/quote", { method: "POST", body: JSON.stringify(params) });
|
|
58
58
|
}
|
|
59
59
|
/** POST /v1/execute: branch on the returned executionType (TRANSACTION / SIGNATURE / DEPOSIT). */
|
|
60
|
-
execute(params) {
|
|
61
|
-
|
|
60
|
+
execute({ idempotencyKey, ...params }) {
|
|
61
|
+
// The API's own rule: anything else fetch refuses to send (a reported NETWORK_ERROR) or RAVN answers 400.
|
|
62
|
+
if (idempotencyKey !== undefined && !/^[\x21-\x7e]{1,255}$/.test(idempotencyKey)) {
|
|
63
|
+
return Promise.reject(new types_1.RavnApiError("INVALID_REQUEST", "idempotencyKey must be 1–255 printable ASCII characters, no spaces", undefined, undefined));
|
|
64
|
+
}
|
|
65
|
+
return this.request("/execute", {
|
|
66
|
+
method: "POST",
|
|
67
|
+
body: JSON.stringify(params),
|
|
68
|
+
...(idempotencyKey ? { headers: { "Idempotency-Key": idempotencyKey } } : {}),
|
|
69
|
+
});
|
|
62
70
|
}
|
|
63
71
|
/** POST /v1/submit-signature: for SIGNATURE-type executions only. Returns the ref to poll getStatus with. */
|
|
64
72
|
submitSignature(params) {
|
|
@@ -189,8 +189,9 @@ export declare function depositFeePayment(execution: ExecutionDTO): DepositFeePa
|
|
|
189
189
|
* stays wallet-agnostic (no RPC dependency); this just gets the ORDER right, which quickstart's
|
|
190
190
|
* own docs flag as the most common integration failure: sending the main tx before the approval
|
|
191
191
|
* has confirmed. A failed API call is retried where repeating it is safe (status polls and
|
|
192
|
-
* submitTransaction on a 429, 5xx or no response; execute
|
|
193
|
-
*
|
|
192
|
+
* submitTransaction on a 429, 5xx or no response; execute, sent with an Idempotency-Key, also when its
|
|
193
|
+
* answer was lost or is still being built, up to 4 tries; submitSignature only when rate limited),
|
|
194
|
+
* waiting the API's retryAfterSec. Once anything
|
|
194
195
|
* is sent, it rejects only with an ExecuteAndTrackError carrying every hash; `onProgress` hears
|
|
195
196
|
* each one first.
|
|
196
197
|
*/
|
|
@@ -35,6 +35,9 @@ const MAX_BACKOFF_MS = 60000;
|
|
|
35
35
|
// ponytail: a fixed cap, ~4 min of backoff at the default interval; past it the call rejects with
|
|
36
36
|
// every hash to resume from. Make it an option if integrators need to ride out longer outages.
|
|
37
37
|
const MAX_ATTEMPTS = 8;
|
|
38
|
+
// An execute asked again with its Idempotency-Key: ~30 s at the default interval, so a call that
|
|
39
|
+
// can't get through fails in about that rather than four minutes.
|
|
40
|
+
const EXECUTE_ATTEMPTS = 4;
|
|
38
41
|
/** The fee payment a DEPOSIT execution asks for beside its deposit; undefined when it has none. */
|
|
39
42
|
function depositFeePayment(execution) {
|
|
40
43
|
if (execution.executionType !== "DEPOSIT" || !execution.fee)
|
|
@@ -61,6 +64,11 @@ const transient = (err) => err instanceof types_1.RavnApiError && (err.code ===
|
|
|
61
64
|
// Refused before anything was done, so safe to repeat even a call that isn't safe to repeat after
|
|
62
65
|
// a BUILD_FAILED (submit-signature: the venue may have taken the order).
|
|
63
66
|
const rateLimited = (err) => err instanceof types_1.RavnApiError && (err.code === "RATE_LIMITED" || err.code === "VENUE_RATE_LIMITED" || err.status === 429);
|
|
67
|
+
// An execute sent with an Idempotency-Key answers a repeat with its first answer, so one whose answer
|
|
68
|
+
// was lost (no response, a gateway timeout) or is still being built (UNAVAILABLE) can be asked again.
|
|
69
|
+
// Never a BUILD_FAILED (re-quote) or an unexpected error, which may have half-built an order.
|
|
70
|
+
const answerLost = (err) => rateLimited(err) ||
|
|
71
|
+
(err instanceof types_1.RavnApiError && (err.code === "NETWORK_ERROR" || err.code === "UNAVAILABLE" || err.status === 504));
|
|
64
72
|
// `promise`, or the signal's reason as soon as it aborts: a wallet or custom fetch may ignore the
|
|
65
73
|
// signal. `promise` is always handled, so one that rejects after the abort is never unhandled.
|
|
66
74
|
const orAbort = (promise, signal) => new Promise((resolve, reject) => {
|
|
@@ -96,8 +104,9 @@ const sleep = (ms, signal) => new Promise((resolve, reject) => {
|
|
|
96
104
|
* stays wallet-agnostic (no RPC dependency); this just gets the ORDER right, which quickstart's
|
|
97
105
|
* own docs flag as the most common integration failure: sending the main tx before the approval
|
|
98
106
|
* has confirmed. A failed API call is retried where repeating it is safe (status polls and
|
|
99
|
-
* submitTransaction on a 429, 5xx or no response; execute
|
|
100
|
-
*
|
|
107
|
+
* submitTransaction on a 429, 5xx or no response; execute, sent with an Idempotency-Key, also when its
|
|
108
|
+
* answer was lost or is still being built, up to 4 tries; submitSignature only when rate limited),
|
|
109
|
+
* waiting the API's retryAfterSec. Once anything
|
|
101
110
|
* is sent, it rejects only with an ExecuteAndTrackError carrying every hash; `onProgress` hears
|
|
102
111
|
* each one first.
|
|
103
112
|
*/
|
|
@@ -135,14 +144,14 @@ async function executeAndTrack(client, params, handlers, options = {}) {
|
|
|
135
144
|
throw stop.signal.reason;
|
|
136
145
|
};
|
|
137
146
|
// Calls `call` until it succeeds, fails in a way `retryable` says repeating can't fix, or
|
|
138
|
-
//
|
|
139
|
-
const withRetries = async (call, retryable) => {
|
|
147
|
+
// `attempts`: waiting the API's retryAfterSec, else an interval doubling from pollIntervalMs.
|
|
148
|
+
const withRetries = async (call, retryable, attempts = MAX_ATTEMPTS) => {
|
|
140
149
|
for (let attempt = 1;; attempt++) {
|
|
141
150
|
try {
|
|
142
151
|
return await call();
|
|
143
152
|
}
|
|
144
153
|
catch (err) {
|
|
145
|
-
if (attempt >=
|
|
154
|
+
if (attempt >= attempts || !retryable(err))
|
|
146
155
|
throw err;
|
|
147
156
|
const askedMs = (err.retryAfterSec ?? 0) * 1000;
|
|
148
157
|
await sleep(Math.max(askedMs, Math.min(pollIntervalMs * 2 ** (attempt - 1), MAX_BACKOFF_MS)), stop.signal);
|
|
@@ -151,9 +160,11 @@ async function executeAndTrack(client, params, handlers, options = {}) {
|
|
|
151
160
|
};
|
|
152
161
|
const run = async () => {
|
|
153
162
|
checkpoint();
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
|
|
163
|
+
// One Idempotency-Key for every try, so a repeat can't open a second order at the venue (a NEAR
|
|
164
|
+
// deposit address, a Houdini or Garden order). Without one (no crypto.randomUUID), only a rate
|
|
165
|
+
// limit is repeated. A BUILD_FAILED wants a fresh quote either way.
|
|
166
|
+
const key = params.idempotencyKey ?? globalThis.crypto?.randomUUID?.();
|
|
167
|
+
const execution = await withRetries(() => orAbort(client.execute({ ...params, ...(key ? { idempotencyKey: key } : {}) }), stop.signal), key ? answerLost : rateLimited, key ? EXECUTE_ATTEMPTS : MAX_ATTEMPTS);
|
|
157
168
|
const { quoteToken } = execution;
|
|
158
169
|
Object.assign(partial, { execution, quoteToken, statusRef: execution.statusRef });
|
|
159
170
|
const pollStatus = async (ref, txHash, feeSent = false) => {
|
package/dist/types.d.ts
CHANGED
|
@@ -151,6 +151,8 @@ export type ExecutionDTO = {
|
|
|
151
151
|
/** True for a sandbox quote: nothing here is real (see GetQuoteParams.sandbox); never send
|
|
152
152
|
* it. executeAndTrack broadcasts nothing for one. */
|
|
153
153
|
sandbox?: true;
|
|
154
|
+
/** This swap's id in RAVN's ledger: settlement webhooks name it (`swapId`). Absent on a sandbox quote. */
|
|
155
|
+
swapId?: string;
|
|
154
156
|
/** Pass this, not the quote's own token, to getStatus. Compose: the post-bind token. */
|
|
155
157
|
quoteToken: string;
|
|
156
158
|
/** The venue's own tracking key (compose hop-2 handle, Garden order id, Relay request id).
|
|
@@ -191,6 +193,8 @@ export type ExecutionDTO = {
|
|
|
191
193
|
/** True for a sandbox quote: nothing here is real (see GetQuoteParams.sandbox); never send
|
|
192
194
|
* it. executeAndTrack broadcasts nothing for one. */
|
|
193
195
|
sandbox?: true;
|
|
196
|
+
/** This swap's id in RAVN's ledger: settlement webhooks name it (`swapId`). Absent on a sandbox quote. */
|
|
197
|
+
swapId?: string;
|
|
194
198
|
/** Pass this, not the quote's own token, to submitSignature and getStatus. */
|
|
195
199
|
quoteToken: string;
|
|
196
200
|
statusRef?: string;
|
|
@@ -206,6 +210,8 @@ export type ExecutionDTO = {
|
|
|
206
210
|
/** True for a sandbox quote: nothing here is real (see GetQuoteParams.sandbox); never send
|
|
207
211
|
* it. executeAndTrack broadcasts nothing for one. */
|
|
208
212
|
sandbox?: true;
|
|
213
|
+
/** This swap's id in RAVN's ledger: settlement webhooks name it (`swapId`). Absent on a sandbox quote. */
|
|
214
|
+
swapId?: string;
|
|
209
215
|
/** Pass this, not the quote's own token, to getStatus. */
|
|
210
216
|
quoteToken: string;
|
|
211
217
|
/** `expiresAt` (ISO 8601), when set, is when the deposit must have ARRIVED (confirmed on
|
|
@@ -334,6 +340,11 @@ export interface ExecuteParams {
|
|
|
334
340
|
refundAddress?: string;
|
|
335
341
|
/** Your end user's IANA timezone, forwarded only to Houdini. Default UTC. */
|
|
336
342
|
clientTimezone?: string;
|
|
343
|
+
/** Sent as the Idempotency-Key header: any unique value per swap attempt (e.g. a UUID), 1–255
|
|
344
|
+
* printable ASCII characters, no spaces (execute throws INVALID_REQUEST otherwise). Calling execute again with the same key and request (quoteToken and any addresses) within a day
|
|
345
|
+
* returns the first answer instead of building again (at a deposit venue, a second deposit
|
|
346
|
+
* address). executeAndTrack sends one itself when you don't. */
|
|
347
|
+
idempotencyKey?: string;
|
|
337
348
|
}
|
|
338
349
|
export interface SubmitSignatureParams {
|
|
339
350
|
quoteToken: string;
|
|
@@ -353,7 +364,7 @@ export interface ErrorMeta {
|
|
|
353
364
|
/** Every `code` the API sends (see the API's Errors page), plus NETWORK_ERROR: no response came
|
|
354
365
|
* back at all (offline, DNS, CORS, a dropped connection). New codes can appear: compare against
|
|
355
366
|
* these, don't switch on them exhaustively. */
|
|
356
|
-
export type RavnErrorCode = "INVALID_REQUEST" | "UNSUPPORTED_TOKEN" | "UNSUPPORTED_CHAIN" | "NO_LIQUIDITY" | "QUOTE_EXPIRED" | "QUOTE_INVALID" | "QUOTE_NOT_EXECUTABLE" | "RATE_LIMITED" | "UNAUTHORIZED" | "NOT_CONFIGURED" | "NOT_FOUND" | "BUILD_FAILED" | "VENUE_RATE_LIMITED" | "ORDER_REJECTED" | "INTERNAL" | "NETWORK_ERROR";
|
|
367
|
+
export type RavnErrorCode = "INVALID_REQUEST" | "UNSUPPORTED_TOKEN" | "UNSUPPORTED_CHAIN" | "NO_LIQUIDITY" | "QUOTE_EXPIRED" | "QUOTE_INVALID" | "QUOTE_NOT_EXECUTABLE" | "RATE_LIMITED" | "UNAUTHORIZED" | "NOT_CONFIGURED" | "NOT_FOUND" | "BUILD_FAILED" | "VENUE_RATE_LIMITED" | "UNAVAILABLE" | "ORDER_REJECTED" | "INTERNAL" | "NETWORK_ERROR";
|
|
357
368
|
/** Thrown for every failed call: a non-2xx response, or none at all (NETWORK_ERROR). `code` is the
|
|
358
369
|
* stable, machine-readable string from the API. */
|
|
359
370
|
export declare class RavnApiError extends Error {
|
|
@@ -363,7 +374,7 @@ export declare class RavnApiError extends Error {
|
|
|
363
374
|
/** The HTTP status; undefined when no response came back (NETWORK_ERROR). */
|
|
364
375
|
readonly status?: number;
|
|
365
376
|
/** Seconds the API asked you to wait before trying the same call again (`details.retryAfterSec`,
|
|
366
|
-
* sent with RATE_LIMITED and
|
|
377
|
+
* sent with RATE_LIMITED, VENUE_RATE_LIMITED and UNAVAILABLE). */
|
|
367
378
|
readonly retryAfterSec?: number;
|
|
368
379
|
/** NETWORK_ERROR: the fetch error underneath. */
|
|
369
380
|
readonly cause?: unknown;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ravnexchange/sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Typed client for the RAVN integrator API (/api/v1): quote, execute, submit-signature, status.",
|
|
5
5
|
"keywords": ["ravn", "cross-chain", "swap", "swap-api", "dex-aggregator", "bitcoin", "native-btc", "solana", "evm", "crypto", "defi", "typescript", "sdk", "api-client"],
|
|
6
6
|
"main": "./dist/index.js",
|