hak-saucerswap-plugin 1.0.0 → 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.
package/dist/index.cjs CHANGED
@@ -2,16 +2,16 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
+ var hederaAgentKit = require('@hashgraph/hedera-agent-kit');
5
6
  var zod = require('zod');
6
- var sdk = require('@hashgraph/sdk');
7
7
  var axios = require('axios');
8
- var hederaAgentKit = require('hedera-agent-kit');
8
+ var sdk = require('@hiero-ledger/sdk');
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 = {
@@ -42,7 +42,8 @@ var SaucerSwapClient = class {
42
42
  this.retries = options.retries ?? 2;
43
43
  this.http = options.http ?? axios__default.default.create({
44
44
  baseURL: options.baseUrl ?? "https://api.saucerswap.finance",
45
- timeout: options.timeoutMs ?? 1e4
45
+ timeout: options.timeoutMs ?? 1e4,
46
+ headers: options.apiKey ? { "x-api-key": options.apiKey } : void 0
46
47
  });
47
48
  }
48
49
  async request(path, params) {
@@ -184,6 +185,7 @@ var resolveSaucerSwapConfig = (context) => {
184
185
  baseUrl: ctxConfig.baseUrl ?? process.env.SAUCERSWAP_BASE_URL ?? DEFAULT_CONFIG.baseUrl,
185
186
  timeoutMs: ctxConfig.timeoutMs ?? toNumber(process.env.SAUCERSWAP_TIMEOUT_MS, DEFAULT_CONFIG.timeoutMs),
186
187
  retries: ctxConfig.retries ?? toNumber(process.env.SAUCERSWAP_RETRIES, DEFAULT_CONFIG.retries),
188
+ apiKey: ctxConfig.apiKey ?? process.env.SAUCERSWAP_API_KEY,
187
189
  routerContractId: ctxConfig.routerContractId ?? process.env.SAUCERSWAP_ROUTER_CONTRACT_ID,
188
190
  routerV2ContractId: ctxConfig.routerV2ContractId ?? process.env.SAUCERSWAP_ROUTER_V2_CONTRACT_ID,
189
191
  wrappedHbarTokenId: ctxConfig.wrappedHbarTokenId ?? process.env.SAUCERSWAP_WRAPPED_HBAR_TOKEN_ID,
@@ -197,6 +199,47 @@ var resolveSaucerSwapConfig = (context) => {
197
199
  };
198
200
  };
199
201
 
202
+ // src/tools/farms.ts
203
+ var farmsInputSchema = zod.z.object({
204
+ poolId: zod.z.number().int().positive().optional().describe("Optional pool ID to filter farms")
205
+ });
206
+ var FarmsTool = class extends hederaAgentKit.BaseTool {
207
+ constructor() {
208
+ super(...arguments);
209
+ this.method = "saucerswap_get_farms";
210
+ this.name = "SaucerSwap Get Farms";
211
+ this.description = "Get active farming opportunities on SaucerSwap.";
212
+ this.parameters = farmsInputSchema;
213
+ }
214
+ async normalizeParams(params, _context, _client) {
215
+ return farmsInputSchema.parse(params);
216
+ }
217
+ async coreAction(args, context, _client) {
218
+ const config = resolveSaucerSwapConfig(context);
219
+ const api = context.saucerswapClient ?? createSaucerSwapClient(config);
220
+ try {
221
+ const farms = await api.getFarms();
222
+ const filtered = args.poolId ? farms.filter((farm) => farm.poolId === args.poolId) : farms;
223
+ return {
224
+ success: true,
225
+ farms: filtered
226
+ };
227
+ } catch (error) {
228
+ return {
229
+ success: false,
230
+ error: error instanceof Error ? error.message : "Unknown error"
231
+ };
232
+ }
233
+ }
234
+ async shouldSecondaryAction(_coreActionResult, _context) {
235
+ return false;
236
+ }
237
+ async secondaryAction(_request, _client, _context) {
238
+ return null;
239
+ }
240
+ };
241
+ var farmsTool = new FarmsTool();
242
+
200
243
  // src/utils/amm.ts
201
244
  var calculatePriceImpact = (inputAmount, outputAmount, poolReserveIn, poolReserveOut) => {
202
245
  const inAmount = Number(inputAmount);
@@ -218,53 +261,6 @@ var applySlippageToAmount = (amount, slippageTolerance) => {
218
261
  const numerator = BigInt(1e4 - bps);
219
262
  return (amountBig * numerator / 10000n).toString();
220
263
  };
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
264
  var normalizeTokenAlias = (token, config) => {
269
265
  const tokenLower = token.toLowerCase();
270
266
  for (const [alias, value] of Object.entries(config.tokenAliases)) {
@@ -292,115 +288,106 @@ var accountIdToSolidityAddress = (accountId) => {
292
288
  var contractIdFromString = (contractId) => {
293
289
  return sdk.ContractId.fromString(contractId);
294
290
  };
295
- var finalizeTransaction = async (transaction, client, context, extra) => {
296
- const mode = context.mode;
297
- if (mode === hederaAgentKit.AgentMode.RETURN_BYTES || mode === "returnBytes" || mode === "RETURN_BYTES") {
298
- const txBytes = await transaction.toBytes();
299
- return {
300
- success: true,
301
- transactionBytes: Buffer.from(txBytes).toString("base64"),
302
- ...extra
303
- };
304
- }
305
- const response = await transaction.execute(client);
306
- const receipt = await response.getReceipt(client);
307
- return {
308
- success: true,
309
- transactionId: response.transactionId?.toString(),
310
- status: receipt.status.toString(),
311
- ...extra
312
- };
313
- };
314
291
 
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;
292
+ // src/utils/units.ts
293
+ var parseUnits = (amount, decimals) => {
294
+ if (!amount || typeof amount !== "string") {
295
+ throw new Error("Amount must be a non-empty string.");
327
296
  }
328
- if (deadlineInput > now + 60) {
329
- return Math.floor(deadlineInput);
297
+ if (decimals < 0) {
298
+ throw new Error("Decimals must be non-negative.");
330
299
  }
331
- return now + Math.round(deadlineInput) * 60;
300
+ const trimmed = amount.trim();
301
+ if (!/^\d+(\.\d+)?$/.test(trimmed)) {
302
+ throw new Error(`Invalid amount format: ${amount}`);
303
+ }
304
+ const [whole, fraction = ""] = trimmed.split(".");
305
+ if (fraction.length > decimals) {
306
+ throw new Error(`Amount has more than ${decimals} decimal places.`);
307
+ }
308
+ const paddedFraction = fraction.padEnd(decimals, "0");
309
+ const combined = `${whole}${paddedFraction}`;
310
+ const normalized = combined.replace(/^0+/, "");
311
+ return normalized === "" ? "0" : normalized;
332
312
  };
333
- var amountToSmallest = (amount, decimals) => {
334
- return parseUnits(amount, decimals);
313
+
314
+ // src/tools/liquidity.ts
315
+ var addLiquidityInputSchema = zod.z.object({
316
+ tokenA: zod.z.string().describe("First token ID"),
317
+ tokenB: zod.z.string().describe("Second token ID"),
318
+ amountA: zod.z.string().describe("Amount of tokenA to add"),
319
+ amountB: zod.z.string().describe("Amount of tokenB to add"),
320
+ slippageTolerance: zod.z.number().optional().default(0.5).describe("Maximum slippage tolerance percentage")
321
+ });
322
+ var removeLiquidityInputSchema = zod.z.object({
323
+ tokenA: zod.z.string().describe("First token ID"),
324
+ tokenB: zod.z.string().describe("Second token ID"),
325
+ lpTokenAmount: zod.z.string().describe("Amount of LP tokens to burn"),
326
+ minAmountA: zod.z.string().describe("Minimum amount of tokenA to receive"),
327
+ minAmountB: zod.z.string().describe("Minimum amount of tokenB to receive")
328
+ });
329
+ var isLiquidityCorePayload = (value) => typeof value === "object" && value !== null && "transaction" in value;
330
+ var addLiquidityPostProcess = (response) => `SaucerSwap add liquidity submitted. Status: ${response.status}. Transaction ID: ${response.transactionId}`;
331
+ var removeLiquidityPostProcess = (response) => `SaucerSwap remove liquidity submitted. Status: ${response.status}. Transaction ID: ${response.transactionId}`;
332
+ var resolveDeadline = (defaultMinutes) => {
333
+ return Math.floor(Date.now() / 1e3) + defaultMinutes * 60;
335
334
  };
336
- var expectedToSmallest = (amount, decimals) => {
337
- return amount.includes(".") ? parseUnits(amount, decimals) : amount;
335
+ var resolveRouterContract = (config) => {
336
+ const routerContractId = config.defaultPoolVersion === "v2" ? config.routerV2ContractId ?? config.routerContractId : config.routerContractId ?? config.routerV2ContractId;
337
+ if (!routerContractId) {
338
+ throw new Error("Missing SaucerSwap router contract ID configuration.");
339
+ }
340
+ return routerContractId;
338
341
  };
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);
342
+ var AddLiquidityTool = class extends hederaAgentKit.BaseTool {
343
+ constructor() {
344
+ super(...arguments);
345
+ this.method = "saucerswap_add_liquidity";
346
+ this.name = "SaucerSwap Add Liquidity";
347
+ this.description = "Add liquidity to a SaucerSwap pool.";
348
+ this.parameters = addLiquidityInputSchema;
349
+ }
350
+ async normalizeParams(params, _context, _client) {
351
+ return addLiquidityInputSchema.parse(params);
352
+ }
353
+ async coreAction(args, context, client) {
346
354
  const config = resolveSaucerSwapConfig(context);
347
355
  const operatorAccountId = client?.operatorAccountId?.toString();
348
356
  const slippageTolerance = args.slippageTolerance ?? 0.5;
349
357
  if (!operatorAccountId) {
350
358
  return {
351
359
  success: false,
352
- error: "Hedera client with an operator account is required for swaps."
360
+ error: "Hedera client with an operator account is required for liquidity actions."
353
361
  };
354
362
  }
355
363
  try {
356
364
  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) {
365
+ const tokenAInput = normalizeTokenAlias(args.tokenA, config);
366
+ const tokenBInput = normalizeTokenAlias(args.tokenB, config);
367
+ const tokenAId = await api.resolveTokenId(tokenAInput);
368
+ const tokenBId = await api.resolveTokenId(tokenBInput);
369
+ const tokenA = await api.getTokenByIdOrSymbol(tokenAId);
370
+ const tokenB = await api.getTokenByIdOrSymbol(tokenBId);
371
+ if (!tokenA || !tokenB) {
364
372
  return {
365
373
  success: false,
366
374
  error: "Unable to resolve token metadata from SaucerSwap API."
367
375
  };
368
376
  }
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);
377
+ const amountADesired = parseUnits(args.amountA, tokenA.decimals);
378
+ const amountBDesired = parseUnits(args.amountB, tokenB.decimals);
379
+ const amountAMin = applySlippageToAmount(amountADesired, slippageTolerance);
380
+ const amountBMin = applySlippageToAmount(amountBDesired, slippageTolerance);
381
+ const routerContractId = resolveRouterContract(config);
382
+ const deadline = resolveDeadline(config.deadlineMinutes);
391
383
  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
- });
384
+ const params = new sdk.ContractFunctionParameters().addAddress(tokenIdToSolidityAddress(requireTokenId(tokenAId))).addAddress(tokenIdToSolidityAddress(requireTokenId(tokenBId))).addUint256(amountADesired).addUint256(amountBDesired).addUint256(amountAMin).addUint256(amountBMin).addAddress(toAddress).addUint256(deadline);
385
+ const transaction = new sdk.ContractExecuteTransaction().setContractId(contractIdFromString(routerContractId)).setGas(config.gasLimit).setFunction("addLiquidity", params);
386
+ const payload = {
387
+ transaction,
388
+ extras: { amountADesired, amountBDesired, amountAMin, amountBMin }
389
+ };
390
+ return payload;
404
391
  } catch (error) {
405
392
  return {
406
393
  success: false,
@@ -408,61 +395,73 @@ var swapTool = {
408
395
  };
409
396
  }
410
397
  }
398
+ async shouldSecondaryAction(coreActionResult, _context) {
399
+ return isLiquidityCorePayload(coreActionResult);
400
+ }
401
+ async secondaryAction(payload, client, context) {
402
+ const result = await hederaAgentKit.handleTransaction(
403
+ payload.transaction,
404
+ client,
405
+ context,
406
+ addLiquidityPostProcess
407
+ );
408
+ return { ...result, ...payload.extras };
409
+ }
411
410
  };
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);
411
+ var RemoveLiquidityTool = class extends hederaAgentKit.BaseTool {
412
+ constructor() {
413
+ super(...arguments);
414
+ this.method = "saucerswap_remove_liquidity";
415
+ this.name = "SaucerSwap Remove Liquidity";
416
+ this.description = "Remove liquidity from a SaucerSwap pool.";
417
+ this.parameters = removeLiquidityInputSchema;
418
+ }
419
+ async normalizeParams(params, _context, _client) {
420
+ return removeLiquidityInputSchema.parse(params);
421
+ }
422
+ async coreAction(args, context, client) {
425
423
  const config = resolveSaucerSwapConfig(context);
426
- const client = context.saucerswapClient;
427
- const api = client ?? createSaucerSwapClient(config);
428
- const slippageTolerance = args.slippageTolerance ?? 0.5;
424
+ const operatorAccountId = client?.operatorAccountId?.toString();
425
+ if (!operatorAccountId) {
426
+ return {
427
+ success: false,
428
+ error: "Hedera client with an operator account is required for liquidity actions."
429
+ };
430
+ }
429
431
  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
- }
432
+ const api = context.saucerswapClient ?? createSaucerSwapClient(config);
433
+ const tokenAInput = normalizeTokenAlias(args.tokenA, config);
434
+ const tokenBInput = normalizeTokenAlias(args.tokenB, config);
435
+ const tokenAId = await api.resolveTokenId(tokenAInput);
436
+ const tokenBId = await api.resolveTokenId(tokenBInput);
437
+ const pool = await api.getPoolByTokens(tokenAId, tokenBId, config.defaultPoolVersion);
438
+ if (!pool) {
439
+ return {
440
+ success: false,
441
+ error: "Unable to locate pool for the provided token pair."
442
+ };
453
443
  }
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);
444
+ const tokenA = await api.getTokenByIdOrSymbol(tokenAId);
445
+ const tokenB = await api.getTokenByIdOrSymbol(tokenBId);
446
+ if (!tokenA || !tokenB) {
447
+ return {
448
+ success: false,
449
+ error: "Unable to resolve token metadata from SaucerSwap API."
450
+ };
460
451
  }
461
- return {
462
- success: true,
463
- quote: normalized,
464
- minOutput
452
+ const lpAmount = parseUnits(args.lpTokenAmount, pool.lpToken.decimals);
453
+ const minAmountA = parseUnits(args.minAmountA, tokenA.decimals);
454
+ const minAmountB = parseUnits(args.minAmountB, tokenB.decimals);
455
+ const routerContractId = resolveRouterContract(config);
456
+ const deadline = resolveDeadline(config.deadlineMinutes);
457
+ const toAddress = accountIdToSolidityAddress(operatorAccountId);
458
+ const params = new sdk.ContractFunctionParameters().addAddress(tokenIdToSolidityAddress(requireTokenId(tokenAId))).addAddress(tokenIdToSolidityAddress(requireTokenId(tokenBId))).addUint256(lpAmount).addUint256(minAmountA).addUint256(minAmountB).addAddress(toAddress).addUint256(deadline);
459
+ const transaction = new sdk.ContractExecuteTransaction().setContractId(contractIdFromString(routerContractId)).setGas(config.gasLimit).setFunction("removeLiquidity", params);
460
+ const payload = {
461
+ transaction,
462
+ extras: { lpAmount, minAmountA, minAmountB }
465
463
  };
464
+ return payload;
466
465
  } catch (error) {
467
466
  return {
468
467
  success: false,
@@ -470,20 +469,39 @@ var quoteTool = {
470
469
  };
471
470
  }
472
471
  }
472
+ async shouldSecondaryAction(coreActionResult, _context) {
473
+ return isLiquidityCorePayload(coreActionResult);
474
+ }
475
+ async secondaryAction(payload, client, context) {
476
+ const result = await hederaAgentKit.handleTransaction(
477
+ payload.transaction,
478
+ client,
479
+ context,
480
+ removeLiquidityPostProcess
481
+ );
482
+ return { ...result, ...payload.extras };
483
+ }
473
484
  };
485
+ var addLiquidityTool = new AddLiquidityTool();
486
+ var removeLiquidityTool = new RemoveLiquidityTool();
474
487
  var poolsInputSchema = zod.z.object({
475
488
  tokenA: zod.z.string().optional().describe("First token ID or symbol"),
476
489
  tokenB: zod.z.string().optional().describe("Second token ID or symbol"),
477
490
  version: zod.z.enum(["v1", "v2"]).optional().describe("Pool version"),
478
491
  limit: zod.z.number().int().positive().optional().describe("Maximum number of pools to return")
479
492
  });
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);
493
+ var PoolsTool = class extends hederaAgentKit.BaseTool {
494
+ constructor() {
495
+ super(...arguments);
496
+ this.method = "saucerswap_get_pools";
497
+ this.name = "SaucerSwap Get Pools";
498
+ this.description = "Query SaucerSwap liquidity pools and reserves.";
499
+ this.parameters = poolsInputSchema;
500
+ }
501
+ async normalizeParams(params, _context, _client) {
502
+ return poolsInputSchema.parse(params);
503
+ }
504
+ async coreAction(args, context, _client) {
487
505
  const config = resolveSaucerSwapConfig(context);
488
506
  const api = context.saucerswapClient ?? createSaucerSwapClient(config);
489
507
  try {
@@ -516,76 +534,100 @@ var poolsTool = {
516
534
  };
517
535
  }
518
536
  }
537
+ async shouldSecondaryAction(_coreActionResult, _context) {
538
+ return false;
539
+ }
540
+ async secondaryAction(_request, _client, _context) {
541
+ return null;
542
+ }
519
543
  };
520
- var addLiquidityInputSchema = zod.z.object({
521
- tokenA: zod.z.string().describe("First token ID"),
522
- tokenB: zod.z.string().describe("Second token ID"),
523
- amountA: zod.z.string().describe("Amount of tokenA to add"),
524
- amountB: zod.z.string().describe("Amount of tokenB to add"),
525
- slippageTolerance: zod.z.number().optional().default(0.5).describe("Maximum slippage tolerance percentage")
526
- });
527
- var removeLiquidityInputSchema = zod.z.object({
528
- tokenA: zod.z.string().describe("First token ID"),
529
- tokenB: zod.z.string().describe("Second token ID"),
530
- lpTokenAmount: zod.z.string().describe("Amount of LP tokens to burn"),
531
- minAmountA: zod.z.string().describe("Minimum amount of tokenA to receive"),
532
- minAmountB: zod.z.string().describe("Minimum amount of tokenB to receive")
533
- });
534
- var resolveDeadline2 = (defaultMinutes) => {
535
- return Math.floor(Date.now() / 1e3) + defaultMinutes * 60;
544
+ var poolsTool = new PoolsTool();
545
+
546
+ // src/utils/quote.ts
547
+ var readAmount = (value) => {
548
+ if (typeof value === "string") {
549
+ return value;
550
+ }
551
+ if (typeof value === "number" && Number.isFinite(value)) {
552
+ return value.toString();
553
+ }
554
+ return null;
536
555
  };
537
- var resolveRouterContract = (config) => {
538
- const routerContractId = config.defaultPoolVersion === "v2" ? config.routerV2ContractId ?? config.routerContractId : config.routerContractId ?? config.routerV2ContractId;
539
- if (!routerContractId) {
540
- throw new Error("Missing SaucerSwap router contract ID configuration.");
556
+ var normalizeSwapQuote = (quote, fallbackAmountIn) => {
557
+ const amountIn = readAmount(quote.amountIn) ?? fallbackAmountIn;
558
+ const expectedOutput = readAmount(quote.expectedOutput) ?? readAmount(quote.amountOut) ?? readAmount(quote.amountOutMin) ?? "";
559
+ if (!expectedOutput) {
560
+ throw new Error("SaucerSwap quote did not include an output amount.");
541
561
  }
542
- return routerContractId;
562
+ return {
563
+ amountIn,
564
+ expectedOutput,
565
+ priceImpact: typeof quote.priceImpact === "number" ? quote.priceImpact : null,
566
+ route: Array.isArray(quote.route) ? quote.route : [],
567
+ raw: quote
568
+ };
543
569
  };
544
- var addLiquidityTool = {
545
- method: "saucerswap_add_liquidity",
546
- name: "SaucerSwap Add Liquidity",
547
- description: "Add liquidity to a SaucerSwap pool.",
548
- parameters: addLiquidityInputSchema,
549
- execute: async (client, context, params) => {
550
- const args = addLiquidityInputSchema.parse(params);
570
+
571
+ // src/tools/quote.ts
572
+ var quoteInputSchema = zod.z.object({
573
+ fromToken: zod.z.string().describe("Token ID to swap from (e.g., 'HBAR' or '0.0.123456')"),
574
+ toToken: zod.z.string().describe("Token ID to swap to"),
575
+ amount: zod.z.string().describe("Amount to swap (decimal format)"),
576
+ slippageTolerance: zod.z.number().optional().default(0.5).describe("Maximum slippage tolerance percentage")
577
+ });
578
+ var QuoteTool = class extends hederaAgentKit.BaseTool {
579
+ constructor() {
580
+ super(...arguments);
581
+ this.method = "saucerswap_get_swap_quote";
582
+ this.name = "SaucerSwap Get Swap Quote";
583
+ this.description = "Get a price quote for swapping tokens on SaucerSwap.";
584
+ this.parameters = quoteInputSchema;
585
+ }
586
+ async normalizeParams(params, _context, _client) {
587
+ return quoteInputSchema.parse(params);
588
+ }
589
+ async coreAction(args, context, _client) {
551
590
  const config = resolveSaucerSwapConfig(context);
552
- const operatorAccountId = client?.operatorAccountId?.toString();
591
+ const cachedApi = context.saucerswapClient;
592
+ const api = cachedApi ?? createSaucerSwapClient(config);
553
593
  const slippageTolerance = args.slippageTolerance ?? 0.5;
554
- if (!operatorAccountId) {
555
- return {
556
- success: false,
557
- error: "Hedera client with an operator account is required for liquidity actions."
558
- };
559
- }
560
594
  try {
561
- const api = context.saucerswapClient ?? createSaucerSwapClient(config);
562
- const tokenAInput = normalizeTokenAlias(args.tokenA, config);
563
- const tokenBInput = normalizeTokenAlias(args.tokenB, config);
564
- const tokenAId = await api.resolveTokenId(tokenAInput);
565
- const tokenBId = await api.resolveTokenId(tokenBInput);
566
- const tokenA = await api.getTokenByIdOrSymbol(tokenAId);
567
- const tokenB = await api.getTokenByIdOrSymbol(tokenBId);
568
- if (!tokenA || !tokenB) {
569
- return {
570
- success: false,
571
- error: "Unable to resolve token metadata from SaucerSwap API."
572
- };
573
- }
574
- const amountADesired = parseUnits(args.amountA, tokenA.decimals);
575
- const amountBDesired = parseUnits(args.amountB, tokenB.decimals);
576
- const amountAMin = applySlippageToAmount(amountADesired, slippageTolerance);
577
- const amountBMin = applySlippageToAmount(amountBDesired, slippageTolerance);
578
- const routerContractId = resolveRouterContract(config);
579
- const deadline = resolveDeadline2(config.deadlineMinutes);
580
- const toAddress = accountIdToSolidityAddress(operatorAccountId);
581
- 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
- const transaction = new sdk.ContractExecuteTransaction().setContractId(contractIdFromString(routerContractId)).setGas(config.gasLimit).setFunction("addLiquidity", params2);
583
- return await finalizeTransaction(transaction, client, context, {
584
- amountADesired,
585
- amountBDesired,
586
- amountAMin,
587
- amountBMin
595
+ const fromToken = normalizeTokenAlias(args.fromToken, config);
596
+ const toToken = normalizeTokenAlias(args.toToken, config);
597
+ const fromTokenId = await api.resolveTokenId(fromToken);
598
+ const toTokenId = await api.resolveTokenId(toToken);
599
+ const rawQuote = await api.getSwapQuote({
600
+ fromToken: fromTokenId,
601
+ toToken: toTokenId,
602
+ amount: args.amount
588
603
  });
604
+ const normalized = normalizeSwapQuote(rawQuote, args.amount);
605
+ if (normalized.priceImpact === null) {
606
+ const pool = await api.getPoolByTokens(fromTokenId, toTokenId, config.defaultPoolVersion);
607
+ if (pool && rawQuote.amountIn && rawQuote.amountOut) {
608
+ const isAToB = pool.tokenA.id === fromTokenId;
609
+ const reserveIn = isAToB ? pool.tokenReserveA : pool.tokenReserveB;
610
+ const reserveOut = isAToB ? pool.tokenReserveB : pool.tokenReserveA;
611
+ normalized.priceImpact = calculatePriceImpact(
612
+ rawQuote.amountIn,
613
+ rawQuote.amountOut,
614
+ reserveIn,
615
+ reserveOut
616
+ );
617
+ }
618
+ }
619
+ let minOutput = null;
620
+ if (normalized.expectedOutput.includes(".")) {
621
+ const expectedNumber = Number(normalized.expectedOutput);
622
+ minOutput = Number.isFinite(expectedNumber) ? (expectedNumber * (1 - slippageTolerance / 100)).toString() : null;
623
+ } else {
624
+ minOutput = applySlippageToAmount(normalized.expectedOutput, slippageTolerance);
625
+ }
626
+ return {
627
+ success: true,
628
+ quote: normalized,
629
+ minOutput
630
+ };
589
631
  } catch (error) {
590
632
  return {
591
633
  success: false,
@@ -593,83 +635,110 @@ var addLiquidityTool = {
593
635
  };
594
636
  }
595
637
  }
638
+ async shouldSecondaryAction(_coreActionResult, _context) {
639
+ return false;
640
+ }
641
+ async secondaryAction(_request, _client, _context) {
642
+ return null;
643
+ }
644
+ };
645
+ var quoteTool = new QuoteTool();
646
+ var swapInputSchema = zod.z.object({
647
+ fromToken: zod.z.string().describe("Token ID to swap from (e.g., 'HBAR' or '0.0.123456')"),
648
+ toToken: zod.z.string().describe("Token ID to swap to"),
649
+ amount: zod.z.string().describe("Amount to swap (decimal format)"),
650
+ slippageTolerance: zod.z.number().optional().default(0.5).describe("Maximum slippage tolerance percentage"),
651
+ deadline: zod.z.number().optional().describe("Transaction deadline in minutes from now or a unix timestamp")
652
+ });
653
+ var isSwapCorePayload = (value) => typeof value === "object" && value !== null && "transaction" in value;
654
+ var swapPostProcess = (response) => `SaucerSwap swap submitted. Status: ${response.status}. Transaction ID: ${response.transactionId}`;
655
+ var resolveDeadline2 = (deadlineInput, defaultMinutes) => {
656
+ const now = Math.floor(Date.now() / 1e3);
657
+ if (!deadlineInput) {
658
+ return now + defaultMinutes * 60;
659
+ }
660
+ if (deadlineInput > now + 60) {
661
+ return Math.floor(deadlineInput);
662
+ }
663
+ return now + Math.round(deadlineInput) * 60;
596
664
  };
597
- var removeLiquidityTool = {
598
- method: "saucerswap_remove_liquidity",
599
- name: "SaucerSwap Remove Liquidity",
600
- description: "Remove liquidity from a SaucerSwap pool.",
601
- parameters: removeLiquidityInputSchema,
602
- execute: async (client, context, params) => {
603
- const args = removeLiquidityInputSchema.parse(params);
665
+ var amountToSmallest = (amount, decimals) => {
666
+ return parseUnits(amount, decimals);
667
+ };
668
+ var expectedToSmallest = (amount, decimals) => {
669
+ return amount.includes(".") ? parseUnits(amount, decimals) : amount;
670
+ };
671
+ var SwapTool = class extends hederaAgentKit.BaseTool {
672
+ constructor() {
673
+ super(...arguments);
674
+ this.method = "saucerswap_swap_tokens";
675
+ this.name = "SaucerSwap Swap Tokens";
676
+ this.description = "Execute a token swap on SaucerSwap DEX.";
677
+ this.parameters = swapInputSchema;
678
+ }
679
+ async normalizeParams(params, _context, _client) {
680
+ return swapInputSchema.parse(params);
681
+ }
682
+ async coreAction(args, context, client) {
604
683
  const config = resolveSaucerSwapConfig(context);
605
684
  const operatorAccountId = client?.operatorAccountId?.toString();
685
+ const slippageTolerance = args.slippageTolerance ?? 0.5;
606
686
  if (!operatorAccountId) {
607
687
  return {
608
688
  success: false,
609
- error: "Hedera client with an operator account is required for liquidity actions."
689
+ error: "Hedera client with an operator account is required for swaps."
610
690
  };
611
691
  }
612
692
  try {
613
693
  const api = context.saucerswapClient ?? createSaucerSwapClient(config);
614
- const tokenAInput = normalizeTokenAlias(args.tokenA, config);
615
- const tokenBInput = normalizeTokenAlias(args.tokenB, config);
616
- const tokenAId = await api.resolveTokenId(tokenAInput);
617
- const tokenBId = await api.resolveTokenId(tokenBInput);
618
- const pool = await api.getPoolByTokens(tokenAId, tokenBId, config.defaultPoolVersion);
619
- if (!pool) {
694
+ const fromTokenAlias = normalizeTokenAlias(args.fromToken, config);
695
+ const toTokenAlias = normalizeTokenAlias(args.toToken, config);
696
+ const fromTokenId = await api.resolveTokenId(fromTokenAlias);
697
+ const toTokenId = await api.resolveTokenId(toTokenAlias);
698
+ const fromTokenMeta = await api.getTokenByIdOrSymbol(fromTokenId);
699
+ const toTokenMeta = await api.getTokenByIdOrSymbol(toTokenId);
700
+ if (!fromTokenMeta || !toTokenMeta) {
620
701
  return {
621
702
  success: false,
622
- error: "Unable to locate pool for the provided token pair."
703
+ error: "Unable to resolve token metadata from SaucerSwap API."
623
704
  };
624
705
  }
625
- const tokenA = await api.getTokenByIdOrSymbol(tokenAId);
626
- const tokenB = await api.getTokenByIdOrSymbol(tokenBId);
627
- if (!tokenA || !tokenB) {
706
+ const quote = normalizeSwapQuote(
707
+ await api.getSwapQuote({
708
+ fromToken: fromTokenId,
709
+ toToken: toTokenId,
710
+ amount: args.amount
711
+ }),
712
+ args.amount
713
+ );
714
+ const amountInSmallest = amountToSmallest(args.amount, fromTokenMeta.decimals);
715
+ const expectedOutSmallest = expectedToSmallest(quote.expectedOutput, toTokenMeta.decimals);
716
+ const minOutSmallest = applySlippageToAmount(expectedOutSmallest, slippageTolerance);
717
+ const routerContractId = config.defaultPoolVersion === "v2" ? config.routerV2ContractId ?? config.routerContractId : config.routerContractId ?? config.routerV2ContractId;
718
+ if (!routerContractId) {
628
719
  return {
629
720
  success: false,
630
- error: "Unable to resolve token metadata from SaucerSwap API."
721
+ error: "Missing SaucerSwap router contract ID configuration."
631
722
  };
632
723
  }
633
- const lpAmount = parseUnits(args.lpTokenAmount, pool.lpToken.decimals);
634
- const minAmountA = parseUnits(args.minAmountA, tokenA.decimals);
635
- const minAmountB = parseUnits(args.minAmountB, tokenB.decimals);
636
- const routerContractId = resolveRouterContract(config);
637
- const deadline = resolveDeadline2(config.deadlineMinutes);
724
+ const deadline = resolveDeadline2(args.deadline, config.deadlineMinutes);
638
725
  const toAddress = accountIdToSolidityAddress(operatorAccountId);
639
- const params2 = new sdk.ContractFunctionParameters().addAddress(tokenIdToSolidityAddress(requireTokenId(tokenAId))).addAddress(tokenIdToSolidityAddress(requireTokenId(tokenBId))).addUint256(lpAmount).addUint256(minAmountA).addUint256(minAmountB).addAddress(toAddress).addUint256(deadline);
640
- const transaction = new sdk.ContractExecuteTransaction().setContractId(contractIdFromString(routerContractId)).setGas(config.gasLimit).setFunction("removeLiquidity", params2);
641
- return await finalizeTransaction(transaction, client, context, {
642
- lpAmount,
643
- minAmountA,
644
- minAmountB
645
- });
646
- } catch (error) {
647
- return {
648
- success: false,
649
- error: error instanceof Error ? error.message : "Unknown error"
650
- };
651
- }
652
- }
653
- };
654
- var farmsInputSchema = zod.z.object({
655
- poolId: zod.z.number().int().positive().optional().describe("Optional pool ID to filter farms")
656
- });
657
- var farmsTool = {
658
- method: "saucerswap_get_farms",
659
- name: "SaucerSwap Get Farms",
660
- description: "Get active farming opportunities on SaucerSwap.",
661
- parameters: farmsInputSchema,
662
- execute: async (_client, context, params) => {
663
- const args = farmsInputSchema.parse(params);
664
- const config = resolveSaucerSwapConfig(context);
665
- const api = context.saucerswapClient ?? createSaucerSwapClient(config);
666
- try {
667
- const farms = await api.getFarms();
668
- const filtered = args.poolId ? farms.filter((farm) => farm.poolId === args.poolId) : farms;
669
- return {
670
- success: true,
671
- farms: filtered
726
+ const path = [
727
+ tokenIdToSolidityAddress(requireTokenId(fromTokenId)),
728
+ tokenIdToSolidityAddress(requireTokenId(toTokenId))
729
+ ];
730
+ const params = new sdk.ContractFunctionParameters().addUint256(amountInSmallest).addUint256(minOutSmallest).addAddressArray(path).addAddress(toAddress).addUint256(deadline);
731
+ const transaction = new sdk.ContractExecuteTransaction().setContractId(contractIdFromString(routerContractId)).setGas(config.gasLimit).setFunction("swapExactTokensForTokens", params);
732
+ const payload = {
733
+ transaction,
734
+ extras: {
735
+ estimatedOutput: quote.expectedOutput,
736
+ minOutput: minOutSmallest,
737
+ priceImpact: quote.priceImpact,
738
+ route: quote.route
739
+ }
672
740
  };
741
+ return payload;
673
742
  } catch (error) {
674
743
  return {
675
744
  success: false,
@@ -677,9 +746,30 @@ var farmsTool = {
677
746
  };
678
747
  }
679
748
  }
749
+ async shouldSecondaryAction(coreActionResult, _context) {
750
+ return isSwapCorePayload(coreActionResult);
751
+ }
752
+ async secondaryAction(payload, client, context) {
753
+ const result = await hederaAgentKit.handleTransaction(
754
+ payload.transaction,
755
+ client,
756
+ context,
757
+ swapPostProcess
758
+ );
759
+ return { ...result, ...payload.extras };
760
+ }
680
761
  };
762
+ var swapTool = new SwapTool();
681
763
 
682
764
  // src/index.ts
765
+ var saucerswapPluginToolNames = {
766
+ SAUCERSWAP_GET_SWAP_QUOTE_TOOL: "saucerswap_get_swap_quote",
767
+ SAUCERSWAP_SWAP_TOKENS_TOOL: "saucerswap_swap_tokens",
768
+ SAUCERSWAP_GET_POOLS_TOOL: "saucerswap_get_pools",
769
+ SAUCERSWAP_ADD_LIQUIDITY_TOOL: "saucerswap_add_liquidity",
770
+ SAUCERSWAP_REMOVE_LIQUIDITY_TOOL: "saucerswap_remove_liquidity",
771
+ SAUCERSWAP_GET_FARMS_TOOL: "saucerswap_get_farms"
772
+ };
683
773
  var saucerswapPlugin = {
684
774
  name: "saucerswap",
685
775
  description: "Integration with SaucerSwap DEX for token swaps, liquidity provision, and yield farming",
@@ -688,5 +778,6 @@ var saucerswapPlugin = {
688
778
 
689
779
  exports.default = saucerswapPlugin;
690
780
  exports.saucerswapPlugin = saucerswapPlugin;
781
+ exports.saucerswapPluginToolNames = saucerswapPluginToolNames;
691
782
  //# sourceMappingURL=index.cjs.map
692
783
  //# sourceMappingURL=index.cjs.map