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 +264 -263
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +264 -263
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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/
|
|
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/
|
|
316
|
-
var
|
|
317
|
-
|
|
318
|
-
|
|
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
|
-
|
|
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
|
-
|
|
413
|
-
|
|
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
|
-
|
|
475
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
655
|
-
|
|
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
|
|
658
|
-
method: "
|
|
659
|
-
name: "SaucerSwap Get
|
|
660
|
-
description: "
|
|
661
|
-
parameters:
|
|
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 =
|
|
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
|
|
668
|
-
const
|
|
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
|
-
|
|
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,
|