@bitflowlabs/core-sdk 2.3.0 → 2.3.2-beta.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/src/BitflowSDK.ts DELETED
@@ -1,1244 +0,0 @@
1
- import { STACKS_MAINNET } from '@stacks/network';
2
- import type { StacksProvider } from '@stacks/connect';
3
- import { validateStacksAddress } from '@stacks/transactions';
4
- import {
5
- SwapContext,
6
- Token,
7
- SwapOptions,
8
- SelectedSwapRoute,
9
- QuoteResult,
10
- SwapExecutionData,
11
- RouteQuote,
12
- SwapDataParamsAndPostConditions,
13
- BitflowSDKConfig,
14
- } from './types';
15
- import { fetchAllTokensFromAPI } from './helpers/fetchDataHelper';
16
- import { executeSwapHelper } from './helpers/callSwapHelper';
17
- import {
18
- callReadOnlyFunctionHelper,
19
- callReadOnlyFunctionHelperWithoutScaling,
20
- } from './helpers/callReadOnlyHelper';
21
- import { fetchPossibleSwapsFromAPI } from './helpers/fetchPossibleSwap';
22
- import { getContractInterfaceAndFunction } from './helpers/getContractInterfaceAndFunction';
23
- import { configs, validateConfig } from './config';
24
- import { executeGetParams } from './helpers/callGetSwapParams';
25
- import {
26
- ActionFunctionArgs,
27
- AggregatorRouteData,
28
- ALLOWED_CONTRACTS,
29
- CancelGroupOrderResponse,
30
- CancelOrderResponse,
31
- CreateGroupOrderParams,
32
- CreateGroupOrderResponse,
33
- CreateOrderParams,
34
- CreateOrderResponse,
35
- GetGroupOrderResponse,
36
- GetKeeperContractParams,
37
- GetKeeperContractResponse,
38
- GetOrderResponse,
39
- GetQuoteParams,
40
- GetQuoteResponse,
41
- GetUserResponse,
42
- KeeperOrderRouteData,
43
- } from './keeper/types';
44
- import {
45
- cancelGroupOrderAPI,
46
- cancelOrderAPI,
47
- createGroupOrderAPI,
48
- createOrderAPI,
49
- getGroupOrderAPI,
50
- getOrCreateKeeperContractAPI,
51
- getOrderAPI,
52
- getQuoteAPI,
53
- getUserAPI,
54
- } from './keeper/keeperAPI';
55
-
56
- export const safeStringify = (obj: any, indent = 2) => {
57
- return JSON.stringify(
58
- obj,
59
- (_, value) => (typeof value === 'bigint' ? value.toString() + 'n' : value),
60
- indent
61
- );
62
- };
63
-
64
- export class BitflowSDK {
65
- private context: SwapContext;
66
- private stacksProvider: StacksProvider | undefined;
67
-
68
- constructor(config?: Partial<BitflowSDKConfig>) {
69
- if (config) Object.assign(configs, config);
70
-
71
- validateConfig();
72
-
73
- this.context = {
74
- availableTokens: [],
75
- contractInterfaces: {},
76
- functionArgs: {},
77
- network: STACKS_MAINNET,
78
- swapOptions: {},
79
- };
80
-
81
- this.initializeContext();
82
- }
83
- private async loadConnectDependencies() {
84
- if (typeof window === 'undefined') {
85
- throw new Error(
86
- 'Connect features are only available in browser environments'
87
- );
88
- }
89
-
90
- try {
91
- const { getStacksProvider } = await import('@stacks/connect');
92
- if (!this.stacksProvider) {
93
- this.stacksProvider = await getStacksProvider();
94
- }
95
- return this.stacksProvider;
96
- } catch (error) {
97
- console.error('Error loading Stacks Connect:', error);
98
- throw new Error('Failed to load Stacks Connect dependencies');
99
- }
100
- }
101
- private async initializeContext(): Promise<void> {
102
- this.context.availableTokens = await fetchAllTokensFromAPI();
103
- }
104
-
105
- public async getAvailableTokens(): Promise<Token[]> {
106
- if (this.context.availableTokens.length === 0) {
107
- await this.initializeContext();
108
- }
109
- return this.context.availableTokens;
110
- }
111
-
112
- public async getKeeperTokens(): Promise<Token[]> {
113
- if (this.context.availableTokens.length === 0) {
114
- await this.initializeContext();
115
- }
116
- return this.context.availableTokens.filter((token) => token.isKeeperToken);
117
- }
118
-
119
- public async getPossibleSwaps(tokenX: string): Promise<SwapOptions> {
120
- if (!this.context.swapOptions[tokenX]) {
121
- this.context.swapOptions[tokenX] = await fetchPossibleSwapsFromAPI(
122
- tokenX
123
- );
124
- }
125
- return this.context.swapOptions[tokenX];
126
- }
127
-
128
- public async getKeeperPossibleSwaps(tokenX: string): Promise<SwapOptions> {
129
- // Use a separate cache key for keeper routes
130
- const cacheKey = `keeper_${tokenX}`;
131
- if (!this.context.swapOptions[cacheKey]) {
132
- this.context.swapOptions[cacheKey] = await fetchPossibleSwapsFromAPI(
133
- tokenX,
134
- 'KEEPER'
135
- );
136
- }
137
- return this.context.swapOptions[cacheKey];
138
- }
139
-
140
- public async getAllPossibleTokenY(tokenX: string): Promise<string[]> {
141
- const swapOptions = await this.getPossibleSwaps(tokenX);
142
- return Object.keys(swapOptions);
143
- }
144
-
145
- public async getAllKeeperPossibleTokenY(tokenX: string): Promise<string[]> {
146
- const swapOptions = await this.getKeeperPossibleSwaps(tokenX);
147
- return Object.keys(swapOptions);
148
- }
149
-
150
- public async getAllPossibleTokenYRoutes(
151
- tokenX: string,
152
- tokenY: string
153
- ): Promise<SelectedSwapRoute[]> {
154
- const swapOptions = await this.getPossibleSwaps(tokenX);
155
- return swapOptions[tokenY] || [];
156
- }
157
-
158
- public async getAllKeeperPossibleTokenYRoutes(
159
- tokenX: string,
160
- tokenY: string
161
- ): Promise<SelectedSwapRoute[]> {
162
- const swapOptions = await this.getKeeperPossibleSwaps(tokenX);
163
- return swapOptions[tokenY] || [];
164
- }
165
-
166
- public async getQuoteForRoute(
167
- tokenX: string,
168
- tokenY: string,
169
- amountInput: number
170
- ): Promise<QuoteResult> {
171
- const providerAddress = configs.BITFLOW_PROVIDER_ADDRESS;
172
-
173
- const routes = await this.getAllPossibleTokenYRoutes(tokenX, tokenY);
174
- const allRoutes: RouteQuote[] = [];
175
-
176
- for (let routeIndex = 0; routeIndex < routes.length; routeIndex++) {
177
- const route = routes[routeIndex];
178
- try {
179
- if (!route.quoteData) {
180
- console.warn(
181
- `Skipping route ${routeIndex + 1} due to null quoteData:`,
182
- route
183
- );
184
- continue;
185
- }
186
-
187
- const {
188
- contract,
189
- function: functionName,
190
- parameters,
191
- } = route.quoteData;
192
-
193
- if (!contract || !functionName || !parameters) {
194
- console.warn(
195
- `Skipping route ${
196
- routeIndex + 1
197
- } due to missing required properties:`,
198
- route.quoteData
199
- );
200
- continue;
201
- }
202
-
203
- const [contractDeployer, contractName] = contract.split('.');
204
-
205
- if (!this.context.contractInterfaces[contract]) {
206
- this.context.contractInterfaces[contract] =
207
- await getContractInterfaceAndFunction(
208
- contractDeployer,
209
- contractName,
210
- functionName
211
- );
212
- }
213
- const { interface: contractInterface, functionArgs } =
214
- this.context.contractInterfaces[contract];
215
-
216
- const params = { ...parameters };
217
-
218
- if ('dx' in params && params.dx === null) {
219
- params.dx = amountInput;
220
- } else if ('dy' in params && params.dy === null) {
221
- params.dy = amountInput;
222
- } else if ('amount' in params && params.amount === null) {
223
- params.amount = amountInput;
224
- } else if ('amt-in' in params && params['amt-in'] === null) {
225
- params['amt-in'] = amountInput;
226
- } else if ('amt-in-max' in params && params['amt-in-max'] === null) {
227
- params['amt-in-max'] = amountInput;
228
- } else if ('y-amount' in params && params['y-amount'] === null) {
229
- params['y-amount'] = amountInput;
230
- params['x-amount'] = amountInput;
231
- } else if ('x-amount' in params && params['x-amount'] === null) {
232
- params['x-amount'] = amountInput;
233
- } else {
234
- params.dx = amountInput;
235
- }
236
-
237
- // Robust provider check
238
- const functionRequiresProvider = functionArgs.some((arg: { name: string; }) => arg.name === 'provider');
239
- if (
240
- functionRequiresProvider &&
241
- (params.provider === null || params.provider === undefined) &&
242
- providerAddress && validateStacksAddress(providerAddress)
243
- ) {
244
- params.provider = providerAddress;
245
- }
246
-
247
- const { convertedResult, rawResult, tokenXDecimals, tokenYDecimals } =
248
- await callReadOnlyFunctionHelper(
249
- contractDeployer,
250
- contractName,
251
- functionName,
252
- params,
253
- contractDeployer,
254
- tokenX,
255
- tokenY,
256
- route.swapData,
257
- this.context
258
- );
259
-
260
- if (typeof convertedResult === 'number' && convertedResult > 0) {
261
- const updatedQuoteData = {
262
- ...route.quoteData,
263
- parameters: { ...params },
264
- };
265
-
266
- const updatedSwapData = {
267
- ...route.swapData,
268
- parameters: {
269
- ...route.swapData.parameters,
270
- amount:
271
- params.amount ||
272
- params.dx ||
273
- params['amt-in'] ||
274
- params['amt-in-max'] ||
275
- params['y-amount'] ||
276
- params['x-amount'] ||
277
- params.dy,
278
- dx:
279
- params.amount ||
280
- params.dx ||
281
- params['amt-in'] ||
282
- params['amt-in-max'] ||
283
- params['y-amount'] ||
284
- params['x-amount'] ||
285
- params.dy,
286
- dy:
287
- params.amount ||
288
- params.dx ||
289
- params['amt-in'] ||
290
- params['amt-in-max'] ||
291
- params['y-amount'] ||
292
- params['x-amount'] ||
293
- params.dy,
294
- 'amt-in':
295
- params.amount ||
296
- params.dx ||
297
- params['amt-in'] ||
298
- params['amt-in-max'] ||
299
- params['y-amount'] ||
300
- params['x-amount'] ||
301
- params.dy,
302
- 'amt-in-max':
303
- params.amount ||
304
- params.dx ||
305
- params['amt-in'] ||
306
- params['amt-in-max'] ||
307
- params['y-amount'] ||
308
- params['x-amount'] ||
309
- params.dy,
310
- 'y-amount':
311
- params.amount ||
312
- params.dx ||
313
- params['amt-in'] ||
314
- params['amt-in-max'] ||
315
- params['y-amount'] ||
316
- params['x-amount'] ||
317
- params.dy,
318
- 'x-amount':
319
- params.amount ||
320
- params.dx ||
321
- params['amt-in'] ||
322
- params['amt-in-max'] ||
323
- params['y-amount'] ||
324
- params['x-amount'] ||
325
- params.dy,
326
-
327
- 'min-received': rawResult,
328
- 'min-dy': rawResult,
329
- 'min-dz': rawResult,
330
- 'min-dw': rawResult,
331
- 'amt-out': rawResult,
332
- 'amt-out-min': rawResult,
333
- 'min-x-amount': rawResult,
334
- 'min-dv': rawResult,
335
- 'min-y-amount': rawResult,
336
- 'min-dx': rawResult,
337
- 'provider':
338
- providerAddress ||
339
- params.provider
340
- },
341
- };
342
-
343
- const quoteResult: RouteQuote = {
344
- route: {
345
- ...route,
346
- quoteData: updatedQuoteData,
347
- swapData: updatedSwapData,
348
- },
349
- quote: convertedResult,
350
- params: params,
351
- quoteData: updatedQuoteData,
352
- swapData: updatedSwapData,
353
- dexPath: route.dex_path,
354
- tokenPath: route.token_path,
355
- tokenXDecimals: tokenXDecimals,
356
- tokenYDecimals: tokenYDecimals,
357
- };
358
-
359
- allRoutes.push(quoteResult);
360
- } else {
361
- throw new Error('Invalid quote result');
362
- }
363
- } catch (error) {
364
- console.warn(
365
- `Failed to get quote for route ${routeIndex + 1}:`,
366
- route,
367
- error
368
- );
369
- allRoutes.push({
370
- route,
371
- quote: null,
372
- params: route.quoteData
373
- ? { ...route.quoteData.parameters, amountInput }
374
- : { amountInput },
375
- quoteData: route.quoteData,
376
- swapData: route.swapData,
377
- dexPath: route.dex_path,
378
- tokenPath: route.token_path,
379
- tokenXDecimals: route.tokenXDecimals,
380
- tokenYDecimals: route.tokenYDecimals,
381
- error: (error as Error).message,
382
- });
383
- }
384
- }
385
-
386
- allRoutes.sort((a, b) => (b.quote || 0) - (a.quote || 0));
387
-
388
- const result = {
389
- bestRoute: allRoutes[0]?.quote !== null ? allRoutes[0] : null,
390
- allRoutes,
391
- inputData: { tokenX, tokenY, amountInput, provider: providerAddress },
392
- };
393
-
394
- return result;
395
- }
396
-
397
- public async getKeeperQuoteForRoute(
398
- tokenX: string,
399
- tokenY: string,
400
- amountInput: number
401
- ): Promise<QuoteResult> {
402
- const COMPATIBLE_DEX_PATHS = new Set([
403
- 'BITFLOW_STABLE_XY_2',
404
- 'BITFLOW_XYK_XY_2',
405
- ]);
406
-
407
- const isCompatibleRoute = (dexPath: string[]): boolean => {
408
- return dexPath.every((path) => COMPATIBLE_DEX_PATHS.has(path));
409
- };
410
-
411
- let routes = await this.getAllPossibleTokenYRoutes(tokenX, tokenY);
412
- routes = routes.filter((route) => isCompatibleRoute(route.dex_path));
413
-
414
- const allRoutes: RouteQuote[] = [];
415
-
416
- for (let routeIndex = 0; routeIndex < routes.length; routeIndex++) {
417
- const route = routes[routeIndex];
418
-
419
- try {
420
- if (!route.quoteData) {
421
- console.warn(
422
- `Skipping route ${routeIndex + 1} due to null quoteData:`,
423
- route
424
- );
425
- continue;
426
- }
427
-
428
- const {
429
- contract,
430
- function: functionName,
431
- parameters,
432
- } = route.quoteData;
433
-
434
- if (!contract || !functionName || !parameters) {
435
- console.warn(
436
- `Skipping route ${
437
- routeIndex + 1
438
- } due to missing required properties:`,
439
- route.quoteData
440
- );
441
- continue;
442
- }
443
-
444
- const [contractDeployer, contractName] = contract.split('.');
445
-
446
- if (!this.context.contractInterfaces[contract]) {
447
- this.context.contractInterfaces[contract] =
448
- await getContractInterfaceAndFunction(
449
- contractDeployer,
450
- contractName,
451
- functionName
452
- );
453
- }
454
- const { interface: contractInterface, functionArgs } =
455
- this.context.contractInterfaces[contract];
456
-
457
- const params = { ...parameters };
458
-
459
- if ('dx' in params && params.dx === null) {
460
- params.dx = amountInput;
461
- } else if ('dy' in params && params.dy === null) {
462
- params.dy = amountInput;
463
- } else if ('amount' in params && params.amount === null) {
464
- params.amount = amountInput;
465
- } else if ('amt-in' in params && params['amt-in'] === null) {
466
- params['amt-in'] = amountInput;
467
- } else if ('amt-in-max' in params && params['amt-in-max'] === null) {
468
- params['amt-in-max'] = amountInput;
469
- } else if ('y-amount' in params && params['y-amount'] === null) {
470
- params['y-amount'] = amountInput;
471
- params['x-amount'] = amountInput;
472
- } else if ('x-amount' in params && params['x-amount'] === null) {
473
- params['x-amount'] = amountInput;
474
- } else {
475
- params.dx = amountInput;
476
- }
477
-
478
- const { convertedResult, rawResult, tokenXDecimals, tokenYDecimals } =
479
- await callReadOnlyFunctionHelper(
480
- contractDeployer,
481
- contractName,
482
- functionName,
483
- params,
484
- contractDeployer,
485
- tokenX,
486
- tokenY,
487
- route.swapData,
488
- this.context
489
- );
490
-
491
- if (typeof convertedResult === 'number' && convertedResult > 0) {
492
- const updatedQuoteData = {
493
- ...route.quoteData,
494
- parameters: { ...params },
495
- };
496
-
497
- const updatedSwapData = {
498
- ...route.swapData,
499
- parameters: {
500
- ...route.swapData.parameters,
501
- amount:
502
- params.amount ||
503
- params.dx ||
504
- params['amt-in'] ||
505
- params['amt-in-max'] ||
506
- params['y-amount'] ||
507
- params['x-amount'] ||
508
- params.dy,
509
- dx:
510
- params.amount ||
511
- params.dx ||
512
- params['amt-in'] ||
513
- params['amt-in-max'] ||
514
- params['y-amount'] ||
515
- params['x-amount'] ||
516
- params.dy,
517
- 'amt-in':
518
- params.amount ||
519
- params.dx ||
520
- params['amt-in'] ||
521
- params['amt-in-max'] ||
522
- params['y-amount'] ||
523
- params['x-amount'] ||
524
- params.dy,
525
- 'amt-in-max':
526
- params.amount ||
527
- params.dx ||
528
- params['amt-in'] ||
529
- params['amt-in-max'] ||
530
- params['y-amount'] ||
531
- params['x-amount'] ||
532
- params.dy,
533
- 'y-amount':
534
- params.amount ||
535
- params.dx ||
536
- params['amt-in'] ||
537
- params['amt-in-max'] ||
538
- params['y-amount'] ||
539
- params['x-amount'] ||
540
- params.dy,
541
- 'x-amount':
542
- params.amount ||
543
- params.dx ||
544
- params['amt-in'] ||
545
- params['amt-in-max'] ||
546
- params['y-amount'] ||
547
- params['x-amount'] ||
548
- params.dy,
549
- 'min-received': rawResult,
550
- 'min-dy': rawResult,
551
- 'min-dz': rawResult,
552
- 'min-dw': rawResult,
553
- 'amt-out': rawResult,
554
- 'amt-out-min': rawResult,
555
- 'min-x-amount': rawResult,
556
- 'min-dv': rawResult,
557
- 'min-y-amount': rawResult,
558
- 'min-dx': rawResult,
559
- },
560
- };
561
-
562
- const quoteResult: RouteQuote = {
563
- route: {
564
- ...route,
565
- quoteData: updatedQuoteData,
566
- swapData: updatedSwapData,
567
- },
568
- quote: convertedResult,
569
- params: params,
570
- quoteData: updatedQuoteData,
571
- swapData: updatedSwapData,
572
- dexPath: route.dex_path,
573
- tokenPath: route.token_path,
574
- tokenXDecimals: tokenXDecimals,
575
- tokenYDecimals: tokenYDecimals,
576
- };
577
-
578
- allRoutes.push(quoteResult);
579
- } else {
580
- throw new Error('Invalid quote result');
581
- }
582
- } catch (error) {
583
- console.warn(
584
- `Failed to get quote for route ${routeIndex + 1}:`,
585
- route,
586
- error
587
- );
588
- allRoutes.push({
589
- route,
590
- quote: null,
591
- params: route.quoteData
592
- ? { ...route.quoteData.parameters, amountInput }
593
- : { amountInput },
594
- quoteData: route.quoteData,
595
- swapData: route.swapData,
596
- dexPath: route.dex_path,
597
- tokenPath: route.token_path,
598
- tokenXDecimals: route.tokenXDecimals,
599
- tokenYDecimals: route.tokenYDecimals,
600
- error: (error as Error).message,
601
- });
602
- }
603
- }
604
-
605
- allRoutes.sort((a, b) => (b.quote || 0) - (a.quote || 0));
606
- const result = {
607
- bestRoute: allRoutes[0]?.quote !== null ? allRoutes[0] : null,
608
- allRoutes,
609
- inputData: { tokenX, tokenY, amountInput },
610
- };
611
-
612
- return result;
613
- }
614
-
615
- public async getKeeperQuoteForRouteWithoutScaling(
616
- tokenX: string,
617
- tokenY: string,
618
- amountInput: number
619
- ): Promise<QuoteResult> {
620
- const COMPATIBLE_DEX_PATHS = new Set([
621
- 'BITFLOW_STABLE_XY_2',
622
- 'BITFLOW_XYK_XY_2',
623
- ]);
624
-
625
- const isCompatibleRoute = (dexPath: string[]): boolean => {
626
- return dexPath.every((path) => COMPATIBLE_DEX_PATHS.has(path));
627
- };
628
-
629
- let routes = await this.getAllPossibleTokenYRoutes(tokenX, tokenY);
630
- routes = routes.filter((route) => isCompatibleRoute(route.dex_path));
631
-
632
- const allRoutes: RouteQuote[] = [];
633
-
634
- for (let routeIndex = 0; routeIndex < routes.length; routeIndex++) {
635
- const route = routes[routeIndex];
636
-
637
- try {
638
- if (!route.quoteData) {
639
- console.warn(
640
- `Skipping route ${routeIndex + 1} due to null quoteData:`,
641
- route
642
- );
643
- continue;
644
- }
645
-
646
- const {
647
- contract,
648
- function: functionName,
649
- parameters,
650
- } = route.quoteData;
651
-
652
- if (!contract || !functionName || !parameters) {
653
- console.warn(
654
- `Skipping route ${
655
- routeIndex + 1
656
- } due to missing required properties:`,
657
- route.quoteData
658
- );
659
- continue;
660
- }
661
-
662
- const [contractDeployer, contractName] = contract.split('.');
663
-
664
- if (!this.context.contractInterfaces[contract]) {
665
- this.context.contractInterfaces[contract] =
666
- await getContractInterfaceAndFunction(
667
- contractDeployer,
668
- contractName,
669
- functionName
670
- );
671
- }
672
- const { interface: contractInterface, functionArgs } =
673
- this.context.contractInterfaces[contract];
674
-
675
- const params = { ...parameters };
676
-
677
- if ('dx' in params && params.dx === null) {
678
- params.dx = amountInput;
679
- } else if ('dy' in params && params.dy === null) {
680
- params.dy = amountInput;
681
- } else if ('amount' in params && params.amount === null) {
682
- params.amount = amountInput;
683
- } else if ('amt-in' in params && params['amt-in'] === null) {
684
- params['amt-in'] = amountInput;
685
- } else if ('amt-in-max' in params && params['amt-in-max'] === null) {
686
- params['amt-in-max'] = amountInput;
687
- } else if ('y-amount' in params && params['y-amount'] === null) {
688
- params['y-amount'] = amountInput;
689
- params['x-amount'] = amountInput;
690
- } else if ('x-amount' in params && params['x-amount'] === null) {
691
- params['x-amount'] = amountInput;
692
- } else {
693
- params.dx = amountInput;
694
- }
695
-
696
- const { convertedResult, rawResult, tokenXDecimals, tokenYDecimals } =
697
- await callReadOnlyFunctionHelperWithoutScaling(
698
- contractDeployer,
699
- contractName,
700
- functionName,
701
- params,
702
- contractDeployer,
703
- tokenX,
704
- tokenY,
705
- route.swapData,
706
- this.context
707
- );
708
-
709
- if (typeof convertedResult === 'number' && convertedResult > 0) {
710
- const updatedQuoteData = {
711
- ...route.quoteData,
712
- parameters: { ...params },
713
- };
714
-
715
- const updatedSwapData = {
716
- ...route.swapData,
717
- parameters: {
718
- ...route.swapData.parameters,
719
- amount:
720
- params.amount ||
721
- params.dx ||
722
- params['amt-in'] ||
723
- params['amt-in-max'] ||
724
- params['y-amount'] ||
725
- params['x-amount'] ||
726
- params.dy,
727
- dx:
728
- params.amount ||
729
- params.dx ||
730
- params['amt-in'] ||
731
- params['amt-in-max'] ||
732
- params['y-amount'] ||
733
- params['x-amount'] ||
734
- params.dy,
735
- 'amt-in':
736
- params.amount ||
737
- params.dx ||
738
- params['amt-in'] ||
739
- params['amt-in-max'] ||
740
- params['y-amount'] ||
741
- params['x-amount'] ||
742
- params.dy,
743
- 'amt-in-max':
744
- params.amount ||
745
- params.dx ||
746
- params['amt-in'] ||
747
- params['amt-in-max'] ||
748
- params['y-amount'] ||
749
- params['x-amount'] ||
750
- params.dy,
751
- 'y-amount':
752
- params.amount ||
753
- params.dx ||
754
- params['amt-in'] ||
755
- params['amt-in-max'] ||
756
- params['y-amount'] ||
757
- params['x-amount'] ||
758
- params.dy,
759
- 'x-amount':
760
- params.amount ||
761
- params.dx ||
762
- params['amt-in'] ||
763
- params['amt-in-max'] ||
764
- params['y-amount'] ||
765
- params['x-amount'] ||
766
- params.dy,
767
- 'min-received': rawResult,
768
- 'min-dy': rawResult,
769
- 'min-dz': rawResult,
770
- 'min-dw': rawResult,
771
- 'amt-out': rawResult,
772
- 'amt-out-min': rawResult,
773
- 'min-x-amount': rawResult,
774
- 'min-dv': rawResult,
775
- 'min-y-amount': rawResult,
776
- 'min-dx': rawResult,
777
- },
778
- };
779
-
780
- const quoteResult: RouteQuote = {
781
- route: {
782
- ...route,
783
- quoteData: updatedQuoteData,
784
- swapData: updatedSwapData,
785
- },
786
- quote: convertedResult,
787
- params: params,
788
- quoteData: updatedQuoteData,
789
- swapData: updatedSwapData,
790
- dexPath: route.dex_path,
791
- tokenPath: route.token_path,
792
- tokenXDecimals: tokenXDecimals,
793
- tokenYDecimals: tokenYDecimals,
794
- };
795
-
796
- allRoutes.push(quoteResult);
797
- } else {
798
- throw new Error('Invalid quote result');
799
- }
800
- } catch (error) {
801
- console.warn(
802
- `Failed to get quote for route ${routeIndex + 1}:`,
803
- route,
804
- error
805
- );
806
- allRoutes.push({
807
- route,
808
- quote: null,
809
- params: route.quoteData
810
- ? { ...route.quoteData.parameters, amountInput }
811
- : { amountInput },
812
- quoteData: route.quoteData,
813
- swapData: route.swapData,
814
- dexPath: route.dex_path,
815
- tokenPath: route.token_path,
816
- tokenXDecimals: route.tokenXDecimals,
817
- tokenYDecimals: route.tokenYDecimals,
818
- error: (error as Error).message,
819
- });
820
- }
821
- }
822
-
823
- allRoutes.sort((a, b) => (b.quote || 0) - (a.quote || 0));
824
- const result = {
825
- bestRoute: allRoutes[0]?.quote !== null ? allRoutes[0] : null,
826
- allRoutes,
827
- inputData: { tokenX, tokenY, amountInput },
828
- };
829
-
830
- return result;
831
- }
832
- public async getSwapParams(
833
- swapExecutionData: SwapExecutionData,
834
- senderAddress: string,
835
- slippageTolerance: number = 0.015
836
- ): Promise<SwapDataParamsAndPostConditions> {
837
- const { route, amount, tokenXDecimals, tokenYDecimals } = swapExecutionData;
838
-
839
- const executionData: SwapExecutionData = {
840
- route: {
841
- ...route,
842
- swapData: {
843
- ...route.swapData,
844
- parameters: {
845
- ...route.swapData.parameters,
846
- amount:
847
- route.swapData.parameters.amount ||
848
- amount ||
849
- route.swapData.parameters['amt-in'] ||
850
- route.swapData.parameters['amt-in-max'] ||
851
- route.swapData.parameters['y-amount'],
852
- dx:
853
- route.swapData.parameters.dx ||
854
- amount ||
855
- route.swapData.parameters['amt-in'] ||
856
- route.swapData.parameters['amt-in-max'] ||
857
- route.swapData.parameters['y-amount'],
858
- 'amt-in':
859
- route.swapData.parameters.dx ||
860
- amount ||
861
- route.swapData.parameters['amt-in'] ||
862
- route.swapData.parameters['amt-in-max'] ||
863
- route.swapData.parameters['y-amount'] ||
864
- route.swapData.parameters.dy,
865
- 'amt-in-max':
866
- route.swapData.parameters.dx ||
867
- amount ||
868
- route.swapData.parameters['amt-in'] ||
869
- route.swapData.parameters['amt-in-max'] ||
870
- route.swapData.parameters['y-amount'] ||
871
- route.swapData.parameters.dy,
872
- 'y-amount':
873
- route.swapData.parameters.dx ||
874
- amount ||
875
- route.swapData.parameters['amt-in'] ||
876
- route.swapData.parameters['amt-in-max'] ||
877
- route.swapData.parameters['y-amount'] ||
878
- route.swapData.parameters.dy,
879
- dy:
880
- route.swapData.parameters.dy ||
881
- amount ||
882
- route.swapData.parameters['amt-in'] ||
883
- route.swapData.parameters['amt-in-max'] ||
884
- route.swapData.parameters['y-amount'] ||
885
- route.swapData.parameters.dy,
886
- provider:
887
- route.swapData.parameters.provider
888
- },
889
- },
890
- },
891
- amount,
892
- tokenXDecimals: tokenXDecimals,
893
- tokenYDecimals: tokenYDecimals,
894
- };
895
-
896
- return await executeGetParams(
897
- executionData,
898
- senderAddress,
899
- slippageTolerance,
900
- this.context
901
- );
902
- }
903
-
904
- public async executeSwap(
905
- swapExecutionData: SwapExecutionData,
906
- senderAddress: string,
907
- slippageTolerance: number = 0.015,
908
- stacksProvider?: StacksProvider,
909
- onFinish?: (data: any) => void,
910
- onCancel?: () => void
911
- ): Promise<void> {
912
- const swapParams = await this.getSwapParams(
913
- swapExecutionData,
914
- senderAddress,
915
- slippageTolerance
916
- );
917
-
918
- if (typeof window === 'undefined') {
919
- throw new Error(
920
- 'executeSwap is only available in browser environments. ' +
921
- 'For Node.js environments, use getSwapParams to get the transaction parameters ' +
922
- 'and handle the transaction execution separately.'
923
- );
924
- }
925
-
926
- try {
927
- let provider: StacksProvider;
928
- if (stacksProvider) {
929
- provider = stacksProvider;
930
- } else {
931
- const loadedProvider = await this.loadConnectDependencies();
932
- if (!loadedProvider) {
933
- throw new Error('Failed to initialize Stacks provider');
934
- }
935
- provider = loadedProvider;
936
- }
937
-
938
- await executeSwapHelper(
939
- swapParams,
940
- senderAddress,
941
- this.context,
942
- provider,
943
- onFinish,
944
- onCancel
945
- );
946
- } catch (error) {
947
- if (
948
- error instanceof Error &&
949
- error.message.includes('only available in browser environments')
950
- ) {
951
- throw error;
952
- }
953
- console.error('Error executing swap:', error);
954
- throw new Error(
955
- `Failed to execute swap: ${
956
- error instanceof Error ? error.message : 'Unknown error'
957
- }`
958
- );
959
- }
960
- }
961
-
962
- public async prepareSwap(
963
- swapExecutionData: SwapExecutionData,
964
- senderAddress: string,
965
- slippageTolerance: number = 0.015
966
- ): Promise<SwapDataParamsAndPostConditions> {
967
- return await this.getSwapParams(
968
- swapExecutionData,
969
- senderAddress,
970
- slippageTolerance
971
- );
972
- }
973
-
974
- public async getOrCreateKeeperContract(
975
- params: GetKeeperContractParams
976
- ): Promise<GetKeeperContractResponse> {
977
- try {
978
- return await getOrCreateKeeperContractAPI(params);
979
- } catch (error) {
980
- console.error('Error in BitflowSDK.getOrCreateKeeperContract:', error);
981
- throw error;
982
- }
983
- }
984
-
985
- public async getOrder(orderId: string): Promise<GetOrderResponse> {
986
- try {
987
- return await getOrderAPI(orderId);
988
- } catch (error) {
989
- console.error('Error in BitflowSDK.getOrder:', error);
990
- throw error;
991
- }
992
- }
993
-
994
- public async getUser(stacksAddress: string): Promise<GetUserResponse> {
995
- try {
996
- return await getUserAPI(stacksAddress);
997
- } catch (error) {
998
- console.error('Error in BitflowSDK.getUser:', error);
999
- throw error;
1000
- }
1001
- }
1002
-
1003
- public async createOrder(
1004
- params: CreateOrderParams
1005
- ): Promise<CreateOrderResponse> {
1006
- try {
1007
- return await createOrderAPI(params);
1008
- } catch (error) {
1009
- console.error('Error in BitflowSDK.createOrder:', error);
1010
- throw error;
1011
- }
1012
- }
1013
-
1014
- public async getQuote(params: GetQuoteParams): Promise<GetQuoteResponse> {
1015
- try {
1016
- return await getQuoteAPI(params);
1017
- } catch (error) {
1018
- console.error('Error in BitflowSDK.getQuote:', error);
1019
- throw error;
1020
- }
1021
- }
1022
-
1023
- private async transformRouteToActionArgs(
1024
- route: RouteQuote
1025
- ): Promise<ActionFunctionArgs> {
1026
- const actionFunctionArgs: ActionFunctionArgs = {
1027
- tokenList: {},
1028
- xykPoolList: {},
1029
- stableswapPoolList: {},
1030
- boolList: {},
1031
- };
1032
-
1033
- const swapData = route.swapData;
1034
- if (!swapData || !swapData.parameters) {
1035
- throw new Error(
1036
- 'Invalid route data - missing swapData or swapData.parameters'
1037
- );
1038
- }
1039
-
1040
- try {
1041
- const availableTokens = await this.getAvailableTokens();
1042
- const tokenPath = route.tokenPath;
1043
- const expandedTokenPath: string[] = [];
1044
-
1045
- // Expand token path
1046
- for (let i = 0; i < tokenPath.length - 1; i++) {
1047
- expandedTokenPath.push(tokenPath[i]);
1048
- if (i > 0 && i < tokenPath.length - 1) {
1049
- expandedTokenPath.push(tokenPath[i]);
1050
- }
1051
- }
1052
- expandedTokenPath.push(tokenPath[tokenPath.length - 1]);
1053
-
1054
- // Map tokens to contract identifiers
1055
- const tokenList: Record<string, string> = {};
1056
- for (let index = 0; index < expandedTokenPath.length; index++) {
1057
- const tokenId = expandedTokenPath[index];
1058
- let contractIdentifier: string;
1059
-
1060
- if (tokenId === 'token-stx') {
1061
- contractIdentifier =
1062
- 'SM1793C4R5PZ4NS4VQ4WMP7SKKYVH8JZEWSZ9HCCR.token-stx-v-1-2';
1063
- } else {
1064
- const token = availableTokens.find((t) => t.tokenId === tokenId);
1065
- if (!token || !token.tokenContract) {
1066
- throw new Error(
1067
- `Could not find contract identifier for token ${tokenId}`
1068
- );
1069
- }
1070
- contractIdentifier = token.tokenContract;
1071
- }
1072
-
1073
- const key = String.fromCharCode(97 + index); // 97 = 'a'
1074
- tokenList[key] = contractIdentifier;
1075
- }
1076
-
1077
- actionFunctionArgs.tokenList = tokenList;
1078
-
1079
- // Determine if this is a stableswap or XYK route
1080
- const dexPath = route.dexPath;
1081
- const isStableswapRoute = dexPath.some((path) =>
1082
- path.toLowerCase().includes('stable')
1083
- );
1084
-
1085
- // Transform pool lists
1086
- const transformPoolList = (pools: any): Record<string, string> => {
1087
- if (typeof pools === 'string') {
1088
- return { a: pools };
1089
- }
1090
- if (Array.isArray(pools)) {
1091
- const poolObject: Record<string, string> = {};
1092
- const letters = 'abcdefghijklmnopqrstuvwxyz';
1093
- pools.forEach((pool, index) => {
1094
- poolObject[letters[index]] = pool;
1095
- });
1096
- return poolObject;
1097
- }
1098
- if (typeof pools === 'object' && pools !== null) {
1099
- return pools;
1100
- }
1101
- return {};
1102
- };
1103
-
1104
- // Handle pools based on whether they're stableswap or XYK
1105
- if (swapData.parameters['pool-trait']) {
1106
- const poolIdentifier = swapData.parameters['pool-trait'];
1107
- const transformedPool = transformPoolList(poolIdentifier);
1108
-
1109
- // Check if the pool name contains 'stableswap'
1110
- if (
1111
- isStableswapRoute ||
1112
- (typeof poolIdentifier === 'string' &&
1113
- poolIdentifier.toLowerCase().includes('stableswap'))
1114
- ) {
1115
- actionFunctionArgs.stableswapPoolList = transformedPool;
1116
- } else {
1117
- actionFunctionArgs.xykPoolList = transformedPool;
1118
- }
1119
- } else {
1120
- // Handle specific pool types
1121
- if (swapData.parameters['xyk-pools']) {
1122
- actionFunctionArgs.xykPoolList = transformPoolList(
1123
- swapData.parameters['xyk-pools']
1124
- );
1125
- }
1126
-
1127
- if (swapData.parameters['stableswap-pools']) {
1128
- actionFunctionArgs.stableswapPoolList = transformPoolList(
1129
- swapData.parameters['stableswap-pools']
1130
- );
1131
- }
1132
- }
1133
-
1134
- if ('swaps-reversed' in swapData.parameters) {
1135
- actionFunctionArgs.boolList = {
1136
- a: swapData.parameters['swaps-reversed'].toString(),
1137
- };
1138
- } else {
1139
- delete actionFunctionArgs.boolList;
1140
- }
1141
-
1142
- const trait = this.mapDexPathToActionTrait(dexPath);
1143
- actionFunctionArgs.actionTrait = trait;
1144
-
1145
- return actionFunctionArgs;
1146
- } catch (error) {
1147
- throw error;
1148
- }
1149
- }
1150
-
1151
- private mapDexPathToActionTrait(dexPath: string[]): string {
1152
- const isXYK = dexPath.some((d) => d.toLowerCase().includes('xyk'));
1153
- const isStable = dexPath.some((d) => d.toLowerCase().includes('stable'));
1154
-
1155
- if (isXYK && !isStable) {
1156
- return 'SM1793C4R5PZ4NS4VQ4WMP7SKKYVH8JZEWSZ9HCCR.keeper-action-1-v-1-1';
1157
- }
1158
- if (isXYK && isStable) {
1159
- return 'SM1793C4R5PZ4NS4VQ4WMP7SKKYVH8JZEWSZ9HCCR.keeper-action-2-v-1-1';
1160
- }
1161
- if (!isXYK && isStable) {
1162
- return 'SM1793C4R5PZ4NS4VQ4WMP7SKKYVH8JZEWSZ9HCCR.keeper-action-3-v-1-1';
1163
- }
1164
-
1165
- throw new Error(`Unsupported DEX path: ${dexPath.join(', ')}`);
1166
- }
1167
-
1168
- public async getKeeperAggregatorRouteData(
1169
- tokenX: string,
1170
- tokenY: string,
1171
- amountX: number
1172
- ): Promise<ActionFunctionArgs> {
1173
- try {
1174
- const quoteResult = await this.getKeeperQuoteForRouteWithoutScaling(
1175
- tokenX,
1176
- tokenY,
1177
- amountX
1178
- );
1179
-
1180
- if (
1181
- !quoteResult ||
1182
- !quoteResult.allRoutes ||
1183
- quoteResult.allRoutes.length === 0
1184
- ) {
1185
- throw new Error('No routes found');
1186
- }
1187
-
1188
- const { bestRoute } = quoteResult;
1189
- if (!bestRoute) {
1190
- throw new Error('No best route found for keeper-compatible DEX paths');
1191
- }
1192
-
1193
- const actionFunctionArgs = await this.transformRouteToActionArgs(
1194
- bestRoute
1195
- );
1196
- return actionFunctionArgs;
1197
- } catch (error) {
1198
- throw error;
1199
- }
1200
- }
1201
-
1202
- public async createGroupOrder(
1203
- params: CreateGroupOrderParams
1204
- ): Promise<CreateGroupOrderResponse> {
1205
- try {
1206
- return await createGroupOrderAPI(params);
1207
- } catch (error) {
1208
- console.error('Error in BitflowSDK.createGroupOrder:', error);
1209
- throw error;
1210
- }
1211
- }
1212
-
1213
- public async getGroupOrder(
1214
- groupId: string,
1215
- includeOrders?: boolean
1216
- ): Promise<GetGroupOrderResponse> {
1217
- try {
1218
- return await getGroupOrderAPI(groupId, includeOrders);
1219
- } catch (error) {
1220
- console.error('Error in BitflowSDK.getGroupOrder:', error);
1221
- throw error;
1222
- }
1223
- }
1224
-
1225
- public async cancelOrder(orderId: string): Promise<CancelOrderResponse> {
1226
- try {
1227
- return await cancelOrderAPI(orderId);
1228
- } catch (error) {
1229
- console.error('Error in BitflowSDK.cancelOrder:', error);
1230
- throw error;
1231
- }
1232
- }
1233
-
1234
- public async cancelGroupOrder(
1235
- groupId: string
1236
- ): Promise<CancelGroupOrderResponse> {
1237
- try {
1238
- return await cancelGroupOrderAPI(groupId);
1239
- } catch (error) {
1240
- console.error('Error in BitflowSDK.cancelGroupOrder:', error);
1241
- throw error;
1242
- }
1243
- }
1244
- }