@riftresearch/sdk 0.12.1 → 0.12.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/README.md CHANGED
@@ -16,15 +16,15 @@ Then use it:
16
16
  import { RiftSdk, Currencies, createCurrency } from '@riftresearch/sdk'
17
17
  import { createPublicClient, createWalletClient, http } from 'viem'
18
18
  import { privateKeyToAccount } from 'viem/accounts'
19
- import { ethereum } from 'viem/chains'
19
+ import { mainnet } from 'viem/chains'
20
20
 
21
21
 
22
22
  // Setup viem clients
23
23
  const account = privateKeyToAccount('0x...')
24
- const publicClient = createPublicClient({ chain: ethereum, transport: http(process.env.ETH_RPC) })
24
+ const publicClient = createPublicClient({ chain: mainnet, transport: http(process.env.ETH_RPC) })
25
25
  const walletClient = createWalletClient({
26
26
  account,
27
- chain: ethereum,
27
+ chain: mainnet,
28
28
  transport: http(process.env.ETH_RPC),
29
29
  })
30
30
 
package/dist/index.d.ts CHANGED
@@ -128,6 +128,10 @@ interface ExactOutputQuoteResponse extends QuoteResponseBase {
128
128
  * Use `mode` to determine which fields are specified vs calculated.
129
129
  */
130
130
  type QuoteResponse = ExactInputQuoteResponse | ExactOutputQuoteResponse;
131
+ interface LimitPricing {
132
+ buyAmount: U256;
133
+ sellAmount: U256;
134
+ }
131
135
  declare const SWAP_STATUSES: readonly ["waiting_for_deposit", "deposit_confirming", "initiating_payout", "confirming_payout", "swap_complete", "refunding_user", "failed"];
132
136
  type SwapStatus = (typeof SWAP_STATUSES)[number];
133
137
  interface SwapStatusResponse {
@@ -307,6 +311,7 @@ interface SupportedModes {
307
311
  * - EVM → EVM (same chain): both modes supported
308
312
  */
309
313
  declare function getSupportedModes(from: Currency, to: Currency): SupportedModes;
314
+ import { Chain as Chain3 } from "viem";
310
315
  import { Account, PublicClient, Transport, WalletClient } from "viem";
311
316
  import { Chain as Chain2 } from "viem/chains";
312
317
  type RiftSwap = SwapStatusResponse;
@@ -405,6 +410,15 @@ type SendBitcoinFn = (params: {
405
410
  amountSats: string;
406
411
  }) => Promise<void>;
407
412
  type ExecuteSwapStepType = "approval" | "transaction" | "signature";
413
+ type CreateLimitOrderOptions<chain extends Chain2 | undefined = Chain2 | undefined> = ExecuteSwapContext<chain> & {
414
+ from: Currency;
415
+ to: Currency;
416
+ pricing: LimitPricing;
417
+ destinationAddress: string;
418
+ refundAddress?: string;
419
+ approvalMode?: "full" | "partial";
420
+ validForSeconds?: number;
421
+ };
408
422
  type ExecuteSwapOnExecuteStepCallback = (type: ExecuteSwapStepType) => void | Promise<void>;
409
423
  type ExecuteSwapContext<chain extends Chain2 | undefined = Chain2 | undefined> = {
410
424
  /** Viem PublicClient for reading chain data */
@@ -467,6 +481,9 @@ declare class RiftSdk {
467
481
  * })
468
482
  */
469
483
  getQuote(params: QuoteParameters): Promise<GetQuoteResult>;
484
+ createLimitOrder<chain extends Chain3 | undefined = Chain3 | undefined>(options: CreateLimitOrderOptions<chain>): Promise<SwapResult>;
485
+ private assertPositiveIntegerString;
486
+ private executeOrderFlow;
470
487
  /**
471
488
  * Execute a single step from the server's execution steps.
472
489
  * Dispatch based on `action` (the execution mechanism).
@@ -502,4 +519,4 @@ declare class RiftSdk {
502
519
  getSwapStatus(swapId: string): Promise<SwapStatusResponse>;
503
520
  }
504
521
  declare function createRiftSdk(options: RiftSdkOptions): RiftSdk;
505
- export { getSupportedModes, detectRoute, createRiftSdk, createCurrency, TokenIdentifier, SwapStatus, SwapRouterApiError, SwapRoute, SwapResult, SwapResponse, SupportedModes, SendBitcoinFn, RiftSwap, RiftSdkOptions, RiftSdk, QuoteResult, QuoteParameters, NativeToken, GetQuoteResult, ExecutionStep, ExecutionAction, ExecuteSwapStepType, ExecuteSwapOptions, ExecuteSwapOnExecuteStepCallback, EvmChain, EvmCallStep, EvmCallKind, Erc20Token, Currency, Currencies, Chain, BtcTransferStep, BtcTransferKind, BitcoinChain };
522
+ export { getSupportedModes, detectRoute, createRiftSdk, createCurrency, TokenIdentifier, SwapStatus, SwapRouterApiError, SwapRoute, SwapResult, SwapResponse, SupportedModes, SendBitcoinFn, RiftSwap, RiftSdkOptions, RiftSdk, QuoteResult, QuoteParameters, NativeToken, LimitPricing, GetQuoteResult, ExecutionStep, ExecutionAction, ExecuteSwapStepType, ExecuteSwapOptions, ExecuteSwapOnExecuteStepCallback, EvmChain, EvmCallStep, EvmCallKind, Erc20Token, Currency, Currencies, CreateLimitOrderOptions, Chain, BtcTransferStep, BtcTransferKind, BitcoinChain };
package/dist/index.js CHANGED
@@ -153,7 +153,7 @@ function getSupportedModes(from, to) {
153
153
  return { exactInput: true, exactOutput: false };
154
154
  }
155
155
  // src/sdk.ts
156
- import { erc20Abi } from "viem";
156
+ import { erc20Abi, isAddress } from "viem";
157
157
 
158
158
  // src/client.ts
159
159
  async function request(baseUrl, path, init) {
@@ -225,20 +225,28 @@ function createClient(baseUrl) {
225
225
  const swap = (params) => {
226
226
  const swapId = encodeURIComponent(params.swapId);
227
227
  return {
228
- get: () => get(normalizedBaseUrl, `/swap/${swapId}`),
228
+ get: () => get(normalizedBaseUrl, `/order/${swapId}`),
229
229
  tx: {
230
- post: (body) => postJson(normalizedBaseUrl, `/swap/${swapId}/tx`, body)
230
+ post: (body) => postJson(normalizedBaseUrl, `/order/${swapId}/tx`, body)
231
231
  },
232
232
  "refresh-step": {
233
- post: (body) => postJson(normalizedBaseUrl, `/swap/${swapId}/refresh-step`, body)
233
+ post: (body) => postJson(normalizedBaseUrl, `/order/${swapId}/refresh-step`, body)
234
234
  }
235
235
  };
236
236
  };
237
237
  swap.post = (body) => postJson(normalizedBaseUrl, "/swap", body);
238
+ const market = {
239
+ post: (body) => postJson(normalizedBaseUrl, "/order/market", body)
240
+ };
241
+ const limit = {
242
+ post: (body) => postJson(normalizedBaseUrl, "/order/limit", body)
243
+ };
238
244
  return {
239
245
  quote: {
240
246
  post: (body) => postJson(normalizedBaseUrl, "/quote", body)
241
247
  },
248
+ market,
249
+ limit,
242
250
  swap
243
251
  };
244
252
  }
@@ -295,7 +303,6 @@ class RiftSdk {
295
303
  throw new Error("slippageBps must be between 0 and 10000");
296
304
  }
297
305
  const route = detectRoute(params.from, params.to);
298
- const isMonochain = route.type === "dex_monochain";
299
306
  const quoteRequest = {
300
307
  type: params.mode === "exact_input" ? "EXACT_INPUT" : "EXACT_OUTPUT",
301
308
  from: params.from,
@@ -328,60 +335,164 @@ class RiftSdk {
328
335
  this.logDebug("running preflight balance check");
329
336
  await this.assertSufficientBalance(params.from, quote.from.expected, context);
330
337
  }
331
- this.logDebug("creating swap", { quoteId: riftQuote.id });
332
- const senderAddress = params.from.chain.kind === "EVM" ? this.getAddress(context) : undefined;
333
- const swapResponse = this.unwrapEdenResult(await this.riftClient.swap.post({
334
- id: riftQuote.id,
335
- ...senderAddress ? { senderAddress } : {},
336
- destinationAddress: context.destinationAddress,
337
- refundAddress,
338
- integratorName: this.integratorName,
339
- approvalMode: params.approvalMode
340
- }));
341
- this.logDebug("swap created", {
342
- swapId: swapResponse.swapId,
343
- steps: swapResponse.executionSteps.length
344
- });
345
- this.assertEvmChainMatchForSteps(swapResponse.executionSteps, context);
346
- for (const step of swapResponse.executionSteps) {
347
- this.logDebug("executing step", {
348
- stepId: step.id,
349
- action: step.action,
350
- kind: "kind" in step ? step.kind : undefined,
351
- chainId: "chainId" in step ? step.chainId : undefined
352
- });
353
- const result = await this.executeStep(step, context, swapResponse.swapId, route);
354
- this.logDebug("step completed", {
355
- stepId: step.id,
356
- txHash: result.txHash,
357
- cowswapOrderId: result.cowswapOrderId
358
- });
359
- if (this.shouldReportStepResult(step, result)) {
360
- this.logDebug("reporting step result", {
361
- stepId: step.id,
362
- kind: "kind" in step ? step.kind : undefined,
363
- monochain: isMonochain
364
- });
365
- this.unwrapEdenResult(await this.riftClient.swap({ swapId: swapResponse.swapId }).tx.post({
366
- stepId: step.id,
367
- ...result
338
+ return this.executeOrderFlow({
339
+ context,
340
+ route,
341
+ chained: isChained,
342
+ createOrder: async () => {
343
+ this.logDebug("creating market order", { quoteId: riftQuote.id });
344
+ const senderAddress = params.from.chain.kind === "EVM" ? this.getAddress(context) : undefined;
345
+ return this.unwrapEdenResult(await this.riftClient.market.post({
346
+ id: riftQuote.id,
347
+ ...senderAddress ? { senderAddress } : {},
348
+ destinationAddress: context.destinationAddress,
349
+ refundAddress,
350
+ integratorName: this.integratorName,
351
+ approvalMode: params.approvalMode
368
352
  }));
369
353
  }
370
- }
371
- this.logDebug("fetching swap status", {
372
- swapId: swapResponse.swapId
373
354
  });
374
- const swap = this.unwrapEdenResult(await this.riftClient.swap({ swapId: swapResponse.swapId }).get());
375
- this.logDebug("swap fetched", {
376
- swapId: swapResponse.swapId,
377
- status: swap.status
355
+ }
356
+ };
357
+ }
358
+ async createLimitOrder(options) {
359
+ if (!options?.destinationAddress) {
360
+ throw new Error("destinationAddress is required to create a limit order");
361
+ }
362
+ if (options.from.token.kind === "TOKEN") {
363
+ if (!isAddress(options.from.token.address)) {
364
+ throw new Error("from.token.address must be a valid EVM token address");
365
+ }
366
+ }
367
+ if (options.to.token.kind === "TOKEN" && !isAddress(options.to.token.address)) {
368
+ throw new Error("to.token.address must be a valid EVM token address");
369
+ }
370
+ this.assertPositiveIntegerString(options.pricing.buyAmount, "pricing.buyAmount");
371
+ this.assertPositiveIntegerString(options.pricing.sellAmount, "pricing.sellAmount");
372
+ if (options.validForSeconds !== undefined && (!Number.isInteger(options.validForSeconds) || options.validForSeconds <= 0)) {
373
+ throw new Error("validForSeconds must be a positive integer when provided");
374
+ }
375
+ let refundAddress;
376
+ let senderAddress;
377
+ if (options.from.chain.kind === "BITCOIN") {
378
+ if (options.from.token.kind !== "NATIVE") {
379
+ throw new Error("BTC-start limit orders must use native BTC as the input");
380
+ }
381
+ if (!options.refundAddress) {
382
+ throw new Error("refundAddress is required for BTC-start limit orders (Bitcoin refund address).");
383
+ }
384
+ if (isCbBtc(options.to)) {
385
+ throw new Error("BTC -> cbBTC limit orders are not supported");
386
+ }
387
+ refundAddress = options.refundAddress;
388
+ } else {
389
+ senderAddress = this.getAddress(options);
390
+ refundAddress = options.refundAddress ?? senderAddress;
391
+ if (!isAddress(refundAddress)) {
392
+ throw new Error("refundAddress must be a valid EVM address for EVM-start limit orders");
393
+ }
394
+ }
395
+ if (options.to.chain.kind === "BITCOIN") {
396
+ if (options.from.chain.kind !== "EVM") {
397
+ throw new Error("Bitcoin-payout limit orders require an EVM source currency.");
398
+ }
399
+ if (isCbBtc(options.from)) {
400
+ throw new Error("cbBTC -> BTC limit orders are not supported");
401
+ }
402
+ } else {
403
+ if (!isAddress(options.destinationAddress)) {
404
+ throw new Error("destinationAddress must be a valid EVM address for EVM-destination limit orders");
405
+ }
406
+ if (options.to.chain.chainId !== 1 && options.to.chain.chainId !== 8453) {
407
+ throw new Error(`Unsupported limit order chainId: ${options.to.chain.chainId}. Expected 1 or 8453.`);
408
+ }
409
+ if (options.from.chain.kind === "EVM") {
410
+ if (options.from.chain.chainId !== options.to.chain.chainId) {
411
+ throw new Error("EVM-start limit orders currently require from/to on the same EVM chain.");
412
+ }
413
+ }
414
+ }
415
+ this.logDebug("resolved limit order refund address", { refundAddress });
416
+ const route = detectRoute(options.from, options.to);
417
+ if (this.preflightCheckBalances && options.from.chain.kind === "EVM") {
418
+ this.logDebug("running limit-order preflight balance check");
419
+ await this.assertSufficientBalance(options.from, options.pricing.sellAmount, options);
420
+ }
421
+ return this.executeOrderFlow({
422
+ context: options,
423
+ route,
424
+ chained: false,
425
+ createOrder: async () => {
426
+ const request2 = {
427
+ from: options.from,
428
+ to: options.to,
429
+ ...senderAddress ? { senderAddress } : {},
430
+ pricing: options.pricing,
431
+ destinationAddress: options.destinationAddress,
432
+ refundAddress,
433
+ ...options.approvalMode ? { approvalMode: options.approvalMode } : {},
434
+ ...typeof options.validForSeconds === "number" ? { validForSeconds: options.validForSeconds } : {},
435
+ integratorName: this.integratorName
436
+ };
437
+ this.logDebug("creating limit order", {
438
+ to: request2.to,
439
+ pricing: request2.pricing
378
440
  });
379
- return this.buildSwapResult(swap, {
380
- chained: isChained,
381
- riftSwapId: swapResponse.swapId
441
+ return this.unwrapEdenResult(await this.riftClient.limit.post(request2));
442
+ }
443
+ });
444
+ }
445
+ assertPositiveIntegerString(value, field) {
446
+ if (!/^\d+$/.test(value)) {
447
+ throw new Error(`${field} must be a positive integer string`);
448
+ }
449
+ if (BigInt(value) <= 0n) {
450
+ throw new Error(`${field} must be greater than zero`);
451
+ }
452
+ }
453
+ async executeOrderFlow(params) {
454
+ const swapResponse = await params.createOrder();
455
+ this.logDebug("order created", {
456
+ swapId: swapResponse.swapId,
457
+ steps: swapResponse.executionSteps.length
458
+ });
459
+ this.assertEvmChainMatchForSteps(swapResponse.executionSteps, params.context);
460
+ for (const step of swapResponse.executionSteps) {
461
+ this.logDebug("executing step", {
462
+ stepId: step.id,
463
+ action: step.action,
464
+ kind: "kind" in step ? step.kind : undefined,
465
+ chainId: "chainId" in step ? step.chainId : undefined
466
+ });
467
+ const result = await this.executeStep(step, params.context, swapResponse.swapId, params.route);
468
+ this.logDebug("step completed", {
469
+ stepId: step.id,
470
+ txHash: result.txHash,
471
+ cowswapOrderId: result.cowswapOrderId
472
+ });
473
+ if (this.shouldReportStepResult(step, result)) {
474
+ this.logDebug("reporting step result", {
475
+ stepId: step.id,
476
+ kind: "kind" in step ? step.kind : undefined
382
477
  });
478
+ this.unwrapEdenResult(await this.riftClient.swap({ swapId: swapResponse.swapId }).tx.post({
479
+ stepId: step.id,
480
+ ...result
481
+ }));
383
482
  }
384
- };
483
+ }
484
+ this.logDebug("fetching swap status", {
485
+ swapId: swapResponse.swapId
486
+ });
487
+ const swap = this.unwrapEdenResult(await this.riftClient.swap({ swapId: swapResponse.swapId }).get());
488
+ this.logDebug("swap fetched", {
489
+ swapId: swapResponse.swapId,
490
+ status: swap.status
491
+ });
492
+ return this.buildSwapResult(swap, {
493
+ chained: params.chained,
494
+ riftSwapId: swapResponse.swapId
495
+ });
385
496
  }
386
497
  async executeStep(step, context, swapId, route) {
387
498
  switch (step.action) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riftresearch/sdk",
3
- "version": "0.12.1",
3
+ "version": "0.12.2",
4
4
  "description": "SDK for swapping between bitcoin and evm chains",
5
5
  "license": "MIT",
6
6
  "files": [