@bitflowlabs/core-sdk 1.0.1 → 2.0.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 (64) hide show
  1. package/dist/src/BitflowSDK.d.ts +23 -2
  2. package/dist/src/BitflowSDK.js +536 -82
  3. package/dist/src/BitflowSDK.js.map +1 -1
  4. package/dist/src/config.d.ts +1 -1
  5. package/dist/src/config.js +6 -4
  6. package/dist/src/config.js.map +1 -1
  7. package/dist/src/helpers/callReadOnlyHelper.js +10 -4
  8. package/dist/src/helpers/callReadOnlyHelper.js.map +1 -1
  9. package/dist/src/helpers/callSwapHelper.d.ts +2 -2
  10. package/dist/src/helpers/callSwapHelper.js +48 -7
  11. package/dist/src/helpers/callSwapHelper.js.map +1 -1
  12. package/dist/src/helpers/fetchDataHelper.d.ts +1 -1
  13. package/dist/src/helpers/fetchDataHelper.js +50 -17
  14. package/dist/src/helpers/fetchDataHelper.js.map +1 -1
  15. package/dist/src/helpers/fetchPossibleSwap.d.ts +2 -2
  16. package/dist/src/helpers/fetchPossibleSwap.js +10 -2
  17. package/dist/src/helpers/fetchPossibleSwap.js.map +1 -1
  18. package/dist/src/helpers/handleResultHelper.js +15 -15
  19. package/dist/src/helpers/handleResultHelper.js.map +1 -1
  20. package/dist/src/helpers/newPostConditionsHelper.d.ts +2 -0
  21. package/dist/src/helpers/newPostConditionsHelper.js +145 -0
  22. package/dist/src/helpers/newPostConditionsHelper.js.map +1 -0
  23. package/dist/src/helpers/postConditionsHelper.js +38 -13
  24. package/dist/src/helpers/postConditionsHelper.js.map +1 -1
  25. package/dist/src/index.d.ts +3 -2
  26. package/dist/src/index.js +1 -0
  27. package/dist/src/index.js.map +1 -1
  28. package/dist/src/keeper/keeperAPI.d.ts +10 -0
  29. package/dist/src/keeper/keeperAPI.js +332 -0
  30. package/dist/src/keeper/keeperAPI.js.map +1 -0
  31. package/dist/src/keeper/types.d.ts +253 -0
  32. package/dist/src/keeper/types.js +65 -0
  33. package/dist/src/keeper/types.js.map +1 -0
  34. package/dist/src/test-keeper-routes.d.ts +1 -0
  35. package/dist/src/test-keeper-routes.js +67 -0
  36. package/dist/src/test-keeper-routes.js.map +1 -0
  37. package/dist/src/test-raw-token-response.d.ts +1 -0
  38. package/dist/src/test-raw-token-response.js +79 -0
  39. package/dist/src/test-raw-token-response.js.map +1 -0
  40. package/dist/src/test-sdk.d.ts +1 -0
  41. package/dist/src/test-sdk.js +229 -0
  42. package/dist/src/test-sdk.js.map +1 -0
  43. package/dist/src/test-token.fetch.d.ts +1 -0
  44. package/dist/src/test-token.fetch.js +63 -0
  45. package/dist/src/test-token.fetch.js.map +1 -0
  46. package/dist/src/types.d.ts +27 -2
  47. package/package.json +6 -5
  48. package/src/BitflowSDK.ts +675 -97
  49. package/src/config.ts +9 -6
  50. package/src/helpers/callReadOnlyHelper.ts +12 -7
  51. package/src/helpers/callSwapHelper.ts +21 -7
  52. package/src/helpers/fetchDataHelper.ts +58 -19
  53. package/src/helpers/fetchPossibleSwap.ts +18 -4
  54. package/src/helpers/handleResultHelper.ts +17 -16
  55. package/src/helpers/newPostConditionsHelper.ts +172 -0
  56. package/src/helpers/postConditionsHelper.ts +71 -19
  57. package/src/index.ts +3 -2
  58. package/src/keeper/keeperAPI.ts +435 -0
  59. package/src/keeper/types.ts +293 -0
  60. package/src/test-keeper-routes.ts +76 -0
  61. package/src/test-raw-token-response.ts +124 -0
  62. package/src/test-sdk.ts +262 -0
  63. package/src/test-token.fetch.ts +72 -0
  64. package/src/types.ts +29 -2
package/src/BitflowSDK.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { StacksMainnet } from '@stacks/network';
2
- import { StacksProvider } from '@stacks/connect';
1
+ import { STACKS_MAINNET } from "@stacks/network";
2
+ import type { StacksProvider } from "@stacks/connect";
3
3
  import {
4
4
  SwapContext,
5
5
  Token,
@@ -10,17 +10,48 @@ import {
10
10
  RouteQuote,
11
11
  SwapDataParamsAndPostConditions,
12
12
  BitflowSDKConfig,
13
- } from './types';
14
- import { fetchAllTokensFromAPI } from './helpers/fetchDataHelper';
15
- import { executeSwapHelper } from './helpers/callSwapHelper';
16
- import { callReadOnlyFunctionHelper } from './helpers/callReadOnlyHelper';
17
- import { fetchPossibleSwapsFromAPI } from './helpers/fetchPossibleSwap';
18
- import { getContractInterfaceAndFunction } from './helpers/getContractInterfaceAndFunction';
19
- import { configs, validateConfig } from './config';
20
- import { executeGetParams } from './helpers/callGetSwapParams';
13
+ } from "./types";
14
+ import { fetchAllTokensFromAPI } from "./helpers/fetchDataHelper";
15
+ import { executeSwapHelper } from "./helpers/callSwapHelper";
16
+ import { callReadOnlyFunctionHelper } from "./helpers/callReadOnlyHelper";
17
+ import { fetchPossibleSwapsFromAPI } from "./helpers/fetchPossibleSwap";
18
+ import { getContractInterfaceAndFunction } from "./helpers/getContractInterfaceAndFunction";
19
+ import { configs, validateConfig } from "./config";
20
+ import { executeGetParams } from "./helpers/callGetSwapParams";
21
+ import {
22
+ ActionFunctionArgs,
23
+ AggregatorRouteData,
24
+ ALLOWED_CONTRACTS,
25
+ CancelGroupOrderResponse,
26
+ CancelOrderResponse,
27
+ CreateGroupOrderParams,
28
+ CreateGroupOrderResponse,
29
+ CreateOrderParams,
30
+ CreateOrderResponse,
31
+ GetGroupOrderResponse,
32
+ GetKeeperContractParams,
33
+ GetKeeperContractResponse,
34
+ GetOrderResponse,
35
+ GetQuoteParams,
36
+ GetQuoteResponse,
37
+ GetUserResponse,
38
+ KeeperOrderRouteData,
39
+ } from "./keeper/types";
40
+ import {
41
+ cancelGroupOrderAPI,
42
+ cancelOrderAPI,
43
+ createGroupOrderAPI,
44
+ createOrderAPI,
45
+ getGroupOrderAPI,
46
+ getOrCreateKeeperContractAPI,
47
+ getOrderAPI,
48
+ getQuoteAPI,
49
+ getUserAPI,
50
+ } from "./keeper/keeperAPI";
21
51
 
22
52
  export class BitflowSDK {
23
53
  private context: SwapContext;
54
+ private stacksProvider: StacksProvider | undefined;
24
55
 
25
56
  constructor(config?: Partial<BitflowSDKConfig>) {
26
57
  if (config) Object.assign(configs, config);
@@ -31,13 +62,30 @@ export class BitflowSDK {
31
62
  availableTokens: [],
32
63
  contractInterfaces: {},
33
64
  functionArgs: {},
34
- network: new StacksMainnet(),
65
+ network: STACKS_MAINNET,
35
66
  swapOptions: {},
36
67
  };
37
68
 
38
69
  this.initializeContext();
39
70
  }
71
+ private async loadConnectDependencies() {
72
+ if (typeof window === "undefined") {
73
+ throw new Error(
74
+ "Connect features are only available in browser environments"
75
+ );
76
+ }
40
77
 
78
+ try {
79
+ const { getStacksProvider } = await import("@stacks/connect");
80
+ if (!this.stacksProvider) {
81
+ this.stacksProvider = await getStacksProvider();
82
+ }
83
+ return this.stacksProvider;
84
+ } catch (error) {
85
+ console.error("Error loading Stacks Connect:", error);
86
+ throw new Error("Failed to load Stacks Connect dependencies");
87
+ }
88
+ }
41
89
  private async initializeContext(): Promise<void> {
42
90
  this.context.availableTokens = await fetchAllTokensFromAPI();
43
91
  }
@@ -49,6 +97,13 @@ export class BitflowSDK {
49
97
  return this.context.availableTokens;
50
98
  }
51
99
 
100
+ public async getKeeperTokens(): Promise<Token[]> {
101
+ if (this.context.availableTokens.length === 0) {
102
+ await this.initializeContext();
103
+ }
104
+ return this.context.availableTokens.filter((token) => token.isKeeperToken);
105
+ }
106
+
52
107
  public async getPossibleSwaps(tokenX: string): Promise<SwapOptions> {
53
108
  if (!this.context.swapOptions[tokenX]) {
54
109
  this.context.swapOptions[tokenX] = await fetchPossibleSwapsFromAPI(
@@ -58,11 +113,28 @@ export class BitflowSDK {
58
113
  return this.context.swapOptions[tokenX];
59
114
  }
60
115
 
116
+ public async getKeeperPossibleSwaps(tokenX: string): Promise<SwapOptions> {
117
+ // Use a separate cache key for keeper routes
118
+ const cacheKey = `keeper_${tokenX}`;
119
+ if (!this.context.swapOptions[cacheKey]) {
120
+ this.context.swapOptions[cacheKey] = await fetchPossibleSwapsFromAPI(
121
+ tokenX,
122
+ "KEEPER"
123
+ );
124
+ }
125
+ return this.context.swapOptions[cacheKey];
126
+ }
127
+
61
128
  public async getAllPossibleTokenY(tokenX: string): Promise<string[]> {
62
129
  const swapOptions = await this.getPossibleSwaps(tokenX);
63
130
  return Object.keys(swapOptions);
64
131
  }
65
132
 
133
+ public async getAllKeeperPossibleTokenY(tokenX: string): Promise<string[]> {
134
+ const swapOptions = await this.getKeeperPossibleSwaps(tokenX);
135
+ return Object.keys(swapOptions);
136
+ }
137
+
66
138
  public async getAllPossibleTokenYRoutes(
67
139
  tokenX: string,
68
140
  tokenY: string
@@ -71,6 +143,14 @@ export class BitflowSDK {
71
143
  return swapOptions[tokenY] || [];
72
144
  }
73
145
 
146
+ public async getAllKeeperPossibleTokenYRoutes(
147
+ tokenX: string,
148
+ tokenY: string
149
+ ): Promise<SelectedSwapRoute[]> {
150
+ const swapOptions = await this.getKeeperPossibleSwaps(tokenX);
151
+ return swapOptions[tokenY] || [];
152
+ }
153
+
74
154
  public async getQuoteForRoute(
75
155
  tokenX: string,
76
156
  tokenY: string,
@@ -82,7 +162,6 @@ export class BitflowSDK {
82
162
  for (let routeIndex = 0; routeIndex < routes.length; routeIndex++) {
83
163
  const route = routes[routeIndex];
84
164
  try {
85
- // Check if quoteData is null or undefined
86
165
  if (!route.quoteData) {
87
166
  console.warn(
88
167
  `Skipping route ${routeIndex + 1} due to null quoteData:`,
@@ -97,7 +176,6 @@ export class BitflowSDK {
97
176
  parameters,
98
177
  } = route.quoteData;
99
178
 
100
- // Check if any required property is missing
101
179
  if (!contract || !functionName || !parameters) {
102
180
  console.warn(
103
181
  `Skipping route ${
@@ -108,7 +186,7 @@ export class BitflowSDK {
108
186
  continue;
109
187
  }
110
188
 
111
- const [contractDeployer, contractName] = contract.split('.');
189
+ const [contractDeployer, contractName] = contract.split(".");
112
190
 
113
191
  if (!this.context.contractInterfaces[contract]) {
114
192
  this.context.contractInterfaces[contract] =
@@ -123,21 +201,21 @@ export class BitflowSDK {
123
201
 
124
202
  const params = { ...parameters };
125
203
 
126
- if ('dx' in params && params.dx === null) {
204
+ if ("dx" in params && params.dx === null) {
127
205
  params.dx = amountInput;
128
- } else if ('dy' in params && params.dy === null) {
206
+ } else if ("dy" in params && params.dy === null) {
129
207
  params.dy = amountInput;
130
- } else if ('amount' in params && params.amount === null) {
208
+ } else if ("amount" in params && params.amount === null) {
131
209
  params.amount = amountInput;
132
- } else if ('amt-in' in params && params['amt-in'] === null) {
133
- params['amt-in'] = amountInput;
134
- } else if ('amt-in-max' in params && params['amt-in-max'] === null) {
135
- params['amt-in-max'] = amountInput;
136
- } else if ('y-amount' in params && params['y-amount'] === null) {
137
- params['y-amount'] = amountInput;
138
- params['x-amount'] = amountInput;
139
- } else if ('x-amount' in params && params['x-amount'] === null) {
140
- params['x-amount'] = amountInput;
210
+ } else if ("amt-in" in params && params["amt-in"] === null) {
211
+ params["amt-in"] = amountInput;
212
+ } else if ("amt-in-max" in params && params["amt-in-max"] === null) {
213
+ params["amt-in-max"] = amountInput;
214
+ } else if ("y-amount" in params && params["y-amount"] === null) {
215
+ params["y-amount"] = amountInput;
216
+ params["x-amount"] = amountInput;
217
+ } else if ("x-amount" in params && params["x-amount"] === null) {
218
+ params["x-amount"] = amountInput;
141
219
  } else {
142
220
  params.dx = amountInput;
143
221
  }
@@ -155,7 +233,7 @@ export class BitflowSDK {
155
233
  this.context
156
234
  );
157
235
 
158
- if (typeof convertedResult === 'number' && convertedResult > 0) {
236
+ if (typeof convertedResult === "number" && convertedResult > 0) {
159
237
  const updatedQuoteData = {
160
238
  ...route.quoteData,
161
239
  parameters: { ...params },
@@ -168,70 +246,70 @@ export class BitflowSDK {
168
246
  amount:
169
247
  params.amount ||
170
248
  params.dx ||
171
- params['amt-in'] ||
172
- params['amt-in-max'] ||
173
- params['y-amount'] ||
174
- params['x-amount'] ||
249
+ params["amt-in"] ||
250
+ params["amt-in-max"] ||
251
+ params["y-amount"] ||
252
+ params["x-amount"] ||
175
253
  params.dy,
176
254
  dx:
177
255
  params.amount ||
178
256
  params.dx ||
179
- params['amt-in'] ||
180
- params['amt-in-max'] ||
181
- params['y-amount'] ||
182
- params['x-amount'] ||
257
+ params["amt-in"] ||
258
+ params["amt-in-max"] ||
259
+ params["y-amount"] ||
260
+ params["x-amount"] ||
183
261
  params.dy,
184
262
  dy:
185
263
  params.amount ||
186
264
  params.dx ||
187
- params['amt-in'] ||
188
- params['amt-in-max'] ||
189
- params['y-amount'] ||
190
- params['x-amount'] ||
265
+ params["amt-in"] ||
266
+ params["amt-in-max"] ||
267
+ params["y-amount"] ||
268
+ params["x-amount"] ||
191
269
  params.dy,
192
- 'amt-in':
270
+ "amt-in":
193
271
  params.amount ||
194
272
  params.dx ||
195
- params['amt-in'] ||
196
- params['amt-in-max'] ||
197
- params['y-amount'] ||
198
- params['x-amount'] ||
273
+ params["amt-in"] ||
274
+ params["amt-in-max"] ||
275
+ params["y-amount"] ||
276
+ params["x-amount"] ||
199
277
  params.dy,
200
- 'amt-in-max':
278
+ "amt-in-max":
201
279
  params.amount ||
202
280
  params.dx ||
203
- params['amt-in'] ||
204
- params['amt-in-max'] ||
205
- params['y-amount'] ||
206
- params['x-amount'] ||
281
+ params["amt-in"] ||
282
+ params["amt-in-max"] ||
283
+ params["y-amount"] ||
284
+ params["x-amount"] ||
207
285
  params.dy,
208
- 'y-amount':
286
+ "y-amount":
209
287
  params.amount ||
210
288
  params.dx ||
211
- params['amt-in'] ||
212
- params['amt-in-max'] ||
213
- params['y-amount'] ||
214
- params['x-amount'] ||
289
+ params["amt-in"] ||
290
+ params["amt-in-max"] ||
291
+ params["y-amount"] ||
292
+ params["x-amount"] ||
215
293
  params.dy,
216
- 'x-amount':
294
+ "x-amount":
217
295
  params.amount ||
218
296
  params.dx ||
219
- params['amt-in'] ||
220
- params['amt-in-max'] ||
221
- params['y-amount'] ||
222
- params['x-amount'] ||
297
+ params["amt-in"] ||
298
+ params["amt-in-max"] ||
299
+ params["y-amount"] ||
300
+ params["x-amount"] ||
223
301
  params.dy,
224
302
 
225
- 'min-received': rawResult,
226
- 'min-dy': rawResult,
227
- 'min-dz': rawResult,
228
- 'min-dw': rawResult,
229
- 'amt-out': rawResult,
230
- 'amt-out-min': rawResult,
231
- 'min-x-amount': rawResult,
232
- 'min-dv': rawResult,
233
- 'min-y-amount': rawResult,
234
- 'min-dx': rawResult,
303
+ "min-received": rawResult,
304
+ "min-dy": rawResult,
305
+ "min-dz": rawResult,
306
+ "min-dw": rawResult,
307
+ "amt-out": rawResult,
308
+ "amt-out-min": rawResult,
309
+ "min-x-amount": rawResult,
310
+ "min-dv": rawResult,
311
+ "min-y-amount": rawResult,
312
+ "min-dx": rawResult,
235
313
  },
236
314
  };
237
315
 
@@ -253,7 +331,7 @@ export class BitflowSDK {
253
331
 
254
332
  allRoutes.push(quoteResult);
255
333
  } else {
256
- throw new Error('Invalid quote result');
334
+ throw new Error("Invalid quote result");
257
335
  }
258
336
  } catch (error) {
259
337
  console.warn(
@@ -289,6 +367,224 @@ export class BitflowSDK {
289
367
  return result;
290
368
  }
291
369
 
370
+ public async getKeeperQuoteForRoute(
371
+ tokenX: string,
372
+ tokenY: string,
373
+ amountInput: number
374
+ ): Promise<QuoteResult> {
375
+ const COMPATIBLE_DEX_PATHS = new Set([
376
+ "BITFLOW_STABLE_XY_2",
377
+ "BITFLOW_XYK_XY_2",
378
+ ]);
379
+
380
+ const isCompatibleRoute = (dexPath: string[]): boolean => {
381
+ return dexPath.every((path) => COMPATIBLE_DEX_PATHS.has(path));
382
+ };
383
+
384
+ let routes = await this.getAllPossibleTokenYRoutes(tokenX, tokenY);
385
+ routes = routes.filter((route) => isCompatibleRoute(route.dex_path));
386
+
387
+ const allRoutes: RouteQuote[] = [];
388
+
389
+ for (let routeIndex = 0; routeIndex < routes.length; routeIndex++) {
390
+ const route = routes[routeIndex];
391
+
392
+ try {
393
+ if (!route.quoteData) {
394
+ console.warn(
395
+ `Skipping route ${routeIndex + 1} due to null quoteData:`,
396
+ route
397
+ );
398
+ continue;
399
+ }
400
+
401
+ const {
402
+ contract,
403
+ function: functionName,
404
+ parameters,
405
+ } = route.quoteData;
406
+
407
+ if (!contract || !functionName || !parameters) {
408
+ console.warn(
409
+ `Skipping route ${
410
+ routeIndex + 1
411
+ } due to missing required properties:`,
412
+ route.quoteData
413
+ );
414
+ continue;
415
+ }
416
+
417
+ const [contractDeployer, contractName] = contract.split(".");
418
+
419
+ if (!this.context.contractInterfaces[contract]) {
420
+ this.context.contractInterfaces[contract] =
421
+ await getContractInterfaceAndFunction(
422
+ contractDeployer,
423
+ contractName,
424
+ functionName
425
+ );
426
+ }
427
+ const { interface: contractInterface, functionArgs } =
428
+ this.context.contractInterfaces[contract];
429
+
430
+ const params = { ...parameters };
431
+
432
+ if ("dx" in params && params.dx === null) {
433
+ params.dx = amountInput;
434
+ } else if ("dy" in params && params.dy === null) {
435
+ params.dy = amountInput;
436
+ } else if ("amount" in params && params.amount === null) {
437
+ params.amount = amountInput;
438
+ } else if ("amt-in" in params && params["amt-in"] === null) {
439
+ params["amt-in"] = amountInput;
440
+ } else if ("amt-in-max" in params && params["amt-in-max"] === null) {
441
+ params["amt-in-max"] = amountInput;
442
+ } else if ("y-amount" in params && params["y-amount"] === null) {
443
+ params["y-amount"] = amountInput;
444
+ params["x-amount"] = amountInput;
445
+ } else if ("x-amount" in params && params["x-amount"] === null) {
446
+ params["x-amount"] = amountInput;
447
+ } else {
448
+ params.dx = amountInput;
449
+ }
450
+
451
+ const { convertedResult, rawResult, tokenXDecimals, tokenYDecimals } =
452
+ await callReadOnlyFunctionHelper(
453
+ contractDeployer,
454
+ contractName,
455
+ functionName,
456
+ params,
457
+ contractDeployer,
458
+ tokenX,
459
+ tokenY,
460
+ route.swapData,
461
+ this.context
462
+ );
463
+
464
+ if (typeof convertedResult === "number" && convertedResult > 0) {
465
+ const updatedQuoteData = {
466
+ ...route.quoteData,
467
+ parameters: { ...params },
468
+ };
469
+
470
+ const updatedSwapData = {
471
+ ...route.swapData,
472
+ parameters: {
473
+ ...route.swapData.parameters,
474
+ amount:
475
+ params.amount ||
476
+ params.dx ||
477
+ params["amt-in"] ||
478
+ params["amt-in-max"] ||
479
+ params["y-amount"] ||
480
+ params["x-amount"] ||
481
+ params.dy,
482
+ dx:
483
+ params.amount ||
484
+ params.dx ||
485
+ params["amt-in"] ||
486
+ params["amt-in-max"] ||
487
+ params["y-amount"] ||
488
+ params["x-amount"] ||
489
+ params.dy,
490
+ "amt-in":
491
+ params.amount ||
492
+ params.dx ||
493
+ params["amt-in"] ||
494
+ params["amt-in-max"] ||
495
+ params["y-amount"] ||
496
+ params["x-amount"] ||
497
+ params.dy,
498
+ "amt-in-max":
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
+ "y-amount":
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
+ "x-amount":
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
+ "min-received": rawResult,
523
+ "min-dy": rawResult,
524
+ "min-dz": rawResult,
525
+ "min-dw": rawResult,
526
+ "amt-out": rawResult,
527
+ "amt-out-min": rawResult,
528
+ "min-x-amount": rawResult,
529
+ "min-dv": rawResult,
530
+ "min-y-amount": rawResult,
531
+ "min-dx": rawResult,
532
+ },
533
+ };
534
+
535
+ const quoteResult: RouteQuote = {
536
+ route: {
537
+ ...route,
538
+ quoteData: updatedQuoteData,
539
+ swapData: updatedSwapData,
540
+ },
541
+ quote: convertedResult,
542
+ params: params,
543
+ quoteData: updatedQuoteData,
544
+ swapData: updatedSwapData,
545
+ dexPath: route.dex_path,
546
+ tokenPath: route.token_path,
547
+ tokenXDecimals: tokenXDecimals,
548
+ tokenYDecimals: tokenYDecimals,
549
+ };
550
+
551
+ allRoutes.push(quoteResult);
552
+ } else {
553
+ throw new Error("Invalid quote result");
554
+ }
555
+ } catch (error) {
556
+ console.warn(
557
+ `Failed to get quote for route ${routeIndex + 1}:`,
558
+ route,
559
+ error
560
+ );
561
+ allRoutes.push({
562
+ route,
563
+ quote: null,
564
+ params: route.quoteData
565
+ ? { ...route.quoteData.parameters, amountInput }
566
+ : { amountInput },
567
+ quoteData: route.quoteData,
568
+ swapData: route.swapData,
569
+ dexPath: route.dex_path,
570
+ tokenPath: route.token_path,
571
+ tokenXDecimals: route.tokenXDecimals,
572
+ tokenYDecimals: route.tokenYDecimals,
573
+ error: (error as Error).message,
574
+ });
575
+ }
576
+ }
577
+
578
+ allRoutes.sort((a, b) => (b.quote || 0) - (a.quote || 0));
579
+ const result = {
580
+ bestRoute: allRoutes[0]?.quote !== null ? allRoutes[0] : null,
581
+ allRoutes,
582
+ inputData: { tokenX, tokenY, amountInput },
583
+ };
584
+
585
+ return result;
586
+ }
587
+
292
588
  public async getSwapParams(
293
589
  swapExecutionData: SwapExecutionData,
294
590
  senderAddress: string,
@@ -306,42 +602,42 @@ export class BitflowSDK {
306
602
  amount:
307
603
  route.swapData.parameters.amount ||
308
604
  amount ||
309
- route.swapData.parameters['amt-in'] ||
310
- route.swapData.parameters['amt-in-max'] ||
311
- route.swapData.parameters['y-amount'],
605
+ route.swapData.parameters["amt-in"] ||
606
+ route.swapData.parameters["amt-in-max"] ||
607
+ route.swapData.parameters["y-amount"],
312
608
  dx:
313
609
  route.swapData.parameters.dx ||
314
610
  amount ||
315
- route.swapData.parameters['amt-in'] ||
316
- route.swapData.parameters['amt-in-max'] ||
317
- route.swapData.parameters['y-amount'],
318
- 'amt-in':
611
+ route.swapData.parameters["amt-in"] ||
612
+ route.swapData.parameters["amt-in-max"] ||
613
+ route.swapData.parameters["y-amount"],
614
+ "amt-in":
319
615
  route.swapData.parameters.dx ||
320
616
  amount ||
321
- route.swapData.parameters['amt-in'] ||
322
- route.swapData.parameters['amt-in-max'] ||
323
- route.swapData.parameters['y-amount'] ||
617
+ route.swapData.parameters["amt-in"] ||
618
+ route.swapData.parameters["amt-in-max"] ||
619
+ route.swapData.parameters["y-amount"] ||
324
620
  route.swapData.parameters.dy,
325
- 'amt-in-max':
621
+ "amt-in-max":
326
622
  route.swapData.parameters.dx ||
327
623
  amount ||
328
- route.swapData.parameters['amt-in'] ||
329
- route.swapData.parameters['amt-in-max'] ||
330
- route.swapData.parameters['y-amount'] ||
624
+ route.swapData.parameters["amt-in"] ||
625
+ route.swapData.parameters["amt-in-max"] ||
626
+ route.swapData.parameters["y-amount"] ||
331
627
  route.swapData.parameters.dy,
332
- 'y-amount':
628
+ "y-amount":
333
629
  route.swapData.parameters.dx ||
334
630
  amount ||
335
- route.swapData.parameters['amt-in'] ||
336
- route.swapData.parameters['amt-in-max'] ||
337
- route.swapData.parameters['y-amount'] ||
631
+ route.swapData.parameters["amt-in"] ||
632
+ route.swapData.parameters["amt-in-max"] ||
633
+ route.swapData.parameters["y-amount"] ||
338
634
  route.swapData.parameters.dy,
339
635
  dy:
340
636
  route.swapData.parameters.dy ||
341
637
  amount ||
342
- route.swapData.parameters['amt-in'] ||
343
- route.swapData.parameters['amt-in-max'] ||
344
- route.swapData.parameters['y-amount'] ||
638
+ route.swapData.parameters["amt-in"] ||
639
+ route.swapData.parameters["amt-in-max"] ||
640
+ route.swapData.parameters["y-amount"] ||
345
641
  route.swapData.parameters.dy,
346
642
  },
347
643
  },
@@ -373,13 +669,295 @@ export class BitflowSDK {
373
669
  slippageTolerance
374
670
  );
375
671
 
376
- await executeSwapHelper(
377
- swapParams,
672
+ if (typeof window === "undefined") {
673
+ throw new Error(
674
+ "executeSwap is only available in browser environments. " +
675
+ "For Node.js environments, use getSwapParams to get the transaction parameters " +
676
+ "and handle the transaction execution separately."
677
+ );
678
+ }
679
+
680
+ try {
681
+ let provider: StacksProvider;
682
+ if (stacksProvider) {
683
+ provider = stacksProvider;
684
+ } else {
685
+ const loadedProvider = await this.loadConnectDependencies();
686
+ if (!loadedProvider) {
687
+ throw new Error("Failed to initialize Stacks provider");
688
+ }
689
+ provider = loadedProvider;
690
+ }
691
+
692
+ await executeSwapHelper(
693
+ swapParams,
694
+ senderAddress,
695
+ this.context,
696
+ provider,
697
+ onFinish,
698
+ onCancel
699
+ );
700
+ } catch (error) {
701
+ if (
702
+ error instanceof Error &&
703
+ error.message.includes("only available in browser environments")
704
+ ) {
705
+ throw error;
706
+ }
707
+ console.error("Error executing swap:", error);
708
+ throw new Error(
709
+ `Failed to execute swap: ${
710
+ error instanceof Error ? error.message : "Unknown error"
711
+ }`
712
+ );
713
+ }
714
+ }
715
+
716
+ public async prepareSwap(
717
+ swapExecutionData: SwapExecutionData,
718
+ senderAddress: string,
719
+ slippageTolerance: number = 0.015
720
+ ): Promise<SwapDataParamsAndPostConditions> {
721
+ return await this.getSwapParams(
722
+ swapExecutionData,
378
723
  senderAddress,
379
- this.context,
380
- stacksProvider,
381
- onFinish,
382
- onCancel
724
+ slippageTolerance
383
725
  );
384
726
  }
727
+
728
+ public async getOrCreateKeeperContract(
729
+ params: GetKeeperContractParams
730
+ ): Promise<GetKeeperContractResponse> {
731
+ try {
732
+ return await getOrCreateKeeperContractAPI(params);
733
+ } catch (error) {
734
+ console.error("Error in BitflowSDK.getOrCreateKeeperContract:", error);
735
+ throw error;
736
+ }
737
+ }
738
+
739
+ public async getOrder(orderId: string): Promise<GetOrderResponse> {
740
+ try {
741
+ return await getOrderAPI(orderId);
742
+ } catch (error) {
743
+ console.error("Error in BitflowSDK.getOrder:", error);
744
+ throw error;
745
+ }
746
+ }
747
+
748
+ public async getUser(stacksAddress: string): Promise<GetUserResponse> {
749
+ try {
750
+ return await getUserAPI(stacksAddress);
751
+ } catch (error) {
752
+ console.error("Error in BitflowSDK.getUser:", error);
753
+ throw error;
754
+ }
755
+ }
756
+
757
+ public async createOrder(
758
+ params: CreateOrderParams
759
+ ): Promise<CreateOrderResponse> {
760
+ try {
761
+ return await createOrderAPI(params);
762
+ } catch (error) {
763
+ console.error("Error in BitflowSDK.createOrder:", error);
764
+ throw error;
765
+ }
766
+ }
767
+
768
+ public async getQuote(params: GetQuoteParams): Promise<GetQuoteResponse> {
769
+ try {
770
+ return await getQuoteAPI(params);
771
+ } catch (error) {
772
+ console.error("Error in BitflowSDK.getQuote:", error);
773
+ throw error;
774
+ }
775
+ }
776
+
777
+ private mapDexPathToActionTrait(dexPath: string[]): string {
778
+ const isXYK = dexPath.some((d) => d.toLowerCase().includes("xyk"));
779
+ const isStable = dexPath.some((d) => d.toLowerCase().includes("stable"));
780
+
781
+ if (isXYK && !isStable) {
782
+ return "SM1793C4R5PZ4NS4VQ4WMP7SKKYVH8JZEWSZ9HCCR.keeper-action-1-v-1-1";
783
+ }
784
+ if (isXYK && isStable) {
785
+ return "SM1793C4R5PZ4NS4VQ4WMP7SKKYVH8JZEWSZ9HCCR.keeper-action-2-v-1-1";
786
+ }
787
+ if (!isXYK && isStable) {
788
+ return "SM1793C4R5PZ4NS4VQ4WMP7SKKYVH8JZEWSZ9HCCR.keeper-action-3-v-1-1";
789
+ }
790
+
791
+ throw new Error(`Unsupported DEX path: ${dexPath.join(", ")}`);
792
+ }
793
+
794
+ private async transformRouteToActionArgs(
795
+ route: RouteQuote
796
+ ): Promise<ActionFunctionArgs> {
797
+ const actionFunctionArgs: ActionFunctionArgs = {
798
+ tokenList: {},
799
+ xykPoolList: {},
800
+ stableswapPoolList: {},
801
+ boolList: {},
802
+ };
803
+
804
+ const swapData = route.swapData;
805
+ if (!swapData || !swapData.parameters) {
806
+ throw new Error(
807
+ "Invalid route data - missing swapData or swapData.parameters"
808
+ );
809
+ }
810
+
811
+ const availableTokens = await this.getAvailableTokens();
812
+ const tokenPath = route.tokenPath;
813
+ const expandedTokenPath: string[] = [];
814
+
815
+ for (let i = 0; i < tokenPath.length - 1; i++) {
816
+ expandedTokenPath.push(tokenPath[i]);
817
+ if (i > 0 && i < tokenPath.length - 1) {
818
+ expandedTokenPath.push(tokenPath[i]);
819
+ }
820
+ }
821
+ expandedTokenPath.push(tokenPath[tokenPath.length - 1]);
822
+
823
+ const tokenList: Record<string, string> = {};
824
+ expandedTokenPath.forEach((tokenId, index) => {
825
+ let contractIdentifier: string;
826
+ if (tokenId === "token-stx") {
827
+ contractIdentifier =
828
+ "SM1793C4R5PZ4NS4VQ4WMP7SKKYVH8JZEWSZ9HCCR.token-stx-v-1-2";
829
+ } else {
830
+ const token = availableTokens.find((t) => t.tokenId === tokenId);
831
+ if (!token || !token.tokenContract) {
832
+ throw new Error(
833
+ `Could not find contract identifier for token ${tokenId}`
834
+ );
835
+ }
836
+ contractIdentifier = token.tokenContract;
837
+ }
838
+ const key = String.fromCharCode(97 + index); // 97 = 'a'
839
+ tokenList[key] = contractIdentifier;
840
+ });
841
+
842
+ actionFunctionArgs.tokenList = tokenList;
843
+
844
+ const transformPoolList = (pools: any): Record<string, string> => {
845
+ if (typeof pools === "string") {
846
+ return { a: pools };
847
+ }
848
+ if (Array.isArray(pools)) {
849
+ const poolObject: Record<string, string> = {};
850
+ const letters = "abcdefghijklmnopqrstuvwxyz";
851
+ pools.forEach((pool, index) => {
852
+ poolObject[letters[index]] = pool;
853
+ });
854
+ return poolObject;
855
+ }
856
+ if (typeof pools === "object" && pools !== null) {
857
+ return pools;
858
+ }
859
+ return {};
860
+ };
861
+
862
+ if (swapData.parameters["xyk-pools"]) {
863
+ actionFunctionArgs.xykPoolList = transformPoolList(
864
+ swapData.parameters["xyk-pools"]
865
+ );
866
+ } else if (swapData.parameters["pool-trait"]) {
867
+ actionFunctionArgs.xykPoolList = transformPoolList(
868
+ swapData.parameters["pool-trait"]
869
+ );
870
+ }
871
+
872
+ if (swapData.parameters["stableswap-pools"]) {
873
+ actionFunctionArgs.stableswapPoolList =
874
+ swapData.parameters["stableswap-pools"];
875
+ }
876
+
877
+ if ("swaps-reversed" in swapData.parameters) {
878
+ actionFunctionArgs.boolList = {
879
+ a: swapData.parameters["swaps-reversed"].toString(),
880
+ };
881
+ } else {
882
+ delete actionFunctionArgs.boolList;
883
+ }
884
+
885
+ const dexPath = route.dexPath;
886
+ const trait = this.mapDexPathToActionTrait(dexPath);
887
+ actionFunctionArgs.actionTrait = trait;
888
+
889
+ return actionFunctionArgs;
890
+ }
891
+
892
+ public async getKeeperAggregatorRouteData(
893
+ tokenX: string,
894
+ tokenY: string,
895
+ amountX: number
896
+ ): Promise<ActionFunctionArgs> {
897
+ const quoteResult = await this.getKeeperQuoteForRoute(
898
+ tokenX,
899
+ tokenY,
900
+ amountX
901
+ );
902
+
903
+ if (
904
+ !quoteResult ||
905
+ !quoteResult.allRoutes ||
906
+ quoteResult.allRoutes.length === 0
907
+ ) {
908
+ throw new Error("No routes found");
909
+ }
910
+
911
+ const { bestRoute } = quoteResult;
912
+ if (!bestRoute) {
913
+ throw new Error("No best route found for keeper-compatible DEX paths");
914
+ }
915
+
916
+ const actionFunctionArgs = this.transformRouteToActionArgs(bestRoute);
917
+
918
+ return actionFunctionArgs;
919
+ }
920
+
921
+ public async createGroupOrder(
922
+ params: CreateGroupOrderParams
923
+ ): Promise<CreateGroupOrderResponse> {
924
+ try {
925
+ return await createGroupOrderAPI(params);
926
+ } catch (error) {
927
+ console.error("Error in BitflowSDK.createGroupOrder:", error);
928
+ throw error;
929
+ }
930
+ }
931
+
932
+ public async getGroupOrder(
933
+ groupId: string,
934
+ includeOrders?: boolean
935
+ ): Promise<GetGroupOrderResponse> {
936
+ try {
937
+ return await getGroupOrderAPI(groupId, includeOrders);
938
+ } catch (error) {
939
+ console.error("Error in BitflowSDK.getGroupOrder:", error);
940
+ throw error;
941
+ }
942
+ }
943
+
944
+ public async cancelOrder(orderId: string): Promise<CancelOrderResponse> {
945
+ try {
946
+ return await cancelOrderAPI(orderId);
947
+ } catch (error) {
948
+ console.error("Error in BitflowSDK.cancelOrder:", error);
949
+ throw error;
950
+ }
951
+ }
952
+
953
+ public async cancelGroupOrder(
954
+ groupId: string
955
+ ): Promise<CancelGroupOrderResponse> {
956
+ try {
957
+ return await cancelGroupOrderAPI(groupId);
958
+ } catch (error) {
959
+ console.error("Error in BitflowSDK.cancelGroupOrder:", error);
960
+ throw error;
961
+ }
962
+ }
385
963
  }