@riftresearch/sdk 0.8.0 → 0.10.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.
package/dist/index.d.ts CHANGED
@@ -148,7 +148,7 @@ type ExecutionAction = "evm_call" | "btc_transfer";
148
148
  /**
149
149
  * Step kinds grouped by action type.
150
150
  */
151
- type EvmCallKind = "approval" | "transfer_erc20" | "oneinch_swap" | "dex_swap";
151
+ type EvmCallKind = "approval" | "transfer_erc20" | "dex_swap";
152
152
  type BtcTransferKind = "transfer_btc";
153
153
  /**
154
154
  * EVM Call step - execute calldata on an EVM chain.
@@ -376,6 +376,8 @@ type SendBitcoinFn = (params: {
376
376
  /** Amount to send in satoshis */
377
377
  amountSats: string;
378
378
  }) => Promise<void>;
379
+ type ExecuteSwapStepType = "approval" | "transaction";
380
+ type ExecuteSwapOnExecuteStepCallback = (type: ExecuteSwapStepType) => void | Promise<void>;
379
381
  type ExecuteSwapContext<chain extends Chain2 | undefined = Chain2 | undefined> = {
380
382
  /** Viem PublicClient for reading chain data */
381
383
  publicClient?: PublicClient<Transport, chain>;
@@ -383,6 +385,11 @@ type ExecuteSwapContext<chain extends Chain2 | undefined = Chain2 | undefined> =
383
385
  walletClient?: WalletClient<Transport, chain, Account | undefined>;
384
386
  /** Function to send Bitcoin (implement using your preferred wallet) */
385
387
  sendBitcoin?: SendBitcoinFn;
388
+ /**
389
+ * Optional callback invoked immediately before prompting the user's wallet to
390
+ * sign/send a transaction (after estimateGas/simulation has succeeded).
391
+ */
392
+ onExecuteStep?: ExecuteSwapOnExecuteStepCallback;
386
393
  };
387
394
  type ExecuteSwapOptions<chain extends Chain2 | undefined = Chain2 | undefined> = ExecuteSwapContext<chain> & {
388
395
  /** Address to receive the output tokens */
@@ -395,6 +402,12 @@ interface RiftSdkOptions {
395
402
  apiUrl?: string;
396
403
  /** Enable verbose debug logging for swap execution */
397
404
  debug?: boolean;
405
+ /**
406
+ * Controls how EVM transactions are broadcast:
407
+ * - "sdk" (default): wallet signs via `walletClient.signTransaction`, SDK simulates and broadcasts
408
+ * - "wallet": wallet signs + broadcasts via `walletClient.sendTransaction`
409
+ */
410
+ evmBroadcastMode?: "wallet" | "sdk";
398
411
  /** Optional preflight checks before executing swaps */
399
412
  preflight?: {
400
413
  /** Check sender balance before executing EVM steps (default: true) */
@@ -408,6 +421,7 @@ declare class RiftSdk {
408
421
  private preflightCheckBalances;
409
422
  private integratorName;
410
423
  private debug;
424
+ private evmBroadcastMode;
411
425
  constructor(options: RiftSdkOptions);
412
426
  private logDebug;
413
427
  private unwrapEdenResult;
@@ -462,4 +476,4 @@ declare class RiftSdk {
462
476
  getSwapStatus(swapId: string): Promise<SwapStatusResponse>;
463
477
  }
464
478
  declare function createRiftSdk(options: RiftSdkOptions): RiftSdk;
465
- export { getSupportedModes, detectRoute, createRiftSdk, createCurrency, TokenIdentifier, SwapStatus, SwapRouterApiError, SwapRoute, SwapResult, SwapResponse, SupportedModes, SendBitcoinFn, RiftSwap, RiftSdkOptions, RiftSdk, QuoteResult, QuoteQuality, QuoteParameters, NativeToken, GetQuoteResult, ExecutionStep, ExecutionAction, ExecuteSwapOptions, EvmChain, EvmCallStep, EvmCallKind, Erc20Token, Currency, Currencies, Chain, BtcTransferStep, BtcTransferKind, BitcoinChain };
479
+ export { getSupportedModes, detectRoute, createRiftSdk, createCurrency, TokenIdentifier, SwapStatus, SwapRouterApiError, SwapRoute, SwapResult, SwapResponse, SupportedModes, SendBitcoinFn, RiftSwap, RiftSdkOptions, RiftSdk, QuoteResult, QuoteQuality, QuoteParameters, NativeToken, GetQuoteResult, ExecutionStep, ExecutionAction, ExecuteSwapStepType, ExecuteSwapOptions, ExecuteSwapOnExecuteStepCallback, EvmChain, EvmCallStep, EvmCallKind, Erc20Token, Currency, Currencies, Chain, BtcTransferStep, BtcTransferKind, BitcoinChain };
package/dist/index.js CHANGED
@@ -164,18 +164,42 @@ function createClient(baseUrl) {
164
164
  // src/sdk.ts
165
165
  var GAS_LIMIT_MULTIPLIER_NUMERATOR = 3n;
166
166
  var GAS_LIMIT_MULTIPLIER_DENOMINATOR = 2n;
167
+ var FLASHBOTS_PROTECT_RPC_URL = "https://rpc.flashbots.net";
168
+ async function sendFlashbotsRawTransaction(serializedTransaction) {
169
+ const response = await fetch(FLASHBOTS_PROTECT_RPC_URL, {
170
+ method: "POST",
171
+ headers: { "content-type": "application/json" },
172
+ body: JSON.stringify({
173
+ jsonrpc: "2.0",
174
+ id: 1,
175
+ method: "eth_sendRawTransaction",
176
+ params: [serializedTransaction]
177
+ })
178
+ });
179
+ const json = await response.json().catch(() => null);
180
+ const errorMessage = typeof json?.error?.message === "string" ? json.error.message : undefined;
181
+ if (!response.ok || errorMessage) {
182
+ throw new Error(`Flashbots submission failed: ${errorMessage ?? `HTTP ${response.status}`}`);
183
+ }
184
+ if (!json || typeof json.result !== "string") {
185
+ throw new Error("Flashbots submission failed: invalid response");
186
+ }
187
+ return json.result;
188
+ }
167
189
 
168
190
  class RiftSdk {
169
191
  riftClient;
170
192
  preflightCheckBalances;
171
193
  integratorName;
172
194
  debug;
195
+ evmBroadcastMode;
173
196
  constructor(options) {
174
197
  const baseUrl = (options.apiUrl ?? "https://api.rift.trade").replace(/\/$/, "");
175
198
  this.riftClient = createClient(baseUrl);
176
199
  this.preflightCheckBalances = options.preflight?.checkBalances !== false;
177
200
  this.integratorName = options.integratorName;
178
201
  this.debug = options.debug ?? false;
202
+ this.evmBroadcastMode = options.evmBroadcastMode ?? "sdk";
179
203
  }
180
204
  logDebug(message, data) {
181
205
  if (!this.debug)
@@ -268,15 +292,16 @@ class RiftSdk {
268
292
  kind: "kind" in step ? step.kind : undefined,
269
293
  chainId: "chainId" in step ? step.chainId : undefined
270
294
  });
271
- const result = await this.executeStep(step, context);
295
+ const result = await this.executeStep(step, context, swapResponse.swapId);
272
296
  this.logDebug("step completed", {
273
297
  stepId: step.id,
274
298
  txHash: result.txHash
275
299
  });
276
- if (isMonochain && step.action === "evm_call" && (step.kind === "dex_swap" || step.kind === "oneinch_swap") && result.txHash) {
300
+ if (step.action === "evm_call" && step.kind === "dex_swap" && result.txHash) {
277
301
  this.logDebug("reporting step result", {
278
302
  stepId: step.id,
279
- dexSwap: true
303
+ dexSwap: true,
304
+ monochain: isMonochain
280
305
  });
281
306
  this.unwrapEdenResult(await this.riftClient.swap({ swapId: swapResponse.swapId }).tx.post({
282
307
  stepId: step.id,
@@ -299,15 +324,15 @@ class RiftSdk {
299
324
  }
300
325
  };
301
326
  }
302
- async executeStep(step, context) {
327
+ async executeStep(step, context, swapId) {
303
328
  switch (step.action) {
304
329
  case "evm_call":
305
- return this.executeEvmCallStep(step, context);
330
+ return this.executeEvmCallStep(step, context, swapId);
306
331
  case "btc_transfer":
307
332
  return this.executeBtcTransferStep(step, context);
308
333
  }
309
334
  }
310
- async executeEvmCallStep(step, context) {
335
+ async executeEvmCallStep(step, context, swapId) {
311
336
  const walletClient = this.requireWalletClient(context);
312
337
  const publicClient = this.requirePublicClient(context);
313
338
  const account = walletClient.account;
@@ -325,33 +350,133 @@ class RiftSdk {
325
350
  return {};
326
351
  }
327
352
  }
328
- const txRequest = {
329
- account,
330
- to: step.to,
331
- data: step.calldata,
332
- value: step.value ? BigInt(step.value) : undefined
333
- };
334
- const estimatedGas = await publicClient.estimateGas(txRequest);
335
- const gasLimit = (estimatedGas * GAS_LIMIT_MULTIPLIER_NUMERATOR + GAS_LIMIT_MULTIPLIER_DENOMINATOR - 1n) / GAS_LIMIT_MULTIPLIER_DENOMINATOR;
336
- this.logDebug("using buffered gas limit", {
337
- stepId: step.id,
338
- estimatedGas: estimatedGas.toString(),
339
- gasLimit: gasLimit.toString()
340
- });
341
- const txHash = await walletClient.sendTransaction({
342
- ...txRequest,
343
- gas: gasLimit
344
- });
345
- const receipt = await publicClient.waitForTransactionReceipt({
346
- hash: txHash
347
- });
348
- if (receipt.status !== "success") {
349
- throw new Error(`EVM step transaction reverted (${step.kind}) with hash ${txHash}`);
353
+ let effectiveStep = step;
354
+ let refreshedForFinalSimulation = false;
355
+ while (true) {
356
+ let txRequest = {
357
+ account,
358
+ to: effectiveStep.to,
359
+ data: effectiveStep.calldata,
360
+ value: effectiveStep.value ? BigInt(effectiveStep.value) : undefined
361
+ };
362
+ let estimatedGas;
363
+ try {
364
+ estimatedGas = await publicClient.estimateGas(txRequest);
365
+ } catch (estimateError) {
366
+ if (effectiveStep.kind !== "dex_swap") {
367
+ throw estimateError;
368
+ }
369
+ this.logDebug("estimateGas failed; attempting refresh-step", {
370
+ swapId,
371
+ stepId: effectiveStep.id,
372
+ error: estimateError instanceof Error ? estimateError.message : String(estimateError)
373
+ });
374
+ let refreshed;
375
+ try {
376
+ refreshed = this.unwrapEdenResult(await this.riftClient.swap({ swapId })["refresh-step"].post({ stepId: effectiveStep.id }));
377
+ } catch (refreshError) {
378
+ throw new Error(`estimateGas failed for dex_swap step and refresh-step failed: ${refreshError instanceof Error ? refreshError.message : String(refreshError)}`);
379
+ }
380
+ if (!refreshed?.step) {
381
+ throw new Error("estimateGas failed for dex_swap step and refresh-step returned no step");
382
+ }
383
+ if (refreshed.step.kind !== "dex_swap") {
384
+ throw new Error(`refresh-step returned unexpected step kind: ${refreshed.step.kind}`);
385
+ }
386
+ effectiveStep = refreshed.step;
387
+ txRequest = {
388
+ account,
389
+ to: effectiveStep.to,
390
+ data: effectiveStep.calldata,
391
+ value: effectiveStep.value ? BigInt(effectiveStep.value) : undefined
392
+ };
393
+ estimatedGas = await publicClient.estimateGas(txRequest);
394
+ }
395
+ const gasLimit = (estimatedGas * GAS_LIMIT_MULTIPLIER_NUMERATOR + GAS_LIMIT_MULTIPLIER_DENOMINATOR - 1n) / GAS_LIMIT_MULTIPLIER_DENOMINATOR;
396
+ this.logDebug("using buffered gas limit", {
397
+ stepId: step.id,
398
+ estimatedGas: estimatedGas.toString(),
399
+ gasLimit: gasLimit.toString()
400
+ });
401
+ if (this.evmBroadcastMode === "wallet") {
402
+ await context.onExecuteStep?.(effectiveStep.kind === "approval" ? "approval" : "transaction");
403
+ const txHash2 = await walletClient.sendTransaction({
404
+ ...txRequest,
405
+ gas: gasLimit
406
+ });
407
+ const receipt2 = await publicClient.waitForTransactionReceipt({
408
+ hash: txHash2
409
+ });
410
+ if (receipt2.status !== "success") {
411
+ throw new Error(`EVM step transaction reverted (${effectiveStep.kind}) with hash ${txHash2}`);
412
+ }
413
+ return { txHash: txHash2 };
414
+ }
415
+ const nonce = await publicClient.getTransactionCount({
416
+ address: account.address,
417
+ blockTag: "pending"
418
+ });
419
+ const feeEstimate = await publicClient.estimateFeesPerGas().catch(() => {
420
+ return;
421
+ });
422
+ const feeParams = {};
423
+ if (feeEstimate?.maxFeePerGas !== undefined && feeEstimate?.maxPriorityFeePerGas !== undefined) {
424
+ feeParams.maxFeePerGas = feeEstimate.maxFeePerGas;
425
+ feeParams.maxPriorityFeePerGas = feeEstimate.maxPriorityFeePerGas;
426
+ } else if (feeEstimate?.gasPrice !== undefined) {
427
+ feeParams.gasPrice = feeEstimate.gasPrice;
428
+ }
429
+ const txToSign = {
430
+ ...txRequest,
431
+ gas: gasLimit,
432
+ nonce,
433
+ ...feeParams
434
+ };
435
+ await context.onExecuteStep?.(effectiveStep.kind === "approval" ? "approval" : "transaction");
436
+ const serializedTransaction = await walletClient.signTransaction(txToSign);
437
+ try {
438
+ const { nonce: _nonce, ...callRequest } = txToSign;
439
+ await publicClient.call({
440
+ ...callRequest,
441
+ blockTag: "pending"
442
+ });
443
+ } catch (callError) {
444
+ if (effectiveStep.kind === "dex_swap" && !refreshedForFinalSimulation) {
445
+ refreshedForFinalSimulation = true;
446
+ this.logDebug("final simulation failed for dex_swap; attempting refresh-step before broadcast", {
447
+ swapId,
448
+ stepId: effectiveStep.id,
449
+ error: callError instanceof Error ? callError.message : String(callError)
450
+ });
451
+ const refreshed = this.unwrapEdenResult(await this.riftClient.swap({ swapId })["refresh-step"].post({ stepId: effectiveStep.id }));
452
+ if (!refreshed?.step || refreshed.step.kind !== "dex_swap") {
453
+ throw new Error("refresh-step returned invalid step");
454
+ }
455
+ effectiveStep = refreshed.step;
456
+ continue;
457
+ }
458
+ throw callError;
459
+ }
460
+ let txHash;
461
+ if (effectiveStep.kind === "dex_swap" && effectiveStep.chainId === 1) {
462
+ txHash = await sendFlashbotsRawTransaction(serializedTransaction);
463
+ } else {
464
+ txHash = await publicClient.sendRawTransaction({
465
+ serializedTransaction
466
+ });
467
+ }
468
+ const receipt = await publicClient.waitForTransactionReceipt({
469
+ hash: txHash
470
+ });
471
+ if (receipt.status !== "success") {
472
+ throw new Error(`EVM step transaction reverted (${effectiveStep.kind}) with hash ${txHash}`);
473
+ }
474
+ return { txHash };
350
475
  }
351
- return { txHash };
352
476
  }
353
477
  async executeBtcTransferStep(step, context) {
354
478
  const sendBitcoin = this.requireSendBitcoin(context);
479
+ await context.onExecuteStep?.("transaction");
355
480
  await sendBitcoin({
356
481
  recipient: step.toAddress,
357
482
  amountSats: step.amountSats
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riftresearch/sdk",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
4
4
  "description": "SDK for swapping between bitcoin and evm chains",
5
5
  "license": "MIT",
6
6
  "files": [