@ravnexchange/sdk 0.2.0 → 0.2.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.
- 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 +43 -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).
|
|
@@ -186,11 +188,21 @@ export type ExecutionDTO = {
|
|
|
186
188
|
* stays with RAVN.
|
|
187
189
|
*/
|
|
188
190
|
fee?: DepositFeeDTO;
|
|
191
|
+
/** EVM: everything to send, in order (approval.reset, approval, the swap or the deposit
|
|
192
|
+
* transfer, RAVN's fee). Each once the one before has succeeded (stop if one reverts), or, when
|
|
193
|
+
* `batch` is true, all in one atomic wallet batch (EIP-5792 `wallet_sendCalls`,
|
|
194
|
+
* `atomicRequired: true`): simulate it first, and send them one by one if that fails or the
|
|
195
|
+
* last call needs more than ~400k gas. */
|
|
196
|
+
calls?: CallDTO[];
|
|
197
|
+
/** Whether `calls` (more than one) may go in one atomic wallet batch. */
|
|
198
|
+
batch?: boolean;
|
|
189
199
|
} | {
|
|
190
200
|
executionType: "SIGNATURE";
|
|
191
201
|
/** True for a sandbox quote: nothing here is real (see GetQuoteParams.sandbox); never send
|
|
192
202
|
* it. executeAndTrack broadcasts nothing for one. */
|
|
193
203
|
sandbox?: true;
|
|
204
|
+
/** This swap's id in RAVN's ledger: settlement webhooks name it (`swapId`). Absent on a sandbox quote. */
|
|
205
|
+
swapId?: string;
|
|
194
206
|
/** Pass this, not the quote's own token, to submitSignature and getStatus. */
|
|
195
207
|
quoteToken: string;
|
|
196
208
|
statusRef?: string;
|
|
@@ -206,6 +218,8 @@ export type ExecutionDTO = {
|
|
|
206
218
|
/** True for a sandbox quote: nothing here is real (see GetQuoteParams.sandbox); never send
|
|
207
219
|
* it. executeAndTrack broadcasts nothing for one. */
|
|
208
220
|
sandbox?: true;
|
|
221
|
+
/** This swap's id in RAVN's ledger: settlement webhooks name it (`swapId`). Absent on a sandbox quote. */
|
|
222
|
+
swapId?: string;
|
|
209
223
|
/** Pass this, not the quote's own token, to getStatus. */
|
|
210
224
|
quoteToken: string;
|
|
211
225
|
/** `expiresAt` (ISO 8601), when set, is when the deposit must have ARRIVED (confirmed on
|
|
@@ -215,6 +229,13 @@ export type ExecutionDTO = {
|
|
|
215
229
|
amount: string;
|
|
216
230
|
chainId?: number;
|
|
217
231
|
expiresAt?: string;
|
|
232
|
+
/** Bitcoin, from a native SegWit (bc1q…) userAddress: the deposit (and RAVN's fee as a second
|
|
233
|
+
* output) as a ready-to-sign PSBT, base64, change back to it. Absent when it can't be built. */
|
|
234
|
+
psbt?: string;
|
|
235
|
+
/** Solana, when the deposit carries RAVN's fee: deposit and fee as one unsigned transaction,
|
|
236
|
+
* base64. Sign and send it within a minute (its blockhash expires; a retry with the same
|
|
237
|
+
* Idempotency-Key returns this same one). */
|
|
238
|
+
transaction?: string;
|
|
218
239
|
};
|
|
219
240
|
/** Poll getStatus with this, not `deposit.address`. */
|
|
220
241
|
statusRef: string;
|
|
@@ -223,7 +244,22 @@ export type ExecutionDTO = {
|
|
|
223
244
|
* fee is owed to you. A fee paid on a swap the venue then refunds stays with RAVN and nothing
|
|
224
245
|
* is owed on it, so send an EVM fee payment once the deposit has confirmed. */
|
|
225
246
|
fee?: DepositFeeDTO;
|
|
247
|
+
/** EVM: everything to send, in order (approval.reset, approval, the swap or the deposit
|
|
248
|
+
* transfer, RAVN's fee). Each once the one before has succeeded (stop if one reverts), or, when
|
|
249
|
+
* `batch` is true, all in one atomic wallet batch (EIP-5792 `wallet_sendCalls`,
|
|
250
|
+
* `atomicRequired: true`): simulate it first, and send them one by one if that fails or the
|
|
251
|
+
* last call needs more than ~400k gas. */
|
|
252
|
+
calls?: CallDTO[];
|
|
253
|
+
/** Whether `calls` (more than one) may go in one atomic wallet batch. */
|
|
254
|
+
batch?: boolean;
|
|
226
255
|
};
|
|
256
|
+
/** One EVM transaction to send, as given. */
|
|
257
|
+
export interface CallDTO {
|
|
258
|
+
to: string;
|
|
259
|
+
data: string;
|
|
260
|
+
value: string;
|
|
261
|
+
chainId: number;
|
|
262
|
+
}
|
|
227
263
|
/** RAVN's integrator fee beside a deposit (or a transaction), in the input token, paid by your user
|
|
228
264
|
* to RAVN; your share is paid out as usual. `deposit.amount` (the transaction) already leaves it out. */
|
|
229
265
|
export interface DepositFeeDTO {
|
|
@@ -334,6 +370,11 @@ export interface ExecuteParams {
|
|
|
334
370
|
refundAddress?: string;
|
|
335
371
|
/** Your end user's IANA timezone, forwarded only to Houdini. Default UTC. */
|
|
336
372
|
clientTimezone?: string;
|
|
373
|
+
/** Sent as the Idempotency-Key header: any unique value per swap attempt (e.g. a UUID), 1–255
|
|
374
|
+
* 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
|
|
375
|
+
* returns the first answer instead of building again (at a deposit venue, a second deposit
|
|
376
|
+
* address). executeAndTrack sends one itself when you don't. */
|
|
377
|
+
idempotencyKey?: string;
|
|
337
378
|
}
|
|
338
379
|
export interface SubmitSignatureParams {
|
|
339
380
|
quoteToken: string;
|
|
@@ -353,7 +394,7 @@ export interface ErrorMeta {
|
|
|
353
394
|
/** Every `code` the API sends (see the API's Errors page), plus NETWORK_ERROR: no response came
|
|
354
395
|
* back at all (offline, DNS, CORS, a dropped connection). New codes can appear: compare against
|
|
355
396
|
* 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";
|
|
397
|
+
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
398
|
/** Thrown for every failed call: a non-2xx response, or none at all (NETWORK_ERROR). `code` is the
|
|
358
399
|
* stable, machine-readable string from the API. */
|
|
359
400
|
export declare class RavnApiError extends Error {
|
|
@@ -363,7 +404,7 @@ export declare class RavnApiError extends Error {
|
|
|
363
404
|
/** The HTTP status; undefined when no response came back (NETWORK_ERROR). */
|
|
364
405
|
readonly status?: number;
|
|
365
406
|
/** Seconds the API asked you to wait before trying the same call again (`details.retryAfterSec`,
|
|
366
|
-
* sent with RATE_LIMITED and
|
|
407
|
+
* sent with RATE_LIMITED, VENUE_RATE_LIMITED and UNAVAILABLE). */
|
|
367
408
|
readonly retryAfterSec?: number;
|
|
368
409
|
/** NETWORK_ERROR: the fetch error underneath. */
|
|
369
410
|
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.2",
|
|
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",
|