Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
type SwapBestRouteResult,
} from "./lib/dex";
import { bridgeTokenTool } from "./tools/bridge";
import { stellarGetBalanceTool, stellarGetAccountInfoTool } from "./tools/stellar";
import {
Horizon,
Keypair,
Expand Down Expand Up @@ -157,6 +158,51 @@ export class AgentClient {
});
}

/**
* Get the balance of a Stellar account.
* @param publicKey Optional Stellar public key (defaults to client's publicKey)
*/
async getBalance(publicKey?: string) {
const targetPublicKey = publicKey || this.publicKey;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Using publicKey || this.publicKey silently falls back on empty-string input, which can query the wrong account instead of rejecting invalid caller input.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At agent.ts, line 166:

<comment>Using `publicKey || this.publicKey` silently falls back on empty-string input, which can query the wrong account instead of rejecting invalid caller input.</comment>

<file context>
@@ -157,6 +158,51 @@ export class AgentClient {
+   * @param publicKey Optional Stellar public key (defaults to client's publicKey)
+   */
+  async getBalance(publicKey?: string) {
+    const targetPublicKey = publicKey || this.publicKey;
+    if (!targetPublicKey) {
+      throw new Error("Public key is required to fetch balance.");
</file context>

if (!targetPublicKey) {
throw new Error("Public key is required to fetch balance.");
}

const result = await stellarGetBalanceTool.func({
publicKey: targetPublicKey,
network: this.network,
});

try {
return JSON.parse(result);
} catch (e) {
// If it's not JSON (e.g. error message), return as is
return result;
}
}

/**
* Get full details of a Stellar account.
* @param publicKey Optional Stellar public key (defaults to client's publicKey)
*/
async getAccountInfo(publicKey?: string) {
const targetPublicKey = publicKey || this.publicKey;
if (!targetPublicKey) {
throw new Error("Public key is required to fetch account info.");
}

const result = await stellarGetAccountInfoTool.func({
publicKey: targetPublicKey,
network: this.network,
});

try {
return JSON.parse(result);
} catch (e) {
return result;
}
}

/**
* Liquidity Pool operations.
*/
Expand Down
6 changes: 4 additions & 2 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { bridgeTokenTool } from "./tools/bridge";
import { StellarLiquidityContractTool } from "./tools/contract";
import { StellarDexTool } from "./tools/dex";
import { StellarContractTool } from "./tools/stake";
import { stellarSendPaymentTool } from "./tools/stellar";
import { stellarSendPaymentTool, stellarGetBalanceTool, stellarGetAccountInfoTool } from "./tools/stellar";
import {
AgentClient,
AgentConfig,
Expand Down Expand Up @@ -36,5 +36,7 @@ export const stellarTools = [
StellarDexTool,
StellarLiquidityContractTool,
StellarContractTool,
stellarSendPaymentTool
stellarSendPaymentTool,
stellarGetBalanceTool,
stellarGetAccountInfoTool
];
78 changes: 78 additions & 0 deletions tools/stellar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,82 @@ export const stellarSendPaymentTool = new DynamicStructuredTool({
return `Transaction failed: ${errorMessage}`;
}
},
});

export const stellarGetBalanceTool = new DynamicStructuredTool({
name: "stellar_get_balance",
description: "Get the balance of a Stellar account. Returns balances for XLM and all other assets.",
schema: z.object({
publicKey: z.string().describe("The Stellar public key to check the balance for"),
network: z.enum(["testnet", "mainnet"]).optional().describe("The Stellar network to use (defaults to testnet)"),
}),
func: async ({ publicKey, network = "testnet" }: { publicKey: string; network?: string }) => {
try {
if (!StellarSdk.StrKey.isValidEd25519PublicKey(publicKey)) {
throw new Error("Invalid Stellar public key.");
}

const horizonUrl = network === "mainnet"
? "https://horizon.stellar.org"
: "https://horizon-testnet.stellar.org";

const server = new StellarSdk.Horizon.Server(horizonUrl);
const account = await server.loadAccount(publicKey);

const balances = account.balances.map((balance: any) => {
if (balance.asset_type === "native") {
return {
asset: "XLM",
balance: balance.balance,
};
} else {
return {
asset: `${balance.asset_code}:${balance.asset_issuer}`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Non-native balance formatting assumes asset_code/asset_issuer always exist, misreporting liquidity_pool_shares balances as undefined:undefined.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tools/stellar.ts, line 97:

<comment>Non-native balance formatting assumes `asset_code`/`asset_issuer` always exist, misreporting `liquidity_pool_shares` balances as `undefined:undefined`.</comment>

<file context>
@@ -64,4 +64,82 @@ export const stellarSendPaymentTool = new DynamicStructuredTool({
+          };
+        } else {
+          return {
+            asset: `${balance.asset_code}:${balance.asset_issuer}`,
+            balance: balance.balance,
+          };
</file context>
Suggested change
asset: `${balance.asset_code}:${balance.asset_issuer}`,
asset:
balance.asset_type === "liquidity_pool_shares"
? `LP:${balance.liquidity_pool_id}`
: `${balance.asset_code}:${balance.asset_issuer}`,

balance: balance.balance,
};
}
});

return JSON.stringify(balances, null, 2);
} catch (error) {
const errorMessage =
(error as { response?: { data?: { title?: string } }; message?: string })
.response?.data?.title ||
(error as Error).message ||
"Unknown error occurred";
return `Failed to fetch balance: ${errorMessage}`;
}
},
});

export const stellarGetAccountInfoTool = new DynamicStructuredTool({
name: "stellar_get_account_info",
description: "Get full details of a Stellar account, including sequence number, signers, and thresholds.",
schema: z.object({
publicKey: z.string().describe("The Stellar public key to get info for"),
network: z.enum(["testnet", "mainnet"]).optional().describe("The Stellar network to use (defaults to testnet)"),
}),
func: async ({ publicKey, network = "testnet" }: { publicKey: string; network?: string }) => {
try {
if (!StellarSdk.StrKey.isValidEd25519PublicKey(publicKey)) {
throw new Error("Invalid Stellar public key.");
}

const horizonUrl = network === "mainnet"
? "https://horizon.stellar.org"
: "https://horizon-testnet.stellar.org";

const server = new StellarSdk.Horizon.Server(horizonUrl);
const account = await server.loadAccount(publicKey);

return JSON.stringify(account, (key, value) => (key === "_links" || key === "balances" ? undefined : value), 2);
} catch (error) {
const errorMessage =
(error as { response?: { data?: { title?: string } }; message?: string })
.response?.data?.title ||
(error as Error).message ||
"Unknown error occurred";
return `Failed to fetch account info: ${errorMessage}`;
}
},
});
Loading