@riftresearch/sdk 0.12.0 → 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 +3 -3
- package/dist/index.d.ts +19 -1
- package/dist/index.js +165 -54
- package/package.json +1 -1
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 {
|
|
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:
|
|
24
|
+
const publicClient = createPublicClient({ chain: mainnet, transport: http(process.env.ETH_RPC) })
|
|
25
25
|
const walletClient = createWalletClient({
|
|
26
26
|
account,
|
|
27
|
-
chain:
|
|
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 {
|
|
@@ -304,8 +308,10 @@ interface SupportedModes {
|
|
|
304
308
|
* - BTC → cbBTC: both modes supported
|
|
305
309
|
* - BTC → ERC20 (non-cbBTC): only exact_input (Rift doesn't support exact_output for chained swaps)
|
|
306
310
|
* - ERC20 → BTC: both modes supported
|
|
311
|
+
* - EVM → EVM (same chain): both modes supported
|
|
307
312
|
*/
|
|
308
313
|
declare function getSupportedModes(from: Currency, to: Currency): SupportedModes;
|
|
314
|
+
import { Chain as Chain3 } from "viem";
|
|
309
315
|
import { Account, PublicClient, Transport, WalletClient } from "viem";
|
|
310
316
|
import { Chain as Chain2 } from "viem/chains";
|
|
311
317
|
type RiftSwap = SwapStatusResponse;
|
|
@@ -404,6 +410,15 @@ type SendBitcoinFn = (params: {
|
|
|
404
410
|
amountSats: string;
|
|
405
411
|
}) => Promise<void>;
|
|
406
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
|
+
};
|
|
407
422
|
type ExecuteSwapOnExecuteStepCallback = (type: ExecuteSwapStepType) => void | Promise<void>;
|
|
408
423
|
type ExecuteSwapContext<chain extends Chain2 | undefined = Chain2 | undefined> = {
|
|
409
424
|
/** Viem PublicClient for reading chain data */
|
|
@@ -466,6 +481,9 @@ declare class RiftSdk {
|
|
|
466
481
|
* })
|
|
467
482
|
*/
|
|
468
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;
|
|
469
487
|
/**
|
|
470
488
|
* Execute a single step from the server's execution steps.
|
|
471
489
|
* Dispatch based on `action` (the execution mechanism).
|
|
@@ -501,4 +519,4 @@ declare class RiftSdk {
|
|
|
501
519
|
getSwapStatus(swapId: string): Promise<SwapStatusResponse>;
|
|
502
520
|
}
|
|
503
521
|
declare function createRiftSdk(options: RiftSdkOptions): RiftSdk;
|
|
504
|
-
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
|
@@ -142,7 +142,7 @@ function getSupportedModes(from, to) {
|
|
|
142
142
|
return { exactInput: true, exactOutput: true };
|
|
143
143
|
}
|
|
144
144
|
if (route.type === "dex_monochain") {
|
|
145
|
-
return { exactInput: true, exactOutput:
|
|
145
|
+
return { exactInput: true, exactOutput: true };
|
|
146
146
|
}
|
|
147
147
|
if (route.direction === "to_btc") {
|
|
148
148
|
return { exactInput: true, exactOutput: true };
|
|
@@ -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, `/
|
|
228
|
+
get: () => get(normalizedBaseUrl, `/order/${swapId}`),
|
|
229
229
|
tx: {
|
|
230
|
-
post: (body) => postJson(normalizedBaseUrl, `/
|
|
230
|
+
post: (body) => postJson(normalizedBaseUrl, `/order/${swapId}/tx`, body)
|
|
231
231
|
},
|
|
232
232
|
"refresh-step": {
|
|
233
|
-
post: (body) => postJson(normalizedBaseUrl, `/
|
|
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.
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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.
|
|
380
|
-
|
|
381
|
-
|
|
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) {
|