diff --git a/packages/plugin-defi/package.json b/packages/plugin-defi/package.json index a60e803ca..470ded66a 100644 --- a/packages/plugin-defi/package.json +++ b/packages/plugin-defi/package.json @@ -58,6 +58,7 @@ "redaxios": "^0.5.1", "rpc-websockets": "^10.0.0", "solana-agent-kit": "workspace:*", + "torchsdk": "1.0.4", "zod": "^3.24.1" }, "peerDependencies": { diff --git a/packages/plugin-defi/src/index.ts b/packages/plugin-defi/src/index.ts index ef3b8ca53..5c3d9ed6c 100644 --- a/packages/plugin-defi/src/index.ts +++ b/packages/plugin-defi/src/index.ts @@ -219,6 +219,38 @@ import { executeSwap, } from "./okx/tools"; +// Import Torch tools & actions +import { + torchListTokens, + torchGetToken, + torchBuyToken, + torchSellToken, + torchStarToken, + torchCreateToken, + torchGetMessages, + torchConfirm, + torchGetLendingInfo, + torchGetLoanPosition, + torchBorrowToken, + torchRepayLoan, + torchLiquidateLoan, +} from "./torch/tools"; +import { + torchListTokensAction, + torchGetTokenAction, + torchBuyTokenAction, + torchSellTokenAction, + torchStarTokenAction, + torchCreateTokenAction, + torchGetMessagesAction, + torchConfirmAction, + torchGetLendingInfoAction, + torchGetLoanAction, + torchBorrowAction, + torchRepayAction, + torchLiquidateAction, +} from "./torch/actions"; + // Define and export the plugin const DefiPlugin = { name: "defi", @@ -338,6 +370,21 @@ const DefiPlugin = { getLiquidity, getChainData, executeSwap, + + // Torch methods + torchListTokens, + torchGetToken, + torchBuyToken, + torchSellToken, + torchStarToken, + torchCreateToken, + torchGetMessages, + torchConfirm, + torchGetLendingInfo, + torchGetLoanPosition, + torchBorrowToken, + torchRepayLoan, + torchLiquidateLoan, }, // Combine all actions @@ -443,6 +490,21 @@ const DefiPlugin = { getLiquidityAction, getChainDataAction, executeSwapAction, + + // Torch actions + torchListTokensAction, + torchGetTokenAction, + torchBuyTokenAction, + torchSellTokenAction, + torchStarTokenAction, + torchCreateTokenAction, + torchGetMessagesAction, + torchConfirmAction, + torchGetLendingInfoAction, + torchGetLoanAction, + torchBorrowAction, + torchRepayAction, + torchLiquidateAction, ], // Initialize function diff --git a/packages/plugin-defi/src/torch/actions/index.ts b/packages/plugin-defi/src/torch/actions/index.ts new file mode 100644 index 000000000..ea41e4f45 --- /dev/null +++ b/packages/plugin-defi/src/torch/actions/index.ts @@ -0,0 +1,700 @@ +import { Action, SolanaAgentKit } from "solana-agent-kit"; +import { z } from "zod"; +import { + torchListTokens, + torchGetToken, + torchBuyToken, + torchSellToken, + torchStarToken, + torchCreateToken, + torchGetMessages, + torchConfirm, + torchGetLendingInfo, + torchGetLoanPosition, + torchBorrowToken, + torchRepayLoan, + torchLiquidateLoan, +} from "../tools"; + +export const torchListTokensAction: Action = { + name: "TORCH_LIST_TOKENS", + similes: [ + "list torch tokens", + "browse torch market", + "find bonding curve tokens", + "show torch launchpad tokens", + "what tokens are on torch market", + ], + description: + "List tokens on Torch Market - a fair-launch DAO launchpad on Solana with bonding curves, community treasuries, and democratic governance. Filter by status (bonding, complete, migrated) and sort by newest, volume, or marketcap.", + examples: [ + [ + { + input: { status: "bonding", sort: "volume", limit: 10 }, + output: { + status: "success", + tokens: [{ mint: "ABC...", name: "Example", symbol: "EX", progress_percent: 45 }], + count: 10, + }, + explanation: "List the top 10 bonding tokens by volume on Torch Market", + }, + ], + ], + schema: z.object({ + status: z + .enum(["bonding", "complete", "migrated", "all"]) + .optional() + .describe("Filter by token status"), + sort: z.enum(["newest", "volume", "marketcap"]).optional().describe("Sort order"), + limit: z.number().positive().max(100).optional().describe("Number of tokens to return"), + }), + handler: async (agent: SolanaAgentKit, input: Record) => { + try { + const tokens = await torchListTokens(agent, input.status, input.sort, input.limit); + return { + status: "success", + tokens, + count: tokens.length, + message: `Found ${tokens.length} tokens on Torch Market`, + }; + } catch (error: any) { + return { + status: "error", + message: `Failed to list tokens: ${error.message}`, + }; + } + }, +}; + +export const torchGetTokenAction: Action = { + name: "TORCH_GET_TOKEN", + similes: [ + "get torch token info", + "torch token details", + "check torch token", + "lookup token on torch", + ], + description: + "Get detailed information about a specific token on Torch Market, including price, progress, treasury state, vote counts, and creator SAID verification status.", + examples: [ + [ + { + input: { mint: "ABC123..." }, + output: { + status: "success", + token: { name: "Example", symbol: "EX", progress_percent: 45, votes_burn: 100 }, + }, + explanation: "Get details for a specific Torch token", + }, + ], + ], + schema: z.object({ + mint: z.string().describe("Token mint address"), + }), + handler: async (agent: SolanaAgentKit, input: Record) => { + try { + const token = await torchGetToken(agent, input.mint); + return { + status: "success", + token, + message: `${token.name} (${token.symbol}) - ${token.progress_percent.toFixed(1)}% complete`, + }; + } catch (error: any) { + return { + status: "error", + message: `Failed to get token: ${error.message}`, + }; + } + }, +}; + +export const torchBuyTokenAction: Action = { + name: "TORCH_BUY_TOKEN", + similes: [ + "buy token on torch", + "purchase torch token", + "buy on torch market", + "invest in torch token", + ], + description: + "Buy tokens on Torch Market bonding curve. Specify amount in SOL. 10% of tokens go to community treasury, 90% to you. 1% protocol fee on buys. On your FIRST buy of a token you must include a vote ('burn' or 'return') for how the treasury should be handled at graduation. You can optionally include a message (max 500 chars) which will be bundled as an on-chain SPL Memo -- skin-in-the-game communication.", + examples: [ + [ + { + input: { mint: "ABC123...", amountSol: 0.1, vote: "burn" }, + output: { + status: "success", + signature: "5xKp...", + message: "Bought tokens for 0.1 SOL (voted: burn)", + }, + explanation: "First buy requires a vote -- vote burn to reduce supply at graduation", + }, + ], + [ + { + input: { mint: "ABC123...", amountSol: 0.05, message: "Bullish on this project!" }, + output: { + status: "success", + signature: "5xKp...", + message: "Bought tokens for 0.05 SOL with message", + }, + explanation: "Subsequent buy with an on-chain message bundled in", + }, + ], + ], + schema: z.object({ + mint: z.string().describe("Token mint address"), + amountSol: z.number().positive().describe("Amount of SOL to spend"), + slippagePercent: z + .number() + .positive() + .max(50) + .optional() + .describe("Slippage tolerance as percentage (default 1%)"), + vote: z + .enum(["burn", "return"]) + .optional() + .describe( + "Treasury vote -- REQUIRED on first buy. 'burn' = destroy treasury tokens (deflationary), 'return' = add to Raydium LP", + ), + message: z + .string() + .max(500) + .optional() + .describe( + "Optional message to bundle as on-chain SPL Memo (max 500 chars). Skin-in-the-game: every message has a provable trade behind it.", + ), + }), + handler: async (agent: SolanaAgentKit, input: Record) => { + try { + const lamports = Math.floor(input.amountSol * 1e9); + const bps = input.slippagePercent ? Math.floor(input.slippagePercent * 100) : 100; + const signature = await torchBuyToken( + agent, + input.mint, + lamports, + bps, + input.vote, + input.message, + ); + const parts = [`Bought tokens for ${input.amountSol} SOL`]; + if (input.vote) parts.push(`(voted: ${input.vote})`); + if (input.message) parts.push("with message"); + return { + status: "success", + signature, + message: parts.join(" "), + }; + } catch (error: any) { + return { + status: "error", + message: `Buy failed: ${error.message}`, + }; + } + }, +}; + +export const torchSellTokenAction: Action = { + name: "TORCH_SELL_TOKEN", + similes: ["sell token on torch", "sell torch token", "exit torch position"], + description: + "Sell tokens back to Torch Market bonding curve. No sell fees. Specify amount in tokens. You can optionally include a message (max 500 chars) which will be bundled as an on-chain SPL Memo -- skin-in-the-game communication.", + examples: [ + [ + { + input: { mint: "ABC123...", amountTokens: 1000000 }, + output: { + status: "success", + signature: "5xKp...", + message: "Sold 1M tokens", + }, + explanation: "Sell 1M tokens back to the bonding curve", + }, + ], + [ + { + input: { mint: "ABC123...", amountTokens: 500000, message: "Taking profits, gl everyone" }, + output: { + status: "success", + signature: "5xKp...", + message: "Sold 500K tokens with message", + }, + explanation: "Sell tokens with an on-chain message bundled in", + }, + ], + ], + schema: z.object({ + mint: z.string().describe("Token mint address"), + amountTokens: z.number().positive().describe("Amount of tokens to sell"), + slippagePercent: z + .number() + .positive() + .max(50) + .optional() + .describe("Slippage tolerance as percentage (default 1%)"), + message: z + .string() + .max(500) + .optional() + .describe( + "Optional message to bundle as on-chain SPL Memo (max 500 chars). Skin-in-the-game: every message has a provable trade behind it.", + ), + }), + handler: async (agent: SolanaAgentKit, input: Record) => { + try { + const baseUnits = Math.floor(input.amountTokens * 1e6); + const bps = input.slippagePercent ? Math.floor(input.slippagePercent * 100) : 100; + const signature = await torchSellToken(agent, input.mint, baseUnits, bps, input.message); + const parts = [`Sold ${input.amountTokens.toLocaleString()} tokens`]; + if (input.message) parts.push("with message"); + return { + status: "success", + signature, + message: parts.join(" "), + }; + } catch (error: any) { + return { + status: "error", + message: `Sell failed: ${error.message}`, + }; + } + }, +}; + +export const torchStarTokenAction: Action = { + name: "TORCH_STAR_TOKEN", + similes: ["star torch token", "support torch token", "like token on torch"], + description: + "Star a token on Torch Market to signal sybil-resistant support (costs 0.05 SOL). When tokens reach 2000 stars, creators receive the accumulated ~100 SOL.", + examples: [ + [ + { + input: { mint: "ABC123..." }, + output: { + status: "success", + signature: "5xKp...", + message: "Starred token for 0.05 SOL", + }, + explanation: "Star a token to show support", + }, + ], + ], + schema: z.object({ + mint: z.string().describe("Token mint address to star"), + }), + handler: async (agent: SolanaAgentKit, input: Record) => { + try { + const signature = await torchStarToken(agent, input.mint); + return { + status: "success", + signature, + cost: 0.05, + message: "Starred token (cost: 0.05 SOL)", + }; + } catch (error: any) { + return { + status: "error", + message: `Star failed: ${error.message}`, + }; + } + }, +}; + +export const torchCreateTokenAction: Action = { + name: "TORCH_CREATE_TOKEN", + similes: [ + "create token on torch", + "launch token on torch", + "create my own token", + "make a torch token", + "deploy token to torch market", + ], + description: + "Launch a new community on Torch Market with automatic bonding curve, community treasury, governance vote, and Raydium migration. Every token is a DAO seed. You need to provide a metadata_uri pointing to a JSON file with name, symbol, description, and image URL.", + examples: [ + [ + { + input: { + name: "My Agent Token", + symbol: "MAT", + metadataUri: "https://arweave.net/abc123", + }, + output: { + status: "success", + signature: "5xKp...", + mint: "NEW_MINT_ADDRESS", + message: "Created token My Agent Token ($MAT)", + }, + explanation: "Create a new token with bonding curve", + }, + ], + ], + schema: z.object({ + name: z.string().max(32).describe("Token name (max 32 characters)"), + symbol: z.string().max(10).describe("Token symbol (max 10 characters)"), + metadataUri: z + .string() + .url() + .describe("URI pointing to token metadata JSON with name, symbol, description, and image"), + }), + handler: async (agent: SolanaAgentKit, input: Record) => { + try { + const result = await torchCreateToken(agent, input.name, input.symbol, input.metadataUri); + return { + status: "success", + signature: result.signature, + mint: result.mint, + message: `Created token ${input.name} ($${input.symbol})`, + }; + } catch (error: any) { + return { + status: "error", + message: `Create token failed: ${error.message}`, + }; + } + }, +}; + +export const torchGetMessagesAction: Action = { + name: "TORCH_GET_MESSAGES", + similes: [ + "get torch messages", + "read torch messages", + "see messages on torch", + "what are agents saying on torch", + "read token chat", + ], + description: + "Get messages from a token's page on Torch Market. Messages are bundled with trades, so every message has a provable buy or sell behind it. Use this to read what agents and humans are saying and verify their positions.", + examples: [ + [ + { + input: { mint: "ABC123...", limit: 20 }, + output: { + status: "success", + messages: [{ memo: "Hello from an AI!", sender: "5xKp...", timestamp: 1234567890 }], + count: 1, + }, + explanation: "Read the last 20 messages on a token's page", + }, + ], + ], + schema: z.object({ + mint: z.string().describe("Token mint address"), + limit: z + .number() + .positive() + .max(100) + .optional() + .describe("Number of messages to return (default 50, max 100)"), + }), + handler: async (agent: SolanaAgentKit, input: Record) => { + try { + const messages = await torchGetMessages(agent, input.mint, input.limit); + return { + status: "success", + messages, + count: messages.length, + message: `Found ${messages.length} messages`, + }; + } catch (error: any) { + return { + status: "error", + message: `Failed to get messages: ${error.message}`, + }; + } + }, +}; + +export const torchConfirmAction: Action = { + name: "TORCH_CONFIRM", + similes: [ + "confirm torch transaction", + "report torch transaction", + "torch reputation", + "said reputation torch", + ], + description: + "Report a successful Torch Market transaction to SAID Protocol for reputation. Call this after any confirmed transaction to build your trust score: token launch (+15), trade (+5), governance vote (+10). Requires SAID registration.", + examples: [ + [ + { + input: { signature: "5xKp..." }, + output: { + status: "success", + event_type: "trade_complete", + feedback_sent: true, + message: "Transaction confirmed, +5 reputation", + }, + explanation: "Confirm a trade for SAID reputation points", + }, + ], + ], + schema: z.object({ + signature: z.string().describe("Transaction signature to confirm"), + }), + handler: async (agent: SolanaAgentKit, input: Record) => { + try { + const result = await torchConfirm(agent, input.signature); + const points: Record = { + token_launch: 15, + trade_complete: 5, + governance_vote: 10, + }; + const earned = points[result.event_type] || 0; + return { + status: "success", + confirmed: result.confirmed, + event_type: result.event_type, + feedback_sent: result.feedback_sent, + message: `Transaction confirmed, +${earned} reputation`, + }; + } catch (error: any) { + return { + status: "error", + message: `Confirm failed: ${error.message}`, + }; + } + }, +}; + +// ============================================================================ +// Treasury Lending Actions +// ============================================================================ + +export const torchGetLendingInfoAction: Action = { + name: "TORCH_GET_LENDING_INFO", + similes: [ + "torch lending info", + "check torch lending", + "is lending enabled on torch", + "torch treasury lending rates", + ], + description: + "Get lending state for a migrated Torch token. Returns interest rates, LTV limits, utilization, and active loan count. Lending is always enabled on migrated tokens.", + examples: [ + [ + { + input: { mint: "ABC123..." }, + output: { + status: "success", + interest_rate_bps: 200, + max_ltv_bps: 5000, + active_loans: 3, + }, + explanation: "Get current lending rates and utilization", + }, + ], + ], + schema: z.object({ + mint: z.string().describe("Token mint address"), + }), + handler: async (agent: SolanaAgentKit, input: Record) => { + try { + const info = await torchGetLendingInfo(agent, input.mint); + return { + status: "success", + ...info, + message: `Lending active: ${info.interest_rate_bps / 100}% per epoch, ${info.active_loans} loans, ${(info.total_sol_lent / 1e9).toFixed(2)} SOL lent`, + }; + } catch (error: any) { + return { + status: "error", + message: `Failed to get lending info: ${error.message}`, + }; + } + }, +}; + +export const torchGetLoanAction: Action = { + name: "TORCH_GET_LOAN", + similes: [ + "torch loan position", + "check my torch loan", + "torch loan health", + "am I liquidatable on torch", + ], + description: + "Get loan position details for a wallet on a Torch token. Shows collateral locked, SOL owed, accrued interest, current LTV, and health status (healthy/at_risk/liquidatable/none).", + examples: [ + [ + { + input: { mint: "ABC123..." }, + output: { + status: "success", + health: "healthy", + current_ltv_bps: 4020, + total_owed: 1005000000, + }, + explanation: "Check your loan position health", + }, + ], + ], + schema: z.object({ + mint: z.string().describe("Token mint address"), + wallet: z.string().optional().describe("Wallet to check (defaults to your wallet)"), + }), + handler: async (agent: SolanaAgentKit, input: Record) => { + try { + const position = await torchGetLoanPosition(agent, input.mint, input.wallet); + if (position.health === "none") { + return { + status: "success", + ...position, + message: "No active loan position", + }; + } + return { + status: "success", + ...position, + message: `Loan: ${(position.total_owed / 1e9).toFixed(4)} SOL owed, LTV ${(position.current_ltv_bps / 100).toFixed(1)}%, ${position.health}`, + }; + } catch (error: any) { + return { + status: "error", + message: `Failed to get loan position: ${error.message}`, + }; + } + }, +}; + +export const torchBorrowAction: Action = { + name: "TORCH_BORROW", + similes: [ + "borrow sol on torch", + "torch treasury borrow", + "lock tokens borrow sol", + "leverage torch tokens", + ], + description: + "Borrow SOL from a Torch token's treasury using tokens as collateral. Lock tokens in the collateral vault and receive SOL up to 50% of collateral value (max LTV). Token must be migrated and lending must be enabled. Note: 1% Token-2022 fee applies on collateral deposit.", + examples: [ + [ + { + input: { mint: "ABC123...", collateralTokens: 50000, solToBorrow: 1 }, + output: { + status: "success", + signature: "5xKp...", + message: "Borrowed 1 SOL with 50000 tokens as collateral", + }, + explanation: "Lock 50,000 tokens and borrow 1 SOL", + }, + ], + ], + schema: z.object({ + mint: z.string().describe("Token mint address"), + collateralTokens: z + .number() + .min(0) + .describe( + "Tokens to lock as collateral (in whole tokens, not base units). Can be 0 if adding debt to existing position.", + ), + solToBorrow: z.number().min(0).describe("SOL to borrow. Can be 0 if just adding collateral."), + }), + handler: async (agent: SolanaAgentKit, input: Record) => { + try { + const collateralBaseUnits = Math.floor(input.collateralTokens * 1e6); + const borrowLamports = Math.floor(input.solToBorrow * 1e9); + const signature = await torchBorrowToken( + agent, + input.mint, + collateralBaseUnits, + borrowLamports, + ); + return { + status: "success", + signature, + message: `Borrowed ${input.solToBorrow} SOL with ${input.collateralTokens.toLocaleString()} tokens as collateral`, + }; + } catch (error: any) { + return { + status: "error", + message: `Borrow failed: ${error.message}`, + }; + } + }, +}; + +export const torchRepayAction: Action = { + name: "TORCH_REPAY", + similes: [ + "repay torch loan", + "pay back torch borrow", + "close torch loan", + "return borrowed sol torch", + ], + description: + "Repay borrowed SOL on Torch Market. Interest is paid first, then principal. If you repay the full amount owed, all collateral tokens are returned to your wallet. Partial repay reduces debt but collateral stays locked.", + examples: [ + [ + { + input: { mint: "ABC123...", solAmount: 1.05 }, + output: { + status: "success", + signature: "5xKp...", + message: "Repaid 1.05 SOL", + }, + explanation: "Repay 1.05 SOL of borrowed debt", + }, + ], + ], + schema: z.object({ + mint: z.string().describe("Token mint address"), + solAmount: z.number().positive().describe("SOL to repay"), + }), + handler: async (agent: SolanaAgentKit, input: Record) => { + try { + const lamports = Math.floor(input.solAmount * 1e9); + const signature = await torchRepayLoan(agent, input.mint, lamports); + return { + status: "success", + signature, + message: `Repaid ${input.solAmount} SOL`, + }; + } catch (error: any) { + return { + status: "error", + message: `Repay failed: ${error.message}`, + }; + } + }, +}; + +export const torchLiquidateAction: Action = { + name: "TORCH_LIQUIDATE", + similes: ["liquidate torch loan", "torch liquidation", "liquidate underwater position torch"], + description: + "Liquidate an underwater loan position on Torch Market. Permissionless -- anyone can call when a borrower's LTV exceeds 65%. You pay SOL to the treasury and receive collateral tokens at a 10% bonus (profitable keeper operation).", + examples: [ + [ + { + input: { mint: "ABC123...", borrower: "BORROWER_WALLET..." }, + output: { + status: "success", + signature: "5xKp...", + message: "Liquidated position, received collateral + 10% bonus", + }, + explanation: "Liquidate an underwater loan for profit", + }, + ], + ], + schema: z.object({ + mint: z.string().describe("Token mint address"), + borrower: z.string().describe("Wallet address of the borrower to liquidate"), + }), + handler: async (agent: SolanaAgentKit, input: Record) => { + try { + const signature = await torchLiquidateLoan(agent, input.mint, input.borrower); + return { + status: "success", + signature, + message: "Liquidated position, received collateral + 10% bonus", + }; + } catch (error: any) { + return { + status: "error", + message: `Liquidation failed: ${error.message}`, + }; + } + }, +}; diff --git a/packages/plugin-defi/src/torch/tools/index.ts b/packages/plugin-defi/src/torch/tools/index.ts new file mode 100644 index 000000000..9969ff7e9 --- /dev/null +++ b/packages/plugin-defi/src/torch/tools/index.ts @@ -0,0 +1,351 @@ +import { type SolanaAgentKit, signOrSendTX } from "solana-agent-kit"; +import { + getTokens, + getToken, + getMessages, + getLendingInfo, + getLoanPosition, + buildBuyTransaction, + buildSellTransaction, + buildCreateTokenTransaction, + buildStarTransaction, + buildBorrowTransaction, + buildRepayTransaction, + buildLiquidateTransaction, + confirmTransaction, +} from "torchsdk"; +import type { + TokenSummary, + TokenDetail, + TokenMessage, + LendingInfo, + LoanPositionInfo, +} from "torchsdk"; + +// Re-export SDK types with Torch-prefixed names for backwards compatibility +export type TorchToken = TokenSummary; +export type TorchTokenDetail = TokenDetail; +export type TorchMessage = TokenMessage; +export type TorchLendingInfo = LendingInfo; +export type TorchLoanPosition = LoanPositionInfo; + +export interface TorchConfirmResult { + confirmed: boolean; + event_type: "token_launch" | "trade_complete" | "governance_vote" | "unknown"; + feedback_sent: boolean; +} + +const SAID_API_URL = "https://api.saidprotocol.com/api"; + +/** + * List tokens on Torch Market + * @param agent SolanaAgentKit instance + * @param status Filter by status: bonding, complete, migrated, or all + * @param sort Sort by: newest, volume, or marketcap + * @param limit Number of tokens to return (max 100) + * @returns Array of token summaries + */ +export const torchListTokens = async ( + agent: SolanaAgentKit, + status?: "bonding" | "complete" | "migrated" | "all", + sort?: "newest" | "volume" | "marketcap", + limit?: number, +): Promise => { + const result = await getTokens(agent.connection, { + status: status || "all", + sort, + limit, + }); + return result.tokens; +}; + +/** + * Get detailed information about a token + * @param agent SolanaAgentKit instance + * @param mint Token mint address + * @returns Token details including treasury state and votes + */ +export const torchGetToken = async ( + agent: SolanaAgentKit, + mint: string, +): Promise => { + return getToken(agent.connection, mint); +}; + +/** + * Buy tokens on Torch Market bonding curve + * @param agent SolanaAgentKit instance + * @param mint Token mint address + * @param amountLamports Amount of SOL in lamports (1 SOL = 1e9 lamports) + * @param slippageBps Slippage tolerance in basis points (default 100 = 1%) + * @param vote Vote on treasury outcome -- required on first buy, omit on subsequent buys. + * "burn" = destroy treasury tokens (deflationary), "return" = add to LP (deeper liquidity) + * @param message Optional message to bundle with the trade (SPL Memo, max 500 chars) + * @returns Transaction signature + */ +export const torchBuyToken = async ( + agent: SolanaAgentKit, + mint: string, + amountLamports: number, + slippageBps: number = 100, + vote?: "burn" | "return", + message?: string, +) => { + const result = await buildBuyTransaction(agent.connection, { + mint, + buyer: agent.wallet.publicKey.toBase58(), + amount_sol: amountLamports, + slippage_bps: slippageBps, + ...(vote ? { vote } : {}), + ...(message ? { message } : {}), + }); + + return signOrSendTX(agent, result.transaction); +}; + +/** + * Sell tokens back to Torch Market bonding curve + * @param agent SolanaAgentKit instance + * @param mint Token mint address + * @param amountTokens Amount of tokens in base units (6 decimals) + * @param slippageBps Slippage tolerance in basis points (default 100 = 1%) + * @param message Optional message to bundle with the trade (SPL Memo, max 500 chars) + * @returns Transaction signature + */ +export const torchSellToken = async ( + agent: SolanaAgentKit, + mint: string, + amountTokens: number, + slippageBps: number = 100, + message?: string, +) => { + const result = await buildSellTransaction(agent.connection, { + mint, + seller: agent.wallet.publicKey.toBase58(), + amount_tokens: amountTokens, + slippage_bps: slippageBps, + ...(message ? { message } : {}), + }); + + return signOrSendTX(agent, result.transaction); +}; + +/** + * Star a token to show support (costs 0.05 SOL) + * @param agent SolanaAgentKit instance + * @param mint Token mint address + * @returns Transaction signature + */ +export const torchStarToken = async (agent: SolanaAgentKit, mint: string) => { + const result = await buildStarTransaction(agent.connection, { + mint, + user: agent.wallet.publicKey.toBase58(), + }); + + return signOrSendTX(agent, result.transaction); +}; + +/** + * Create a new token on Torch Market with automatic bonding curve + * + * This allows AI agents to launch their own tokens. The token will have: + * - Automatic bonding curve for price discovery + * - Community treasury (10% of buys) + * - Graduation at 200 SOL with Raydium migration + * - Democratic voting on treasury outcome + * + * @param agent SolanaAgentKit instance + * @param name Token name (max 32 characters) + * @param symbol Token symbol (max 10 characters) + * @param metadataUri URI pointing to token metadata JSON (Metaplex standard) + * @returns Transaction signature and new token mint address + */ +export const torchCreateToken = async ( + agent: SolanaAgentKit, + name: string, + symbol: string, + metadataUri: string, +) => { + const result = await buildCreateTokenTransaction(agent.connection, { + creator: agent.wallet.publicKey.toBase58(), + name, + symbol, + metadata_uri: metadataUri, + }); + + const signature = await signOrSendTX(agent, result.transaction); + return { + signature, + mint: result.mint.toBase58(), + }; +}; + +/** + * Get messages (memos) from a token's page + * AI agents can use this to read what other agents are saying + * @param agent SolanaAgentKit instance + * @param mint Token mint address + * @param limit Number of messages to return (max 100) + * @returns Array of messages + */ +export const torchGetMessages = async ( + agent: SolanaAgentKit, + mint: string, + limit: number = 50, +): Promise => { + const result = await getMessages(agent.connection, mint, limit); + return result.messages; +}; + +// ============================================================================ +// Treasury Lending Tools +// ============================================================================ + +/** + * Get lending configuration and state for a migrated token + * @param agent SolanaAgentKit instance + * @param mint Token mint address + * @returns Lending info including rates, caps, and active loan stats + */ +export const torchGetLendingInfo = async ( + agent: SolanaAgentKit, + mint: string, +): Promise => { + return getLendingInfo(agent.connection, mint); +}; + +/** + * Get loan position for a wallet on a specific token + * @param agent SolanaAgentKit instance + * @param mint Token mint address + * @param wallet Wallet address to check (defaults to agent's wallet) + * @returns Loan position details including collateral, debt, LTV, and health + */ +export const torchGetLoanPosition = async ( + agent: SolanaAgentKit, + mint: string, + wallet?: string, +): Promise => { + const w = wallet || agent.wallet.publicKey.toBase58(); + return getLoanPosition(agent.connection, mint, w); +}; + +/** + * Borrow SOL from treasury using tokens as collateral + * + * Lock tokens in a collateral vault and receive SOL. The token must be + * migrated to Raydium and lending must be enabled. Max LTV is 50%. + * + * Note: Token-2022's 1% transfer fee applies when depositing collateral. + * + * @param agent SolanaAgentKit instance + * @param mint Token mint address + * @param collateralAmount Tokens to lock as collateral (base units, 6 decimals). Can be 0 if adding debt only. + * @param solToBorrow SOL to borrow in lamports. Can be 0 if adding collateral only. + * @returns Transaction signature + */ +export const torchBorrowToken = async ( + agent: SolanaAgentKit, + mint: string, + collateralAmount: number, + solToBorrow: number, +) => { + const result = await buildBorrowTransaction(agent.connection, { + mint, + borrower: agent.wallet.publicKey.toBase58(), + collateral_amount: collateralAmount, + sol_to_borrow: solToBorrow, + }); + + return signOrSendTX(agent, result.transaction); +}; + +/** + * Repay borrowed SOL and receive collateral back + * + * Interest is paid first, then principal. If sol_amount >= total owed, + * this is a full repay and all collateral tokens are returned. + * + * @param agent SolanaAgentKit instance + * @param mint Token mint address + * @param solAmount SOL to repay in lamports + * @returns Transaction signature + */ +export const torchRepayLoan = async (agent: SolanaAgentKit, mint: string, solAmount: number) => { + const result = await buildRepayTransaction(agent.connection, { + mint, + borrower: agent.wallet.publicKey.toBase58(), + sol_amount: solAmount, + }); + + return signOrSendTX(agent, result.transaction); +}; + +/** + * Liquidate an underwater loan position + * + * Permissionless -- anyone can call when a borrower's LTV exceeds the + * liquidation threshold (default 65%). Liquidator pays SOL to treasury + * and receives collateral tokens + 10% bonus. + * + * @param agent SolanaAgentKit instance + * @param mint Token mint address + * @param borrower Wallet address of the borrower to liquidate + * @returns Transaction signature + */ +export const torchLiquidateLoan = async (agent: SolanaAgentKit, mint: string, borrower: string) => { + const result = await buildLiquidateTransaction(agent.connection, { + mint, + liquidator: agent.wallet.publicKey.toBase58(), + borrower, + }); + + return signOrSendTX(agent, result.transaction); +}; + +/** + * Confirm a transaction with SAID Protocol for reputation + * + * After a transaction is confirmed on-chain, call this to report + * success to SAID Protocol. This builds your trust score: + * - token_launch: +15 reputation + * - trade_complete: +5 reputation + * - governance_vote: +10 reputation + * + * @param agent SolanaAgentKit instance + * @param signature Transaction signature to confirm + * @returns Confirmation result with event type and reputation feedback status + */ +export const torchConfirm = async ( + agent: SolanaAgentKit, + signature: string, +): Promise => { + const wallet = agent.wallet.publicKey.toBase58(); + + // Confirm on-chain via SDK (reads RPC directly) + const result = await confirmTransaction(agent.connection, signature, wallet); + + // Send feedback to SAID Protocol for reputation + let feedbackSent = false; + try { + const saidRes = await fetch(`${SAID_API_URL}/feedback`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + wallet, + signature, + event_type: result.event_type, + success: true, + }), + }); + feedbackSent = saidRes.ok; + } catch { + // SAID feedback is best-effort + } + + return { + confirmed: result.confirmed, + event_type: result.event_type, + feedback_sent: feedbackSent, + }; +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e618a61ca..01f8c6560 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -264,6 +264,9 @@ importers: solana-agent-kit: specifier: workspace:* version: link:../core + torchsdk: + specifier: ^1.0.0 + version: 1.0.0(@solana/web3.js@1.98.2(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.8.3)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.3)(utf-8-validate@5.0.10) zod: specifier: ^3.24.1 version: 3.24.1 @@ -811,6 +814,10 @@ packages: resolution: {integrity: sha512-QUqpoEK+gi2S6nlYc2atgT2r41TT3caWr/cPUEL8n8Md9437trZ68STknq897b82p5mW0XrTBNOzRbmIRJtfsA==} engines: {node: '>=17'} + '@coral-xyz/anchor@0.32.1': + resolution: {integrity: sha512-zAyxFtfeje2FbMA1wzgcdVs7Hng/MijPKpRijoySPCicnvcTQs/+dnPZ/cR+LcXM9v9UYSyW81uRNYZtN5G4yg==} + engines: {node: '>=17'} + '@coral-xyz/borsh@0.26.0': resolution: {integrity: sha512-uCZ0xus0CszQPHYfWAqKS5swS1UxvePu83oOF+TWpUkedsNlg6p2p4azxZNSSqwXb9uXMFgxhuMBX9r3Xoi0vQ==} engines: {node: '>=10'} @@ -2757,6 +2764,12 @@ packages: peerDependencies: '@solana/web3.js': ^1.95.5 + '@solana/spl-token@0.4.14': + resolution: {integrity: sha512-u09zr96UBpX4U685MnvQsNzlvw9TiY005hk1vJmJr7gMJldoPG1eYU5/wNEyOA5lkMLiR/gOi9SFD4MefOYEsA==} + engines: {node: '>=16'} + peerDependencies: + '@solana/web3.js': ^1.95.5 + '@solana/spl-token@0.4.6': resolution: {integrity: sha512-1nCnUqfHVtdguFciVWaY/RKcQz1IF4b31jnKgAmjU9QVN1q7dRUkTEWJZgTYIEtsULjVnC9jRqlhgGN39WbKKA==} engines: {node: '>=16'} @@ -6230,6 +6243,11 @@ packages: toml@3.0.0: resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==} + torchsdk@1.0.0: + resolution: {integrity: sha512-c3gaRIPZ3XPbfk71ufjWoP8eEGwkiRN8JEEmcqgqvtn2eoDuq9xEpRTlU+iEuJkS8kMoRFBWYzMp6E5KFLbH+g==} + peerDependencies: + '@solana/web3.js': ^1.98.0 + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -7522,6 +7540,27 @@ snapshots: - typescript - utf-8-validate + '@coral-xyz/anchor@0.32.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.8.3)(utf-8-validate@5.0.10)': + dependencies: + '@coral-xyz/anchor-errors': 0.31.1 + '@coral-xyz/borsh': 0.31.1(@solana/web3.js@1.98.2(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.8.3)(utf-8-validate@5.0.10)) + '@noble/hashes': 1.7.2 + '@solana/web3.js': 1.98.2(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.8.3)(utf-8-validate@5.0.10) + bn.js: 5.2.2 + bs58: 4.0.1 + buffer-layout: 1.2.2 + camelcase: 6.3.0 + cross-fetch: 3.2.0(encoding@0.1.13) + eventemitter3: 4.0.7 + pako: 2.1.0 + superstruct: 0.15.5 + toml: 3.0.0 + transitivePeerDependencies: + - bufferutil + - encoding + - typescript + - utf-8-validate + '@coral-xyz/borsh@0.26.0(@solana/web3.js@1.98.2(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.8.3)(utf-8-validate@5.0.10))': dependencies: '@solana/web3.js': 1.98.2(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.8.3)(utf-8-validate@5.0.10) @@ -11068,6 +11107,21 @@ snapshots: - typescript - utf-8-validate + '@solana/spl-token@0.4.14(@solana/web3.js@1.98.2(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.8.3)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.3)(utf-8-validate@5.0.10)': + dependencies: + '@solana/buffer-layout': 4.0.1 + '@solana/buffer-layout-utils': 0.2.0(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.8.3)(utf-8-validate@5.0.10) + '@solana/spl-token-group': 0.0.7(@solana/web3.js@1.98.2(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.8.3)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.3) + '@solana/spl-token-metadata': 0.1.6(@solana/web3.js@1.98.2(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.8.3)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.3) + '@solana/web3.js': 1.98.2(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.8.3)(utf-8-validate@5.0.10) + buffer: 6.0.3 + transitivePeerDependencies: + - bufferutil + - encoding + - fastestsmallesttextencoderdecoder + - typescript + - utf-8-validate + '@solana/spl-token@0.4.6(@solana/web3.js@1.98.2(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.8.3)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.3)(utf-8-validate@5.0.10)': dependencies: '@solana/buffer-layout': 4.0.1 @@ -15497,6 +15551,19 @@ snapshots: toml@3.0.0: {} + torchsdk@1.0.0(@solana/web3.js@1.98.2(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.8.3)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.3)(utf-8-validate@5.0.10): + dependencies: + '@coral-xyz/anchor': 0.32.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.8.3)(utf-8-validate@5.0.10) + '@solana/spl-token': 0.4.14(@solana/web3.js@1.98.2(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.8.3)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.3)(utf-8-validate@5.0.10) + '@solana/web3.js': 1.98.2(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.8.3)(utf-8-validate@5.0.10) + bs58: 6.0.0 + transitivePeerDependencies: + - bufferutil + - encoding + - fastestsmallesttextencoderdecoder + - typescript + - utf-8-validate + tr46@0.0.3: {} tr46@1.0.1: