@ravnexchange/sdk 0.2.2 → 0.2.4
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/execute-and-track.d.ts +32 -3
- package/dist/execute-and-track.js +110 -29
- package/dist/types.d.ts +16 -9
- package/package.json +1 -1
|
@@ -39,7 +39,8 @@ export interface ExecuteAndTrackHandlers {
|
|
|
39
39
|
* (the same hash repeated, or just one, for one transaction). Not a batch id or a 4337
|
|
40
40
|
* userOpHash: RAVN proves its fee from that transaction. When given, a token-paid RAVN fee rides
|
|
41
41
|
* in the swap's own confirmation, and so does the swap's approval when it needs one; a
|
|
42
|
-
* native-coin fee is always a plain transaction of its own
|
|
42
|
+
* TRANSACTION's native-coin fee is always a plain transaction of its own (with sendDeposit, a
|
|
43
|
+
* DEPOSIT's calls go together only when its `batch` says so). The batch's gas limit is your
|
|
43
44
|
* wallet's (EIP-5792 carries none): give it headroom, as a wallet estimating it tight reverts
|
|
44
45
|
* the whole batch. Reject only when nothing was sent: a rejection loses the swap's hash.
|
|
45
46
|
*/
|
|
@@ -51,6 +52,20 @@ export interface ExecuteAndTrackHandlers {
|
|
|
51
52
|
* TRANSACTION carrying `submit` (broadcastViaRavn quotes), which RAVN checks and sends itself.
|
|
52
53
|
*/
|
|
53
54
|
signSolanaTransaction?(serialized: string): Promise<string>;
|
|
55
|
+
/**
|
|
56
|
+
* With `sendDeposit`: sign a Bitcoin DEPOSIT's ready-made PSBT (`deposit.psbt`, base64: the
|
|
57
|
+
* deposit, RAVN's fee as a second output, change back to the sender), finalize and broadcast it,
|
|
58
|
+
* and return its txid. Reject only when nothing was broadcast: a rejection loses its hash, and
|
|
59
|
+
* executeAndTrack called again builds a new deposit.
|
|
60
|
+
*/
|
|
61
|
+
sendBitcoinPsbt?(psbt: string): Promise<string>;
|
|
62
|
+
/**
|
|
63
|
+
* With `sendDeposit`: sign a Solana DEPOSIT's ready-made transaction (`deposit.transaction`,
|
|
64
|
+
* base64: the deposit and RAVN's fee) and send it, returning its signature. It must land within
|
|
65
|
+
* about a minute of execute (its blockhash expires). Reject only when nothing was sent: a
|
|
66
|
+
* rejection loses its signature, and executeAndTrack called again builds a new deposit.
|
|
67
|
+
*/
|
|
68
|
+
sendSolanaTransaction?(serialized: string): Promise<string>;
|
|
54
69
|
/**
|
|
55
70
|
* The API sends `approval` whenever the venue generically needs an allowance: it deliberately
|
|
56
71
|
* never reads the chain to check whether the caller already has one (that's an RPC round-trip
|
|
@@ -89,6 +104,17 @@ export interface ExecuteAndTrackOptions {
|
|
|
89
104
|
* sends is in the error's `partial`. A submitSignature or submitTransaction call already under
|
|
90
105
|
* way is awaited first: its answer is the swap's only handle. */
|
|
91
106
|
signal?: AbortSignal;
|
|
107
|
+
/**
|
|
108
|
+
* Send a DEPOSIT too, from the ready-to-sign payload RAVN returns with RAVN's fee inside it, then
|
|
109
|
+
* track it like any swap: EVM `calls` (sendCalls when `batch` allows, else the deposit, its
|
|
110
|
+
* receipt, then the fee), Bitcoin `deposit.psbt` (sendBitcoinPsbt), Solana `deposit.transaction`
|
|
111
|
+
* (sendSolanaTransaction). A deposit it can't send (no such payload, e.g. a Bitcoin sender that
|
|
112
|
+
* isn't bc1q…, or no handler for its chain) comes back unsent, as without this option, with
|
|
113
|
+
* `depositNotSent` saying why: send that one and `feePayment` yourself. Default false: a deposit
|
|
114
|
+
* is never sent from here. Turning it on, send a deposit yourself only when `depositNotSent` is
|
|
115
|
+
* set, or the user pays twice (a sandbox one has neither it nor `txHash`: it's never sent).
|
|
116
|
+
*/
|
|
117
|
+
sendDeposit?: boolean;
|
|
92
118
|
/** Called with every hash the moment it exists (approval, swap with its statusRef, fee) and with
|
|
93
119
|
* every status poll. An error it throws is ignored: it never stops a swap that's out. */
|
|
94
120
|
onProgress?(progress: ExecuteAndTrackProgress): void;
|
|
@@ -118,9 +144,12 @@ export interface ExecuteAndTrackResult {
|
|
|
118
144
|
/** Why RAVN's fee wasn't sent (or its receipt not seen): the swap still runs and is tracked, but
|
|
119
145
|
* no fee is owed to you on it unless `feeTxHash` is set and it landed. */
|
|
120
146
|
feeError?: unknown;
|
|
121
|
-
/** DEPOSIT only: the fee payment to make beside the deposit (see depositFeePayment).
|
|
147
|
+
/** DEPOSIT only: the fee payment to make beside the deposit (see depositFeePayment). Absent once
|
|
148
|
+
* sendDeposit sent the deposit: its fee went with it. */
|
|
122
149
|
feePayment?: DepositFeePayment;
|
|
123
|
-
/**
|
|
150
|
+
/** With sendDeposit, a DEPOSIT it didn't send, and why: send it and `feePayment` yourself. */
|
|
151
|
+
depositNotSent?: string;
|
|
152
|
+
/** Set once pollUntilTerminal reaches a terminal status. Absent for a DEPOSIT this didn't send, or when polling is turned off. */
|
|
124
153
|
finalStatus?: StatusDTO;
|
|
125
154
|
}
|
|
126
155
|
/** What executeAndTrack knew when it reported progress or stopped: every hash sent so far, and the
|
|
@@ -192,7 +192,7 @@ async function executeAndTrack(client, params, handlers, options = {}) {
|
|
|
192
192
|
// A sandbox execution is never real, but a venue with no sandbox stand-in returns its live
|
|
193
193
|
// payload: nothing in it is signed or sent (a signed order or Solana payment is a live
|
|
194
194
|
// instrument even though RAVN drops it). RAVN answers its status synthetically, for any ref.
|
|
195
|
-
if (execution.sandbox && execution.executionType !== "DEPOSIT") {
|
|
195
|
+
if (execution.sandbox && (execution.executionType !== "DEPOSIT" || options.sendDeposit)) {
|
|
196
196
|
const statusRef = execution.statusRef ?? SANDBOX_REF;
|
|
197
197
|
return { execution, quoteToken, statusRef, finalStatus: await pollStatus(statusRef) };
|
|
198
198
|
}
|
|
@@ -217,6 +217,26 @@ async function executeAndTrack(client, params, handlers, options = {}) {
|
|
|
217
217
|
}
|
|
218
218
|
return hash;
|
|
219
219
|
};
|
|
220
|
+
// RAVN's fee on its own, only once `hash` (the swap, or the deposit) has confirmed: a reverted,
|
|
221
|
+
// dropped or replaced one pays no fee. Declined or failed (or stopped first), the swap still
|
|
222
|
+
// runs and is tracked; no fee is owed on it. Confirmed before polling starts: a swap that fills
|
|
223
|
+
// first would otherwise end the polling on the one poll that had to prove the fee.
|
|
224
|
+
const sendFeeAfter = async (hash, chainId, fee, statusTxHash) => {
|
|
225
|
+
let feeTxHash;
|
|
226
|
+
try {
|
|
227
|
+
if (!receiptSucceeded(await orAbort(handlers.waitForReceipt(hash, chainId), stop.signal), hash)) {
|
|
228
|
+
throw new Error("executeAndTrack: no successful receipt for the swap (or deposit) transaction itself, so RAVN's fee was not sent");
|
|
229
|
+
}
|
|
230
|
+
checkpoint();
|
|
231
|
+
feeTxHash = await handlers.sendTransaction(fee);
|
|
232
|
+
report("fee_sent", { feeTxHash, statusTxHash: statusTxHash(feeTxHash) });
|
|
233
|
+
await orAbort(handlers.waitForReceipt(feeTxHash, fee.chainId), stop.signal);
|
|
234
|
+
return { feeTxHash };
|
|
235
|
+
}
|
|
236
|
+
catch (feeError) {
|
|
237
|
+
return { feeTxHash, feeError };
|
|
238
|
+
}
|
|
239
|
+
};
|
|
220
240
|
if (execution.executionType === "TRANSACTION" && execution.submit) {
|
|
221
241
|
// Never broadcast this one ourselves: a payment Layerswap can't match sits at Layerswap, and
|
|
222
242
|
// RAVN's check before sending is what catches it.
|
|
@@ -298,25 +318,8 @@ async function executeAndTrack(client, params, handlers, options = {}) {
|
|
|
298
318
|
txHash = await handlers.sendTransaction(transaction);
|
|
299
319
|
sent = true;
|
|
300
320
|
report("swap_sent", { txHash, statusRef: execution.statusRef ?? txHash, statusTxHash: proof(txHash) });
|
|
301
|
-
if (feeTx)
|
|
302
|
-
|
|
303
|
-
// no fee. Declined or failed (or stopped first), the swap still runs and is tracked; no
|
|
304
|
-
// fee is owed on it.
|
|
305
|
-
try {
|
|
306
|
-
if (!receiptSucceeded(await orAbort(handlers.waitForReceipt(txHash, transaction.chainId), stop.signal), txHash)) {
|
|
307
|
-
throw new Error("executeAndTrack: no successful receipt for the swap transaction itself, so RAVN's fee was not sent");
|
|
308
|
-
}
|
|
309
|
-
checkpoint();
|
|
310
|
-
feeTxHash = await handlers.sendTransaction(feeTx);
|
|
311
|
-
report("fee_sent", { feeTxHash, statusTxHash: proof(txHash, feeTxHash) });
|
|
312
|
-
// Confirmed before polling starts: a swap that fills first would otherwise end the
|
|
313
|
-
// polling on the one poll that had to prove the fee.
|
|
314
|
-
await orAbort(handlers.waitForReceipt(feeTxHash, feeTx.chainId), stop.signal);
|
|
315
|
-
}
|
|
316
|
-
catch (err) {
|
|
317
|
-
feeError = err;
|
|
318
|
-
}
|
|
319
|
-
}
|
|
321
|
+
if (feeTx)
|
|
322
|
+
({ feeTxHash, feeError } = await sendFeeAfter(txHash, transaction.chainId, feeTx, (f) => proof(txHash, f)));
|
|
320
323
|
}
|
|
321
324
|
// Stopped before or during the fee: say so, even with polling off (no fee was sent on it).
|
|
322
325
|
checkpoint();
|
|
@@ -357,19 +360,97 @@ async function executeAndTrack(client, params, handlers, options = {}) {
|
|
|
357
360
|
report("swap_sent", { statusRef });
|
|
358
361
|
return { execution, quoteToken, resetTxHash: partial.resetTxHash, approvalTxHash, statusRef, finalStatus: await pollStatus(statusRef) };
|
|
359
362
|
}
|
|
360
|
-
// DEPOSIT:
|
|
361
|
-
//
|
|
362
|
-
//
|
|
363
|
-
//
|
|
364
|
-
//
|
|
363
|
+
// DEPOSIT, unsent: the user moves funds to execution.deposit.address out-of-band, and this
|
|
364
|
+
// helper has no way to know when that happens. Returned without polling; start polling yourself
|
|
365
|
+
// (getStatus / useRavnStatus) once the deposit is actually sent. Pay `feePayment` as it says,
|
|
366
|
+
// pass an EVM fee transaction's hash as getStatus's `txHash`, and poll RAVN to a terminal status
|
|
367
|
+
// even if the venue's own tracker says delivered first.
|
|
365
368
|
// A fee it can't describe never stops the deposit, which fills without it.
|
|
369
|
+
const { statusRef, deposit, calls, fee } = execution;
|
|
370
|
+
const unsent = (depositNotSent) => {
|
|
371
|
+
const base = { execution, quoteToken, statusRef, ...(depositNotSent ? { depositNotSent } : {}) };
|
|
372
|
+
try {
|
|
373
|
+
return { ...base, feePayment: depositFeePayment(execution) };
|
|
374
|
+
}
|
|
375
|
+
catch (feeError) {
|
|
376
|
+
return { ...base, feeError };
|
|
377
|
+
}
|
|
378
|
+
};
|
|
366
379
|
checkpoint();
|
|
367
|
-
|
|
368
|
-
return
|
|
380
|
+
if (!options.sendDeposit)
|
|
381
|
+
return unsent();
|
|
382
|
+
// Typed away, but a newer API can answer with a type this version doesn't know: never sent.
|
|
383
|
+
const type = execution.executionType;
|
|
384
|
+
if (type !== "DEPOSIT")
|
|
385
|
+
return unsent(`executionType ${type} isn't one this SDK version can send`);
|
|
386
|
+
// sendDeposit: from the payload RAVN built, RAVN's fee inside it.
|
|
387
|
+
if (calls?.length) {
|
|
388
|
+
const feeTx = fee?.transaction;
|
|
389
|
+
const [depositTx, ...rest] = calls;
|
|
390
|
+
// As RAVN builds them: deposit.amount to deposit.address (the coin, or the token's
|
|
391
|
+
// transfer(address,uint256) call), then the fee exactly as given.
|
|
392
|
+
const same = (a, b) => a.to?.toLowerCase() === b.to?.toLowerCase() && a.data?.toLowerCase() === b.data?.toLowerCase() && BigInt(a.value ?? "0") === BigInt(b.value ?? "0");
|
|
393
|
+
const to = deposit.address.toLowerCase();
|
|
394
|
+
const amount = BigInt(deposit.amount);
|
|
395
|
+
const value = BigInt(depositTx.value ?? "0");
|
|
396
|
+
const paysDeposit = (value === amount && depositTx.to?.toLowerCase() === to && (depositTx.data ?? "0x") === "0x") ||
|
|
397
|
+
(value === 0n && depositTx.data?.toLowerCase() === `0xa9059cbb${to.slice(2).padStart(64, "0")}${amount.toString(16).padStart(64, "0")}`);
|
|
398
|
+
if (!paysDeposit || (feeTx ? !(rest.length === 1 && same(rest[0], feeTx)) : rest.length !== 0)) {
|
|
399
|
+
return unsent("its calls aren't the deposit of deposit.amount to deposit.address, then RAVN's fee as given");
|
|
400
|
+
}
|
|
401
|
+
const evmHash = (hash) => (EVM_TX_HASH.test(hash) ? hash : undefined);
|
|
402
|
+
let txHash;
|
|
403
|
+
let feeTxHash;
|
|
404
|
+
let feeError;
|
|
405
|
+
checkpoint();
|
|
406
|
+
if (feeTx && execution.batch && handlers.sendCalls) {
|
|
407
|
+
const hashes = await handlers.sendCalls(calls);
|
|
408
|
+
sent = true;
|
|
409
|
+
// Fewer hashes back than calls: the last one carried the rest.
|
|
410
|
+
if (!hashes[0])
|
|
411
|
+
throw new Error("executeAndTrack: sendCalls returned no transaction hash");
|
|
412
|
+
txHash = hashes[0];
|
|
413
|
+
feeTxHash = hashes[Math.min(1, hashes.length - 1)];
|
|
414
|
+
report("swap_sent", { txHash, statusRef });
|
|
415
|
+
report("fee_sent", { feeTxHash, statusTxHash: evmHash(feeTxHash) });
|
|
416
|
+
}
|
|
417
|
+
else {
|
|
418
|
+
txHash = await handlers.sendTransaction(depositTx);
|
|
419
|
+
sent = true;
|
|
420
|
+
report("swap_sent", { txHash, statusRef });
|
|
421
|
+
if (feeTx)
|
|
422
|
+
({ feeTxHash, feeError } = await sendFeeAfter(txHash, depositTx.chainId, feeTx, evmHash));
|
|
423
|
+
}
|
|
424
|
+
checkpoint();
|
|
425
|
+
const statusTxHash = feeTxHash && evmHash(feeTxHash);
|
|
426
|
+
if (feeTxHash && !statusTxHash) {
|
|
427
|
+
feeError ?? (feeError = new Error(`executeAndTrack: ${feeTxHash} is not an on-chain transaction hash, so RAVN can't prove the fee`));
|
|
428
|
+
}
|
|
429
|
+
partial.feeError = feeError;
|
|
430
|
+
return {
|
|
431
|
+
execution, quoteToken, txHash, statusRef, statusTxHash, feeTxHash, feeError,
|
|
432
|
+
finalStatus: await pollStatus(statusRef, statusTxHash, !!statusTxHash),
|
|
433
|
+
};
|
|
369
434
|
}
|
|
370
|
-
|
|
371
|
-
|
|
435
|
+
// Bitcoin and Solana: one transaction pays the deposit and RAVN's fee; RAVN finds the fee there.
|
|
436
|
+
const payload = deposit.psbt ?? deposit.transaction;
|
|
437
|
+
if (!payload) {
|
|
438
|
+
const chainId = deposit.chainId ?? fee?.chainId;
|
|
439
|
+
return unsent(chainId === BITCOIN_CHAIN_ID
|
|
440
|
+
? "RAVN returned no PSBT for it (it builds one only from a bc1q… userAddress, when it can within a few seconds)"
|
|
441
|
+
: chainId === SOLANA_CHAIN_ID
|
|
442
|
+
? "RAVN returned no transaction for it (it builds one only for a deposit carrying RAVN's fee, when it can within a few seconds)"
|
|
443
|
+
: "RAVN returned nothing ready to sign for it");
|
|
372
444
|
}
|
|
445
|
+
const handler = deposit.psbt ? "sendBitcoinPsbt" : "sendSolanaTransaction";
|
|
446
|
+
if (!handlers[handler])
|
|
447
|
+
return unsent(`no ${handler} handler was provided`);
|
|
448
|
+
checkpoint();
|
|
449
|
+
// Called as a method: a class-instance wallet needs its `this`.
|
|
450
|
+
const txHash = await handlers[handler](payload);
|
|
451
|
+
sent = true;
|
|
452
|
+
report("swap_sent", { txHash, statusRef });
|
|
453
|
+
return { execution, quoteToken, txHash, statusRef, finalStatus: await pollStatus(statusRef) };
|
|
373
454
|
};
|
|
374
455
|
try {
|
|
375
456
|
return await run();
|
package/dist/types.d.ts
CHANGED
|
@@ -65,7 +65,8 @@ export interface QuoteDTO {
|
|
|
65
65
|
* minimum shown can no longer be met). */
|
|
66
66
|
expiresAt: number;
|
|
67
67
|
/** Present on 2-hop (`venue.id === "compose"`) and Relay dest-call quotes. Sign hop 1; hop 2 is
|
|
68
|
-
* a unique deposit, not a second wallet tx.
|
|
68
|
+
* a unique deposit, not a second wallet tx. The stablecoin route also sweeps its leftover through
|
|
69
|
+
* 0x (`0x_swap`), which isn't listed here. */
|
|
69
70
|
hops?: {
|
|
70
71
|
id: string;
|
|
71
72
|
name: string;
|
|
@@ -331,16 +332,22 @@ export interface GetQuoteParams {
|
|
|
331
332
|
* wait; a quote with slippage.noMinimum counts only 2000 ms past the limit). A positive integer;
|
|
332
333
|
* default 6000, clamped to 1000–15000. An API that predates it ignores it. */
|
|
333
334
|
timeoutMs?: number;
|
|
334
|
-
/**
|
|
335
|
+
/** Venues never to use, e.g. a risk objection to one of them. An excluded venue is used
|
|
336
|
+
* nowhere in the route, including the steps of RAVN's multi-step routes (compose, relay quotes
|
|
337
|
+
* with hops); a multi-step route that needs it isn't offered. Venues that swap for you may still
|
|
338
|
+
* route through it on their own (see onlyVenues). */
|
|
335
339
|
excludeVenues?: string[];
|
|
336
340
|
/** Race only these venues, e.g. ["bebop"] for an RFQ-only race. Every other venue is left
|
|
337
|
-
* out, including ones RAVN adds later; excludeVenues still applies on top.
|
|
338
|
-
* venue
|
|
339
|
-
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
342
|
-
*
|
|
343
|
-
*
|
|
341
|
+
* out, including ones RAVN adds later; excludeVenues still applies on top. It covers every
|
|
342
|
+
* venue RAVN routes funds through: a multi-step route is offered only when its own venue.id and
|
|
343
|
+
* every venue in its hops are listed, e.g. ["compose", "jupiter", "relay"] or
|
|
344
|
+
* ["relay", "kyberswap"]. RAVN's stablecoin route also needs "0x_swap" beside its hops: it
|
|
345
|
+
* sweeps the leftover. Venues that swap for you may route through other venues of their own
|
|
346
|
+
* choosing, which neither list can reach: Layerswap (a Solana token often goes through Jupiter
|
|
347
|
+
* first), Relay, Rift, Across (when it swaps before or after bridging) and Mayan (Jupiter on
|
|
348
|
+
* Solana, other DEXs such as Nordstern on EVM). An empty list is rejected (INVALID_REQUEST); if
|
|
349
|
+
* none of them can quote, NO_LIQUIDITY lists every left-out venue in
|
|
350
|
+
* error.details.excludedVenues. */
|
|
344
351
|
onlyVenues?: string[];
|
|
345
352
|
/** Settle privately: the race narrows to NEAR Intents (Confidential Intents) and Houdini Swap,
|
|
346
353
|
* and NO_LIQUIDITY replaces a public fill if neither can quote. Houdini is sent the request's IP,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ravnexchange/sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
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",
|