hak-saucerswap-plugin 1.0.0 → 1.0.1

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/dist/index.cjs CHANGED
@@ -3,15 +3,15 @@
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var zod = require('zod');
6
- var sdk = require('@hashgraph/sdk');
7
6
  var axios = require('axios');
7
+ var sdk = require('@hashgraph/sdk');
8
8
  var hederaAgentKit = require('hedera-agent-kit');
9
9
 
10
10
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
11
 
12
12
  var axios__default = /*#__PURE__*/_interopDefault(axios);
13
13
 
14
- // src/tools/swap.ts
14
+ // src/tools/farms.ts
15
15
 
16
16
  // src/api/endpoints.ts
17
17
  var SAUCER_ENDPOINTS = {
@@ -197,6 +197,35 @@ var resolveSaucerSwapConfig = (context) => {
197
197
  };
198
198
  };
199
199
 
200
+ // src/tools/farms.ts
201
+ var farmsInputSchema = zod.z.object({
202
+ poolId: zod.z.number().int().positive().optional().describe("Optional pool ID to filter farms")
203
+ });
204
+ var farmsTool = {
205
+ method: "saucerswap_get_farms",
206
+ name: "SaucerSwap Get Farms",
207
+ description: "Get active farming opportunities on SaucerSwap.",
208
+ parameters: farmsInputSchema,
209
+ execute: async (_client, context, params) => {
210
+ const args = farmsInputSchema.parse(params);
211
+ const config = resolveSaucerSwapConfig(context);
212
+ const api = context.saucerswapClient ?? createSaucerSwapClient(config);
213
+ try {
214
+ const farms = await api.getFarms();
215
+ const filtered = args.poolId ? farms.filter((farm) => farm.poolId === args.poolId) : farms;
216
+ return {
217
+ success: true,
218
+ farms: filtered
219
+ };
220
+ } catch (error) {
221
+ return {
222
+ success: false,
223
+ error: error instanceof Error ? error.message : "Unknown error"
224
+ };
225
+ }
226
+ }
227
+ };
228
+
200
229
  // src/utils/amm.ts
201
230
  var calculatePriceImpact = (inputAmount, outputAmount, poolReserveIn, poolReserveOut) => {
202
231
  const inAmount = Number(inputAmount);
@@ -218,53 +247,6 @@ var applySlippageToAmount = (amount, slippageTolerance) => {
218
247
  const numerator = BigInt(1e4 - bps);
219
248
  return (amountBig * numerator / 10000n).toString();
220
249
  };
221
-
222
- // src/utils/quote.ts
223
- var readAmount = (value) => {
224
- if (typeof value === "string") {
225
- return value;
226
- }
227
- if (typeof value === "number" && Number.isFinite(value)) {
228
- return value.toString();
229
- }
230
- return null;
231
- };
232
- var normalizeSwapQuote = (quote, fallbackAmountIn) => {
233
- const amountIn = readAmount(quote.amountIn) ?? fallbackAmountIn;
234
- const expectedOutput = readAmount(quote.expectedOutput) ?? readAmount(quote.amountOut) ?? readAmount(quote.amountOutMin) ?? "";
235
- if (!expectedOutput) {
236
- throw new Error("SaucerSwap quote did not include an output amount.");
237
- }
238
- return {
239
- amountIn,
240
- expectedOutput,
241
- priceImpact: typeof quote.priceImpact === "number" ? quote.priceImpact : null,
242
- route: Array.isArray(quote.route) ? quote.route : [],
243
- raw: quote
244
- };
245
- };
246
-
247
- // src/utils/units.ts
248
- var parseUnits = (amount, decimals) => {
249
- if (!amount || typeof amount !== "string") {
250
- throw new Error("Amount must be a non-empty string.");
251
- }
252
- if (decimals < 0) {
253
- throw new Error("Decimals must be non-negative.");
254
- }
255
- const trimmed = amount.trim();
256
- if (!/^\d+(\.\d+)?$/.test(trimmed)) {
257
- throw new Error(`Invalid amount format: ${amount}`);
258
- }
259
- const [whole, fraction = ""] = trimmed.split(".");
260
- if (fraction.length > decimals) {
261
- throw new Error(`Amount has more than ${decimals} decimal places.`);
262
- }
263
- const paddedFraction = fraction.padEnd(decimals, "0");
264
- const combined = `${whole}${paddedFraction}`;
265
- const normalized = combined.replace(/^0+/, "");
266
- return normalized === "" ? "0" : normalized;
267
- };
268
250
  var normalizeTokenAlias = (token, config) => {
269
251
  const tokenLower = token.toLowerCase();
270
252
  for (const [alias, value] of Object.entries(config.tokenAliases)) {
@@ -312,211 +294,29 @@ var finalizeTransaction = async (transaction, client, context, extra) => {
312
294
  };
313
295
  };
314
296
 
315
- // src/tools/swap.ts
316
- var swapInputSchema = zod.z.object({
317
- fromToken: zod.z.string().describe("Token ID to swap from (e.g., 'HBAR' or '0.0.123456')"),
318
- toToken: zod.z.string().describe("Token ID to swap to"),
319
- amount: zod.z.string().describe("Amount to swap (decimal format)"),
320
- slippageTolerance: zod.z.number().optional().default(0.5).describe("Maximum slippage tolerance percentage"),
321
- deadline: zod.z.number().optional().describe("Transaction deadline in minutes from now or a unix timestamp")
322
- });
323
- var resolveDeadline = (deadlineInput, defaultMinutes) => {
324
- const now = Math.floor(Date.now() / 1e3);
325
- if (!deadlineInput) {
326
- return now + defaultMinutes * 60;
327
- }
328
- if (deadlineInput > now + 60) {
329
- return Math.floor(deadlineInput);
297
+ // src/utils/units.ts
298
+ var parseUnits = (amount, decimals) => {
299
+ if (!amount || typeof amount !== "string") {
300
+ throw new Error("Amount must be a non-empty string.");
330
301
  }
331
- return now + Math.round(deadlineInput) * 60;
332
- };
333
- var amountToSmallest = (amount, decimals) => {
334
- return parseUnits(amount, decimals);
335
- };
336
- var expectedToSmallest = (amount, decimals) => {
337
- return amount.includes(".") ? parseUnits(amount, decimals) : amount;
338
- };
339
- var swapTool = {
340
- method: "saucerswap_swap_tokens",
341
- name: "SaucerSwap Swap Tokens",
342
- description: "Execute a token swap on SaucerSwap DEX.",
343
- parameters: swapInputSchema,
344
- execute: async (client, context, params) => {
345
- const args = swapInputSchema.parse(params);
346
- const config = resolveSaucerSwapConfig(context);
347
- const operatorAccountId = client?.operatorAccountId?.toString();
348
- const slippageTolerance = args.slippageTolerance ?? 0.5;
349
- if (!operatorAccountId) {
350
- return {
351
- success: false,
352
- error: "Hedera client with an operator account is required for swaps."
353
- };
354
- }
355
- try {
356
- const api = context.saucerswapClient ?? createSaucerSwapClient(config);
357
- const fromTokenAlias = normalizeTokenAlias(args.fromToken, config);
358
- const toTokenAlias = normalizeTokenAlias(args.toToken, config);
359
- const fromTokenId = await api.resolveTokenId(fromTokenAlias);
360
- const toTokenId = await api.resolveTokenId(toTokenAlias);
361
- const fromTokenMeta = await api.getTokenByIdOrSymbol(fromTokenId);
362
- const toTokenMeta = await api.getTokenByIdOrSymbol(toTokenId);
363
- if (!fromTokenMeta || !toTokenMeta) {
364
- return {
365
- success: false,
366
- error: "Unable to resolve token metadata from SaucerSwap API."
367
- };
368
- }
369
- const quote = normalizeSwapQuote(
370
- await api.getSwapQuote({
371
- fromToken: fromTokenId,
372
- toToken: toTokenId,
373
- amount: args.amount
374
- }),
375
- args.amount
376
- );
377
- const amountInSmallest = amountToSmallest(args.amount, fromTokenMeta.decimals);
378
- const expectedOutSmallest = expectedToSmallest(
379
- quote.expectedOutput,
380
- toTokenMeta.decimals
381
- );
382
- const minOutSmallest = applySlippageToAmount(expectedOutSmallest, slippageTolerance);
383
- const routerContractId = config.defaultPoolVersion === "v2" ? config.routerV2ContractId ?? config.routerContractId : config.routerContractId ?? config.routerV2ContractId;
384
- if (!routerContractId) {
385
- return {
386
- success: false,
387
- error: "Missing SaucerSwap router contract ID configuration."
388
- };
389
- }
390
- const deadline = resolveDeadline(args.deadline, config.deadlineMinutes);
391
- const toAddress = accountIdToSolidityAddress(operatorAccountId);
392
- const path = [
393
- tokenIdToSolidityAddress(requireTokenId(fromTokenId)),
394
- tokenIdToSolidityAddress(requireTokenId(toTokenId))
395
- ];
396
- const params2 = new sdk.ContractFunctionParameters().addUint256(amountInSmallest).addUint256(minOutSmallest).addAddressArray(path).addAddress(toAddress).addUint256(deadline);
397
- const transaction = new sdk.ContractExecuteTransaction().setContractId(contractIdFromString(routerContractId)).setGas(config.gasLimit).setFunction("swapExactTokensForTokens", params2);
398
- return await finalizeTransaction(transaction, client, context, {
399
- estimatedOutput: quote.expectedOutput,
400
- minOutput: minOutSmallest,
401
- priceImpact: quote.priceImpact,
402
- route: quote.route
403
- });
404
- } catch (error) {
405
- return {
406
- success: false,
407
- error: error instanceof Error ? error.message : "Unknown error"
408
- };
409
- }
302
+ if (decimals < 0) {
303
+ throw new Error("Decimals must be non-negative.");
410
304
  }
411
- };
412
- var quoteInputSchema = zod.z.object({
413
- fromToken: zod.z.string().describe("Token ID to swap from (e.g., 'HBAR' or '0.0.123456')"),
414
- toToken: zod.z.string().describe("Token ID to swap to"),
415
- amount: zod.z.string().describe("Amount to swap (decimal format)"),
416
- slippageTolerance: zod.z.number().optional().default(0.5).describe("Maximum slippage tolerance percentage")
417
- });
418
- var quoteTool = {
419
- method: "saucerswap_get_swap_quote",
420
- name: "SaucerSwap Get Swap Quote",
421
- description: "Get a price quote for swapping tokens on SaucerSwap.",
422
- parameters: quoteInputSchema,
423
- execute: async (_client, context, params) => {
424
- const args = quoteInputSchema.parse(params);
425
- const config = resolveSaucerSwapConfig(context);
426
- const client = context.saucerswapClient;
427
- const api = client ?? createSaucerSwapClient(config);
428
- const slippageTolerance = args.slippageTolerance ?? 0.5;
429
- try {
430
- const fromToken = normalizeTokenAlias(args.fromToken, config);
431
- const toToken = normalizeTokenAlias(args.toToken, config);
432
- const fromTokenId = await api.resolveTokenId(fromToken);
433
- const toTokenId = await api.resolveTokenId(toToken);
434
- const rawQuote = await api.getSwapQuote({
435
- fromToken: fromTokenId,
436
- toToken: toTokenId,
437
- amount: args.amount
438
- });
439
- const normalized = normalizeSwapQuote(rawQuote, args.amount);
440
- if (normalized.priceImpact === null) {
441
- const pool = await api.getPoolByTokens(fromTokenId, toTokenId, config.defaultPoolVersion);
442
- if (pool && rawQuote.amountIn && rawQuote.amountOut) {
443
- const isAToB = pool.tokenA.id === fromTokenId;
444
- const reserveIn = isAToB ? pool.tokenReserveA : pool.tokenReserveB;
445
- const reserveOut = isAToB ? pool.tokenReserveB : pool.tokenReserveA;
446
- normalized.priceImpact = calculatePriceImpact(
447
- rawQuote.amountIn,
448
- rawQuote.amountOut,
449
- reserveIn,
450
- reserveOut
451
- );
452
- }
453
- }
454
- let minOutput = null;
455
- if (normalized.expectedOutput.includes(".")) {
456
- const expectedNumber = Number(normalized.expectedOutput);
457
- minOutput = Number.isFinite(expectedNumber) ? (expectedNumber * (1 - slippageTolerance / 100)).toString() : null;
458
- } else {
459
- minOutput = applySlippageToAmount(normalized.expectedOutput, slippageTolerance);
460
- }
461
- return {
462
- success: true,
463
- quote: normalized,
464
- minOutput
465
- };
466
- } catch (error) {
467
- return {
468
- success: false,
469
- error: error instanceof Error ? error.message : "Unknown error"
470
- };
471
- }
305
+ const trimmed = amount.trim();
306
+ if (!/^\d+(\.\d+)?$/.test(trimmed)) {
307
+ throw new Error(`Invalid amount format: ${amount}`);
472
308
  }
473
- };
474
- var poolsInputSchema = zod.z.object({
475
- tokenA: zod.z.string().optional().describe("First token ID or symbol"),
476
- tokenB: zod.z.string().optional().describe("Second token ID or symbol"),
477
- version: zod.z.enum(["v1", "v2"]).optional().describe("Pool version"),
478
- limit: zod.z.number().int().positive().optional().describe("Maximum number of pools to return")
479
- });
480
- var poolsTool = {
481
- method: "saucerswap_get_pools",
482
- name: "SaucerSwap Get Pools",
483
- description: "Query SaucerSwap liquidity pools and reserves.",
484
- parameters: poolsInputSchema,
485
- execute: async (_client, context, params) => {
486
- const args = poolsInputSchema.parse(params);
487
- const config = resolveSaucerSwapConfig(context);
488
- const api = context.saucerswapClient ?? createSaucerSwapClient(config);
489
- try {
490
- const version = args.version ?? config.defaultPoolVersion;
491
- const pools = await api.getPools(version);
492
- let filtered = pools;
493
- if (args.tokenA && args.tokenB) {
494
- const tokenA = normalizeTokenAlias(args.tokenA, config);
495
- const tokenB = normalizeTokenAlias(args.tokenB, config);
496
- const tokenAId = await api.resolveTokenId(tokenA);
497
- const tokenBId = await api.resolveTokenId(tokenB);
498
- filtered = pools.filter((pool) => {
499
- const direct = pool.tokenA.id === tokenAId && pool.tokenB.id === tokenBId;
500
- const inverse = pool.tokenA.id === tokenBId && pool.tokenB.id === tokenAId;
501
- return direct || inverse;
502
- });
503
- }
504
- if (args.limit && args.limit > 0) {
505
- filtered = filtered.slice(0, args.limit);
506
- }
507
- return {
508
- success: true,
509
- version,
510
- pools: filtered
511
- };
512
- } catch (error) {
513
- return {
514
- success: false,
515
- error: error instanceof Error ? error.message : "Unknown error"
516
- };
517
- }
309
+ const [whole, fraction = ""] = trimmed.split(".");
310
+ if (fraction.length > decimals) {
311
+ throw new Error(`Amount has more than ${decimals} decimal places.`);
518
312
  }
313
+ const paddedFraction = fraction.padEnd(decimals, "0");
314
+ const combined = `${whole}${paddedFraction}`;
315
+ const normalized = combined.replace(/^0+/, "");
316
+ return normalized === "" ? "0" : normalized;
519
317
  };
318
+
319
+ // src/tools/liquidity.ts
520
320
  var addLiquidityInputSchema = zod.z.object({
521
321
  tokenA: zod.z.string().describe("First token ID"),
522
322
  tokenB: zod.z.string().describe("Second token ID"),
@@ -531,7 +331,7 @@ var removeLiquidityInputSchema = zod.z.object({
531
331
  minAmountA: zod.z.string().describe("Minimum amount of tokenA to receive"),
532
332
  minAmountB: zod.z.string().describe("Minimum amount of tokenB to receive")
533
333
  });
534
- var resolveDeadline2 = (defaultMinutes) => {
334
+ var resolveDeadline = (defaultMinutes) => {
535
335
  return Math.floor(Date.now() / 1e3) + defaultMinutes * 60;
536
336
  };
537
337
  var resolveRouterContract = (config) => {
@@ -576,7 +376,7 @@ var addLiquidityTool = {
576
376
  const amountAMin = applySlippageToAmount(amountADesired, slippageTolerance);
577
377
  const amountBMin = applySlippageToAmount(amountBDesired, slippageTolerance);
578
378
  const routerContractId = resolveRouterContract(config);
579
- const deadline = resolveDeadline2(config.deadlineMinutes);
379
+ const deadline = resolveDeadline(config.deadlineMinutes);
580
380
  const toAddress = accountIdToSolidityAddress(operatorAccountId);
581
381
  const params2 = new sdk.ContractFunctionParameters().addAddress(tokenIdToSolidityAddress(requireTokenId(tokenAId))).addAddress(tokenIdToSolidityAddress(requireTokenId(tokenBId))).addUint256(amountADesired).addUint256(amountBDesired).addUint256(amountAMin).addUint256(amountBMin).addAddress(toAddress).addUint256(deadline);
582
382
  const transaction = new sdk.ContractExecuteTransaction().setContractId(contractIdFromString(routerContractId)).setGas(config.gasLimit).setFunction("addLiquidity", params2);
@@ -634,7 +434,7 @@ var removeLiquidityTool = {
634
434
  const minAmountA = parseUnits(args.minAmountA, tokenA.decimals);
635
435
  const minAmountB = parseUnits(args.minAmountB, tokenB.decimals);
636
436
  const routerContractId = resolveRouterContract(config);
637
- const deadline = resolveDeadline2(config.deadlineMinutes);
437
+ const deadline = resolveDeadline(config.deadlineMinutes);
638
438
  const toAddress = accountIdToSolidityAddress(operatorAccountId);
639
439
  const params2 = new sdk.ContractFunctionParameters().addAddress(tokenIdToSolidityAddress(requireTokenId(tokenAId))).addAddress(tokenIdToSolidityAddress(requireTokenId(tokenBId))).addUint256(lpAmount).addUint256(minAmountA).addUint256(minAmountB).addAddress(toAddress).addUint256(deadline);
640
440
  const transaction = new sdk.ContractExecuteTransaction().setContractId(contractIdFromString(routerContractId)).setGas(config.gasLimit).setFunction("removeLiquidity", params2);
@@ -651,25 +451,226 @@ var removeLiquidityTool = {
651
451
  }
652
452
  }
653
453
  };
654
- var farmsInputSchema = zod.z.object({
655
- poolId: zod.z.number().int().positive().optional().describe("Optional pool ID to filter farms")
454
+ var poolsInputSchema = zod.z.object({
455
+ tokenA: zod.z.string().optional().describe("First token ID or symbol"),
456
+ tokenB: zod.z.string().optional().describe("Second token ID or symbol"),
457
+ version: zod.z.enum(["v1", "v2"]).optional().describe("Pool version"),
458
+ limit: zod.z.number().int().positive().optional().describe("Maximum number of pools to return")
656
459
  });
657
- var farmsTool = {
658
- method: "saucerswap_get_farms",
659
- name: "SaucerSwap Get Farms",
660
- description: "Get active farming opportunities on SaucerSwap.",
661
- parameters: farmsInputSchema,
460
+ var poolsTool = {
461
+ method: "saucerswap_get_pools",
462
+ name: "SaucerSwap Get Pools",
463
+ description: "Query SaucerSwap liquidity pools and reserves.",
464
+ parameters: poolsInputSchema,
662
465
  execute: async (_client, context, params) => {
663
- const args = farmsInputSchema.parse(params);
466
+ const args = poolsInputSchema.parse(params);
664
467
  const config = resolveSaucerSwapConfig(context);
665
468
  const api = context.saucerswapClient ?? createSaucerSwapClient(config);
666
469
  try {
667
- const farms = await api.getFarms();
668
- const filtered = args.poolId ? farms.filter((farm) => farm.poolId === args.poolId) : farms;
470
+ const version = args.version ?? config.defaultPoolVersion;
471
+ const pools = await api.getPools(version);
472
+ let filtered = pools;
473
+ if (args.tokenA && args.tokenB) {
474
+ const tokenA = normalizeTokenAlias(args.tokenA, config);
475
+ const tokenB = normalizeTokenAlias(args.tokenB, config);
476
+ const tokenAId = await api.resolveTokenId(tokenA);
477
+ const tokenBId = await api.resolveTokenId(tokenB);
478
+ filtered = pools.filter((pool) => {
479
+ const direct = pool.tokenA.id === tokenAId && pool.tokenB.id === tokenBId;
480
+ const inverse = pool.tokenA.id === tokenBId && pool.tokenB.id === tokenAId;
481
+ return direct || inverse;
482
+ });
483
+ }
484
+ if (args.limit && args.limit > 0) {
485
+ filtered = filtered.slice(0, args.limit);
486
+ }
669
487
  return {
670
488
  success: true,
671
- farms: filtered
489
+ version,
490
+ pools: filtered
491
+ };
492
+ } catch (error) {
493
+ return {
494
+ success: false,
495
+ error: error instanceof Error ? error.message : "Unknown error"
672
496
  };
497
+ }
498
+ }
499
+ };
500
+
501
+ // src/utils/quote.ts
502
+ var readAmount = (value) => {
503
+ if (typeof value === "string") {
504
+ return value;
505
+ }
506
+ if (typeof value === "number" && Number.isFinite(value)) {
507
+ return value.toString();
508
+ }
509
+ return null;
510
+ };
511
+ var normalizeSwapQuote = (quote, fallbackAmountIn) => {
512
+ const amountIn = readAmount(quote.amountIn) ?? fallbackAmountIn;
513
+ const expectedOutput = readAmount(quote.expectedOutput) ?? readAmount(quote.amountOut) ?? readAmount(quote.amountOutMin) ?? "";
514
+ if (!expectedOutput) {
515
+ throw new Error("SaucerSwap quote did not include an output amount.");
516
+ }
517
+ return {
518
+ amountIn,
519
+ expectedOutput,
520
+ priceImpact: typeof quote.priceImpact === "number" ? quote.priceImpact : null,
521
+ route: Array.isArray(quote.route) ? quote.route : [],
522
+ raw: quote
523
+ };
524
+ };
525
+
526
+ // src/tools/quote.ts
527
+ var quoteInputSchema = zod.z.object({
528
+ fromToken: zod.z.string().describe("Token ID to swap from (e.g., 'HBAR' or '0.0.123456')"),
529
+ toToken: zod.z.string().describe("Token ID to swap to"),
530
+ amount: zod.z.string().describe("Amount to swap (decimal format)"),
531
+ slippageTolerance: zod.z.number().optional().default(0.5).describe("Maximum slippage tolerance percentage")
532
+ });
533
+ var quoteTool = {
534
+ method: "saucerswap_get_swap_quote",
535
+ name: "SaucerSwap Get Swap Quote",
536
+ description: "Get a price quote for swapping tokens on SaucerSwap.",
537
+ parameters: quoteInputSchema,
538
+ execute: async (_client, context, params) => {
539
+ const args = quoteInputSchema.parse(params);
540
+ const config = resolveSaucerSwapConfig(context);
541
+ const client = context.saucerswapClient;
542
+ const api = client ?? createSaucerSwapClient(config);
543
+ const slippageTolerance = args.slippageTolerance ?? 0.5;
544
+ try {
545
+ const fromToken = normalizeTokenAlias(args.fromToken, config);
546
+ const toToken = normalizeTokenAlias(args.toToken, config);
547
+ const fromTokenId = await api.resolveTokenId(fromToken);
548
+ const toTokenId = await api.resolveTokenId(toToken);
549
+ const rawQuote = await api.getSwapQuote({
550
+ fromToken: fromTokenId,
551
+ toToken: toTokenId,
552
+ amount: args.amount
553
+ });
554
+ const normalized = normalizeSwapQuote(rawQuote, args.amount);
555
+ if (normalized.priceImpact === null) {
556
+ const pool = await api.getPoolByTokens(fromTokenId, toTokenId, config.defaultPoolVersion);
557
+ if (pool && rawQuote.amountIn && rawQuote.amountOut) {
558
+ const isAToB = pool.tokenA.id === fromTokenId;
559
+ const reserveIn = isAToB ? pool.tokenReserveA : pool.tokenReserveB;
560
+ const reserveOut = isAToB ? pool.tokenReserveB : pool.tokenReserveA;
561
+ normalized.priceImpact = calculatePriceImpact(
562
+ rawQuote.amountIn,
563
+ rawQuote.amountOut,
564
+ reserveIn,
565
+ reserveOut
566
+ );
567
+ }
568
+ }
569
+ let minOutput = null;
570
+ if (normalized.expectedOutput.includes(".")) {
571
+ const expectedNumber = Number(normalized.expectedOutput);
572
+ minOutput = Number.isFinite(expectedNumber) ? (expectedNumber * (1 - slippageTolerance / 100)).toString() : null;
573
+ } else {
574
+ minOutput = applySlippageToAmount(normalized.expectedOutput, slippageTolerance);
575
+ }
576
+ return {
577
+ success: true,
578
+ quote: normalized,
579
+ minOutput
580
+ };
581
+ } catch (error) {
582
+ return {
583
+ success: false,
584
+ error: error instanceof Error ? error.message : "Unknown error"
585
+ };
586
+ }
587
+ }
588
+ };
589
+ var swapInputSchema = zod.z.object({
590
+ fromToken: zod.z.string().describe("Token ID to swap from (e.g., 'HBAR' or '0.0.123456')"),
591
+ toToken: zod.z.string().describe("Token ID to swap to"),
592
+ amount: zod.z.string().describe("Amount to swap (decimal format)"),
593
+ slippageTolerance: zod.z.number().optional().default(0.5).describe("Maximum slippage tolerance percentage"),
594
+ deadline: zod.z.number().optional().describe("Transaction deadline in minutes from now or a unix timestamp")
595
+ });
596
+ var resolveDeadline2 = (deadlineInput, defaultMinutes) => {
597
+ const now = Math.floor(Date.now() / 1e3);
598
+ if (!deadlineInput) {
599
+ return now + defaultMinutes * 60;
600
+ }
601
+ if (deadlineInput > now + 60) {
602
+ return Math.floor(deadlineInput);
603
+ }
604
+ return now + Math.round(deadlineInput) * 60;
605
+ };
606
+ var amountToSmallest = (amount, decimals) => {
607
+ return parseUnits(amount, decimals);
608
+ };
609
+ var expectedToSmallest = (amount, decimals) => {
610
+ return amount.includes(".") ? parseUnits(amount, decimals) : amount;
611
+ };
612
+ var swapTool = {
613
+ method: "saucerswap_swap_tokens",
614
+ name: "SaucerSwap Swap Tokens",
615
+ description: "Execute a token swap on SaucerSwap DEX.",
616
+ parameters: swapInputSchema,
617
+ execute: async (client, context, params) => {
618
+ const args = swapInputSchema.parse(params);
619
+ const config = resolveSaucerSwapConfig(context);
620
+ const operatorAccountId = client?.operatorAccountId?.toString();
621
+ const slippageTolerance = args.slippageTolerance ?? 0.5;
622
+ if (!operatorAccountId) {
623
+ return {
624
+ success: false,
625
+ error: "Hedera client with an operator account is required for swaps."
626
+ };
627
+ }
628
+ try {
629
+ const api = context.saucerswapClient ?? createSaucerSwapClient(config);
630
+ const fromTokenAlias = normalizeTokenAlias(args.fromToken, config);
631
+ const toTokenAlias = normalizeTokenAlias(args.toToken, config);
632
+ const fromTokenId = await api.resolveTokenId(fromTokenAlias);
633
+ const toTokenId = await api.resolveTokenId(toTokenAlias);
634
+ const fromTokenMeta = await api.getTokenByIdOrSymbol(fromTokenId);
635
+ const toTokenMeta = await api.getTokenByIdOrSymbol(toTokenId);
636
+ if (!fromTokenMeta || !toTokenMeta) {
637
+ return {
638
+ success: false,
639
+ error: "Unable to resolve token metadata from SaucerSwap API."
640
+ };
641
+ }
642
+ const quote = normalizeSwapQuote(
643
+ await api.getSwapQuote({
644
+ fromToken: fromTokenId,
645
+ toToken: toTokenId,
646
+ amount: args.amount
647
+ }),
648
+ args.amount
649
+ );
650
+ const amountInSmallest = amountToSmallest(args.amount, fromTokenMeta.decimals);
651
+ const expectedOutSmallest = expectedToSmallest(quote.expectedOutput, toTokenMeta.decimals);
652
+ const minOutSmallest = applySlippageToAmount(expectedOutSmallest, slippageTolerance);
653
+ const routerContractId = config.defaultPoolVersion === "v2" ? config.routerV2ContractId ?? config.routerContractId : config.routerContractId ?? config.routerV2ContractId;
654
+ if (!routerContractId) {
655
+ return {
656
+ success: false,
657
+ error: "Missing SaucerSwap router contract ID configuration."
658
+ };
659
+ }
660
+ const deadline = resolveDeadline2(args.deadline, config.deadlineMinutes);
661
+ const toAddress = accountIdToSolidityAddress(operatorAccountId);
662
+ const path = [
663
+ tokenIdToSolidityAddress(requireTokenId(fromTokenId)),
664
+ tokenIdToSolidityAddress(requireTokenId(toTokenId))
665
+ ];
666
+ const params2 = new sdk.ContractFunctionParameters().addUint256(amountInSmallest).addUint256(minOutSmallest).addAddressArray(path).addAddress(toAddress).addUint256(deadline);
667
+ const transaction = new sdk.ContractExecuteTransaction().setContractId(contractIdFromString(routerContractId)).setGas(config.gasLimit).setFunction("swapExactTokensForTokens", params2);
668
+ return await finalizeTransaction(transaction, client, context, {
669
+ estimatedOutput: quote.expectedOutput,
670
+ minOutput: minOutSmallest,
671
+ priceImpact: quote.priceImpact,
672
+ route: quote.route
673
+ });
673
674
  } catch (error) {
674
675
  return {
675
676
  success: false,