From 0edb2ed3478dbedb4f97b7ade2d815ca8bd63fa7 Mon Sep 17 00:00:00 2001 From: Feyzanur25 Date: Mon, 23 Mar 2026 14:05:37 +0300 Subject: [PATCH] fix: improve transaction compatibility and tool safety --- lib/contract.ts | 431 +++++++++++++++++++++------------------------- lib/stellar.ts | 39 ++++- tools/bridge.ts | 93 +++++----- tools/contract.ts | 152 ++++++++++------ tools/stake.ts | 96 +++++++---- 5 files changed, 431 insertions(+), 380 deletions(-) diff --git a/lib/contract.ts b/lib/contract.ts index e9fd7cc6..578ec8df 100644 --- a/lib/contract.ts +++ b/lib/contract.ts @@ -1,248 +1,207 @@ import { - Contract, - rpc, - TransactionBuilder, - nativeToScVal, - scValToNative, - xdr, - Networks, - BASE_FEE, - Address, - } from "@stellar/stellar-sdk"; - import { signTransaction } from "./stellar"; - import { buildTransaction } from "../utils/buildTransaction"; - - // Configuration - const rpcUrl = "https://soroban-testnet.stellar.org"; - const contractAddress = "CCUMBJFVC3YJOW3OOR6WTWTESH473ZSXQEGYPQDWXAYYC4J77OT4NVHJ"; // From networks.testnet.contractId - const networkPassphrase = Networks.TESTNET; - - // Utility functions for ScVal conversion - const addressToScVal = (address: string) => { - // Validate address format - if (!address.match(/^[CG][A-Z0-9]{55}$/)) { - throw new Error(`Invalid address format: ${address}`); - } - return nativeToScVal(new Address(address), { type: "address" }); - }; - - const numberToI128 = (value: string | BigInt) => { - return nativeToScVal(typeof value === 'string' ? BigInt(value) : value, { type: "i128" }); - }; - - const booleanToScVal = (value: boolean) => { - return nativeToScVal(value, { type: "bool" }); - }; - - // Core contract interaction function - const contractInt = async (caller: string, functName: string, values: any) => { - try { - const server = new rpc.Server(rpcUrl, { allowHttp: true }); - const sourceAccount = await server.getAccount(caller).catch((err) => { - throw new Error(`Failed to fetch account ${caller}: ${err.message}`); - }); - - const contract = new Contract(contractAddress); - - // Build transaction using unified builder - const sorobanOperation = { - contract, - functionName: functName, - args: values == null ? undefined : Array.isArray(values) ? values : [values], - }; - const transaction = buildTransaction("lp", sourceAccount, sorobanOperation); - - const simulation = await server.simulateTransaction(transaction).catch((err) => { - console.error(`Simulation failed for ${functName}: ${err.message}`); - throw new Error(`Failed to simulate transaction: ${err.message}`); - }); - - console.log(`Simulation response for ${functName}:`, JSON.stringify(simulation, null, 2)); - - if ("results" in simulation && Array.isArray(simulation.results) && simulation.results.length > 0) { - console.log(`Read-only call detected for ${functName}`); - const result = simulation.results[0]; - if (result.xdr) { - try { - // Parse the return value from XDR - const scVal = xdr.ScVal.fromXDR(result.xdr, "base64"); - const parsedValue = scValToNative(scVal); - console.log(`Parsed simulation result for ${functName}:`, parsedValue); - return parsedValue; // Returns string for share_id, array for get_rsrvs - } catch (err) { - console.error(`Failed to parse XDR for ${functName}:`, err); - throw new Error(`Failed to parse simulation result: ${err instanceof Error ? err.message : String(err)}`); - } - } - console.error(`No xdr field in simulation results[0] for ${functName}:`, result); - throw new Error("No return value in simulation results"); - } else if ("error" in simulation) { - console.error(`Simulation error for ${functName}:`, simulation.error); - throw new Error(`Simulation failed: ${simulation.error}`); - } - - // For state-changing calls, prepare and submit transaction - console.log(`Submitting transaction for ${functName}`); - const preparedTx = await server.prepareTransaction(transaction).catch((err) => { - console.error(`Prepare transaction failed for ${functName}: ${err.message}`); - throw new Error(`Failed to prepare transaction: ${err.message}`); - }); - const prepareTxXDR = preparedTx.toXDR(); - - let signedTxResponse: string; - try { - signedTxResponse = signTransaction(prepareTxXDR, networkPassphrase); - } catch (err: any) { - throw new Error(`Failed to sign transaction: ${err.message}`); + Address, + Contract, + Networks, + TransactionBuilder, + nativeToScVal, + rpc, + scValToNative, + xdr, +} from "stellar-sdk"; + +import { signTransaction } from "./stellar"; +import { buildTransaction } from "../utils/buildTransaction"; + +const rpcUrl = "https://soroban-testnet.stellar.org"; +const contractAddress = + "CCUMBJFVC3YJOW3OOR6WTWTESH473ZSXQEGYPQDWXAYYC4J77OT4NVHJ"; +const networkPassphrase = Networks.TESTNET; + +type ContractArg = xdr.ScVal; +type ContractArgs = ContractArg | ContractArg[] | null; + +const getErrorMessage = (error: unknown): string => + error instanceof Error ? error.message : String(error); + +const addressToScVal = (address: string): xdr.ScVal => { + if (!/^[CG][A-Z0-9]{55}$/.test(address)) { + throw new Error(`Invalid address format: ${address}`); + } + + return nativeToScVal(new Address(address), { type: "address" }); +}; + +const numberToI128 = (value: string | bigint): xdr.ScVal => { + return nativeToScVal(typeof value === "string" ? BigInt(value) : value, { + type: "i128", + }); +}; + +const booleanToScVal = (value: boolean): xdr.ScVal => { + return nativeToScVal(value, { type: "bool" }); +}; + +const toArgsArray = (values: ContractArgs): xdr.ScVal[] | undefined => { + if (values == null) return undefined; + return Array.isArray(values) ? values : [values]; +}; + +const contractInt = async ( + caller: string, + functionName: string, + values: ContractArgs +): Promise => { + const server = new rpc.Server(rpcUrl, { allowHttp: true }); + const contract = new Contract(contractAddress); + + try { + const sourceAccount = await server.getAccount(caller).catch( + (error: unknown) => { + throw new Error( + `Failed to fetch account ${caller}: ${getErrorMessage(error)}` + ); } - - // Handle both string and object response from signTransaction - const signedXDR = signedTxResponse - - const tx = TransactionBuilder.fromXDR(signedXDR, Networks.TESTNET); - const txResult = await server.sendTransaction(tx).catch((err) => { - console.error(`Send transaction failed for ${functName}: ${err.message}`); - throw new Error(`Send transaction failed: ${err.message}`); - }); - - let txResponse = await server.getTransaction(txResult.hash); - const maxRetries = 30; - let retries = 0; - - while (txResponse.status === "NOT_FOUND" && retries < maxRetries) { - await new Promise((resolve) => setTimeout(resolve, 1000)); - txResponse = await server.getTransaction(txResult.hash); - retries++; + ); + + const transaction = buildTransaction("lp", sourceAccount, { + contract, + functionName, + args: toArgsArray(values), + }); + + const simulation = await server.simulateTransaction(transaction).catch( + (error: unknown) => { + throw new Error( + `Failed to simulate transaction: ${getErrorMessage(error)}` + ); } + ); - if (txResponse.status === "NOT_FOUND") { - return { hash: txResult.hash, status: "PENDING", message: "Transaction is still pending. Please check status later using this hash." }; + if ( + "results" in simulation && + Array.isArray(simulation.results) && + simulation.results.length > 0 + ) { + const result = simulation.results[0]; + + if (!result.xdr) { + throw new Error("No return value in simulation results"); } - - if (txResponse.status !== "SUCCESS") { - console.error(`Transaction failed for ${functName} with status: ${txResponse.status}`, JSON.stringify(txResponse, null, 2)); - throw new Error(`Transaction failed with status: ${txResponse.status}`); + + const scVal = xdr.ScVal.fromXDR(result.xdr, "base64"); + return scValToNative(scVal); + } + + if ("error" in simulation) { + throw new Error(`Simulation failed: ${simulation.error}`); + } + + const preparedTx = await server.prepareTransaction(transaction).catch( + (error: unknown) => { + throw new Error( + `Failed to prepare transaction: ${getErrorMessage(error)}` + ); } - - // Parse return value if present (e.g., for withdraw) - if (txResponse.returnValue) { - try { - // returnValue is already an ScVal, no need for fromXDR - const parsedValue = scValToNative(txResponse.returnValue); - console.log(`Parsed transaction result for ${functName}:`, parsedValue); - return parsedValue; // Returns array for withdraw - } catch (err) { - console.error(`Failed to parse transaction return value for ${functName}:`, err); - throw new Error(`Failed to parse transaction result: ${err instanceof Error ? err.message : String(err)}`); - } + ); + + const signedXdr = signTransaction(preparedTx.toXDR(), networkPassphrase); + const tx = TransactionBuilder.fromXDR(signedXdr, networkPassphrase); + + const txResult = await server.sendTransaction(tx).catch( + (error: unknown) => { + throw new Error(`Send transaction failed: ${getErrorMessage(error)}`); } - - return null; // No return value for void functions - } catch (error: unknown) { - const errorMessage = error instanceof Error ? error.message : String(error); - console.error(`Error in contract interaction (${functName}):`, errorMessage); - throw error; - } - }; - - // Contract interaction functions - export async function getShareId(caller: string): Promise { - try { - const result = await contractInt(caller, "share_id", null); - console.log("Share ID:", result); - return result as string | null; - } catch (error: unknown) { - const errorMessage = error instanceof Error ? error.message : String(error); - console.error("Failed to get share ID:", errorMessage); - throw error; + ); + + let txResponse = await server.getTransaction(txResult.hash); + let retries = 0; + + while (txResponse.status === "NOT_FOUND" && retries < 30) { + await new Promise((resolve) => setTimeout(resolve, 1000)); + txResponse = await server.getTransaction(txResult.hash); + retries++; } - } - - export async function deposit( - caller: string, - to: string, - desiredA: string, - minA: string, - desiredB: string, - minB: string - ) { - try { - const toScVal = addressToScVal(to); - const desiredAScVal = numberToI128(desiredA); - const minAScVal = numberToI128(minA); - const desiredBScVal = numberToI128(desiredB); - const minBScVal = numberToI128(minB); - await contractInt(caller, "deposit", [ - toScVal, - desiredAScVal, - minAScVal, - desiredBScVal, - minBScVal, - ]); - console.log(`Deposited successfully to ${to}`); - } catch (error: unknown) { - const errorMessage = error instanceof Error ? error.message : String(error); - console.error("Failed to deposit:", errorMessage); - throw error; + + if (txResponse.status === "NOT_FOUND") { + return { + hash: txResult.hash, + status: "PENDING", + }; } - } - - export async function swap( - caller: string, - to: string, - buyA: boolean, - out: string, - inMax: string - ) { - try { - const toScVal = addressToScVal(to); - const buyAScVal = booleanToScVal(buyA); - const outScVal = numberToI128(out); - const inMaxScVal = numberToI128(inMax); - await contractInt(caller, "swap", [toScVal, buyAScVal, outScVal, inMaxScVal]); - console.log(`Swapped successfully to ${to}`); - } catch (error: unknown) { - const errorMessage = error instanceof Error ? error.message : String(error); - console.error("Failed to swap:", errorMessage); - throw error; + + if (txResponse.status !== "SUCCESS") { + throw new Error(`Transaction failed: ${txResponse.status}`); } - } - - export async function withdraw( - caller: string, - to: string, - shareAmount: string, - minA: string, - minB: string - ): Promise { - try { - const toScVal = addressToScVal(to); - const shareAmountScVal = numberToI128(shareAmount); - const minAScVal = numberToI128(minA); - const minBScVal = numberToI128(minB); - const result = await contractInt(caller, "withdraw", [ - toScVal, - shareAmountScVal, - minAScVal, - minBScVal, - ]); - console.log(`Withdrawn successfully to ${to}:, ${result}`); - return result ? (result as [BigInt, BigInt]) : null; - } catch (error: unknown) { - const errorMessage = error instanceof Error ? error.message : String(error); - console.error("Failed to withdraw:", errorMessage); - throw error; + + if (txResponse.returnValue) { + return scValToNative(txResponse.returnValue); } + + return null; + } catch (error: unknown) { + throw new Error( + `Error in contract interaction (${functionName}): ${getErrorMessage(error)}` + ); } - - export async function getReserves(caller: string): Promise { - try { - const result = await contractInt(caller, "get_rsrvs", null); - console.log("Reserves:", result); - return result ? (result as [BigInt, BigInt]) : null; - } catch (error: unknown) { - const errorMessage = error instanceof Error ? error.message : String(error); - console.error("Failed to get reserves:", errorMessage); - throw error; - } - } \ No newline at end of file +}; + +export async function getShareId(caller: string): Promise { + const result = await contractInt(caller, "share_id", null); + return result ? (result as string) : null; +} + +export async function deposit( + caller: string, + to: string, + desiredA: string, + minA: string, + desiredB: string, + minB: string +): Promise { + await contractInt(caller, "deposit", [ + addressToScVal(to), + numberToI128(desiredA), + numberToI128(minA), + numberToI128(desiredB), + numberToI128(minB), + ]); +} + +export async function swap( + caller: string, + to: string, + buyA: boolean, + out: string, + inMax: string +): Promise { + await contractInt(caller, "swap", [ + addressToScVal(to), + booleanToScVal(buyA), + numberToI128(out), + numberToI128(inMax), + ]); +} + +export async function withdraw( + caller: string, + to: string, + shareAmount: string, + minA: string, + minB: string +): Promise { + const result = await contractInt(caller, "withdraw", [ + addressToScVal(to), + numberToI128(shareAmount), + numberToI128(minA), + numberToI128(minB), + ]); + + return result ? (result as [bigint, bigint]) : null; +} + +export async function getReserves( + caller: string +): Promise { + const result = await contractInt(caller, "get_rsrvs", null); + return result ? (result as [bigint, bigint]) : null; +} \ No newline at end of file diff --git a/lib/stellar.ts b/lib/stellar.ts index 1d66b91d..11063e91 100644 --- a/lib/stellar.ts +++ b/lib/stellar.ts @@ -1,9 +1,34 @@ import { Keypair, TransactionBuilder } from "stellar-sdk"; -export const signTransaction = (txXDR: string, networkPassphrase: string) => { - const keypair = Keypair.fromSecret(`${process.env.STELLAR_PRIVATE_KEY}`); - const transaction = TransactionBuilder.fromXDR(txXDR, networkPassphrase); - transaction.sign(keypair); - return transaction.toXDR(); - }; - \ No newline at end of file +/** + * Signs a Stellar transaction XDR using the private key from env + */ +export function signTransaction( + txXDR: string, + networkPassphrase: string +): string { + const secretKey = process.env.STELLAR_PRIVATE_KEY; + + if (!secretKey) { + throw new Error("Missing STELLAR_PRIVATE_KEY in environment variables"); + } + + if (!txXDR) { + throw new Error("txXDR is required"); + } + + if (!networkPassphrase) { + throw new Error("networkPassphrase is required"); + } + + const keypair = Keypair.fromSecret(secretKey); + + const transaction = TransactionBuilder.fromXDR( + txXDR, + networkPassphrase + ); + + transaction.sign(keypair); + + return transaction.toXDR(); +} \ No newline at end of file diff --git a/tools/bridge.ts b/tools/bridge.ts index c6731764..0efbf5f4 100644 --- a/tools/bridge.ts +++ b/tools/bridge.ts @@ -5,30 +5,21 @@ import { ChainSymbol, FeePaymentMethod, Messenger, - nodeRpcUrlsDefault + nodeRpcUrlsDefault, } from "@allbridge/bridge-core-sdk"; -import { - Keypair, - Keypair as StellarKeypair, - rpc, - TransactionBuilder as StellarTransactionBuilder, - TransactionBuilder, - Networks -} from "@stellar/stellar-sdk"; -import { ensure } from "../utils/utils"; -import { buildTransactionFromXDR } from "../utils/buildTransaction"; -import * as dotenv from "dotenv"; +import { Keypair, Networks, rpc } from "@stellar/stellar-sdk"; import { DynamicStructuredTool } from "@langchain/core/tools"; import { z } from "zod"; +import * as dotenv from "dotenv"; -dotenv.config({ path: ".env" }); +import { ensure } from "../utils/utils"; +import { buildTransactionFromXDR } from "../utils/buildTransaction"; -const fromAddress = process.env.STELLAR_PUBLIC_KEY as string; -const privateKey = process.env.STELLAR_PRIVATE_KEY as string; +dotenv.config({ path: ".env" }); type StellarNetwork = "stellar-testnet" | "stellar-mainnet"; -const STELLAR_NETWORK_CONFIG: Record +const STELLAR_NETWORK_CONFIG: Record< StellarNetwork, { networkPassphrase: string } > = { @@ -63,7 +54,21 @@ export const bridgeTokenTool = new DynamicStructuredTool({ toAddress: string; fromNetwork: StellarNetwork; }) => { - // Mainnet safeguard - additional layer beyond AgentClient + const fromAddress = process.env.STELLAR_PUBLIC_KEY; + const privateKey = process.env.STELLAR_PRIVATE_KEY; + + if (!fromAddress) { + throw new Error( + "[bridge_token] Missing STELLAR_PUBLIC_KEY in environment variables" + ); + } + + if (!privateKey) { + throw new Error( + "[bridge_token] Missing STELLAR_PRIVATE_KEY in environment variables" + ); + } + if ( fromNetwork === "stellar-mainnet" && process.env.ALLOW_MAINNET_BRIDGE !== "true" @@ -75,20 +80,19 @@ export const bridgeTokenTool = new DynamicStructuredTool({ const sdk = new AllbridgeCoreSdk({ ...nodeRpcUrlsDefault, - SRB: `${process.env.SRB_PROVIDER_URL}`, + ...(process.env.SRB_PROVIDER_URL + ? { SRB: process.env.SRB_PROVIDER_URL } + : {}), }); const chainDetailsMap = await sdk.chainDetailsMap(); const sourceToken = ensure( - chainDetailsMap[ChainSymbol.SRB].tokens.find( - (t) => t.symbol === "USDC" - ) + chainDetailsMap[ChainSymbol.SRB].tokens.find((t) => t.symbol === "USDC") ); + const destinationToken = ensure( - chainDetailsMap[ChainSymbol.ETH].tokens.find( - (t) => t.symbol === "USDC" - ) + chainDetailsMap[ChainSymbol.ETH].tokens.find((t) => t.symbol === "USDC") ); const sendParams = { @@ -103,17 +107,15 @@ export const bridgeTokenTool = new DynamicStructuredTool({ gasFeePaymentMethod: FeePaymentMethod.WITH_STABLECOIN, }; - const xdrTx = (await sdk.bridge.rawTxBuilder.send( - sendParams - )) as string; + const xdrTx = (await sdk.bridge.rawTxBuilder.send(sendParams)) as string; - // Use unified transaction builder for XDR-based bridge operations const srbKeypair = Keypair.fromSecret(privateKey); const transaction = buildTransactionFromXDR( "bridge", xdrTx, STELLAR_NETWORK_CONFIG[fromNetwork].networkPassphrase ); + transaction.sign(srbKeypair); let signedTx = transaction.toXDR(); @@ -129,6 +131,7 @@ export const bridgeTokenTool = new DynamicStructuredTool({ restoreXdrTx, STELLAR_NETWORK_CONFIG[fromNetwork].networkPassphrase ); + restoreTx.sign(srbKeypair); const signedRestoreXdrTx = restoreTx.toXDR(); @@ -139,7 +142,6 @@ export const bridgeTokenTool = new DynamicStructuredTool({ sentRestoreXdrTx.hash ); - // Handle FAILED restore explicitly if ( confirmRestoreXdrTx.status === rpc.Api.GetTransactionStatus.FAILED ) { @@ -158,16 +160,14 @@ export const bridgeTokenTool = new DynamicStructuredTool({ }; } - // Get new tx with updated sequences - const xdrTx2 = (await sdk.bridge.rawTxBuilder.send( - sendParams - )) as string; + const xdrTx2 = (await sdk.bridge.rawTxBuilder.send(sendParams)) as string; const transaction2 = buildTransactionFromXDR( "bridge", xdrTx2, STELLAR_NETWORK_CONFIG[fromNetwork].networkPassphrase ); + transaction2.sign(srbKeypair); signedTx = transaction2.toXDR(); } @@ -187,35 +187,30 @@ export const bridgeTokenTool = new DynamicStructuredTool({ throw new Error(`Transaction failed. Hash: ${sent.hash}`); } - // TrustLine check and setup for destinationToken if it is SRB - const destinationTokenSBR = sourceToken; + const stellarSideToken = sourceToken; const balanceLine = await sdk.utils.srb.getBalanceLine( fromAddress, - destinationTokenSBR.tokenAddress + stellarSideToken.tokenAddress ); const notEnoughBalanceLine = !balanceLine || - Big(balanceLine.balance) - .add(amount) - .gt(Big(balanceLine.limit)); + Big(balanceLine.balance).add(amount).gt(Big(balanceLine.limit)); if (notEnoughBalanceLine) { - const xdrTx = - await sdk.utils.srb.buildChangeTrustLineXdrTx({ - sender: fromAddress, - tokenAddress: destinationTokenSBR.tokenAddress, - }); - - // Use unified transaction builder for XDR-based bridge TrustLine operation - const keypair = StellarKeypair.fromSecret(privateKey); + const trustLineXdr = await sdk.utils.srb.buildChangeTrustLineXdrTx({ + sender: fromAddress, + tokenAddress: stellarSideToken.tokenAddress, + }); + const trustTx = buildTransactionFromXDR( "bridge", - xdrTx, + trustLineXdr, STELLAR_NETWORK_CONFIG[fromNetwork].networkPassphrase ); - trustTx.sign(keypair); + + trustTx.sign(srbKeypair); const signedTrustLineTx = trustTx.toXDR(); const submit = await sdk.utils.srb.submitTransactionStellar( diff --git a/tools/contract.ts b/tools/contract.ts index 39dde1fa..9aba8623 100644 --- a/tools/contract.ts +++ b/tools/contract.ts @@ -8,82 +8,132 @@ import { getReserves, } from "../lib/contract"; -// Assuming env variables are already loaded elsewhere -const STELLAR_PUBLIC_KEY = process.env.STELLAR_PUBLIC_KEY!; +const schema = z.object({ + action: z.enum([ + "get_share_id", + "deposit", + "swap", + "withdraw", + "get_reserves", + ]), + to: z.string().optional(), + desiredA: z.string().optional(), + minA: z.string().optional(), + desiredB: z.string().optional(), + minB: z.string().optional(), + buyA: z.boolean().optional(), + out: z.string().optional(), + inMax: z.string().optional(), + shareAmount: z.string().optional(), +}); -if (!STELLAR_PUBLIC_KEY) { - throw new Error("Missing Stellar environment variables"); -} +type Input = z.infer; export const StellarLiquidityContractTool = new DynamicStructuredTool({ name: "stellar_liquidity_contract_tool", - description: - "Interact with a liquidity contract on Stellar Soroban: getShareId, deposit, swap, withdraw, getReserves.", - schema: z.object({ - action: z.enum(["get_share_id", "deposit", "swap", "withdraw", "get_reserves"]), - to: z.string().optional(), // For deposit, swap, withdraw - desiredA: z.string().optional(), // For deposit - minA: z.string().optional(), // For deposit, withdraw - desiredB: z.string().optional(), // For deposit - minB: z.string().optional(), // For deposit, withdraw - buyA: z.boolean().optional(), // For swap - out: z.string().optional(), // For swap - inMax: z.string().optional(), // For swap - shareAmount: z.string().optional(), // For withdraw - }), - func: async ({ - action, - to, - desiredA, - minA, - desiredB, - minB, - buyA, - out, - inMax, - shareAmount, - }) => { + description: "Interact with a liquidity contract on Stellar Soroban", + + schema, + + func: async (input: Input) => { + const publicKey = process.env.STELLAR_PUBLIC_KEY; + + if (!publicKey) { + throw new Error( + "[stellar_liquidity_contract_tool] Missing STELLAR_PUBLIC_KEY" + ); + } + try { - switch (action) { + switch (input.action) { case "get_share_id": { - const result = await getShareId(STELLAR_PUBLIC_KEY); + const result = await getShareId(publicKey); return result ?? "No share ID found."; } + case "deposit": { - if (!to || !desiredA || !minA || !desiredB || !minB) { - throw new Error("to, desiredA, minA, desiredB, and minB are required for deposit"); + if ( + !input.to || + !input.desiredA || + !input.minA || + !input.desiredB || + !input.minB + ) { + throw new Error("Missing deposit parameters"); } - const result = await deposit(STELLAR_PUBLIC_KEY, to, desiredA, minA, desiredB, minB); - return result ??`Deposited successfully to ${to}.`; + + await deposit( + publicKey, + input.to, + input.desiredA, + input.minA, + input.desiredB, + input.minB + ); + + return `Deposited successfully to ${input.to}`; } + case "swap": { - if (!to || buyA === undefined || !out || !inMax) { - throw new Error("to, buyA, out, and inMax are required for swap"); + if ( + !input.to || + input.buyA === undefined || + !input.out || + !input.inMax + ) { + throw new Error("Missing swap parameters"); } - const result=await swap(STELLAR_PUBLIC_KEY, to, buyA, out, inMax); - return result ?? `Swapped successfully to ${to}.`; + + await swap( + publicKey, + input.to, + input.buyA, + input.out, + input.inMax + ); + + return `Swapped successfully to ${input.to}`; } + case "withdraw": { - if (!to || !shareAmount || !minA || !minB) { - throw new Error("to, shareAmount, minA, and minB are required for withdraw"); + if ( + !input.to || + !input.shareAmount || + !input.minA || + !input.minB + ) { + throw new Error("Missing withdraw parameters"); } - const result = await withdraw(STELLAR_PUBLIC_KEY, to, shareAmount, minA, minB); + + const result = await withdraw( + publicKey, + input.to, + input.shareAmount, + input.minA, + input.minB + ); + return result - ? `Withdrawn successfully to ${to}: ${JSON.stringify(result)}` - : "Withdraw failed or returned no value."; + ? `Withdraw successful: ${JSON.stringify(result)}` + : "Withdraw returned no result"; } + case "get_reserves": { - const result = await getReserves(STELLAR_PUBLIC_KEY); + const result = await getReserves(publicKey); + return result ? `Reserves: ${JSON.stringify(result)}` - : "No reserves found."; + : "No reserves found"; } + default: throw new Error("Unsupported action"); } - } catch (error: any) { - console.error("StellarLiquidityContractTool error:", error.message); - throw new Error(`Failed to execute ${action}: ${error.message}`); + } catch (error: unknown) { + const message = + error instanceof Error ? error.message : "Unknown error"; + + throw new Error(`[stellar_liquidity_contract_tool] ${message}`); } }, }); \ No newline at end of file diff --git a/tools/stake.ts b/tools/stake.ts index 10af1185..801cb724 100644 --- a/tools/stake.ts +++ b/tools/stake.ts @@ -8,72 +8,94 @@ import { getStake, } from "../lib/stakeF"; -// Assuming env variables are already loaded elsewhere -const STELLAR_PUBLIC_KEY = process.env.STELLAR_PUBLIC_KEY!; +const schema = z.object({ + action: z.enum([ + "initialize", + "stake", + "unstake", + "claim_rewards", + "get_stake", + ]), + tokenAddress: z.string().optional(), + rewardRate: z.number().optional(), + amount: z.number().optional(), + userAddress: z.string().optional(), +}); -if (!STELLAR_PUBLIC_KEY) { - throw new Error("Missing Stellar environment variables"); -} +type Input = z.infer; export const StellarContractTool = new DynamicStructuredTool({ name: "stellar_contract_tool", description: "Interact with a staking contract on Stellar Soroban: initialize, stake, unstake, claim rewards, or get stake.", - schema: z.object({ - action: z.enum(["initialize", "stake", "unstake", "claim_rewards", "get_stake"]), - tokenAddress: z.string().optional(), // Only for initialize - rewardRate: z.number().optional(), // Only for initialize - amount: z.number().optional(), // For stake/unstake - userAddress: z.string().optional(), // For get_stake - }), - func: async ({ action, tokenAddress, rewardRate, amount, userAddress }) => { + + schema, + + func: async (input: Input) => { + const publicKey = process.env.STELLAR_PUBLIC_KEY; + + if (!publicKey) { + throw new Error("[stellar_contract_tool] Missing STELLAR_PUBLIC_KEY"); + } + try { - switch (action) { + switch (input.action) { case "initialize": { - if (!tokenAddress || rewardRate === undefined) { - throw new Error("tokenAddress and rewardRate are required for initialize"); + if (!input.tokenAddress || input.rewardRate === undefined) { + throw new Error( + "initialize requires: tokenAddress and rewardRate" + ); } - const result = await initialize(STELLAR_PUBLIC_KEY, tokenAddress, rewardRate); + + const result = await initialize( + publicKey, + input.tokenAddress, + input.rewardRate + ); + return result ?? "Contract initialized successfully."; } case "stake": { - if (amount === undefined) { - throw new Error("amount is required for stake"); + if (input.amount === undefined) { + throw new Error("stake requires: amount"); } - const result = await stake(STELLAR_PUBLIC_KEY, amount); - return result ?? `Staked ${amount} successfully.`; + + const result = await stake(publicKey, input.amount); + return result ?? `Staked ${input.amount} successfully.`; } case "unstake": { - if (amount === undefined) { - throw new Error("amount is required for unstake"); + if (input.amount === undefined) { + throw new Error("unstake requires: amount"); } - const result = await unstake(STELLAR_PUBLIC_KEY, amount); - return result ?? `Unstaked ${amount} successfully.`; + + const result = await unstake(publicKey, input.amount); + return result ?? `Unstaked ${input.amount} successfully.`; } case "claim_rewards": { - const result = await claimRewards(STELLAR_PUBLIC_KEY); + const result = await claimRewards(publicKey); return result ?? "Rewards claimed successfully."; } case "get_stake": { - if (!userAddress) { - throw new Error("userAddress is required for get_stake"); + if (!input.userAddress) { + throw new Error("get_stake requires: userAddress"); } - const stakeAmount = await getStake(STELLAR_PUBLIC_KEY, userAddress); - return `Stake for ${userAddress}: ${stakeAmount}`; + + const stakeAmount = await getStake(publicKey, input.userAddress); + return `Stake for ${input.userAddress}: ${stakeAmount}`; } default: - throw new Error("Unsupported action"); + throw new Error(`Unsupported action: ${input.action}`); } - } catch (error: any) { - console.error("StellarContractTool error:", error.message); - throw new Error(`Failed to execute ${action}: ${error.message}`); + } catch (error: unknown) { + const message = + error instanceof Error ? error.message : "Unknown error"; + + throw new Error(`[stellar_contract_tool] ${message}`); } }, -}); - - +}); \ No newline at end of file