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