├── src ├── plugins │ ├── evm_wallet │ │ ├── index.ts │ │ ├── parameters.ts │ │ ├── evmWalletService.ts │ │ └── evmWalletPlugin.ts │ ├── solana_wallet │ │ ├── index.ts │ │ ├── parameters.ts │ │ ├── solanaWalletPlugin.ts │ │ └── solanaWalletService.ts │ ├── aave │ │ ├── index.ts │ │ ├── parameters.ts │ │ └── aavePlugin.ts │ ├── lido │ │ ├── index.ts │ │ ├── parameters.ts │ │ ├── lidoProtocol.ts │ │ └── lidoPlugin.ts │ ├── lulo │ │ ├── index.ts │ │ ├── parameters.ts │ │ └── luloPlugin.ts │ ├── cookie │ │ ├── index.ts │ │ ├── parameters.ts │ │ ├── cookiePlugin.ts │ │ └── cookieClient.ts │ ├── eoracle │ │ ├── index.ts │ │ ├── parameters.ts │ │ ├── eoraclePlugin.ts │ │ └── eoracleService.ts │ ├── jupiter │ │ ├── index.ts │ │ ├── parameters.ts │ │ └── jupiterPlugin.ts │ ├── meteora │ │ ├── index.ts │ │ ├── errors.ts │ │ ├── parameters.ts │ │ └── meteoraPlugin.ts │ ├── uniswap │ │ ├── index.ts │ │ ├── parameters.ts │ │ ├── uniswapProtocol.ts │ │ └── uniswapPlugin.ts │ ├── hyperliquid │ │ ├── index.ts │ │ ├── hyperliquidPlugin.ts │ │ └── parameters.ts │ ├── storyprotocol │ │ ├── index.ts │ │ ├── parameters.ts │ │ └── storyProtocolPlugin.ts │ ├── bonzo │ │ ├── index.ts │ │ ├── parameters.ts │ │ └── bonzoPlugin.ts │ ├── dexscreener │ │ ├── index.ts │ │ ├── parameters.ts │ │ └── dexscreenerPlugin.ts │ ├── stader │ │ ├── index.ts │ │ ├── parameters.ts │ │ └── staderPlugin.ts │ ├── saucerswap │ │ ├── index.ts │ │ ├── saucerSwapPlugin.ts │ │ └── parameters.ts │ ├── hedera_wallet │ │ ├── index.ts │ │ └── parameters.ts │ ├── silo │ │ ├── index.ts │ │ ├── parameters.ts │ │ └── siloPlugin.ts │ ├── mendi │ │ ├── index.ts │ │ ├── parameters.ts │ │ └── mendiPlugin.ts │ └── index.ts ├── core │ ├── classes │ │ ├── index.ts │ │ ├── edwinToolProvider.ts │ │ └── edwinPlugin.ts │ ├── types │ │ ├── index.ts │ │ ├── edwinTool.ts │ │ └── chain.ts │ ├── index.ts │ ├── wallets │ │ ├── solana_wallet │ │ │ ├── clients │ │ │ │ ├── index.ts │ │ │ │ ├── publickey │ │ │ │ │ └── index.ts │ │ │ │ └── phantom │ │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── factory.ts │ │ │ ├── client.ts │ │ │ └── README.md │ │ ├── evm_wallet │ │ │ ├── index.ts │ │ │ └── evm_wallet.ts │ │ ├── hedera_wallet │ │ │ ├── clients │ │ │ │ ├── index.ts │ │ │ │ └── publickey │ │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── factory.ts │ │ │ └── client.ts │ │ ├── wallet.ts │ │ └── index.ts │ └── utils │ │ └── createParameterSchema.ts ├── adapters │ ├── elizaos │ │ ├── index.ts │ │ └── toolParametersGenerator.ts │ ├── index.ts │ ├── langchain │ │ └── index.ts │ └── mcp │ │ └── index.ts ├── client │ └── index.ts ├── errors.ts ├── index.ts └── utils │ ├── index.ts │ └── logger.ts ├── docs └── static │ └── img │ └── edwin_diagram.png ├── .gitignore ├── examples ├── mcp-server │ ├── tsup.config.ts │ ├── tsconfig.json │ ├── claude_desktop_config.json │ ├── package.json │ ├── src │ │ └── index.ts │ └── README.md └── cli-chat-agent │ ├── package.json │ └── chat-agent.ts ├── .prettierrc ├── tsup.config.ts ├── .env.example ├── tsconfig.json ├── tests ├── logger.test.ts ├── jupiter.get_ca.test.ts ├── aave.test.ts ├── mendi.test.ts ├── jupiter.swap.test.ts ├── solana_wallet.test.ts └── silo.test.ts ├── .github └── workflows │ └── publish.yml ├── README.md ├── eslint.config.mjs ├── package.json ├── SECURITY.md └── CONTRIBUTING.md /src/plugins/evm_wallet/index.ts: -------------------------------------------------------------------------------- 1 | export * from './evmWalletPlugin'; 2 | -------------------------------------------------------------------------------- /src/core/classes/index.ts: -------------------------------------------------------------------------------- 1 | export { EdwinPlugin } from './edwinPlugin'; 2 | -------------------------------------------------------------------------------- /src/plugins/solana_wallet/index.ts: -------------------------------------------------------------------------------- 1 | export * from './solanaWalletPlugin'; 2 | -------------------------------------------------------------------------------- /src/core/classes/edwinToolProvider.ts: -------------------------------------------------------------------------------- 1 | export abstract class EdwinService {} 2 | -------------------------------------------------------------------------------- /src/core/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './chain'; 2 | export * from './edwinTool'; 3 | -------------------------------------------------------------------------------- /src/adapters/elizaos/index.ts: -------------------------------------------------------------------------------- 1 | export { generateToolParametersPrompt } from './toolParametersGenerator'; 2 | -------------------------------------------------------------------------------- /src/client/index.ts: -------------------------------------------------------------------------------- 1 | export { Edwin } from './edwin'; 2 | export type { EdwinConfig } from './edwin'; 3 | -------------------------------------------------------------------------------- /src/core/index.ts: -------------------------------------------------------------------------------- 1 | export * from './classes'; 2 | export * from './types'; 3 | export * from './wallets'; 4 | -------------------------------------------------------------------------------- /src/adapters/index.ts: -------------------------------------------------------------------------------- 1 | export * from './elizaos'; 2 | export * from './langchain'; 3 | export * from './mcp'; 4 | -------------------------------------------------------------------------------- /src/plugins/aave/index.ts: -------------------------------------------------------------------------------- 1 | export { aave } from './aavePlugin'; 2 | export { AaveService } from './aaveService'; 3 | -------------------------------------------------------------------------------- /src/plugins/lido/index.ts: -------------------------------------------------------------------------------- 1 | export { lido } from './lidoPlugin'; 2 | export { LidoProtocol } from './lidoProtocol'; 3 | -------------------------------------------------------------------------------- /src/plugins/lulo/index.ts: -------------------------------------------------------------------------------- 1 | export { lulo } from './luloPlugin'; 2 | export { LuloProtocol } from './luloProtocol'; 3 | -------------------------------------------------------------------------------- /docs/static/img/edwin_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwin-finance/edwin/HEAD/docs/static/img/edwin_diagram.png -------------------------------------------------------------------------------- /src/plugins/cookie/index.ts: -------------------------------------------------------------------------------- 1 | export { cookie } from './cookiePlugin'; 2 | export { CookieSwarmClient } from './cookieClient'; 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | .env 4 | .vscode 5 | .logs/ 6 | .DS_Store 7 | /*/.DS_Store 8 | /*/*/.DS_Store 9 | .abstract -------------------------------------------------------------------------------- /src/plugins/eoracle/index.ts: -------------------------------------------------------------------------------- 1 | export { eoracle } from './eoraclePlugin'; 2 | export { EOracleService } from './eoracleService'; 3 | -------------------------------------------------------------------------------- /src/plugins/jupiter/index.ts: -------------------------------------------------------------------------------- 1 | export { jupiter } from './jupiterPlugin'; 2 | export { JupiterService } from './jupiterService'; 3 | -------------------------------------------------------------------------------- /src/plugins/meteora/index.ts: -------------------------------------------------------------------------------- 1 | export { meteora } from './meteoraPlugin'; 2 | export { MeteoraProtocol } from './meteoraProtocol'; 3 | -------------------------------------------------------------------------------- /src/core/wallets/solana_wallet/clients/index.ts: -------------------------------------------------------------------------------- 1 | export * from './keypair'; 2 | export * from './phantom'; 3 | export * from './publickey'; 4 | -------------------------------------------------------------------------------- /src/plugins/uniswap/index.ts: -------------------------------------------------------------------------------- 1 | export { uniswap, UniswapPlugin } from './uniswapPlugin'; 2 | export { UniswapProtocol } from './uniswapProtocol'; 3 | -------------------------------------------------------------------------------- /src/plugins/hyperliquid/index.ts: -------------------------------------------------------------------------------- 1 | export { hyperliquid } from './hyperliquidPlugin'; 2 | export { HyperLiquidService } from './hyperliquidService'; 3 | -------------------------------------------------------------------------------- /src/core/wallets/evm_wallet/index.ts: -------------------------------------------------------------------------------- 1 | export { EdwinEVMWallet } from './evm_wallet'; 2 | export { EdwinEVMPublicKeyWallet } from './evm_public_key_wallet'; 3 | -------------------------------------------------------------------------------- /src/plugins/storyprotocol/index.ts: -------------------------------------------------------------------------------- 1 | export { storyprotocol } from './storyProtocolPlugin'; 2 | export { StoryProtocolService } from './storyProtocolService'; 3 | -------------------------------------------------------------------------------- /src/plugins/bonzo/index.ts: -------------------------------------------------------------------------------- 1 | export { BonzoPlugin, bonzo } from './bonzoPlugin'; 2 | export { BonzoService } from './bonzoService'; 3 | export * from './parameters'; 4 | -------------------------------------------------------------------------------- /src/plugins/dexscreener/index.ts: -------------------------------------------------------------------------------- 1 | export { dexscreener, DexScreenerPlugin } from './dexscreenerPlugin'; 2 | export { DexScreenerService } from './dexscreenerService'; 3 | -------------------------------------------------------------------------------- /src/plugins/stader/index.ts: -------------------------------------------------------------------------------- 1 | export { StaderPlugin, stader } from './staderPlugin'; 2 | export { StaderService } from './staderService'; 3 | export * from './parameters'; 4 | -------------------------------------------------------------------------------- /src/core/wallets/hedera_wallet/clients/index.ts: -------------------------------------------------------------------------------- 1 | // Client implementations 2 | export { KeypairClient } from './keypair'; 3 | export { PublicKeyClient } from './publickey'; 4 | -------------------------------------------------------------------------------- /src/plugins/saucerswap/index.ts: -------------------------------------------------------------------------------- 1 | export { SaucerSwapPlugin, saucerSwap } from './saucerSwapPlugin'; 2 | export { SaucerSwapService } from './saucerSwapService'; 3 | export * from './parameters'; 4 | -------------------------------------------------------------------------------- /src/plugins/hedera_wallet/index.ts: -------------------------------------------------------------------------------- 1 | export { HederaWalletPlugin, hederaWallet } from './hederaWalletPlugin'; 2 | export { HederaWalletService } from './hederaWalletService'; 3 | export * from './parameters'; 4 | -------------------------------------------------------------------------------- /src/core/types/edwinTool.ts: -------------------------------------------------------------------------------- 1 | import { ZodSchema } from 'zod'; 2 | 3 | export interface EdwinTool { 4 | name: string; 5 | description: string; 6 | schema: TSchema; 7 | execute: (params: TSchema['_output']) => Promise; 8 | } 9 | -------------------------------------------------------------------------------- /examples/mcp-server/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup'; 2 | 3 | export default defineConfig({ 4 | entry: ['src/index.ts'], 5 | format: ['cjs', 'esm'], 6 | dts: false, 7 | sourcemap: true, 8 | clean: true, 9 | external: ['edwin-sdk'], 10 | }); 11 | -------------------------------------------------------------------------------- /examples/mcp-server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "rootDir": ".", 6 | "baseUrl": ".", 7 | "composite": true 8 | }, 9 | "include": ["src/**/*"], 10 | "references": [{ "path": "../.." }] 11 | } 12 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "es5", 4 | "singleQuote": true, 5 | "printWidth": 120, 6 | "tabWidth": 4, 7 | "useTabs": false, 8 | "bracketSpacing": true, 9 | "arrowParens": "avoid", 10 | "endOfLine": "lf", 11 | "bracketSameLine": false, 12 | "quoteProps": "as-needed" 13 | } 14 | -------------------------------------------------------------------------------- /src/plugins/meteora/errors.ts: -------------------------------------------------------------------------------- 1 | export class MeteoraStatisticalBugError extends Error { 2 | constructor( 3 | message: string, 4 | public positionAddress: string 5 | ) { 6 | super(message); 7 | this.name = 'MeteoraStatisticalBugError'; 8 | this.positionAddress = positionAddress; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup'; 2 | 3 | export default defineConfig({ 4 | entry: ['src/index.ts'], 5 | format: ['cjs', 'esm'], 6 | dts: true, 7 | splitting: false, 8 | sourcemap: true, 9 | clean: true, 10 | skipNodeModulesBundle: true, 11 | treeshake: true, 12 | legacyOutput: true, 13 | }); 14 | -------------------------------------------------------------------------------- /src/errors.ts: -------------------------------------------------------------------------------- 1 | export class InsufficientBalanceError extends Error { 2 | constructor( 3 | public readonly required: number, 4 | public readonly available: number, 5 | public readonly symbol: string 6 | ) { 7 | super(`Insufficient ${symbol} balance. Required: ${required}, Available: ${available}`); 8 | this.name = 'InsufficientBalanceError'; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/plugins/silo/index.ts: -------------------------------------------------------------------------------- 1 | import { SiloPlugin, silo } from './siloPlugin'; 2 | import { SiloService } from './siloService'; 3 | import { SupplyParameters, WithdrawParameters, SupplyParametersSchema, WithdrawParametersSchema } from './parameters'; 4 | 5 | export { SiloPlugin, SiloService, SupplyParametersSchema, WithdrawParametersSchema, silo }; 6 | 7 | // Re-export types using 'export type' 8 | export type { SupplyParameters, WithdrawParameters }; 9 | -------------------------------------------------------------------------------- /src/plugins/eoracle/parameters.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { createParameterSchema } from '../../core/utils/createParameterSchema'; 3 | 4 | export const PriceParametersSchema = createParameterSchema( 5 | z.object({ 6 | symbol: z.string().min(1).describe('The symbol to get price information for'), 7 | }) 8 | ); 9 | 10 | // Export clean parameter type 11 | export type PriceParameters = typeof PriceParametersSchema.type; 12 | -------------------------------------------------------------------------------- /src/core/wallets/wallet.ts: -------------------------------------------------------------------------------- 1 | export abstract class EdwinWallet { 2 | abstract getAddress(): string; 3 | 4 | /** 5 | * Get balance of current wallet 6 | */ 7 | abstract getBalance(): Promise; 8 | 9 | /** 10 | * Get balance of any wallet address 11 | * @param walletAddress Address of the wallet to check 12 | */ 13 | abstract getBalanceOfWallet(walletAddress: string, ...args: unknown[]): Promise; 14 | } 15 | -------------------------------------------------------------------------------- /src/plugins/lido/parameters.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { createParameterSchema } from '../../core/utils/createParameterSchema'; 3 | 4 | export const StakeParametersSchema = createParameterSchema( 5 | z.object({ 6 | asset: z.string().min(1).describe('The asset to stake'), 7 | amount: z.number().positive().describe('The amount to stake'), 8 | }) 9 | ); 10 | 11 | // Export clean parameter type 12 | export type StakeParameters = typeof StakeParametersSchema.type; 13 | -------------------------------------------------------------------------------- /examples/mcp-server/claude_desktop_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "edwin": { 4 | "command": "node", 5 | "env": { 6 | "SOLANA_RPC_URL": "your_rpc_url_here", 7 | "EVM_PRIVATE_KEY": "your_evm_private_key_here", 8 | "SOLANA_PRIVATE_KEY": "your_solana_private_key_here", 9 | "EDWIN_MCP_MODE": "true" 10 | }, 11 | "args": ["/absolute/path/to/build/index.js"] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # To run examples 2 | OPENAI_API_KEY= 3 | 4 | # To enable evm actions 5 | EVM_PRIVATE_KEY= 6 | 7 | # To enable solana actions 8 | SOLANA_PRIVATE_KEY= 9 | SOLANA_RPC_URL= # Optional 10 | 11 | # To enable hedera actions 12 | HEDERA_ACCOUNT_ID= 13 | HEDERA_PRIVATE_KEY= 14 | HEDERA_NETWORK= # mainnet or testnet 15 | HEDERA_RPC_URL= # Premium RPC URL (e.g., https://mainnet.hedera.validationcloud.io/v1/YOUR_API_KEY) 16 | 17 | # To enable the cookie swarm api 18 | COOKIE_API_KEY= 19 | 20 | # MCP Server 21 | EDWIN_MCP_MODE= -------------------------------------------------------------------------------- /src/core/wallets/index.ts: -------------------------------------------------------------------------------- 1 | export { EdwinWallet } from './wallet'; 2 | export { EdwinEVMWallet, EdwinEVMPublicKeyWallet } from './evm_wallet'; 3 | 4 | // Solana wallet clients 5 | export { BaseSolanaWalletClient, KeypairClient, PhantomClient, type PhantomProvider } from './solana_wallet'; 6 | export type { SolanaWalletClient } from './solana_wallet'; 7 | 8 | // Hedera wallet clients 9 | export { BaseHederaWalletClient, HederaWalletFactory, canSign } from './hedera_wallet'; 10 | export type { HederaWalletClient } from './hedera_wallet'; 11 | -------------------------------------------------------------------------------- /src/core/wallets/hedera_wallet/index.ts: -------------------------------------------------------------------------------- 1 | // Core wallet interfaces and base classes 2 | export type { HederaWalletClient } from './client'; 3 | export { BaseHederaWalletClient } from './base_client'; 4 | 5 | // Client implementations 6 | export * from './clients'; 7 | 8 | // Factory for creating wallet clients 9 | export { HederaWalletFactory, canSign } from './factory'; 10 | 11 | // Re-export specific client types for backwards compatibility 12 | export { KeypairClient } from './clients/keypair'; 13 | export { PublicKeyClient } from './clients/publickey'; 14 | -------------------------------------------------------------------------------- /src/core/wallets/solana_wallet/index.ts: -------------------------------------------------------------------------------- 1 | // Core wallet interfaces and base classes 2 | export type { SolanaWalletClient } from './client'; 3 | export { BaseSolanaWalletClient } from './base_client'; 4 | 5 | // Client implementations 6 | export * from './clients'; 7 | 8 | // Factory for creating wallet clients 9 | export { SolanaWalletFactory, canSign } from './factory'; 10 | 11 | // Re-export legacy wallet types for backwards compatibility 12 | export { KeypairClient } from './clients/keypair'; 13 | export { PublicKeyClient } from './clients/publickey'; 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "declaration": true, 7 | "outDir": "./dist", 8 | "rootDir": "./src", 9 | "strict": true, 10 | "esModuleInterop": true, 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "resolveJsonModule": true, 14 | "isolatedModules": true 15 | }, 16 | "include": ["src/**/*"], 17 | "exclude": ["node_modules", "dist", "test"] 18 | } 19 | -------------------------------------------------------------------------------- /src/plugins/mendi/index.ts: -------------------------------------------------------------------------------- 1 | import { MendiPlugin, mendi } from './mendiPlugin'; 2 | import { MendiService } from './mendiService'; 3 | import { 4 | SupplyParametersSchema as MendiSupplyParametersSchema, 5 | WithdrawParametersSchema as MendiWithdrawParametersSchema, 6 | SupplyParameters as MendiSupplyParameters, 7 | WithdrawParameters as MendiWithdrawParameters, 8 | } from './parameters'; 9 | 10 | export { MendiPlugin, mendi, MendiService, MendiSupplyParametersSchema, MendiWithdrawParametersSchema }; 11 | 12 | export type { MendiSupplyParameters, MendiWithdrawParameters }; 13 | -------------------------------------------------------------------------------- /src/plugins/index.ts: -------------------------------------------------------------------------------- 1 | export * from './aave'; 2 | export * from './lido'; 3 | export * from './lulo'; 4 | export * from './meteora'; 5 | export * from './uniswap'; 6 | export * from './jupiter'; 7 | export * from './cookie'; 8 | export * from './eoracle'; 9 | export * from './storyprotocol'; 10 | export * from './hyperliquid'; 11 | export * from './mendi'; 12 | export * from './dexscreener'; 13 | export * from './evm_wallet'; 14 | export * from './solana_wallet'; 15 | export * from './hedera_wallet'; 16 | export * from './silo'; 17 | export * from './stader'; 18 | export * from './saucerswap'; 19 | export * from './bonzo'; 20 | -------------------------------------------------------------------------------- /src/core/types/chain.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @param type 3 | * @param id - Chain ID, optional for EVM 4 | */ 5 | export type Chain = EvmChain | SolanaChain | HederaChain; 6 | 7 | export type EvmChain = { 8 | type: 'evm'; 9 | id: number; 10 | }; 11 | 12 | export type SolanaChain = { 13 | type: 'solana'; 14 | }; 15 | 16 | export type HederaChain = { 17 | type: 'hedera'; 18 | }; 19 | 20 | import { _SupportedEVMChainList } from '../wallets/evm_wallet/evm_public_key_wallet'; 21 | 22 | export type SupportedEVMChain = (typeof _SupportedEVMChainList)[number]; 23 | 24 | export type SupportedChain = SupportedEVMChain | 'solana' | 'hedera'; 25 | -------------------------------------------------------------------------------- /src/plugins/uniswap/parameters.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { createParameterSchema } from '../../core/utils/createParameterSchema'; 3 | 4 | export const LiquidityParametersSchema = createParameterSchema( 5 | z.object({ 6 | chain: z.string().min(1).describe('The chain to interact with'), 7 | asset: z.string().min(1).describe('The first asset in the pair'), 8 | assetB: z.string().min(1).describe('The second asset in the pair'), 9 | amount: z.number().positive().describe('The amount of the first asset'), 10 | amountB: z.number().positive().describe('The amount of the second asset'), 11 | }) 12 | ); 13 | 14 | // Export clean parameter type 15 | export type LiquidityParameters = typeof LiquidityParametersSchema.type; 16 | -------------------------------------------------------------------------------- /src/core/utils/createParameterSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | /** 4 | * Represents a parameter schema with both validation and type information 5 | */ 6 | export interface ParameterSchema { 7 | /** The Zod schema for validation */ 8 | schema: T; 9 | /** Type information for TypeScript (used for type inference) */ 10 | type: z.infer; 11 | } 12 | 13 | /** 14 | * Creates a parameter schema with typing information 15 | * @param schema - The Zod schema definition 16 | * @returns The schema with type information 17 | */ 18 | export function createParameterSchema(schema: T): ParameterSchema { 19 | // Return both the schema and its inferred type 20 | return { 21 | schema, 22 | type: {} as z.infer, 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /tests/logger.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'vitest'; 2 | import edwinLogger from '../src/utils/logger'; 3 | import dotenv from 'dotenv'; 4 | 5 | // Load environment variables 6 | dotenv.config(); 7 | 8 | describe('Logger Test', () => { 9 | describe('Logger', () => { 10 | it('should log info message', async () => { 11 | edwinLogger.info('Hello, world!'); 12 | }); 13 | 14 | it('should log debug message', async () => { 15 | edwinLogger.debug('Hello, world!'); 16 | }); 17 | 18 | it('should log error message', async () => { 19 | edwinLogger.error('Hello, world!'); 20 | }); 21 | 22 | it('should log warning message', async () => { 23 | edwinLogger.warn('Hello, world!'); 24 | }); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/plugins/cookie/parameters.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { createParameterSchema } from '../../core/utils/createParameterSchema'; 3 | 4 | export const AgentParametersSchema = createParameterSchema( 5 | z.object({ 6 | username: z.string().nullable().optional().describe('Twitter username to search for'), 7 | contractAddress: z.string().nullable().optional().describe('Contract address to search for'), 8 | interval: z.enum(['_3Days', '_7Days']).describe('Time interval for data'), 9 | page: z.number().nullable().optional().describe('Page number for pagination'), 10 | pageSize: z.number().nullable().optional().describe('Number of items per page'), 11 | }) 12 | ); 13 | 14 | // Export clean parameter types 15 | export type AgentParameters = typeof AgentParametersSchema.type; 16 | -------------------------------------------------------------------------------- /src/plugins/lulo/parameters.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { createParameterSchema } from '../../core/utils/createParameterSchema'; 3 | 4 | export const SupplyParametersSchema = createParameterSchema( 5 | z.object({ 6 | asset: z.string().min(1).describe('The asset to supply'), 7 | amount: z.number().positive().describe('The amount to supply'), 8 | }) 9 | ); 10 | 11 | export const WithdrawParametersSchema = createParameterSchema( 12 | z.object({ 13 | asset: z.string().min(1).describe('The asset to withdraw'), 14 | amount: z.number().positive().describe('The amount to withdraw'), 15 | }) 16 | ); 17 | 18 | // Export clean parameter types 19 | export type SupplyParameters = typeof SupplyParametersSchema.type; 20 | export type WithdrawParameters = typeof WithdrawParametersSchema.type; 21 | -------------------------------------------------------------------------------- /examples/cli-chat-agent/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "edwin-cli-chat-agent", 3 | "version": "0.1.0", 4 | "description": "CLI chat agent example using Edwin SDK", 5 | "main": "chat-agent.ts", 6 | "scripts": { 7 | "start": "ts-node chat-agent.ts", 8 | "build": "tsc", 9 | "dev": "ts-node-dev --respawn chat-agent.ts" 10 | }, 11 | "dependencies": { 12 | "@langchain/core": "^0.1.0", 13 | "@langchain/langgraph": "0.2.48", 14 | "@langchain/openai": "^0.0.14", 15 | "dotenv": "^16.4.1", 16 | "edwin-sdk": "file:../..", 17 | "ts-node": "^10.9.2" 18 | }, 19 | "devDependencies": { 20 | "@types/node": "^20.11.16", 21 | "ts-node-dev": "^2.0.0", 22 | "typescript": "^5.3.3" 23 | }, 24 | "engines": { 25 | "node": ">=18.0.0" 26 | }, 27 | "private": true 28 | } 29 | -------------------------------------------------------------------------------- /src/plugins/jupiter/parameters.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { createParameterSchema } from '../../core/utils/createParameterSchema'; 3 | 4 | export const SwapParametersSchema = createParameterSchema( 5 | z.object({ 6 | inputMint: z.string().min(1).describe('The input token mint address'), 7 | outputMint: z.string().min(1).describe('The output token mint address'), 8 | amount: z.union([z.string(), z.number()]).describe('The amount to swap'), 9 | }) 10 | ); 11 | 12 | export const GetTokenAddressSchema = createParameterSchema( 13 | z.object({ 14 | ticker: z.string().min(1).describe('The token ticker to lookup (case-sensitive, should be in UPPERCASE)'), 15 | }) 16 | ); 17 | 18 | // Export clean parameter types 19 | export type SwapParameters = typeof SwapParametersSchema.type; 20 | export type GetTokenAddressParameters = typeof GetTokenAddressSchema.type; 21 | -------------------------------------------------------------------------------- /src/core/classes/edwinPlugin.ts: -------------------------------------------------------------------------------- 1 | import { EdwinTool } from '../types'; 2 | import { EdwinService } from './edwinToolProvider'; 3 | 4 | export abstract class EdwinPlugin { 5 | private tools: EdwinTool[]; 6 | 7 | constructor( 8 | protected name: string, 9 | protected toolProviders: EdwinService[] 10 | ) { 11 | this.tools = []; 12 | } 13 | 14 | protected getToolsArray(): EdwinTool[] { 15 | return this.tools; 16 | } 17 | 18 | /** 19 | * Get all tools provided by this plugin 20 | */ 21 | abstract getTools(): Record; 22 | 23 | /** 24 | * Get tools that don't require signing capabilities 25 | */ 26 | abstract getPublicTools(): Record; 27 | 28 | /** 29 | * Get tools that require signing capabilities 30 | */ 31 | abstract getPrivateTools(): Record; 32 | } 33 | -------------------------------------------------------------------------------- /src/plugins/hyperliquid/hyperliquidPlugin.ts: -------------------------------------------------------------------------------- 1 | import { EdwinPlugin } from '../../core/classes/edwinPlugin'; 2 | import { EdwinTool } from '../../core/types'; 3 | 4 | /** 5 | * HyperLiquid plugin for Edwin 6 | */ 7 | export class HyperLiquidPlugin extends EdwinPlugin { 8 | constructor() { 9 | super('hyperliquid', []); 10 | } 11 | 12 | getTools(): Record { 13 | // Combine public and private tools 14 | return { 15 | ...this.getPublicTools(), 16 | ...this.getPrivateTools(), 17 | }; 18 | } 19 | 20 | getPublicTools(): Record { 21 | // HyperLiquid has no public tools 22 | return {}; 23 | } 24 | 25 | getPrivateTools(): Record { 26 | // HyperLiquid has no private tools 27 | return {}; 28 | } 29 | } 30 | 31 | export const hyperliquid = new HyperLiquidPlugin(); 32 | -------------------------------------------------------------------------------- /src/plugins/aave/parameters.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { createParameterSchema } from '../../core/utils/createParameterSchema'; 3 | 4 | export const SupplyParametersSchema = createParameterSchema( 5 | z.object({ 6 | chain: z.string().min(1).describe('The chain to supply assets on'), 7 | asset: z.string().min(1).describe('The asset to supply'), 8 | amount: z.number().positive().describe('The amount to supply'), 9 | }) 10 | ); 11 | 12 | export const WithdrawParametersSchema = createParameterSchema( 13 | z.object({ 14 | chain: z.string().min(1).describe('The chain to withdraw assets from'), 15 | asset: z.string().min(1).describe('The asset to withdraw'), 16 | amount: z.number().positive().describe('The amount to withdraw'), 17 | }) 18 | ); 19 | 20 | // Export clean parameter types 21 | export type SupplyParameters = typeof SupplyParametersSchema.type; 22 | export type WithdrawParameters = typeof WithdrawParametersSchema.type; 23 | -------------------------------------------------------------------------------- /src/plugins/mendi/parameters.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { createParameterSchema } from '../../core/utils/createParameterSchema'; 3 | 4 | export const SupplyParametersSchema = createParameterSchema( 5 | z.object({ 6 | chain: z.string().min(1).describe('The chain to supply assets on'), 7 | asset: z.string().min(1).describe('The asset to supply'), 8 | amount: z.number().positive().describe('The amount to supply'), 9 | }) 10 | ); 11 | 12 | export const WithdrawParametersSchema = createParameterSchema( 13 | z.object({ 14 | chain: z.string().min(1).describe('The chain to withdraw assets from'), 15 | asset: z.string().min(1).describe('The asset to withdraw'), 16 | amount: z.number().positive().describe('The amount to withdraw'), 17 | }) 18 | ); 19 | 20 | // Export clean parameter types 21 | export type SupplyParameters = typeof SupplyParametersSchema.type; 22 | export type WithdrawParameters = typeof WithdrawParametersSchema.type; 23 | -------------------------------------------------------------------------------- /examples/mcp-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "edwin-mcp-server", 3 | "version": "0.1.0", 4 | "description": "MCP server example for Edwin SDK", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "start": "node dist/index.js", 8 | "dev": "tsup --watch", 9 | "build": "tsup" 10 | }, 11 | "dependencies": { 12 | "@modelcontextprotocol/sdk": "^1.7.0", 13 | "cors": "^2.8.5", 14 | "dotenv": "^16.4.1", 15 | "edwin-sdk": "file:../..", 16 | "express": "^4.18.2", 17 | "express-rate-limit": "^7.1.5", 18 | "raw-body": "^2.5.2", 19 | "zod": "^3.22.4", 20 | "zod-to-json-schema": "^3.22.3" 21 | }, 22 | "devDependencies": { 23 | "@types/cors": "^2.8.17", 24 | "@types/express": "^4.17.21", 25 | "@types/node": "^20.11.16", 26 | "ts-node": "^10.9.2", 27 | "ts-node-dev": "^2.0.0", 28 | "tsup": "^8.0.1", 29 | "typescript": "^5.3.3", 30 | "vitest": "^1.2.2" 31 | }, 32 | "engines": { 33 | "node": ">=18.0.0" 34 | }, 35 | "private": true 36 | } 37 | -------------------------------------------------------------------------------- /src/plugins/lido/lidoProtocol.ts: -------------------------------------------------------------------------------- 1 | import type { SupportedChain } from '../../core/types'; 2 | import type { StakeParameters } from './parameters'; 3 | import { EdwinEVMWallet } from '../../core/wallets/evm_wallet/evm_wallet'; 4 | import { EdwinService } from '../../core/classes/edwinToolProvider'; 5 | 6 | export class LidoProtocol extends EdwinService { 7 | private wallet: EdwinEVMWallet; 8 | 9 | constructor(wallet: EdwinEVMWallet) { 10 | super(); 11 | this.wallet = wallet; 12 | } 13 | 14 | supportedChains: SupportedChain[] = ['mainnet']; 15 | 16 | async getPortfolio(): Promise { 17 | return ''; 18 | } 19 | 20 | async stake(params: StakeParameters): Promise { 21 | const { amount } = params; 22 | 23 | throw new Error(`Not implemented. Params: ${amount}`); 24 | } 25 | 26 | async unstake(params: StakeParameters): Promise { 27 | const { amount } = params; 28 | 29 | throw new Error(`Not implemented. Params: ${amount}`); 30 | } 31 | 32 | async claimRewards(params: StakeParameters): Promise { 33 | const { asset, amount } = params; 34 | throw new Error(`Not implemented. Params: ${asset}, ${amount}`); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/plugins/silo/parameters.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { createParameterSchema } from '../../core/utils/createParameterSchema'; 3 | 4 | /** 5 | * Parameters for supplying assets to Silo protocol 6 | */ 7 | export const SupplyParametersSchema = createParameterSchema( 8 | z.object({ 9 | chain: z.string().min(1).describe('The chain to supply assets on (e.g., sonic)'), 10 | asset: z.string().min(1).describe('The asset to supply (e.g., USDC.e)'), 11 | amount: z.number().positive().describe('The amount to supply'), 12 | collateralOnly: z.boolean().nullable().optional().describe('True if depositing collateral only'), 13 | }) 14 | ); 15 | 16 | /** 17 | * Parameters for withdrawing assets from Silo protocol 18 | */ 19 | export const WithdrawParametersSchema = createParameterSchema( 20 | z.object({ 21 | chain: z.string().min(1).describe('The chain to withdraw assets from (e.g., sonic)'), 22 | asset: z.string().min(1).describe('The asset to withdraw (e.g., USDC.e)'), 23 | amount: z.number().positive().describe('The amount to withdraw'), 24 | }) 25 | ); 26 | 27 | // Export clean parameter types 28 | export type SupplyParameters = typeof SupplyParametersSchema.type; 29 | export type WithdrawParameters = typeof WithdrawParametersSchema.type; 30 | -------------------------------------------------------------------------------- /src/core/wallets/hedera_wallet/clients/publickey/index.ts: -------------------------------------------------------------------------------- 1 | import { AccountId, Transaction, TransactionRecord } from '@hashgraph/sdk'; 2 | import { BaseHederaWalletClient } from '../../base_client'; 3 | 4 | /** 5 | * Read-only Hedera wallet client that can only perform operations that don't require signing 6 | */ 7 | export class PublicKeyClient extends BaseHederaWalletClient { 8 | constructor(accountId: string | AccountId) { 9 | super(accountId); 10 | } 11 | 12 | /** 13 | * Not supported in public key client - throws error 14 | */ 15 | async signTransaction(_transaction: Transaction): Promise { 16 | throw new Error('Cannot sign transactions with a read-only PublicKeyClient'); 17 | } 18 | 19 | /** 20 | * Not supported in public key client - throws error 21 | */ 22 | async sendTransaction(_transaction: Transaction): Promise { 23 | throw new Error('Cannot send transactions with a read-only PublicKeyClient'); 24 | } 25 | 26 | /** 27 | * Not supported in public key client - throws error 28 | */ 29 | async sendTransactionWithResponse( 30 | _transaction: Transaction 31 | ): Promise<{ transactionId: string; record: TransactionRecord }> { 32 | throw new Error('Cannot send transactions with a read-only PublicKeyClient'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to NPM 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | publish: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v3 14 | with: 15 | fetch-depth: 0 16 | 17 | - name: Setup Node.js 18 | uses: actions/setup-node@v3 19 | with: 20 | node-version: '18' 21 | registry-url: 'https://registry.npmjs.org' 22 | 23 | - name: Setup pnpm 24 | uses: pnpm/action-setup@v2 25 | with: 26 | version: 8 27 | 28 | - name: Install dependencies 29 | run: pnpm install --ignore-scripts 30 | 31 | - name: Bump version 32 | id: version-bump 33 | uses: phips28/gh-action-bump-version@master 34 | with: 35 | tag-prefix: 'v' 36 | default-bump-level: 'patch' 37 | minor-wording: 'minor' 38 | major-wording: 'BREAKING,major' 39 | patch-wording: 'fix,patch,docs,chore,refactor,test,style,feature,feat' 40 | env: 41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 42 | 43 | - name: Build package 44 | run: pnpm build 45 | 46 | - name: Publish to NPM 47 | run: pnpm publish --no-git-checks 48 | env: 49 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} -------------------------------------------------------------------------------- /src/core/wallets/hedera_wallet/factory.ts: -------------------------------------------------------------------------------- 1 | import { AccountId, PrivateKey } from '@hashgraph/sdk'; 2 | import { KeypairClient } from './clients/keypair'; 3 | import { PublicKeyClient } from './clients/publickey'; 4 | import { HederaWalletClient } from './client'; 5 | 6 | /** 7 | * Factory functions for creating Hedera wallet clients 8 | */ 9 | export const HederaWalletFactory = { 10 | /** 11 | * Create a KeypairClient from a private key 12 | * @param privateKey Private key string or PrivateKey object 13 | * @param accountId Optional account ID (will be derived if not provided) 14 | * @returns Keypair wallet client instance 15 | */ 16 | fromPrivateKey(privateKey: string | PrivateKey, accountId?: string | AccountId): KeypairClient { 17 | return new KeypairClient(privateKey, accountId); 18 | }, 19 | 20 | /** 21 | * Create a PublicKeyClient from an account ID 22 | * @param accountId Account ID string or AccountId object 23 | * @returns PublicKey wallet client for read-only operations 24 | */ 25 | fromAccountId(accountId: string | AccountId): PublicKeyClient { 26 | return new PublicKeyClient(accountId); 27 | }, 28 | }; 29 | 30 | // Type guard to check if a wallet client supports signing 31 | export function canSign(client: HederaWalletClient): boolean { 32 | // Simply check if the client is a PublicKeyClient 33 | return !(client instanceof PublicKeyClient); 34 | } 35 | -------------------------------------------------------------------------------- /src/plugins/evm_wallet/parameters.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { createParameterSchema } from '../../core/utils/createParameterSchema'; 3 | 4 | // Schema for checking wallet balance on EVM chains 5 | export const EVMBalanceParametersSchema = createParameterSchema( 6 | z.object({ 7 | walletAddress: z.string().describe('The EVM wallet address to check the balance of'), 8 | chainName: z.string().describe('The chain name (e.g., "mainnet", "base", "optimism", "arbitrum")'), 9 | tokenAddress: z 10 | .string() 11 | .nullable() 12 | .optional() 13 | .describe('The optional ERC-20 token address (or empty for native token balance)'), 14 | }) 15 | ); 16 | 17 | // Schema for checking current wallet balance 18 | export const CurrentEVMBalanceParametersSchema = createParameterSchema( 19 | z.object({ 20 | chainName: z 21 | .string() 22 | .nullable() 23 | .optional() 24 | .describe('The chain name (defaults to current chain if not specified)'), 25 | tokenAddress: z 26 | .string() 27 | .nullable() 28 | .optional() 29 | .describe('The optional ERC-20 token address (or empty for native token balance)'), 30 | }) 31 | ); 32 | 33 | // Export clean parameter types 34 | export type EVMBalanceParameters = typeof EVMBalanceParametersSchema.type; 35 | export type CurrentEVMBalanceParameters = typeof CurrentEVMBalanceParametersSchema.type; 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # edwin 🦉 - The DeFAI Layer 2 | 3 | ![Edwin Overview](docs/static/img/edwin_diagram.png) 4 | 5 | Edwin is a TypeScript library that serves as the bridge between AI agents and DeFi protocols. It provides a unified, secure interface for AI agents to interact with various DeFi protocols while abstracting away the complexity of blockchain operations and protocol-specific implementations. This enables the creation of sophisticated DeFAI agents. 6 | 7 | ## Installation 8 | 9 | ```bash 10 | pnpm install edwin-sdk 11 | ``` 12 | 13 | ## Features 14 | 15 | - Lending/Borrowing operations 16 | - Liquidity provision 17 | - Cross-chain support 18 | - Type-safe protocol interactions 19 | - AI-friendly templates 20 | 21 | ## Quick Start 22 | 23 | ```typescript 24 | // Import the required wallet and services 25 | import { EdwinEVMWallet, AaveService } from 'edwin-sdk'; 26 | 27 | // Configure Edwin wallets and services 28 | const wallet = new EdwinEVMWallet(evmPrivateKey as `0x${string}`); 29 | const aave = new AaveService(wallet); 30 | 31 | // Supply tokens to a lending protocol 32 | const result = await aave.supply({ 33 | chain: 'base', 34 | amount: 0.05, 35 | asset: 'usdc', 36 | }); 37 | ``` 38 | 39 | ## Documentation 40 | 41 | For detailed documentation, visit [docs.edwin.finance](https://docs.edwin.finance) 42 | 43 | For developers interested in creating plugins, check out our [Plugin Integration Guide](docs/integration-guide.md). 44 | 45 | ## Contributing 46 | 47 | Contributions are welcome! Please visit our [Contributing Guide](https://docs.edwin.finance) for details. 48 | 49 | ## License 50 | 51 | GPL 52 | -------------------------------------------------------------------------------- /src/plugins/eoracle/eoraclePlugin.ts: -------------------------------------------------------------------------------- 1 | import { EdwinPlugin } from '../../core/classes/edwinPlugin'; 2 | import { EdwinTool, Chain } from '../../core/types'; 3 | import { EOracleService } from './eoracleService'; 4 | import { PriceParametersSchema, PriceParameters } from './parameters'; 5 | 6 | export class EOraclePlugin extends EdwinPlugin { 7 | constructor(apiKey: string) { 8 | super('eoracle', [new EOracleService(apiKey)]); 9 | } 10 | 11 | getTools(): Record { 12 | // Combine public and private tools 13 | return { 14 | ...this.getPublicTools(), 15 | ...this.getPrivateTools(), 16 | }; 17 | } 18 | 19 | getPublicTools(): Record { 20 | const eoracleService = this.toolProviders.find( 21 | provider => provider instanceof EOracleService 22 | ) as EOracleService; 23 | 24 | return { 25 | eoracleGetPrice: { 26 | name: 'eoracle_get_price', 27 | description: 'Get price information for a given symbol', 28 | schema: PriceParametersSchema.schema, 29 | execute: async (params: PriceParameters) => { 30 | return await eoracleService.getPrice(params.symbol); 31 | }, 32 | }, 33 | }; 34 | } 35 | 36 | getPrivateTools(): Record { 37 | // EOraclePlugin has no private tools 38 | return {}; 39 | } 40 | 41 | supportsChain = (chain: Chain) => chain.type === 'evm'; 42 | } 43 | 44 | export const eoracle = (apiKey: string) => new EOraclePlugin(apiKey); 45 | -------------------------------------------------------------------------------- /src/core/wallets/solana_wallet/factory.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from '@solana/web3.js'; 2 | import { KeypairClient } from './clients/keypair'; 3 | import { PhantomClient, type PhantomProvider } from './clients/phantom'; 4 | import { PublicKeyClient } from './clients/publickey'; 5 | import { SolanaWalletClient } from './client'; 6 | 7 | /** 8 | * Factory functions for creating Solana wallet clients 9 | */ 10 | export const SolanaWalletFactory = { 11 | /** 12 | * Create a KeypairClient from a private key 13 | * @param privateKey Base58-encoded private key string 14 | * @returns Keypair wallet client instance 15 | */ 16 | fromPrivateKey(privateKey: string): KeypairClient { 17 | return new KeypairClient(privateKey); 18 | }, 19 | 20 | /** 21 | * Create a PublicKeyClient from a public key 22 | * @param publicKey PublicKey object or base58-encoded string 23 | * @returns PublicKey wallet client for read-only operations 24 | */ 25 | fromPublicKey(publicKey: string | PublicKey): PublicKeyClient { 26 | return new PublicKeyClient(publicKey); 27 | }, 28 | 29 | /** 30 | * Create a PhantomClient from a Phantom provider 31 | * @param provider Phantom wallet provider instance 32 | * @returns Phantom wallet client instance 33 | */ 34 | fromPhantom(provider: PhantomProvider): PhantomClient { 35 | return new PhantomClient(provider); 36 | }, 37 | }; 38 | 39 | // Type guard to check if a wallet client supports signing 40 | export function canSign(client: SolanaWalletClient): boolean { 41 | // Simply check if the client is a PublicKeyClient 42 | return !(client instanceof PublicKeyClient); 43 | } 44 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // Export wallet types 2 | export { EdwinWallet } from './core/wallets/wallet'; 3 | export { EdwinEVMWallet } from './core/wallets/evm_wallet'; 4 | export { 5 | KeypairClient, 6 | PublicKeyClient, 7 | BaseSolanaWalletClient, 8 | SolanaWalletFactory, 9 | } from './core/wallets/solana_wallet'; 10 | export type { SolanaWalletClient } from './core/wallets/solana_wallet'; 11 | 12 | // Export Hedera wallet types 13 | export { BaseHederaWalletClient, HederaWalletFactory, canSign as hederaCanSign } from './core/wallets/hedera_wallet'; 14 | export type { HederaWalletClient } from './core/wallets/hedera_wallet'; 15 | export { 16 | KeypairClient as HederaKeypairClient, 17 | PublicKeyClient as HederaPublicKeyClient, 18 | } from './core/wallets/hedera_wallet/clients'; 19 | 20 | // Export core types 21 | export type * from './core/types'; 22 | 23 | // Export plugins 24 | export * from './plugins/aave'; 25 | export * from './plugins/lido'; 26 | export * from './plugins/lulo'; 27 | export * from './plugins/meteora'; 28 | export * from './plugins/uniswap'; 29 | export * from './plugins/jupiter'; 30 | export * from './plugins/cookie'; 31 | export * from './plugins/eoracle'; 32 | export * from './plugins/storyprotocol'; 33 | export * from './plugins/hyperliquid'; 34 | export * from './plugins/hedera_wallet'; 35 | export * from './plugins/solana_wallet'; 36 | export * from './plugins/evm_wallet'; 37 | export * from './plugins/stader'; 38 | export * from './plugins/bonzo'; 39 | export * from './plugins/saucerswap'; 40 | 41 | // Export client 42 | export { Edwin } from './client/edwin'; 43 | export type { EdwinConfig } from './client'; 44 | 45 | // Export adapters 46 | export * from './adapters'; 47 | -------------------------------------------------------------------------------- /src/plugins/uniswap/uniswapProtocol.ts: -------------------------------------------------------------------------------- 1 | import type { SupportedChain } from '../../core/types'; 2 | import { EdwinEVMWallet } from '../../core/wallets/evm_wallet/evm_wallet'; 3 | import { LiquidityParameters } from './parameters'; 4 | import { EdwinService } from '../../core/classes/edwinToolProvider'; 5 | 6 | export class UniswapProtocol extends EdwinService { 7 | supportedChains: SupportedChain[] = ['mainnet']; 8 | private wallet: EdwinEVMWallet; 9 | 10 | constructor(wallet: EdwinEVMWallet) { 11 | super(); 12 | this.wallet = wallet; 13 | } 14 | 15 | async getPortfolio(): Promise { 16 | return ''; 17 | } 18 | 19 | async swap(params: LiquidityParameters): Promise { 20 | const { chain, asset, amount, assetB, amountB } = params; 21 | 22 | throw new Error(`Not implemented. Params: ${chain} ${asset} ${amount} ${assetB} ${amountB}`); 23 | } 24 | 25 | async addLiquidity(params: LiquidityParameters): Promise<{ liquidityAdded: [number, number] }> { 26 | const { chain, asset, amount, assetB, amountB } = params; 27 | 28 | throw new Error(`Not implemented. Params: ${chain} ${asset} ${amount} ${assetB} ${amountB}`); 29 | } 30 | 31 | async removeLiquidity( 32 | params: LiquidityParameters 33 | ): Promise<{ liquidityRemoved: [number, number]; feesClaimed: [number, number] }> { 34 | const { chain, asset, amount, assetB, amountB } = params; 35 | 36 | throw new Error(`Not implemented. Params: ${chain} ${asset} ${amount} ${assetB} ${amountB}`); 37 | } 38 | 39 | async getQuote(params: LiquidityParameters): Promise { 40 | throw new Error(`Not implemented. Params: ${JSON.stringify(params)}`); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/plugins/cookie/cookiePlugin.ts: -------------------------------------------------------------------------------- 1 | import { EdwinPlugin } from '../../core/classes/edwinPlugin'; 2 | import { EdwinTool, Chain } from '../../core/types'; 3 | import { CookieSwarmClient } from './cookieClient'; 4 | import { AgentParametersSchema, AgentParameters } from './parameters'; 5 | 6 | export class CookiePlugin extends EdwinPlugin { 7 | constructor(apiKey: string) { 8 | super('cookie', [new CookieSwarmClient(apiKey)]); 9 | } 10 | 11 | getTools(): Record { 12 | // Combine public and private tools 13 | return { 14 | ...this.getPublicTools(), 15 | ...this.getPrivateTools(), 16 | }; 17 | } 18 | 19 | getPublicTools(): Record { 20 | const cookieClient = this.toolProviders.find( 21 | provider => provider instanceof CookieSwarmClient 22 | ) as CookieSwarmClient; 23 | 24 | return { 25 | cookieGetAgent: { 26 | name: 'cookie_get_agent', 27 | description: 'Get agent information by Twitter username or contract address', 28 | schema: AgentParametersSchema.schema, 29 | execute: async (params: AgentParameters) => { 30 | if (params.username) { 31 | return await cookieClient.getAgentByTwitter(params); 32 | } 33 | return await cookieClient.getAgentByContract(params); 34 | }, 35 | }, 36 | }; 37 | } 38 | 39 | getPrivateTools(): Record { 40 | // Cookie has no private tools 41 | return {}; 42 | } 43 | 44 | supportsChain = (_: Chain) => true; 45 | } 46 | 47 | export const cookie = (apiKey: string) => new CookiePlugin(apiKey); 48 | -------------------------------------------------------------------------------- /src/core/wallets/evm_wallet/evm_wallet.ts: -------------------------------------------------------------------------------- 1 | import { createWalletClient } from 'viem'; 2 | import { privateKeyToAccount } from 'viem/accounts'; 3 | import type { WalletClient, PrivateKeyAccount } from 'viem'; 4 | import type { SupportedEVMChain } from '../../types'; 5 | import { EdwinEVMPublicKeyWallet } from './evm_public_key_wallet'; 6 | import { ethers, providers } from 'ethers'; 7 | 8 | /** 9 | * EVM wallet that has full capabilities including transaction signing 10 | */ 11 | export class EdwinEVMWallet extends EdwinEVMPublicKeyWallet { 12 | private account: PrivateKeyAccount; 13 | private evmPrivateKey: `0x${string}`; 14 | 15 | constructor(privateKey: `0x${string}`) { 16 | const account = privateKeyToAccount(privateKey); 17 | super(account.address); 18 | this.account = account; 19 | this.evmPrivateKey = privateKey; 20 | } 21 | 22 | /** 23 | * Get the wallet client for a specific chain 24 | */ 25 | getWalletClient(chainName: SupportedEVMChain): WalletClient { 26 | const transport = this.createHttpTransport(chainName); 27 | 28 | const walletClient = createWalletClient({ 29 | chain: this.chains[chainName], 30 | transport, 31 | account: this.account, 32 | }); 33 | 34 | return walletClient; 35 | } 36 | 37 | /** 38 | * Get an ethers.js wallet instance 39 | */ 40 | getEthersWallet(walletClient: WalletClient, provider: providers.JsonRpcProvider): ethers.Wallet { 41 | const ethers_wallet = new ethers.Wallet(this.evmPrivateKey, provider); 42 | return ethers_wallet; 43 | } 44 | 45 | /** 46 | * Get the account (private key) 47 | */ 48 | getSigner(): PrivateKeyAccount { 49 | return this.account; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/plugins/dexscreener/parameters.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { createParameterSchema } from '../../core/utils/createParameterSchema'; 3 | 4 | export const SearchParametersSchema = createParameterSchema( 5 | z.object({ 6 | query: z.string().min(1).describe('Search query for pairs matching the query'), 7 | }) 8 | ); 9 | 10 | export const PairParametersSchema = createParameterSchema( 11 | z.object({ 12 | chainId: z.string().min(1).describe('Chain ID (e.g., "ethereum", "solana")'), 13 | pairId: z.string().min(1).describe('Pair address'), 14 | }) 15 | ); 16 | 17 | export const TokenPairsParametersSchema = createParameterSchema( 18 | z.object({ 19 | chainId: z.string().min(1).describe('Chain ID (e.g., "ethereum", "solana")'), 20 | tokenAddress: z.string().min(1).describe('Token address'), 21 | }) 22 | ); 23 | 24 | export const TokensParametersSchema = createParameterSchema( 25 | z.object({ 26 | chainId: z.string().min(1).describe('Chain ID (e.g., "ethereum", "solana")'), 27 | tokenAddresses: z.string().min(1).describe('Comma-separated token addresses (up to 30)'), 28 | }) 29 | ); 30 | 31 | // Schema for token orders 32 | export const TokenOrdersParametersSchema = createParameterSchema( 33 | z.object({ 34 | chainId: z.string().min(1).describe('Chain ID (e.g., "ethereum", "solana")'), 35 | tokenAddress: z.string().min(1).describe('Token address'), 36 | }) 37 | ); 38 | 39 | // Export clean parameter types 40 | export type SearchParameters = typeof SearchParametersSchema.type; 41 | export type PairParameters = typeof PairParametersSchema.type; 42 | export type TokenPairsParameters = typeof TokenPairsParametersSchema.type; 43 | export type TokensParameters = typeof TokensParametersSchema.type; 44 | export type TokenOrdersParameters = typeof TokenOrdersParametersSchema.type; 45 | -------------------------------------------------------------------------------- /src/plugins/solana_wallet/parameters.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { createParameterSchema } from '../../core/utils/createParameterSchema'; 3 | 4 | // Schema for checking wallet balance on Solana 5 | export const SolanaWalletTokenBalanceParametersSchema = createParameterSchema( 6 | z.object({ 7 | walletAddress: z.string().describe('The Solana wallet address to check the balance of'), 8 | mintAddress: z 9 | .string() 10 | .nullable() 11 | .optional() 12 | .describe('The optional SPL token mint address (or empty for SOL balance)'), 13 | }) 14 | ); 15 | 16 | export const CurrentSolanaWalletTokenBalanceParametersSchema = createParameterSchema( 17 | z.object({ 18 | mintAddress: z 19 | .string() 20 | .nullable() 21 | .optional() 22 | .describe('The optional SPL token mint address (or empty for SOL balance)'), 23 | }) 24 | ); 25 | 26 | // Schema for getting all token balances for a wallet 27 | export const SolanaWalletBalancesParametersSchema = createParameterSchema( 28 | z.object({ 29 | walletAddress: z.string().describe('The Solana wallet address to check all token balances for'), 30 | }) 31 | ); 32 | 33 | // Schema for getting all token balances for the current wallet 34 | export const CurrentSolanaWalletBalancesParametersSchema = createParameterSchema( 35 | z.object({}).describe('No parameters needed') 36 | ); 37 | 38 | // Export clean parameter types 39 | export type SolanaWalletTokenBalanceParameters = typeof SolanaWalletTokenBalanceParametersSchema.type; 40 | export type CurrentSolanaWalletTokenBalanceParameters = typeof CurrentSolanaWalletTokenBalanceParametersSchema.type; 41 | export type SolanaWalletBalancesParameters = typeof SolanaWalletBalancesParametersSchema.type; 42 | export type CurrentSolanaWalletBalancesParameters = typeof CurrentSolanaWalletBalancesParametersSchema.type; 43 | -------------------------------------------------------------------------------- /src/plugins/aave/aavePlugin.ts: -------------------------------------------------------------------------------- 1 | import { EdwinPlugin } from '../../core/classes/edwinPlugin'; 2 | import { EdwinTool, Chain } from '../../core/types'; 3 | import { AaveService } from './aaveService'; 4 | import { EdwinEVMWallet } from '../../core/wallets'; 5 | import { SupplyParametersSchema, WithdrawParametersSchema, SupplyParameters, WithdrawParameters } from './parameters'; 6 | 7 | export class AavePlugin extends EdwinPlugin { 8 | constructor(wallet: EdwinEVMWallet) { 9 | super('aave', [new AaveService(wallet)]); 10 | } 11 | 12 | getTools(): Record { 13 | // Combine public and private tools 14 | return { 15 | ...this.getPublicTools(), 16 | ...this.getPrivateTools(), 17 | }; 18 | } 19 | 20 | getPublicTools(): Record { 21 | // Aave has no public tools 22 | return {}; 23 | } 24 | 25 | getPrivateTools(): Record { 26 | const aaveService = this.toolProviders.find(provider => provider instanceof AaveService) as AaveService; 27 | 28 | return { 29 | aaveSupply: { 30 | name: 'aave_supply', 31 | description: 'Supply assets to Aave protocol', 32 | schema: SupplyParametersSchema.schema, 33 | execute: async (params: SupplyParameters) => { 34 | return await aaveService.supply(params); 35 | }, 36 | }, 37 | aaveWithdraw: { 38 | name: 'aave_withdraw', 39 | description: 'Withdraw assets from Aave protocol', 40 | schema: WithdrawParametersSchema.schema, 41 | execute: async (params: WithdrawParameters) => { 42 | return await aaveService.withdraw(params); 43 | }, 44 | }, 45 | }; 46 | } 47 | 48 | supportsChain = (chain: Chain) => chain.type === 'evm'; 49 | } 50 | 51 | export const aave = (wallet: EdwinEVMWallet) => new AavePlugin(wallet); 52 | -------------------------------------------------------------------------------- /examples/mcp-server/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Edwin } from '../../../src/client/edwin'; 2 | import { getMcpToolsFromEdwin } from '../../../src/adapters/mcp'; 3 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; 4 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; 5 | import dotenv from 'dotenv'; 6 | import edwinLogger from '../../../src/utils/logger'; 7 | 8 | // Load environment variables 9 | dotenv.config(); 10 | 11 | // Set MCP mode before importing the logger 12 | process.env.EDWIN_MCP_MODE = 'true'; 13 | 14 | async function main() { 15 | try { 16 | // Initialize Edwin with wallet configurations 17 | const edwin = new Edwin({ 18 | evmPrivateKey: process.env.EVM_PRIVATE_KEY as `0x${string}`, 19 | solanaPrivateKey: process.env.SOLANA_PRIVATE_KEY, 20 | }); 21 | 22 | // Get MCP tools from Edwin 23 | const mcpTools = await getMcpToolsFromEdwin({ edwin }); 24 | 25 | // Create MCP server instance 26 | const server = new McpServer({ 27 | name: process.env.MCP_SERVER_NAME || 'edwin', 28 | version: process.env.MCP_SERVER_VERSION || '0.1.0', 29 | }); 30 | 31 | // Register tools with the server 32 | for (const tool of mcpTools) { 33 | server.tool(tool.name, tool.description, tool.parameters, tool.execute); 34 | } 35 | 36 | // Create and connect to stdio transport 37 | const transport = new StdioServerTransport(); 38 | await server.connect(transport); 39 | 40 | // Handle graceful shutdown 41 | process.on('SIGINT', async () => { 42 | edwinLogger.info('Shutting down MCP server...'); 43 | await server.close(); 44 | process.exit(0); 45 | }); 46 | 47 | edwinLogger.info('MCP server started successfully'); 48 | } catch (error) { 49 | edwinLogger.error('Failed to start MCP server:', error); 50 | process.exit(1); 51 | } 52 | } 53 | 54 | // Run the server 55 | main(); 56 | -------------------------------------------------------------------------------- /src/plugins/lulo/luloPlugin.ts: -------------------------------------------------------------------------------- 1 | import { EdwinPlugin } from '../../core/classes/edwinPlugin'; 2 | import { EdwinTool, Chain } from '../../core/types'; 3 | import { LuloProtocol } from './luloProtocol'; 4 | import { SolanaWalletClient } from '../../core/wallets/solana_wallet'; 5 | import { SupplyParametersSchema, WithdrawParametersSchema, SupplyParameters, WithdrawParameters } from './parameters'; 6 | 7 | export class LuloPlugin extends EdwinPlugin { 8 | constructor(wallet: SolanaWalletClient) { 9 | super('lulo', [new LuloProtocol(wallet)]); 10 | } 11 | 12 | getTools(): Record { 13 | // Combine public and private tools 14 | return { 15 | ...this.getPublicTools(), 16 | ...this.getPrivateTools(), 17 | }; 18 | } 19 | 20 | getPublicTools(): Record { 21 | // Lulo has no public tools 22 | return {}; 23 | } 24 | 25 | getPrivateTools(): Record { 26 | const luloProtocol = this.toolProviders.find(provider => provider instanceof LuloProtocol) as LuloProtocol; 27 | 28 | return { 29 | luloSupply: { 30 | name: 'lulo_supply', 31 | description: 'Supply assets to Lulo protocol', 32 | schema: SupplyParametersSchema.schema, 33 | execute: async (params: SupplyParameters) => { 34 | return await luloProtocol.supply(params); 35 | }, 36 | }, 37 | luloWithdraw: { 38 | name: 'lulo_withdraw', 39 | description: 'Withdraw assets from Lulo protocol', 40 | schema: WithdrawParametersSchema.schema, 41 | execute: async (params: WithdrawParameters) => { 42 | return await luloProtocol.withdraw(params); 43 | }, 44 | }, 45 | }; 46 | } 47 | 48 | supportsChain = (chain: Chain) => chain.type === 'solana'; 49 | } 50 | 51 | export const lulo = (wallet: SolanaWalletClient) => new LuloPlugin(wallet); 52 | -------------------------------------------------------------------------------- /src/plugins/mendi/mendiPlugin.ts: -------------------------------------------------------------------------------- 1 | import { EdwinPlugin } from '../../core/classes/edwinPlugin'; 2 | import { EdwinTool, Chain } from '../../core/types'; 3 | import { MendiService } from './mendiService'; 4 | import { EdwinEVMWallet } from '../../core/wallets'; 5 | import { SupplyParametersSchema, WithdrawParametersSchema, SupplyParameters, WithdrawParameters } from './parameters'; 6 | 7 | export class MendiPlugin extends EdwinPlugin { 8 | constructor(wallet: EdwinEVMWallet) { 9 | super('mendi', [new MendiService(wallet)]); 10 | } 11 | 12 | getTools(): Record { 13 | // Combine public and private tools 14 | return { 15 | ...this.getPublicTools(), 16 | ...this.getPrivateTools(), 17 | }; 18 | } 19 | 20 | getPublicTools(): Record { 21 | // Mendi has no public tools 22 | return {}; 23 | } 24 | 25 | getPrivateTools(): Record { 26 | const mendiService = this.toolProviders.find(provider => provider instanceof MendiService) as MendiService; 27 | 28 | return { 29 | mendiSupply: { 30 | name: 'mendi_supply', 31 | description: 'Supply assets to Mendi protocol on Linea', 32 | schema: SupplyParametersSchema.schema, 33 | execute: async (params: SupplyParameters) => { 34 | return await mendiService.supply(params); 35 | }, 36 | }, 37 | mendiWithdraw: { 38 | name: 'mendi_withdraw', 39 | description: 'Withdraw assets from Mendi protocol on Linea', 40 | schema: WithdrawParametersSchema.schema, 41 | execute: async (params: WithdrawParameters) => { 42 | return await mendiService.withdraw(params); 43 | }, 44 | }, 45 | }; 46 | } 47 | 48 | supportsChain = (chain: Chain) => chain.type === 'evm'; 49 | } 50 | 51 | export const mendi = (wallet: EdwinEVMWallet) => new MendiPlugin(wallet); 52 | -------------------------------------------------------------------------------- /src/adapters/langchain/index.ts: -------------------------------------------------------------------------------- 1 | import { tool } from '@langchain/core/tools'; 2 | import type { Edwin } from '../../client/index'; 3 | import type { EdwinTool } from '../../core/types'; 4 | import type { EdwinPlugin } from '../../core/classes'; 5 | 6 | export type GetEdwinToolsParams = { 7 | edwin: Edwin; 8 | }; 9 | 10 | function createToolFromEdwinTool(edwinTool: EdwinTool) { 11 | // Using `any` here to avoid TS2589: "Type instantiation is excessively deep and possibly infinite". 12 | // This happens when passing Zod schemas into tool(), due to deep type inference. 13 | // See: https://github.com/colinhacks/zod/issues/577 14 | 15 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 16 | const toolConfig: any = { 17 | name: edwinTool.name.toLowerCase(), 18 | description: edwinTool.description, 19 | schema: edwinTool.schema, 20 | }; 21 | 22 | return tool(async (args: unknown) => { 23 | try { 24 | const result = await edwinTool.execute(args); 25 | return JSON.stringify(result); 26 | } catch (error) { 27 | if (error instanceof Error) { 28 | throw new Error(`${edwinTool.name} failed: ${error.message}`); 29 | } 30 | throw error; 31 | } 32 | }, toolConfig); 33 | } 34 | 35 | /** 36 | * Creates LangChain tools from a list of Edwin plugins 37 | */ 38 | export function getLangchainToolsFromPlugins(plugins: EdwinPlugin[]) { 39 | const tools = []; 40 | for (const plugin of plugins) { 41 | const pluginTools = plugin.getTools(); 42 | for (const tool of Object.values(pluginTools)) { 43 | tools.push(createToolFromEdwinTool(tool)); 44 | } 45 | } 46 | return tools; 47 | } 48 | 49 | /** 50 | * Converts Edwin actions to Langchain tools 51 | */ 52 | export async function getLangchainToolsFromEdwin({ edwin }: GetEdwinToolsParams) { 53 | const toolsRecord = await edwin.getTools(); 54 | return Object.values(toolsRecord).map((tool: EdwinTool) => createToolFromEdwinTool(tool)); 55 | } 56 | -------------------------------------------------------------------------------- /src/plugins/silo/siloPlugin.ts: -------------------------------------------------------------------------------- 1 | import { EdwinPlugin } from '../../core/classes/edwinPlugin'; 2 | import { EdwinTool, Chain } from '../../core/types'; 3 | import { SiloService } from './siloService'; 4 | import { EdwinEVMWallet } from '../../core/wallets'; 5 | import { SupplyParametersSchema, WithdrawParametersSchema, SupplyParameters, WithdrawParameters } from './parameters'; 6 | 7 | /** 8 | * Plugin for interacting with Silo Finance protocol 9 | */ 10 | export class SiloPlugin extends EdwinPlugin { 11 | constructor(wallet: EdwinEVMWallet) { 12 | super('silo', [new SiloService(wallet)]); 13 | } 14 | 15 | getTools(): Record { 16 | // Combine public and private tools 17 | return { 18 | ...this.getPublicTools(), 19 | ...this.getPrivateTools(), 20 | }; 21 | } 22 | 23 | getPublicTools(): Record { 24 | // Silo has no public tools 25 | return {}; 26 | } 27 | 28 | getPrivateTools(): Record { 29 | const siloService = this.toolProviders.find(provider => provider instanceof SiloService) as SiloService; 30 | 31 | return { 32 | siloSupply: { 33 | name: 'silo_supply', 34 | description: 'Supply assets to Silo protocol', 35 | schema: SupplyParametersSchema.schema, 36 | execute: async (params: SupplyParameters) => { 37 | return await siloService.supply(params); 38 | }, 39 | }, 40 | siloWithdraw: { 41 | name: 'silo_withdraw', 42 | description: 'Withdraw assets from Silo protocol', 43 | schema: WithdrawParametersSchema.schema, 44 | execute: async (params: WithdrawParameters) => { 45 | return await siloService.withdraw(params); 46 | }, 47 | }, 48 | }; 49 | } 50 | 51 | supportsChain = (chain: Chain) => chain.type === 'evm'; 52 | } 53 | 54 | export const silo = (wallet: EdwinEVMWallet) => new SiloPlugin(wallet); 55 | -------------------------------------------------------------------------------- /src/plugins/evm_wallet/evmWalletService.ts: -------------------------------------------------------------------------------- 1 | import { EdwinService } from '../../core/classes/edwinToolProvider'; 2 | import { EdwinEVMPublicKeyWallet } from '../../core/wallets'; 3 | import edwinLogger from '../../utils/logger'; 4 | import { EVMBalanceParameters, CurrentEVMBalanceParameters } from './parameters'; 5 | import { SupportedEVMChain } from '../../core/types'; 6 | 7 | export class EVMWalletService extends EdwinService { 8 | constructor(private wallet: EdwinEVMPublicKeyWallet) { 9 | super(); 10 | } 11 | 12 | /** 13 | * Get the balance of any EVM wallet 14 | */ 15 | async getWalletBalance(params: EVMBalanceParameters): Promise { 16 | edwinLogger.info(`Getting balance for EVM wallet: ${params.walletAddress} on ${params.chainName}`); 17 | 18 | try { 19 | return await this.wallet.getBalanceOfWallet( 20 | params.walletAddress as `0x${string}`, 21 | params.chainName as SupportedEVMChain, 22 | params.tokenAddress as `0x${string}` | undefined 23 | ); 24 | } catch (error) { 25 | edwinLogger.error('Failed to get EVM wallet balance:', error); 26 | throw error; 27 | } 28 | } 29 | 30 | /** 31 | * Get the balance of the current EVM wallet 32 | */ 33 | async getCurrentWalletBalance(params: CurrentEVMBalanceParameters): Promise { 34 | const chainName = params.chainName || (this.wallet.getCurrentChain().name as SupportedEVMChain); 35 | edwinLogger.info(`Getting balance for current EVM wallet on ${chainName}`); 36 | 37 | try { 38 | // Use getBalanceOfWallet with current wallet address 39 | return await this.wallet.getBalanceOfWallet( 40 | this.wallet.getAddress(), 41 | chainName as SupportedEVMChain, 42 | params.tokenAddress as `0x${string}` | undefined 43 | ); 44 | } catch (error) { 45 | edwinLogger.error('Failed to get current EVM wallet balance:', error); 46 | throw error; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/core/wallets/solana_wallet/clients/publickey/index.ts: -------------------------------------------------------------------------------- 1 | import { Connection, Keypair, PublicKey, Transaction, VersionedTransaction } from '@solana/web3.js'; 2 | import { BaseSolanaWalletClient } from '../../base_client'; 3 | 4 | /** 5 | * Read-only Solana wallet client that can only perform operations that don't require signing 6 | */ 7 | export class PublicKeyClient extends BaseSolanaWalletClient { 8 | constructor(publicKey: string | PublicKey) { 9 | super(publicKey); 10 | } 11 | 12 | /** 13 | * Not supported in public key client - throws error 14 | */ 15 | async signTransaction(_transaction: T): Promise { 16 | throw new Error('Cannot sign transactions with a read-only PublicKeyClient'); 17 | } 18 | 19 | /** 20 | * Not supported in public key client - throws error 21 | */ 22 | async signAllTransactions(_transactions: T[]): Promise { 23 | throw new Error('Cannot sign transactions with a read-only PublicKeyClient'); 24 | } 25 | 26 | /** 27 | * Not supported in public key client - throws error 28 | */ 29 | async signMessage(_message: Uint8Array): Promise { 30 | throw new Error('Cannot sign messages with a read-only PublicKeyClient'); 31 | } 32 | 33 | /** 34 | * Not supported in public key client - throws error 35 | */ 36 | async sendTransaction( 37 | _connection: Connection, 38 | _transaction: T, 39 | _signers?: Keypair[] 40 | ): Promise { 41 | throw new Error('Cannot send transactions with a read-only PublicKeyClient'); 42 | } 43 | 44 | /** 45 | * Not supported in public key client - throws error 46 | */ 47 | async waitForConfirmationGracefully( 48 | _connection: Connection, 49 | _signature: string, 50 | _timeout?: number 51 | ): Promise<{ err: unknown; confirmationStatus?: 'confirmed' | 'finalized' | 'processed' }> { 52 | throw new Error('Cannot wait for confirmation with a read-only PublicKeyClient'); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/plugins/meteora/parameters.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { createParameterSchema } from '../../core/utils/createParameterSchema'; 3 | 4 | export const AddLiquidityParametersSchema = createParameterSchema( 5 | z.object({ 6 | amount: z 7 | .string() 8 | .describe( 9 | 'The amount of token A to add as liquidity, or the string "auto" to auto-calculate the amount of token A based on amountB' 10 | ), 11 | amountB: z 12 | .string() 13 | .describe( 14 | 'The amount of token B to add as liquidity, or the string "auto" to auto-calculate the amount of token B based on amount' 15 | ), 16 | poolAddress: z.string().min(1).describe('The address of the Meteora pool'), 17 | rangeInterval: z.number().nullable().optional().describe('The price range interval for concentrated liquidity'), 18 | }) 19 | ); 20 | 21 | export const RemoveLiquidityParametersSchema = createParameterSchema( 22 | z.object({ 23 | poolAddress: z.string().min(1).describe('The address of the Meteora pool'), 24 | positionAddress: z.string().nullable().optional().describe('The address of the liquidity position to remove'), 25 | shouldClosePosition: z.boolean().nullable().optional().describe('Whether to completely close the position'), 26 | }) 27 | ); 28 | 29 | export const PoolParametersSchema = createParameterSchema( 30 | z.object({ 31 | poolAddress: z.string().min(1).describe('The address of the Meteora pool'), 32 | }) 33 | ); 34 | 35 | export const GetPoolsParametersSchema = createParameterSchema( 36 | z.object({ 37 | asset: z.string().min(1).describe('The first asset in the pool'), 38 | assetB: z.string().min(1).describe('The second asset in the pool'), 39 | }) 40 | ); 41 | 42 | // Export clean parameter types without the "Type" suffix 43 | export type AddLiquidityParameters = typeof AddLiquidityParametersSchema.type; 44 | export type RemoveLiquidityParameters = typeof RemoveLiquidityParametersSchema.type; 45 | export type PoolParameters = typeof PoolParametersSchema.type; 46 | export type GetPoolsParameters = typeof GetPoolsParametersSchema.type; 47 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import edwinLogger from './logger'; 2 | 3 | const INITIAL_DELAY = 1000; 4 | 5 | /** 6 | * Safely stringifies JSON that may contain BigInt values 7 | * @param obj The object to stringify 8 | * @returns JSON string with BigInt values converted to strings 9 | */ 10 | export function safeJsonStringify(obj: object): string { 11 | return JSON.stringify(obj, (_, value) => { 12 | // Handle both BigInt and BigInt constructor values 13 | if (typeof value === 'bigint' || value?.constructor?.name === 'BigInt') { 14 | return value.toString(); 15 | } 16 | return value; 17 | }); 18 | } 19 | 20 | /** 21 | * Retries an operation with exponential backoff 22 | * @param operation The operation to retry 23 | * @param context A description of the operation 24 | * @returns The result of the operation 25 | */ 26 | async function withRetry(operation: () => Promise, context: string, maxRetries: number = 3): Promise { 27 | let lastError: Error; 28 | for (let attempt = 1; attempt <= maxRetries; attempt++) { 29 | try { 30 | return await operation(); 31 | } catch (error: unknown) { 32 | lastError = error as Error; 33 | const isTimeout = 34 | error instanceof Error && 35 | (error.message.toLowerCase().includes('timeout') || 36 | error.message.toLowerCase().includes('connectionerror')); 37 | 38 | if (!isTimeout) { 39 | throw error; 40 | } 41 | 42 | if (attempt === maxRetries) { 43 | edwinLogger.error(`${context} failed after ${maxRetries} attempts:`, error); 44 | throw new Error(`${context} failed after ${maxRetries} retries: ${lastError.message}`); 45 | } 46 | 47 | const delay = INITIAL_DELAY * attempt; 48 | edwinLogger.warn(`${context} attempt ${attempt} failed, retrying in ${delay}ms:`, error); 49 | await new Promise(resolve => setTimeout(resolve, delay)); 50 | } 51 | } 52 | // lastError will always be defined here since we must have caught at least one error to reach this point 53 | throw lastError!; 54 | } 55 | 56 | export { withRetry }; 57 | -------------------------------------------------------------------------------- /src/plugins/stader/parameters.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { createParameterSchema } from '../../core/utils/createParameterSchema'; 3 | 4 | export const StaderStakeParametersSchema = createParameterSchema( 5 | z.object({ 6 | amount: z.number().positive().describe('The amount of HBAR to stake for HBARX'), 7 | network: z 8 | .enum(['mainnet', 'testnet']) 9 | .optional() 10 | .default('mainnet') 11 | .describe('The Hedera network to use (only mainnet is supported)'), 12 | }) 13 | ); 14 | 15 | export const StaderUnstakeParametersSchema = createParameterSchema( 16 | z.object({ 17 | amount: z.number().positive().describe('The amount of HBARX to unstake'), 18 | network: z 19 | .enum(['mainnet', 'testnet']) 20 | .optional() 21 | .default('mainnet') 22 | .describe('The Hedera network to use (only mainnet is supported)'), 23 | }) 24 | ); 25 | 26 | export const StaderWithdrawParametersSchema = createParameterSchema( 27 | z.object({ 28 | unstakeIndex: z.number().min(0).describe('The unstake index to withdraw'), 29 | network: z 30 | .enum(['mainnet', 'testnet']) 31 | .optional() 32 | .default('mainnet') 33 | .describe('The Hedera network to use (only mainnet is supported)'), 34 | }) 35 | ); 36 | 37 | export const StaderGetBalanceParametersSchema = createParameterSchema( 38 | z.object({ 39 | network: z 40 | .enum(['mainnet', 'testnet']) 41 | .optional() 42 | .default('mainnet') 43 | .describe('The Hedera network to use (only mainnet is supported)'), 44 | }) 45 | ); 46 | 47 | export const CurrentStaderGetBalanceParametersSchema = createParameterSchema(z.object({})); 48 | 49 | // Export clean parameter types 50 | export type StaderStakeParameters = typeof StaderStakeParametersSchema.type; 51 | export type StaderUnstakeParameters = typeof StaderUnstakeParametersSchema.type; 52 | export type StaderWithdrawParameters = typeof StaderWithdrawParametersSchema.type; 53 | export type StaderGetBalanceParameters = typeof StaderGetBalanceParametersSchema.type; 54 | export type CurrentStaderGetBalanceParameters = typeof CurrentStaderGetBalanceParametersSchema.type; 55 | -------------------------------------------------------------------------------- /src/plugins/lido/lidoPlugin.ts: -------------------------------------------------------------------------------- 1 | import { EdwinPlugin } from '../../core/classes/edwinPlugin'; 2 | import { EdwinTool, Chain } from '../../core/types'; 3 | import { LidoProtocol } from './lidoProtocol'; 4 | import { EdwinEVMWallet } from '../../core/wallets'; 5 | import { StakeParametersSchema, StakeParameters } from './parameters'; 6 | 7 | export class LidoPlugin extends EdwinPlugin { 8 | constructor(wallet: EdwinEVMWallet) { 9 | super('lido', [new LidoProtocol(wallet)]); 10 | } 11 | 12 | getTools(): Record { 13 | // Combine public and private tools 14 | return { 15 | ...this.getPublicTools(), 16 | ...this.getPrivateTools(), 17 | }; 18 | } 19 | 20 | getPublicTools(): Record { 21 | // Lido has no public tools 22 | return {}; 23 | } 24 | 25 | getPrivateTools(): Record { 26 | const lidoProtocol = this.toolProviders.find(provider => provider instanceof LidoProtocol) as LidoProtocol; 27 | 28 | return { 29 | lidoStake: { 30 | name: 'lido_stake', 31 | description: 'Stake ETH in Lido', 32 | schema: StakeParametersSchema.schema, 33 | execute: async (params: StakeParameters) => { 34 | return await lidoProtocol.stake(params); 35 | }, 36 | }, 37 | lidoUnstake: { 38 | name: 'lido_unstake', 39 | description: 'Unstake ETH from Lido', 40 | schema: StakeParametersSchema.schema, 41 | execute: async (params: StakeParameters) => { 42 | return await lidoProtocol.unstake(params); 43 | }, 44 | }, 45 | lidoClaimRewards: { 46 | name: 'lido_claim_rewards', 47 | description: 'Claim staking rewards from Lido', 48 | schema: StakeParametersSchema.schema, 49 | execute: async (params: StakeParameters) => { 50 | return await lidoProtocol.claimRewards(params); 51 | }, 52 | }, 53 | }; 54 | } 55 | 56 | supportsChain = (chain: Chain) => chain.type === 'evm'; 57 | } 58 | 59 | export const lido = (wallet: EdwinEVMWallet) => new LidoPlugin(wallet); 60 | -------------------------------------------------------------------------------- /src/plugins/evm_wallet/evmWalletPlugin.ts: -------------------------------------------------------------------------------- 1 | import { EdwinPlugin } from '../../core/classes/edwinPlugin'; 2 | import { EdwinTool, Chain } from '../../core/types'; 3 | import { EVMWalletService } from './evmWalletService'; 4 | import { EdwinEVMPublicKeyWallet } from '../../core/wallets'; 5 | import { 6 | EVMBalanceParameters, 7 | EVMBalanceParametersSchema, 8 | CurrentEVMBalanceParameters, 9 | CurrentEVMBalanceParametersSchema, 10 | } from './parameters'; 11 | 12 | export class EVMWalletPlugin extends EdwinPlugin { 13 | constructor(wallet: EdwinEVMPublicKeyWallet) { 14 | super('evm_wallet', [new EVMWalletService(wallet)]); 15 | } 16 | 17 | getTools(): Record { 18 | // Combine public and private tools 19 | return { 20 | ...this.getPublicTools(), 21 | ...this.getPrivateTools(), 22 | }; 23 | } 24 | 25 | getPublicTools(): Record { 26 | const evmWalletService = this.toolProviders.find( 27 | provider => provider instanceof EVMWalletService 28 | ) as EVMWalletService; 29 | 30 | return { 31 | checkEVMWalletBalance: { 32 | name: 'check_evm_wallet_balance', 33 | description: 'Check the balance of any EVM wallet on any chain', 34 | schema: EVMBalanceParametersSchema.schema, 35 | execute: async (params: EVMBalanceParameters) => { 36 | return await evmWalletService.getWalletBalance(params); 37 | }, 38 | }, 39 | checkCurrentEVMWalletBalance: { 40 | name: 'check_current_evm_wallet_balance', 41 | description: 'Check the balance of your current EVM wallet', 42 | schema: CurrentEVMBalanceParametersSchema.schema, 43 | execute: async (params: CurrentEVMBalanceParameters) => { 44 | return await evmWalletService.getCurrentWalletBalance(params); 45 | }, 46 | }, 47 | }; 48 | } 49 | 50 | getPrivateTools(): Record { 51 | // EVM Wallet has no private tools 52 | return {}; 53 | } 54 | 55 | supportsChain = (chain: Chain) => chain.type !== 'solana'; 56 | } 57 | 58 | // Factory function to create a new instance of the plugin 59 | export const evmWallet = (wallet: EdwinEVMPublicKeyWallet) => new EVMWalletPlugin(wallet); 60 | -------------------------------------------------------------------------------- /src/plugins/uniswap/uniswapPlugin.ts: -------------------------------------------------------------------------------- 1 | import { EdwinPlugin } from '../../core/classes/edwinPlugin'; 2 | import { EdwinTool, Chain } from '../../core/types'; 3 | import { UniswapProtocol } from './uniswapProtocol'; 4 | import { EdwinEVMWallet } from '../../core/wallets'; 5 | import { LiquidityParametersSchema, LiquidityParameters } from './parameters'; 6 | 7 | export class UniswapPlugin extends EdwinPlugin { 8 | constructor(wallet: EdwinEVMWallet) { 9 | super('uniswap', [new UniswapProtocol(wallet)]); 10 | } 11 | 12 | getTools(): Record { 13 | // Combine public and private tools 14 | return { 15 | ...this.getPublicTools(), 16 | ...this.getPrivateTools(), 17 | }; 18 | } 19 | 20 | getPublicTools(): Record { 21 | // Uniswap has no public tools 22 | return {}; 23 | } 24 | 25 | getPrivateTools(): Record { 26 | const uniswapProtocol = this.toolProviders.find( 27 | provider => provider instanceof UniswapProtocol 28 | ) as UniswapProtocol; 29 | 30 | return { 31 | uniswapSwap: { 32 | name: 'uniswap_swap', 33 | description: 'Swap tokens on Uniswap', 34 | schema: LiquidityParametersSchema.schema, 35 | execute: async (params: LiquidityParameters) => { 36 | return await uniswapProtocol.swap(params); 37 | }, 38 | }, 39 | uniswapAddLiquidity: { 40 | name: 'uniswap_add_liquidity', 41 | description: 'Add liquidity to Uniswap pool', 42 | schema: LiquidityParametersSchema.schema, 43 | execute: async (params: LiquidityParameters) => { 44 | return await uniswapProtocol.addLiquidity(params); 45 | }, 46 | }, 47 | uniswapRemoveLiquidity: { 48 | name: 'uniswap_remove_liquidity', 49 | description: 'Remove liquidity from Uniswap pool', 50 | schema: LiquidityParametersSchema.schema, 51 | execute: async (params: LiquidityParameters) => { 52 | return await uniswapProtocol.removeLiquidity(params); 53 | }, 54 | }, 55 | }; 56 | } 57 | 58 | supportsChain = (chain: Chain) => chain.type === 'evm'; 59 | } 60 | 61 | export const uniswap = (wallet: EdwinEVMWallet) => new UniswapPlugin(wallet); 62 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import eslint from "@eslint/js"; 2 | import tseslint from "@typescript-eslint/eslint-plugin"; 3 | import typescript from "@typescript-eslint/parser"; 4 | import prettier from "eslint-config-prettier"; 5 | import vitest from "@vitest/eslint-plugin"; // Add Vitest plugin 6 | 7 | export default [ 8 | // JavaScript and TypeScript files 9 | { 10 | files: ["src/**/*.js", "src/**/*.cjs", "src/**/*.mjs", "src/**/*.ts"], 11 | languageOptions: { 12 | parser: typescript, 13 | parserOptions: { 14 | ecmaVersion: "latest", 15 | sourceType: "module", 16 | project: "./tsconfig.json", // Make sure your tsconfig includes @types/node 17 | }, 18 | globals: { 19 | // Add Node.js globals 20 | NodeJS: "readonly", 21 | console: "readonly", 22 | process: "readonly", 23 | Buffer: "readonly", 24 | __dirname: "readonly", 25 | __filename: "readonly", 26 | module: "readonly", 27 | require: "readonly", 28 | }, 29 | }, 30 | plugins: { 31 | "@typescript-eslint": tseslint, 32 | }, 33 | rules: { 34 | ...eslint.configs.recommended.rules, 35 | ...tseslint.configs.recommended.rules, 36 | "prefer-const": "warn", 37 | "no-constant-binary-expression": "error", 38 | 39 | // Disable no-undef as TypeScript handles this better 40 | "no-undef": "off", 41 | "@typescript-eslint/no-unsafe-function-type": "off", 42 | // Customize TypeScript rules 43 | "@typescript-eslint/no-explicit-any": "warn", 44 | "@typescript-eslint/no-unused-vars": [ 45 | "error", 46 | { 47 | argsIgnorePattern: "^_", 48 | varsIgnorePattern: "^_", 49 | ignoreRestSiblings: true, 50 | }, 51 | ], 52 | }, 53 | }, 54 | // Vitest configuration 55 | { 56 | files: [ 57 | "src/**/*.test.js", 58 | "src/**/*.test.ts", 59 | "src/**/*.spec.js", 60 | "src/**/*.spec.ts", 61 | ], 62 | plugins: { 63 | vitest, // Register Vitest plugin 64 | }, 65 | rules: { 66 | ...vitest.configs.recommended.rules, 67 | }, 68 | }, 69 | // Add prettier as the last config to override other formatting rules 70 | prettier, 71 | ]; 72 | -------------------------------------------------------------------------------- /tests/jupiter.get_ca.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, beforeEach } from 'vitest'; 2 | import { JupiterService } from '../src/plugins/jupiter/jupiterService'; 3 | import { PublicKeyClient } from '../src/core/wallets/solana_wallet/clients/publickey'; 4 | describe('Jupiter Service - Get Token Address Integration Test', () => { 5 | let jupiterService: JupiterService; 6 | 7 | // We need to pass a wallet to JupiterService constructor even though 8 | // we won't use it for the getTokenAddressFromTicker tests 9 | // Create a minimal wallet implementation with just the required methods 10 | const dummyWallet = { 11 | getConnection: () => ({}), 12 | getPublicKey: () => ({}), 13 | signTransaction: () => {}, 14 | getTransactionTokenBalanceChange: async () => 0, 15 | getBalance: async () => 0, 16 | } as unknown as PublicKeyClient; 17 | 18 | beforeEach(() => { 19 | jupiterService = new JupiterService(dummyWallet); 20 | }); 21 | 22 | it('should return the correct contract address for SOL', async () => { 23 | const result = await jupiterService.getTokenAddressFromTicker('SOL'); 24 | 25 | // SOL's address should be wrapped SOL mint address 26 | expect(result).toBe('So11111111111111111111111111111111111111112'); 27 | }); 28 | 29 | it('should return the correct contract address for USDC', async () => { 30 | const result = await jupiterService.getTokenAddressFromTicker('USDC'); 31 | 32 | // USDC's address on Solana 33 | expect(result).toBe('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'); 34 | }); 35 | 36 | it('should return the correct contract address for edwin', async () => { 37 | const result = await jupiterService.getTokenAddressFromTicker('EDWIN'); 38 | 39 | // Edwin's address as specified 40 | expect(result).toBe('GPrg1CgbBvAJS2SCuf9gF7NmQYsWudfyfWy5SUzypump'); 41 | }); 42 | 43 | it('should return the correct contract address for fartcoin', async () => { 44 | const result = await jupiterService.getTokenAddressFromTicker('FARTCOIN'); 45 | 46 | // Fartcoin's address as specified 47 | expect(result).toBe('9BB6NFEcjBCtnNLFko2FqVQBq8HHM13kCyYcdQbgpump'); 48 | }); 49 | 50 | it('should throw an error if the ticker is not found', async () => { 51 | await expect(jupiterService.getTokenAddressFromTicker('nonexistenttoken123456')).rejects.toThrow( 52 | "Token nonexistenttoken123456 not found in Jupiter's verified token list. This token may exist but is not yet verified. Please find and verify the contract address manually." 53 | ); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /tests/aave.test.ts: -------------------------------------------------------------------------------- 1 | import { config } from 'dotenv'; 2 | config(); // Load test environment variables from .env file 3 | 4 | import { describe, it, expect, beforeAll } from 'vitest'; 5 | import { EdwinEVMWallet } from '../src/core/wallets/evm_wallet/evm_wallet'; 6 | import { AaveService } from '../src/plugins/aave/aaveService'; 7 | 8 | // Check if private key is available 9 | const hasPrivateKey = Boolean(process.env.EVM_PRIVATE_KEY); 10 | 11 | const MIN_USDC_REQUIRED = 0.05; 12 | const SEPOLIA_USDC_ADDRESS = '0x94a9d9ac8a22534e3faca9f4e7f2e2cf85d5e4c8' as `0x${string}`; 13 | 14 | // Skip entire test if no private key 15 | let wallet: EdwinEVMWallet | null = null; 16 | let sufficientBalance = false; 17 | 18 | // Check for sufficient balance if we have a private key 19 | if (hasPrivateKey) { 20 | try { 21 | // Create wallet for pre-check only 22 | wallet = new EdwinEVMWallet(process.env.EVM_PRIVATE_KEY as `0x${string}`); 23 | const balance = await wallet.getTokenBalance('sepolia', SEPOLIA_USDC_ADDRESS); 24 | const balanceNum = parseFloat(balance); 25 | 26 | sufficientBalance = balanceNum >= MIN_USDC_REQUIRED; 27 | 28 | console.log(`Pre-check: USDC Balance on Base Sepolia: ${balance}`); 29 | if (!sufficientBalance) { 30 | console.log( 31 | `Skipping AAVE tests: Insufficient Base Sepolia USDC balance (${balance}). Need at least ${MIN_USDC_REQUIRED} USDC.` 32 | ); 33 | } 34 | } catch (error) { 35 | console.error('Error in pre-check balance:', error); 36 | sufficientBalance = false; 37 | } 38 | } 39 | 40 | // Skip entire test suite if no key or insufficient balance 41 | const shouldRunTests = hasPrivateKey && sufficientBalance; 42 | const describeIf = shouldRunTests ? describe : describe.skip; 43 | 44 | describeIf('Edwin AAVE test', () => { 45 | let aave: AaveService; 46 | 47 | beforeAll(() => { 48 | // We already created the wallet in the pre-check 49 | aave = new AaveService(wallet!); 50 | console.log('Running AAVE tests with sufficient balance'); 51 | }); 52 | 53 | it('Test supply action', async () => { 54 | expect(aave).toBeDefined(); 55 | 56 | // Test supply action 57 | const result = await aave.supply({ 58 | chain: 'sepolia', 59 | amount: MIN_USDC_REQUIRED, 60 | asset: 'usdc', 61 | }); 62 | expect(result).toBeDefined(); 63 | }, 60000); // 60 second timeout 64 | 65 | it('Test withdraw action', async () => { 66 | expect(aave).toBeDefined(); 67 | 68 | const result = await aave.withdraw({ 69 | chain: 'sepolia', 70 | amount: MIN_USDC_REQUIRED, 71 | asset: 'usdc', 72 | }); 73 | expect(result).toBeDefined(); 74 | }, 60000); // 60 second timeout 75 | }); 76 | -------------------------------------------------------------------------------- /src/plugins/bonzo/parameters.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { createParameterSchema } from '../../core/utils/createParameterSchema'; 3 | 4 | export const BonzoSupplyParametersSchema = createParameterSchema( 5 | z.object({ 6 | tokenSymbol: z.string().min(1).describe('The token symbol to supply (e.g., WHBAR, USDC, SAUCE)'), 7 | amount: z.number().positive().describe('The amount of tokens to supply to Bonzo Finance'), 8 | network: z 9 | .enum(['mainnet', 'testnet']) 10 | .optional() 11 | .default('mainnet') 12 | .describe('The Hedera network to use (mainnet or testnet)'), 13 | }) 14 | ); 15 | 16 | export const BonzoWithdrawParametersSchema = createParameterSchema( 17 | z.object({ 18 | tokenSymbol: z.string().min(1).describe('The token symbol to withdraw (e.g., WHBAR, USDC, SAUCE)'), 19 | amount: z.number().positive().describe('The amount of tokens to withdraw from Bonzo Finance'), 20 | network: z 21 | .enum(['mainnet', 'testnet']) 22 | .optional() 23 | .default('mainnet') 24 | .describe('The Hedera network to use (mainnet or testnet)'), 25 | }) 26 | ); 27 | 28 | export const BonzoBorrowParametersSchema = createParameterSchema( 29 | z.object({ 30 | tokenSymbol: z.string().min(1).describe('The token symbol to borrow (e.g., WHBAR, USDC, SAUCE)'), 31 | amount: z.number().positive().describe('The amount of tokens to borrow from Bonzo Finance'), 32 | network: z 33 | .enum(['mainnet', 'testnet']) 34 | .optional() 35 | .default('mainnet') 36 | .describe('The Hedera network to use (mainnet or testnet)'), 37 | }) 38 | ); 39 | 40 | export const BonzoGetSuppliedBalanceParametersSchema = createParameterSchema( 41 | z.object({ 42 | tokenSymbol: z.string().min(1).describe('The token symbol to check supplied balance for'), 43 | network: z 44 | .enum(['mainnet', 'testnet']) 45 | .optional() 46 | .default('mainnet') 47 | .describe('The Hedera network to use (mainnet or testnet)'), 48 | }) 49 | ); 50 | 51 | export const CurrentBonzoGetSuppliedBalanceParametersSchema = createParameterSchema( 52 | z.object({ 53 | tokenSymbol: z.string().min(1).describe('The token symbol to check supplied balance for'), 54 | }) 55 | ); 56 | 57 | // Export clean parameter types 58 | export type BonzoSupplyParameters = typeof BonzoSupplyParametersSchema.type; 59 | export type BonzoWithdrawParameters = typeof BonzoWithdrawParametersSchema.type; 60 | export type BonzoBorrowParameters = typeof BonzoBorrowParametersSchema.type; 61 | export type BonzoGetSuppliedBalanceParameters = typeof BonzoGetSuppliedBalanceParametersSchema.type; 62 | export type CurrentBonzoGetSuppliedBalanceParameters = typeof CurrentBonzoGetSuppliedBalanceParametersSchema.type; 63 | -------------------------------------------------------------------------------- /src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | import winston from 'winston'; 2 | import fs from 'fs'; 3 | import path from 'path'; 4 | import os from 'os'; 5 | 6 | // Configure transports based on environment 7 | const transports: winston.transport[] = []; 8 | 9 | // Add console transport only if we're not in MCP mode 10 | if (process.env.EDWIN_MCP_MODE !== 'true') { 11 | transports.push( 12 | new winston.transports.Console({ 13 | format: winston.format.combine( 14 | winston.format.colorize(), 15 | winston.format.timestamp(), 16 | winston.format.printf(({ timestamp, level, message }) => { 17 | return `${timestamp} [${level.toUpperCase()}] ${message}`; 18 | }) 19 | ), 20 | }) 21 | ); 22 | } 23 | 24 | // Add file transport if file logging is enabled or we're in MCP mode 25 | if (process.env.EDWIN_FILE_LOGGING === 'true' || process.env.EDWIN_MCP_MODE === 'true') { 26 | try { 27 | // Get home directory 28 | const homeDir = os.homedir(); 29 | if (!homeDir || homeDir === '/') { 30 | throw new Error('Could not determine home directory for logs'); 31 | } 32 | 33 | // Set up log paths 34 | const baseDir = path.join(homeDir, '.edwin'); 35 | const logsDir = path.join(baseDir, 'logs'); 36 | const logFile = path.join(logsDir, 'edwin.log'); 37 | 38 | // Create directories 39 | fs.mkdirSync(baseDir, { recursive: true, mode: 0o755 }); 40 | fs.mkdirSync(logsDir, { recursive: true, mode: 0o755 }); 41 | 42 | // Add file transport 43 | transports.push( 44 | new winston.transports.File({ 45 | filename: logFile, 46 | format: winston.format.combine(winston.format.timestamp(), winston.format.json()), 47 | maxsize: 5242880, // 5MB 48 | maxFiles: 5, 49 | tailable: true, 50 | }) 51 | ); 52 | } catch (error) { 53 | // In MCP mode, we can't even log this error to console, so we'll have to throw 54 | if (process.env.EDWIN_MCP_MODE === 'true') { 55 | throw new Error(`Failed to set up required file logging for MCP mode: ${error}`); 56 | } 57 | // Otherwise, we'll fall back to console logging 58 | console.warn('Failed to set up file logging:', error); 59 | } 60 | } 61 | 62 | // Create the logger 63 | const edwinLogger = winston.createLogger({ 64 | level: process.env.LOG_LEVEL || 'debug', 65 | exitOnError: false, 66 | levels: winston.config.npm.levels, 67 | format: winston.format.combine( 68 | winston.format.timestamp(), 69 | winston.format.printf(({ timestamp, level, message }) => { 70 | return `${timestamp} [${level.toUpperCase()}] ${message}`; 71 | }) 72 | ), 73 | transports, 74 | }); 75 | 76 | export default edwinLogger; 77 | -------------------------------------------------------------------------------- /tests/mendi.test.ts: -------------------------------------------------------------------------------- 1 | import { config } from 'dotenv'; 2 | config(); // Load test environment variables from .env file 3 | 4 | import { describe, it, expect, beforeAll } from 'vitest'; 5 | import { EdwinEVMWallet } from '../src/core/wallets/evm_wallet/evm_wallet'; 6 | import { MendiService } from '../src/plugins/mendi/mendiService'; 7 | 8 | // Check if private key is available 9 | const hasPrivateKey = Boolean(process.env.EVM_PRIVATE_KEY); 10 | 11 | const MIN_USDC_REQUIRED = 0.5; // USDC minimum for testing 12 | const LINEA_USDC_ADDRESS = '0x176211869cA2b568f2A7D4EE941E073a821EE1ff' as `0x${string}`; 13 | 14 | // Skip entire test if no private key 15 | let wallet: EdwinEVMWallet | null = null; 16 | let sufficientBalance = false; 17 | 18 | // Check for sufficient balance if we have a private key 19 | if (hasPrivateKey) { 20 | try { 21 | // Create wallet for pre-check only 22 | wallet = new EdwinEVMWallet(process.env.EVM_PRIVATE_KEY as `0x${string}`); 23 | const balance = await wallet.getTokenBalance('linea', LINEA_USDC_ADDRESS); 24 | const balanceNum = parseFloat(balance); 25 | 26 | sufficientBalance = balanceNum >= MIN_USDC_REQUIRED; 27 | 28 | console.log(`Pre-check: USDC Balance on Linea: ${balance}`); 29 | if (!sufficientBalance) { 30 | console.log( 31 | `Skipping Mendi tests: Insufficient Linea USDC balance (${balance}). Need at least ${MIN_USDC_REQUIRED} USDC.` 32 | ); 33 | } 34 | } catch (error) { 35 | console.error('Error in pre-check balance:', error); 36 | sufficientBalance = false; 37 | } 38 | } 39 | 40 | // Skip entire test suite if no key or insufficient balance 41 | const shouldRunTests = hasPrivateKey && sufficientBalance; 42 | const describeIf = shouldRunTests ? describe : describe.skip; 43 | 44 | describeIf('Edwin Mendi test', () => { 45 | let mendi: MendiService; 46 | 47 | beforeAll(() => { 48 | // We already created the wallet in the pre-check 49 | mendi = new MendiService(wallet!); 50 | console.log('Running Mendi tests with sufficient balance'); 51 | }); 52 | 53 | it('Test supply action', async () => { 54 | expect(mendi).toBeDefined(); 55 | 56 | // Test supply action 57 | const result = await mendi.supply({ 58 | chain: 'Linea', 59 | amount: MIN_USDC_REQUIRED, // Supply MIN_USDC_REQUIRED USDC 60 | asset: 'usdc', 61 | }); 62 | expect(result).toBeDefined(); 63 | }, 60000); // 60 second timeout 64 | 65 | it('Test withdraw action', async () => { 66 | expect(mendi).toBeDefined(); 67 | 68 | const result = await mendi.withdraw({ 69 | chain: 'Linea', 70 | amount: MIN_USDC_REQUIRED, // Withdraw MIN_USDC_REQUIRED USDC 71 | asset: 'usdc', 72 | }); 73 | expect(result).toBeDefined(); 74 | }, 60000); // 60 second timeout 75 | }); 76 | -------------------------------------------------------------------------------- /examples/mcp-server/README.md: -------------------------------------------------------------------------------- 1 | # Edwin MCP Server 2 | 3 | This is an example implementation of a Model Context Protocol (MCP) server for the Edwin SDK. It allows AI agents to interact with Edwin's tools through a standardized protocol. 4 | 5 | ## Prerequisites 6 | 7 | - Node.js >= 18.0.0 8 | - pnpm (recommended) or npm 9 | - A local Claude instance or access to Claude API 10 | - EVM and/or Solana wallet private keys (depending on your needs) 11 | 12 | ## Installation 13 | 14 | 1. Clone the repository and navigate to the mcp-server directory: 15 | 16 | ```bash 17 | cd examples/mcp-server 18 | ``` 19 | 20 | 2. Install dependencies: 21 | 22 | ```bash 23 | pnpm install 24 | ``` 25 | 26 | 3. Build the project: 27 | 28 | ```bash 29 | pnpm build 30 | ``` 31 | 32 | ## Running the Server 33 | 34 | ### Option 1: Local Development 35 | 36 | 1. Create your environment file: 37 | 38 | ```bash 39 | cp .env.example .env 40 | ``` 41 | 42 | 2. Configure your `.env` file with the following required settings: 43 | - `SOLANA_RPC_URL`: Your RPC endpoint 44 | - `EVM_PRIVATE_KEY`: Your EVM wallet private key 45 | - `SOLANA_PRIVATE_KEY`: Your Solana wallet private key 46 | - `EDWIN_MCP_MODE=true`: Enable MCP mode 47 | 48 | 3. Start the server: 49 | - For production: `pnpm start` 50 | - For development with hot reloading: `pnpm dev` 51 | 52 | ### Option 2: Using with Claude Desktop 53 | 54 | 1. After building the project, use the provided `claude_desktop_config.json` to configure your Claude Desktop: 55 | - Open Claude Desktop 56 | - Go to Settings 57 | - Import the configuration from `claude_desktop_config.json` 58 | 59 | 2. The server will automatically start when needed by Claude Desktop. 60 | 61 | ## Available Tools 62 | 63 | The server exposes all tools configured in your Edwin instance. Common tools include: 64 | 65 | - Wallet operations 66 | - Balance checking 67 | - Transaction signing 68 | - Token transfers 69 | - Contract interactions 70 | 71 | ## Security Considerations 72 | 73 | 1. Never commit your `.env` file with private keys 74 | 2. Use appropriate CORS settings in production 75 | 3. Implement rate limiting for production use 76 | 4. Keep your private keys secure and never share them 77 | 78 | ## Troubleshooting 79 | 80 | 1. **Server won't start**: 81 | - Check if the port is already in use 82 | - Verify your environment variables are set correctly 83 | - Check the logs for specific error messages 84 | 85 | 2. **Tools not available**: 86 | - Ensure your Edwin instance is properly configured 87 | - Check if the tools are properly registered 88 | - Verify your wallet keys are correct 89 | 90 | 3. **Connection issues**: 91 | - Check if the server is running 92 | - Verify the port is correct 93 | - Check network connectivity 94 | 95 | ## Contributing 96 | 97 | Feel free to submit issues and enhancement requests! 98 | 99 | ## License 100 | 101 | This project is licensed under the same terms as the main Edwin SDK. 102 | -------------------------------------------------------------------------------- /src/plugins/jupiter/jupiterPlugin.ts: -------------------------------------------------------------------------------- 1 | import { EdwinPlugin } from '../../core/classes/edwinPlugin'; 2 | import { EdwinTool, Chain } from '../../core/types'; 3 | import { JupiterService } from './jupiterService'; 4 | import { SolanaWalletClient } from '../../core/wallets/solana_wallet'; 5 | import { SwapParametersSchema, SwapParameters, GetTokenAddressSchema, GetTokenAddressParameters } from './parameters'; 6 | 7 | export class JupiterPlugin extends EdwinPlugin { 8 | constructor(wallet: SolanaWalletClient) { 9 | super('jupiter', [new JupiterService(wallet)]); 10 | } 11 | 12 | getTools(): Record { 13 | // Combine public and private tools 14 | return { 15 | ...this.getPublicTools(), 16 | ...this.getPrivateTools(), 17 | }; 18 | } 19 | 20 | getPublicTools(): Record { 21 | const jupiterService = this.toolProviders.find( 22 | provider => provider instanceof JupiterService 23 | ) as JupiterService; 24 | 25 | return { 26 | jupiterGetTokenAddress: { 27 | name: 'jupiter_get_token_address', 28 | description: "Get a token's mint address / contract address (CA) from a ticker name", 29 | schema: GetTokenAddressSchema.schema, 30 | execute: async (params: GetTokenAddressParameters) => { 31 | return await jupiterService.getTokenAddressFromTicker(params.ticker); 32 | }, 33 | }, 34 | }; 35 | } 36 | 37 | getPrivateTools(): Record { 38 | const jupiterService = this.toolProviders.find( 39 | provider => provider instanceof JupiterService 40 | ) as JupiterService; 41 | 42 | return { 43 | jupiterSwap: { 44 | name: 'jupiter_swap', 45 | description: 'Swap tokens using Jupiter aggregator', 46 | schema: SwapParametersSchema.schema, 47 | execute: async (params: SwapParameters) => { 48 | return await jupiterService.swap(params); 49 | }, 50 | }, 51 | jupiterTrade: { 52 | name: 'jupiter_trade', 53 | description: 'Trade tokens using Jupiter aggregator', 54 | schema: SwapParametersSchema.schema, 55 | execute: async (params: SwapParameters) => { 56 | return await jupiterService.swap(params); 57 | }, 58 | }, 59 | jupiterConvert: { 60 | name: 'jupiter_convert', 61 | description: 'Convert tokens using Jupiter aggregator', 62 | schema: SwapParametersSchema.schema, 63 | execute: async (params: SwapParameters) => { 64 | return await jupiterService.swap(params); 65 | }, 66 | }, 67 | }; 68 | } 69 | 70 | supportsChain = (chain: Chain) => chain.type === 'solana'; 71 | } 72 | 73 | export const jupiter = (wallet: SolanaWalletClient) => new JupiterPlugin(wallet); 74 | -------------------------------------------------------------------------------- /src/plugins/eoracle/eoracleService.ts: -------------------------------------------------------------------------------- 1 | import { SupportedChain } from '../../core/types'; 2 | import edwinLogger from '../../utils/logger'; 3 | import { EdwinService } from '../../core/classes/edwinToolProvider'; 4 | 5 | interface PriceResponse { 6 | feed_id: string; 7 | rate: string; 8 | timestamp: number; 9 | } 10 | 11 | interface FeedInfo { 12 | feed_id: string; 13 | description: string; 14 | } 15 | 16 | interface EOracleResponse { 17 | data: T; 18 | success: boolean; 19 | error: string | null; 20 | } 21 | 22 | export class EOracleService extends EdwinService { 23 | private apiKey: string; 24 | private baseUrl: string; 25 | private feedCache = new Map(); 26 | supportedChains: SupportedChain[] = ['base']; 27 | 28 | constructor(apiKey: string) { 29 | super(); 30 | if (!process.env.EORACLE_API_URL) { 31 | throw new Error('EORACLE_API_URL environment variable is not set'); 32 | } 33 | this.apiKey = apiKey; 34 | this.baseUrl = process.env.EORACLE_API_URL; 35 | } 36 | 37 | private async fetch(endpoint: string): Promise { 38 | const response = await fetch(`${this.baseUrl}${endpoint}`, { 39 | headers: { 40 | 'X-API-Key': this.apiKey, 41 | 'Content-Type': 'application/json', 42 | }, 43 | method: 'GET', 44 | }); 45 | 46 | if (!response.ok) { 47 | edwinLogger.error('EOracleAPI Error:', { 48 | status: response.status, 49 | statusText: response.statusText, 50 | }); 51 | throw new Error(`EOracleAPI request failed: ${response.statusText}`); 52 | } 53 | 54 | return response.json(); 55 | } 56 | 57 | private async getFeedId(symbol: string): Promise { 58 | const cachedId = this.feedCache.get(symbol.toUpperCase()); 59 | if (cachedId) return cachedId; 60 | 61 | const response = await this.fetch>('/feeds'); 62 | 63 | if (!response.success || !response.data) { 64 | throw new Error('Failed to fetch feeds'); 65 | } 66 | 67 | const feed = response.data.find(f => f.description.toUpperCase() === symbol.toUpperCase()); 68 | 69 | if (!feed) { 70 | throw new Error(`No feed found for symbol: ${symbol}`); 71 | } 72 | 73 | this.feedCache.set(symbol.toUpperCase(), feed.feed_id); 74 | return feed.feed_id; 75 | } 76 | 77 | async getPrice(symbol: string): Promise { 78 | try { 79 | const feedId = await this.getFeedId(symbol); 80 | const response = await this.fetch>(`/feeds/${feedId}`); 81 | 82 | if (!response.success || !response.data) { 83 | throw new Error(`Failed to get price for ${symbol}`); 84 | } 85 | 86 | return JSON.stringify({ 87 | symbol, 88 | price: response.data.rate, 89 | timestamp: response.data.timestamp, 90 | }); 91 | } catch (error) { 92 | edwinLogger.error('Error fetching price:', error); 93 | throw error; 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/plugins/hyperliquid/parameters.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | /** 4 | * Supported order types for HyperLiquid 5 | */ 6 | export enum OrderType { 7 | MARKET = 'market', 8 | LIMIT = 'limit', 9 | } 10 | 11 | /** 12 | * Supported position types for HyperLiquid 13 | */ 14 | export enum PositionType { 15 | LONG = 'long', 16 | SHORT = 'short', 17 | } 18 | 19 | /** 20 | * Parameters for depositing funds to HyperLiquid 21 | */ 22 | export const DepositParameters = z.object({ 23 | /** 24 | * Amount to deposit 25 | */ 26 | amount: z.number().positive(), 27 | /** 28 | * Asset to deposit (e.g., 'ETH', 'USDC') 29 | */ 30 | asset: z.string(), 31 | }); 32 | 33 | /** 34 | * Type definition for deposit parameters 35 | */ 36 | export type DepositParametersType = z.infer; 37 | 38 | /** 39 | * Parameters for withdrawing funds from HyperLiquid 40 | */ 41 | export const WithdrawParameters = z.object({ 42 | /** 43 | * Amount to withdraw 44 | */ 45 | amount: z.number().positive(), 46 | /** 47 | * Asset to withdraw (e.g., 'ETH', 'USDC') 48 | */ 49 | asset: z.string(), 50 | }); 51 | 52 | /** 53 | * Type definition for withdraw parameters 54 | */ 55 | export type WithdrawParametersType = z.infer; 56 | 57 | /** 58 | * Parameters for opening a position on HyperLiquid 59 | */ 60 | export const OpenPositionParameters = z.object({ 61 | /** 62 | * Asset to trade (e.g., 'BTC', 'ETH') 63 | */ 64 | asset: z.string(), 65 | /** 66 | * Position type (long or short) 67 | */ 68 | positionType: z.nativeEnum(PositionType), 69 | /** 70 | * Order type (market or limit) 71 | */ 72 | orderType: z.nativeEnum(OrderType), 73 | /** 74 | * Size of the position in USD 75 | */ 76 | size: z.number().positive(), 77 | /** 78 | * Leverage to use (e.g., 1, 2, 5, 10, 20) 79 | */ 80 | leverage: z.number().positive().default(1), 81 | /** 82 | * Limit price (required for limit orders) 83 | */ 84 | price: z.number().positive().nullable().optional(), 85 | /** 86 | * Whether to reduce only (close position) 87 | */ 88 | reduceOnly: z.boolean().default(false), 89 | }); 90 | 91 | /** 92 | * Type definition for open position parameters 93 | */ 94 | export type OpenPositionParametersType = z.infer; 95 | 96 | /** 97 | * Parameters for closing a position on HyperLiquid 98 | */ 99 | export const ClosePositionParameters = z.object({ 100 | /** 101 | * Asset to close position for (e.g., 'BTC', 'ETH') 102 | */ 103 | asset: z.string(), 104 | /** 105 | * Order type (market or limit) 106 | */ 107 | orderType: z.nativeEnum(OrderType), 108 | /** 109 | * Percentage of position to close (0-100) 110 | */ 111 | percentage: z.number().min(0).max(100).default(100), 112 | /** 113 | * Limit price (required for limit orders) 114 | */ 115 | price: z.number().positive().nullable().optional(), 116 | }); 117 | 118 | /** 119 | * Type definition for close position parameters 120 | */ 121 | export type ClosePositionParametersType = z.infer; 122 | -------------------------------------------------------------------------------- /src/plugins/meteora/meteoraPlugin.ts: -------------------------------------------------------------------------------- 1 | import { EdwinPlugin } from '../../core/classes/edwinPlugin'; 2 | import { EdwinTool, Chain } from '../../core/types'; 3 | import { MeteoraProtocol } from './meteoraProtocol'; 4 | import { SolanaWalletClient } from '../../core/wallets/solana_wallet'; 5 | import { 6 | // Import schemas with Schema suffix 7 | AddLiquidityParametersSchema, 8 | RemoveLiquidityParametersSchema, 9 | PoolParametersSchema, 10 | GetPoolsParametersSchema, 11 | // Import types with clean names 12 | AddLiquidityParameters, 13 | RemoveLiquidityParameters, 14 | PoolParameters, 15 | GetPoolsParameters, 16 | } from './parameters'; 17 | 18 | export class MeteoraPlugin extends EdwinPlugin { 19 | constructor(wallet: SolanaWalletClient) { 20 | super('meteora', [new MeteoraProtocol(wallet)]); 21 | } 22 | 23 | getTools(): Record { 24 | // Combine public and private tools 25 | return { 26 | ...this.getPublicTools(), 27 | ...this.getPrivateTools(), 28 | }; 29 | } 30 | 31 | getPublicTools(): Record { 32 | const meteoraProtocol = this.toolProviders.find( 33 | provider => provider instanceof MeteoraProtocol 34 | ) as MeteoraProtocol; 35 | 36 | return { 37 | meteoraGetPools: { 38 | name: 'meteora_get_pools', 39 | description: 'Get all pools on a Solana chain', 40 | schema: GetPoolsParametersSchema.schema, 41 | execute: async (params: GetPoolsParameters) => { 42 | return await meteoraProtocol.getPools(params); 43 | }, 44 | }, 45 | }; 46 | } 47 | 48 | getPrivateTools(): Record { 49 | const meteoraProtocol = this.toolProviders.find( 50 | provider => provider instanceof MeteoraProtocol 51 | ) as MeteoraProtocol; 52 | 53 | return { 54 | meteoraAddLiquidity: { 55 | name: 'meteora_add_liquidity', 56 | description: 'Add liquidity to a Meteora pool', 57 | schema: AddLiquidityParametersSchema.schema, 58 | execute: async (params: AddLiquidityParameters) => { 59 | return await meteoraProtocol.addLiquidity(params); 60 | }, 61 | }, 62 | meteoraRemoveLiquidity: { 63 | name: 'meteora_remove_liquidity', 64 | description: 'Remove liquidity from a Meteora pool', 65 | schema: RemoveLiquidityParametersSchema.schema, 66 | execute: async (params: RemoveLiquidityParameters) => { 67 | return await meteoraProtocol.removeLiquidity(params); 68 | }, 69 | }, 70 | meteoraClaimFees: { 71 | name: 'meteora_claim_fees', 72 | description: 'Claim fees from a Meteora pool', 73 | schema: PoolParametersSchema.schema, 74 | execute: async (params: PoolParameters) => { 75 | return await meteoraProtocol.claimFees(params); 76 | }, 77 | }, 78 | }; 79 | } 80 | 81 | supportsChain = (chain: Chain) => chain.type === 'solana'; 82 | } 83 | 84 | export const meteora = (wallet: SolanaWalletClient) => new MeteoraPlugin(wallet); 85 | -------------------------------------------------------------------------------- /src/core/wallets/hedera_wallet/client.ts: -------------------------------------------------------------------------------- 1 | import { Client, Transaction, AccountId, TransactionRecord } from '@hashgraph/sdk'; 2 | 3 | /** 4 | * Interface for a Hedera wallet client in Edwin 5 | */ 6 | export interface HederaWalletClient { 7 | /** 8 | * Get the Hedera JSON-RPC URL for the specified network 9 | * @param network Optional network (testnet, mainnet, previewnet) 10 | * @returns RPC URL string 11 | */ 12 | getHederaRpcUrl?(network?: string): string; 13 | 14 | /** 15 | * The wallet's account ID 16 | */ 17 | readonly accountId: AccountId; 18 | 19 | /** 20 | * Get a Hedera client instance 21 | * @param network Optional network (testnet, mainnet, previewnet) 22 | * @returns Client instance 23 | */ 24 | getClient(network?: string): Client; 25 | 26 | /** 27 | * Get the account ID as a string 28 | * @returns Account ID as string 29 | */ 30 | getAddress(): string; 31 | 32 | /** 33 | * Get account balance in HBAR 34 | * @returns The HBAR balance as a number 35 | */ 36 | getBalance(): Promise; 37 | 38 | /** 39 | * Get balance of any account address 40 | * @param accountId Account ID to check 41 | * @returns The HBAR balance as a number 42 | */ 43 | getBalanceOfAccount(accountId: string): Promise; 44 | 45 | /** 46 | * Get token balance for a specific token 47 | * @param tokenId Token ID to check balance for 48 | * @returns The token balance as a number 49 | */ 50 | getTokenBalance?(tokenId: string): Promise; 51 | 52 | /** 53 | * Get token balance for any account 54 | * @param accountId Account ID to check 55 | * @param tokenId Token ID to check balance for 56 | * @returns The token balance as a number 57 | */ 58 | getTokenBalanceOfAccount?(accountId: string, tokenId: string): Promise; 59 | 60 | /** 61 | * Get token decimals for a specific token 62 | * @param tokenId Token ID to get decimals for 63 | * @returns The number of decimals for the token 64 | */ 65 | getTokenDecimals(tokenId: string): Promise; 66 | 67 | /** 68 | * Get account information for any account 69 | * @param accountId Account ID to get information for 70 | * @returns Account information object 71 | */ 72 | getAccountInfoForAccount?(accountId: string): Promise; 73 | 74 | /** 75 | * Get account information 76 | * @returns Account information object 77 | */ 78 | getAccountInfo(): Promise; 79 | 80 | /** 81 | * Sign a transaction 82 | * @param transaction Transaction to sign 83 | * @returns Signed transaction 84 | */ 85 | signTransaction(transaction: Transaction): Promise; 86 | 87 | /** 88 | * Send a transaction 89 | * @param transaction Transaction to send 90 | * @returns Transaction ID 91 | */ 92 | sendTransaction(transaction: Transaction): Promise; 93 | 94 | /** 95 | * Send a transaction and return full response with record 96 | * @param transaction Transaction to send 97 | * @returns Transaction ID and record 98 | */ 99 | sendTransactionWithResponse?( 100 | transaction: Transaction 101 | ): Promise<{ transactionId: string; record: TransactionRecord }>; 102 | } 103 | -------------------------------------------------------------------------------- /src/plugins/storyprotocol/parameters.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { createParameterSchema } from '../../core/utils/createParameterSchema'; 3 | 4 | export const RegisterIPAssetParametersSchema = createParameterSchema( 5 | z.object({ 6 | name: z.string().min(1).describe('The name of the IP asset'), 7 | description: z.string().min(1).describe('The description of the IP asset'), 8 | mediaUrl: z.string().url().describe('The URL to the media file of the IP asset'), 9 | contentHash: z.string().min(1).describe('The hash of the content'), 10 | externalUrl: z.string().url().nullable().optional().describe('An optional external URL for the IP asset'), 11 | }) 12 | ); 13 | 14 | export const AttachTermsParametersSchema = createParameterSchema( 15 | z.object({ 16 | ipId: z.string().min(1).describe('The ID of the IP asset'), 17 | termsUrl: z.string().url().describe('The URL to the terms document'), 18 | termsHash: z.string().min(1).describe('The hash of the terms document'), 19 | royaltyPercentage: z.number().describe('The royalty percentage'), 20 | }) 21 | ); 22 | 23 | export const MintLicenseTokenParametersSchema = createParameterSchema( 24 | z.object({ 25 | ipId: z.string().min(1).describe('The ID of the IP asset'), 26 | licenseTermsUrl: z.string().url().describe('The URL to the license terms document'), 27 | licenseTermsHash: z.string().min(1).describe('The hash of the license terms document'), 28 | mintTo: z.string().min(1).describe('The address to mint the license token to'), 29 | }) 30 | ); 31 | 32 | export const RegisterDerivativeParametersSchema = createParameterSchema( 33 | z.object({ 34 | parentIpId: z.string().min(1).describe('The ID of the parent IP asset'), 35 | name: z.string().min(1).describe('The name of the derivative IP asset'), 36 | description: z.string().min(1).describe('The description of the derivative IP asset'), 37 | mediaUrl: z.string().url().describe('The URL to the media file of the derivative IP asset'), 38 | contentHash: z.string().min(1).describe('The hash of the content'), 39 | externalUrl: z 40 | .string() 41 | .url() 42 | .nullable() 43 | .optional() 44 | .describe('An optional external URL for the derivative IP asset'), 45 | isCommercial: z.boolean().describe('Whether the derivative is for commercial use'), 46 | }) 47 | ); 48 | 49 | export const PayIPAssetParametersSchema = createParameterSchema( 50 | z.object({ 51 | ipId: z.string().min(1).describe('The ID of the IP asset'), 52 | amount: z.string().min(1).describe('The amount to pay'), 53 | }) 54 | ); 55 | 56 | export const ClaimRevenueParametersSchema = createParameterSchema( 57 | z.object({ 58 | ipId: z.string().min(1).describe('The ID of the IP asset'), 59 | }) 60 | ); 61 | 62 | // Export clean parameter types 63 | export type RegisterIPAssetParameters = typeof RegisterIPAssetParametersSchema.type; 64 | export type AttachTermsParameters = typeof AttachTermsParametersSchema.type; 65 | export type MintLicenseTokenParameters = typeof MintLicenseTokenParametersSchema.type; 66 | export type RegisterDerivativeParameters = typeof RegisterDerivativeParametersSchema.type; 67 | export type PayIPAssetParameters = typeof PayIPAssetParametersSchema.type; 68 | export type ClaimRevenueParameters = typeof ClaimRevenueParametersSchema.type; 69 | -------------------------------------------------------------------------------- /src/core/wallets/solana_wallet/client.ts: -------------------------------------------------------------------------------- 1 | import { Connection, Commitment, Keypair, PublicKey, Transaction, VersionedTransaction } from '@solana/web3.js'; 2 | 3 | /** 4 | * Interface for a signing capable Solana wallet client in Edwin 5 | */ 6 | export interface SolanaWalletClient { 7 | /** 8 | * The wallet's public key 9 | */ 10 | readonly publicKey: PublicKey; 11 | 12 | /** 13 | * Get a Solana connection instance 14 | * @param customRpcUrl Optional custom RPC URL 15 | * @param commitment Optional commitment level 16 | * @returns Connection instance 17 | */ 18 | getConnection(customRpcUrl?: string, commitment?: Commitment): Connection; 19 | 20 | /** 21 | * Get the wallet address as a string 22 | * @returns Wallet address as base58 string 23 | */ 24 | getAddress(): string; 25 | 26 | /** 27 | * Get balance of the wallet in SOL or a specific token 28 | * @param mintAddress Optional token mint address. If not provided, returns SOL balance. 29 | * @param commitment Optional commitment level 30 | * @returns The balance as a number 31 | */ 32 | getBalance(mintAddress?: string, commitment?: Commitment): Promise; 33 | 34 | /** 35 | * Get balance of any wallet address 36 | * @param walletAddress Address of the wallet to check 37 | * @param mintAddress Optional token mint address. If not provided, returns SOL balance. 38 | * @param commitment Optional commitment level 39 | * @returns The balance as a number 40 | */ 41 | getBalanceOfWallet(walletAddress: string, mintAddress?: string, commitment?: Commitment): Promise; 42 | 43 | /** 44 | * Get token mint address by symbol 45 | * @param symbol Token symbol to look up 46 | * @returns Token mint address or null if not found 47 | */ 48 | getTokenAddress?(symbol: string): Promise; 49 | 50 | /** 51 | * Sign a transaction 52 | * @param transaction Transaction to sign 53 | * @returns Signed transaction 54 | */ 55 | signTransaction(transaction: T): Promise; 56 | 57 | /** 58 | * Sign multiple transactions 59 | * @param transactions Transactions to sign 60 | * @returns Signed transactions 61 | */ 62 | signAllTransactions(transactions: T[]): Promise; 63 | 64 | /** 65 | * Sign a message 66 | * @param message Message to sign as Uint8Array 67 | * @returns Signature as Uint8Array 68 | */ 69 | signMessage(message: Uint8Array): Promise; 70 | 71 | /** 72 | * Send a transaction 73 | * @param connection Solana connection to use 74 | * @param transaction Transaction to send (can be Transaction or VersionedTransaction) 75 | * @param signers Optional additional signers 76 | * @returns Transaction signature 77 | */ 78 | sendTransaction( 79 | connection: Connection, 80 | transaction: T, 81 | signers?: Keypair[] 82 | ): Promise; 83 | 84 | /** 85 | * Get balance changes for a token from a transaction 86 | * @param signature Transaction signature 87 | * @param mint Token mint address 88 | * @param commitment Optional commitment level 89 | * @returns The balance change as a number 90 | */ 91 | getTransactionTokenBalanceChange(signature: string, mint: string, commitment?: Commitment): Promise; 92 | } 93 | -------------------------------------------------------------------------------- /src/plugins/saucerswap/saucerSwapPlugin.ts: -------------------------------------------------------------------------------- 1 | import { EdwinPlugin } from '../../core/classes/edwinPlugin'; 2 | import { EdwinTool, Chain } from '../../core/types'; 3 | import { SaucerSwapService } from './saucerSwapService'; 4 | import { HederaWalletClient, canSign } from '../../core/wallets/hedera_wallet'; 5 | import { 6 | SaucerSwapQuoteParametersSchema, 7 | SaucerSwapQuoteExactOutputParametersSchema, 8 | SaucerSwapExactInputParametersSchema, 9 | SaucerSwapExactOutputParametersSchema, 10 | SaucerSwapQuoteParameters, 11 | SaucerSwapQuoteExactOutputParameters, 12 | SaucerSwapExactInputParameters, 13 | SaucerSwapExactOutputParameters, 14 | } from './parameters'; 15 | 16 | export class SaucerSwapPlugin extends EdwinPlugin { 17 | constructor(private wallet: HederaWalletClient) { 18 | super('saucerswap', [new SaucerSwapService(wallet)]); 19 | } 20 | 21 | getTools(): Record { 22 | return { 23 | ...this.getPublicTools(), 24 | ...this.getPrivateTools(), 25 | }; 26 | } 27 | 28 | getPublicTools(): Record { 29 | const saucerSwapService = this.toolProviders.find( 30 | provider => provider instanceof SaucerSwapService 31 | ) as SaucerSwapService; 32 | 33 | return { 34 | saucerSwapQuote: { 35 | name: 'saucerswap_get_quote', 36 | description: 'Get a quote for exact input swap on SaucerSwap DEX', 37 | schema: SaucerSwapQuoteParametersSchema.schema, 38 | execute: async (params: SaucerSwapQuoteParameters) => { 39 | return await saucerSwapService.getQuote(params); 40 | }, 41 | }, 42 | saucerSwapQuoteExactOutput: { 43 | name: 'saucerswap_get_quote_exact_output', 44 | description: 'Get a quote for exact output swap on SaucerSwap DEX', 45 | schema: SaucerSwapQuoteExactOutputParametersSchema.schema, 46 | execute: async (params: SaucerSwapQuoteExactOutputParameters) => { 47 | return await saucerSwapService.getQuoteExactOutput(params); 48 | }, 49 | }, 50 | }; 51 | } 52 | 53 | getPrivateTools(): Record { 54 | if (!canSign(this.wallet)) { 55 | return {}; 56 | } 57 | 58 | const saucerSwapService = this.toolProviders.find( 59 | provider => provider instanceof SaucerSwapService 60 | ) as SaucerSwapService; 61 | 62 | return { 63 | saucerSwapExactInput: { 64 | name: 'saucerswap_swap_exact_input', 65 | description: 'Swap an exact amount of input tokens for output tokens on SaucerSwap', 66 | schema: SaucerSwapExactInputParametersSchema.schema, 67 | execute: async (params: SaucerSwapExactInputParameters) => { 68 | return await saucerSwapService.swapExactInput(params); 69 | }, 70 | }, 71 | saucerSwapExactOutput: { 72 | name: 'saucerswap_swap_exact_output', 73 | description: 'Swap input tokens for an exact amount of output tokens on SaucerSwap', 74 | schema: SaucerSwapExactOutputParametersSchema.schema, 75 | execute: async (params: SaucerSwapExactOutputParameters) => { 76 | return await saucerSwapService.swapExactOutput(params); 77 | }, 78 | }, 79 | }; 80 | } 81 | 82 | supportsChain = (chain: Chain) => chain.type === 'hedera'; 83 | } 84 | 85 | export const saucerSwap = (wallet: HederaWalletClient) => new SaucerSwapPlugin(wallet); 86 | -------------------------------------------------------------------------------- /tests/jupiter.swap.test.ts: -------------------------------------------------------------------------------- 1 | import { config } from 'dotenv'; 2 | config(); 3 | 4 | import { describe, expect, it } from 'vitest'; 5 | import { JupiterService } from '../src/plugins/jupiter/jupiterService'; 6 | import { SolanaWalletFactory } from '../src/core/wallets/solana_wallet/factory'; 7 | 8 | const AMOUNT_SOL_TO_SWAP = 0.0001; 9 | 10 | describe('Jupiter Swap Test', () => { 11 | if (!process.env.SOLANA_PRIVATE_KEY) { 12 | throw new Error('SOLANA_PRIVATE_KEY is not set'); 13 | } 14 | 15 | // Create a wallet using the factory method instead of direct instantiation 16 | const wallet = SolanaWalletFactory.fromPrivateKey(process.env.SOLANA_PRIVATE_KEY); 17 | const jupiter = new JupiterService(wallet); 18 | 19 | // USDC mint address on Solana 20 | const USDC_MINT = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'; 21 | const SOL_MINT = 'So11111111111111111111111111111111111111112'; // Native SOL mint address 22 | 23 | it('should swap SOL to USDC and back', async () => { 24 | // Initial balances 25 | const initialSolBalance = await wallet.getBalance(); 26 | const initialUsdcBalance = await wallet.getBalance(USDC_MINT); 27 | console.log('Initial balances:'); 28 | console.log('SOL:', initialSolBalance); 29 | console.log('USDC:', initialUsdcBalance); 30 | 31 | // First swap: SOL to USDC 32 | const txSignature1 = await jupiter.swap({ 33 | inputMint: SOL_MINT, 34 | outputMint: USDC_MINT, 35 | amount: AMOUNT_SOL_TO_SWAP, 36 | }); 37 | console.log(`Swap transaction signature: ${txSignature1}`); 38 | 39 | // Get the swap details from the transaction 40 | const swapResult1 = await jupiter.getSwapDetailsFromTransaction(txSignature1, USDC_MINT); 41 | console.log(`Swap ${AMOUNT_SOL_TO_SWAP} SOL to USDC output amount: ${swapResult1}`); 42 | 43 | // Check balances after first swap 44 | const midSolBalance = await wallet.getBalance(); 45 | const midUsdcBalance = await wallet.getBalance(USDC_MINT); 46 | console.log('\nBalances after first swap:'); 47 | console.log('SOL:', midSolBalance); 48 | console.log('USDC:', midUsdcBalance); 49 | console.log('SOL change:', midSolBalance - initialSolBalance); 50 | console.log('USDC change:', midUsdcBalance - initialUsdcBalance); 51 | 52 | // Second swap: USDC back to SOL 53 | const usdcSwapBack = swapResult1; 54 | const txSignature2 = await jupiter.swap({ 55 | inputMint: USDC_MINT, 56 | outputMint: SOL_MINT, 57 | amount: usdcSwapBack.toString(), 58 | }); 59 | console.log(`Swap transaction signature: ${txSignature2}`); 60 | 61 | // Get the swap details from the transaction 62 | const swapResult2 = await jupiter.getSwapDetailsFromTransaction(txSignature2, SOL_MINT); 63 | console.log(`Swap ${usdcSwapBack} USDC to SOL output amount: ${swapResult2}`); 64 | 65 | // Final balances 66 | const finalSolBalance = await wallet.getBalance(); 67 | const finalUsdcBalance = await wallet.getBalance(USDC_MINT); 68 | console.log('\nFinal balances:'); 69 | console.log('SOL:', finalSolBalance); 70 | console.log('USDC:', finalUsdcBalance); 71 | console.log('Total SOL change:', finalSolBalance - initialSolBalance); 72 | console.log('Total USDC change:', finalUsdcBalance - initialUsdcBalance); 73 | 74 | // Verify the swaps were successful 75 | expect(swapResult1).toBeDefined(); 76 | expect(swapResult2).toBeDefined(); 77 | expect(typeof swapResult1).toBe('number'); 78 | expect(typeof swapResult2).toBe('number'); 79 | }, 60000); // 60 second timeout 80 | }); 81 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "edwin-sdk", 3 | "version": "0.8.53", 4 | "description": "SDK for integrating AI agents with DeFi protocols", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "scripts": { 8 | "build": "pnpm format && pnpm lint && tsup", 9 | "test": "vitest run", 10 | "lint": "eslint src --max-warnings 0", 11 | "prepare": "npm run build", 12 | "format": "prettier -c .prettierrc --write \"**/*.{ts,js,cjs,json,md}\"", 13 | "format:check": "prettier -c .prettierrc --check \"**/*.{ts,js,cjs,json,md}\"", 14 | "start:chatagent": "ts-node examples/cli-chat-agent/chat-agent.ts", 15 | "start:mcp-server": "ts-node examples/mcp-server/src/index.ts", 16 | "example:hedera": "ts-node examples/hedera-plugins-usage.ts", 17 | "example:hedera:js": "node examples/hedera-plugins-usage.js", 18 | "clean": "rimraf node_modules" 19 | }, 20 | "keywords": [ 21 | "defi", 22 | "ai", 23 | "agents", 24 | "blockchain", 25 | "ethereum", 26 | "sdk", 27 | "defi", 28 | "lending", 29 | "borrowing", 30 | "staking", 31 | "yield" 32 | ], 33 | "author": "", 34 | "license": "GPL-3.0-only", 35 | "repository": { 36 | "type": "git", 37 | "url": "git+https://github.com/edwin-finance/edwin.git" 38 | }, 39 | "devDependencies": { 40 | "@eslint/js": "^9.15.0", 41 | "@lifi/data-types": "5.15.5", 42 | "@lifi/sdk": "3.4.1", 43 | "@lifi/types": "16.3.0", 44 | "@types/bn.js": "^5.1.6", 45 | "@types/cors": "^2.8.17", 46 | "@types/express": "^5.0.1", 47 | "@types/node": "^22.17.1", 48 | "@types/supertest": "^6.0.3", 49 | "@typescript-eslint/eslint-plugin": "^8.24.1", 50 | "@typescript-eslint/parser": "^8.24.1", 51 | "cors": "^2.8.5", 52 | "dotenv": "^16.4.7", 53 | "eslint": "^9.21.0", 54 | "express": "^4.21.2", 55 | "express-rate-limit": "^7.5.0", 56 | "husky": "^9.1.7", 57 | "node-cache": "^5.1.2", 58 | "raw-body": "^3.0.0", 59 | "rimraf": "^5.0.10", 60 | "supertest": "^7.1.0", 61 | "ts-node": "^10.9.2", 62 | "tsup": "8.3.5", 63 | "typescript": "^5.0.0", 64 | "vitest": "3.0.8", 65 | "zod": "^3.24.2", 66 | "zod-to-json-schema": "^3.24.5" 67 | }, 68 | "dependencies": { 69 | "@aave/contract-helpers": "^1.30.5", 70 | "@bgd-labs/aave-address-book": "^4.7.4", 71 | "@coral-xyz/anchor": "^0.30.1", 72 | "@hashgraph/sdk": "^2.70.0", 73 | "@jup-ag/api": "^6.0.40", 74 | "@langchain/core": "^0.3.37", 75 | "@langchain/langgraph": "^0.2.39", 76 | "@langchain/openai": "^0.3.17", 77 | "@meteora-ag/dlmm": "1.5.4", 78 | "@modelcontextprotocol/sdk": "^1.7.0", 79 | "@solana/spl-token-registry": "^0.2.4574", 80 | "@solana/web3.js": "^1.98.0", 81 | "@story-protocol/core-sdk": "1.3.0-beta.3", 82 | "@vitest/eslint-plugin": "^1.1.25", 83 | "axios": "^1.7.9", 84 | "bn.js": "^5.2.1", 85 | "bs58": "^6.0.0", 86 | "ccxt": "^4.4.63", 87 | "eslint-config-prettier": "^10.0.1", 88 | "ethers": "^5.7.2", 89 | "helius-sdk": "^1.5.1", 90 | "hyperliquid": "^1.6.2", 91 | "prettier": "^3.4.2", 92 | "viem": "^2.23.5", 93 | "winston": "^3.17.0" 94 | }, 95 | "bugs": { 96 | "url": "https://github.com/edwin-finance/edwin/issues" 97 | }, 98 | "homepage": "https://github.com/edwin-finance/edwin#readme", 99 | "directories": { 100 | "doc": "docs", 101 | "example": "examples", 102 | "test": "tests" 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/plugins/solana_wallet/solanaWalletPlugin.ts: -------------------------------------------------------------------------------- 1 | import { EdwinPlugin } from '../../core/classes/edwinPlugin'; 2 | import { EdwinTool, Chain } from '../../core/types'; 3 | import { SolanaWalletService } from './solanaWalletService'; 4 | import { SolanaWalletClient } from '../../core/wallets/solana_wallet'; 5 | import { 6 | SolanaWalletTokenBalanceParameters, 7 | SolanaWalletTokenBalanceParametersSchema, 8 | CurrentSolanaWalletTokenBalanceParameters, 9 | CurrentSolanaWalletTokenBalanceParametersSchema, 10 | SolanaWalletBalancesParameters, 11 | SolanaWalletBalancesParametersSchema, 12 | CurrentSolanaWalletBalancesParameters, 13 | CurrentSolanaWalletBalancesParametersSchema, 14 | } from './parameters'; 15 | 16 | export class SolanaWalletPlugin extends EdwinPlugin { 17 | constructor(wallet: SolanaWalletClient) { 18 | super('solana_wallet', [new SolanaWalletService(wallet)]); 19 | } 20 | 21 | getTools(): Record { 22 | // Combine public and private tools 23 | return { 24 | ...this.getPublicTools(), 25 | ...this.getPrivateTools(), 26 | }; 27 | } 28 | 29 | getPublicTools(): Record { 30 | const solanaWalletService = this.toolProviders.find( 31 | provider => provider instanceof SolanaWalletService 32 | ) as SolanaWalletService; 33 | 34 | return { 35 | getSolanaWalletTokenBalance: { 36 | name: 'get_solana_wallet_token_balance', 37 | description: 'Get the balance of a Solana wallet and a specific token mint (default is SOL)', 38 | schema: SolanaWalletTokenBalanceParametersSchema.schema, 39 | execute: async (params: SolanaWalletTokenBalanceParameters) => { 40 | return await solanaWalletService.getSolanaWalletTokenBalance(params); 41 | }, 42 | }, 43 | getCurrentSolanaWalletTokenBalance: { 44 | name: 'get_current_solana_wallet_token_balance', 45 | description: 'Get the balance of your current Solana wallet and a specific token mint (default is SOL)', 46 | schema: CurrentSolanaWalletTokenBalanceParametersSchema.schema, 47 | execute: async (params: CurrentSolanaWalletTokenBalanceParameters) => { 48 | return await solanaWalletService.getCurrentSolanaWalletTokenBalance( 49 | params.mintAddress ?? undefined 50 | ); 51 | }, 52 | }, 53 | getSolanaWalletBalances: { 54 | name: 'get_solana_wallet_balances', 55 | description: 'Get all token balances of a Solana wallet', 56 | schema: SolanaWalletBalancesParametersSchema.schema, 57 | execute: async (params: SolanaWalletBalancesParameters) => { 58 | return await solanaWalletService.getSolanaWalletBalances(params); 59 | }, 60 | }, 61 | getCurrentSolanaWalletBalances: { 62 | name: 'get_current_solana_wallet_balances', 63 | description: 'Get all token balances of your current Solana wallet', 64 | schema: CurrentSolanaWalletBalancesParametersSchema.schema, 65 | execute: async (_params: CurrentSolanaWalletBalancesParameters) => { 66 | return await solanaWalletService.getCurrentSolanaWalletBalances(); 67 | }, 68 | }, 69 | }; 70 | } 71 | 72 | getPrivateTools(): Record { 73 | // Solana Wallet has no private tools 74 | return {}; 75 | } 76 | 77 | supportsChain = (chain: Chain) => chain.type === 'solana'; 78 | } 79 | 80 | // Factory function to create a new instance of the plugin 81 | export const solanaWallet = (wallet: SolanaWalletClient) => new SolanaWalletPlugin(wallet); 82 | -------------------------------------------------------------------------------- /src/plugins/stader/staderPlugin.ts: -------------------------------------------------------------------------------- 1 | import { EdwinPlugin } from '../../core/classes/edwinPlugin'; 2 | import { EdwinTool, Chain } from '../../core/types'; 3 | import { StaderService } from './staderService'; 4 | import { HederaWalletClient, canSign } from '../../core/wallets/hedera_wallet'; 5 | import { 6 | StaderStakeParametersSchema, 7 | StaderUnstakeParametersSchema, 8 | StaderWithdrawParametersSchema, 9 | StaderGetBalanceParametersSchema, 10 | CurrentStaderGetBalanceParametersSchema, 11 | StaderStakeParameters, 12 | StaderUnstakeParameters, 13 | StaderWithdrawParameters, 14 | StaderGetBalanceParameters, 15 | CurrentStaderGetBalanceParameters, 16 | } from './parameters'; 17 | 18 | export class StaderPlugin extends EdwinPlugin { 19 | constructor(private wallet: HederaWalletClient) { 20 | super('stader', [new StaderService(wallet)]); 21 | } 22 | 23 | getTools(): Record { 24 | return { 25 | ...this.getPublicTools(), 26 | ...this.getPrivateTools(), 27 | }; 28 | } 29 | 30 | getPublicTools(): Record { 31 | const staderService = this.toolProviders.find(provider => provider instanceof StaderService) as StaderService; 32 | 33 | return { 34 | getStaderStakedBalance: { 35 | name: 'get_stader_staked_balance', 36 | description: 'Get the current HBARX balance (staked HBAR) for a specific network', 37 | schema: StaderGetBalanceParametersSchema.schema, 38 | execute: async (params: StaderGetBalanceParameters) => { 39 | return await staderService.getStakedBalanceByNetwork(params); 40 | }, 41 | }, 42 | getCurrentStaderStakedBalance: { 43 | name: 'get_current_stader_staked_balance', 44 | description: 'Get the current HBARX balance (staked HBAR) for your connected wallet', 45 | schema: CurrentStaderGetBalanceParametersSchema.schema, 46 | execute: async (_params: CurrentStaderGetBalanceParameters) => { 47 | return await staderService.getStakedBalance(); 48 | }, 49 | }, 50 | }; 51 | } 52 | 53 | getPrivateTools(): Record { 54 | if (!canSign(this.wallet)) { 55 | return {}; 56 | } 57 | 58 | const staderService = this.toolProviders.find(provider => provider instanceof StaderService) as StaderService; 59 | 60 | return { 61 | staderStake: { 62 | name: 'stader_stake_hbar', 63 | description: 'Stake HBAR to receive HBARX tokens through Stader protocol', 64 | schema: StaderStakeParametersSchema.schema, 65 | execute: async (params: StaderStakeParameters) => { 66 | return await staderService.stake(params); 67 | }, 68 | }, 69 | staderUnstake: { 70 | name: 'stader_unstake_hbarx', 71 | description: 'Unstake HBARX tokens to initiate withdrawal process (requires 24-hour unbonding period)', 72 | schema: StaderUnstakeParametersSchema.schema, 73 | execute: async (params: StaderUnstakeParameters) => { 74 | return await staderService.unstake(params); 75 | }, 76 | }, 77 | staderWithdraw: { 78 | name: 'stader_withdraw_hbar', 79 | description: 'Withdraw HBAR after unstaking period has completed (24 hours)', 80 | schema: StaderWithdrawParametersSchema.schema, 81 | execute: async (params: StaderWithdrawParameters) => { 82 | return await staderService.withdraw(params); 83 | }, 84 | }, 85 | }; 86 | } 87 | 88 | supportsChain = (chain: Chain) => chain.type === 'hedera'; 89 | } 90 | 91 | export const stader = (wallet: HederaWalletClient) => new StaderPlugin(wallet); 92 | -------------------------------------------------------------------------------- /src/plugins/bonzo/bonzoPlugin.ts: -------------------------------------------------------------------------------- 1 | import { EdwinPlugin } from '../../core/classes/edwinPlugin'; 2 | import { EdwinTool, Chain } from '../../core/types'; 3 | import { BonzoService } from './bonzoService'; 4 | import { HederaWalletClient, canSign } from '../../core/wallets/hedera_wallet'; 5 | import { 6 | BonzoSupplyParametersSchema, 7 | BonzoWithdrawParametersSchema, 8 | BonzoBorrowParametersSchema, 9 | BonzoGetSuppliedBalanceParametersSchema, 10 | CurrentBonzoGetSuppliedBalanceParametersSchema, 11 | BonzoSupplyParameters, 12 | BonzoWithdrawParameters, 13 | BonzoBorrowParameters, 14 | BonzoGetSuppliedBalanceParameters, 15 | CurrentBonzoGetSuppliedBalanceParameters, 16 | } from './parameters'; 17 | 18 | export class BonzoPlugin extends EdwinPlugin { 19 | constructor(private wallet: HederaWalletClient) { 20 | super('bonzo', [new BonzoService(wallet)]); 21 | } 22 | 23 | getTools(): Record { 24 | return { 25 | ...this.getPublicTools(), 26 | ...this.getPrivateTools(), 27 | }; 28 | } 29 | 30 | getPublicTools(): Record { 31 | const bonzoService = this.toolProviders.find(provider => provider instanceof BonzoService) as BonzoService; 32 | 33 | return { 34 | getBonzoSuppliedBalance: { 35 | name: 'get_bonzo_supplied_balance', 36 | description: 'Get the supplied balance for a specific token in Bonzo Finance', 37 | schema: BonzoGetSuppliedBalanceParametersSchema.schema, 38 | execute: async (params: BonzoGetSuppliedBalanceParameters) => { 39 | return await bonzoService.getSuppliedBalance(params); 40 | }, 41 | }, 42 | getCurrentBonzoSuppliedBalance: { 43 | name: 'get_current_bonzo_supplied_balance', 44 | description: 'Get the current supplied balance for your connected wallet in Bonzo Finance', 45 | schema: CurrentBonzoGetSuppliedBalanceParametersSchema.schema, 46 | execute: async (params: CurrentBonzoGetSuppliedBalanceParameters) => { 47 | return await bonzoService.getCurrentSuppliedBalance(params.tokenSymbol); 48 | }, 49 | }, 50 | }; 51 | } 52 | 53 | getPrivateTools(): Record { 54 | if (!canSign(this.wallet)) { 55 | return {}; 56 | } 57 | 58 | const bonzoService = this.toolProviders.find(provider => provider instanceof BonzoService) as BonzoService; 59 | 60 | return { 61 | bonzoSupply: { 62 | name: 'bonzo_supply_tokens', 63 | description: 'Supply tokens to Bonzo Finance lending protocol to earn interest', 64 | schema: BonzoSupplyParametersSchema.schema, 65 | execute: async (params: BonzoSupplyParameters) => { 66 | return await bonzoService.supply(params); 67 | }, 68 | }, 69 | bonzoWithdraw: { 70 | name: 'bonzo_withdraw_tokens', 71 | description: 'Withdraw supplied tokens from Bonzo Finance lending protocol', 72 | schema: BonzoWithdrawParametersSchema.schema, 73 | execute: async (params: BonzoWithdrawParameters) => { 74 | return await bonzoService.withdraw(params); 75 | }, 76 | }, 77 | bonzoBorrow: { 78 | name: 'bonzo_borrow_tokens', 79 | description: 'Borrow tokens from Bonzo Finance lending protocol (requires collateral)', 80 | schema: BonzoBorrowParametersSchema.schema, 81 | execute: async (params: BonzoBorrowParameters) => { 82 | return await bonzoService.borrow(params); 83 | }, 84 | }, 85 | }; 86 | } 87 | 88 | supportsChain = (chain: Chain) => chain.type === 'hedera'; 89 | } 90 | 91 | export const bonzo = (wallet: HederaWalletClient) => new BonzoPlugin(wallet); 92 | -------------------------------------------------------------------------------- /src/core/wallets/solana_wallet/README.md: -------------------------------------------------------------------------------- 1 | # Edwin Solana Wallet Clients 2 | 3 | This module provides a flexible architecture for Solana wallets in Edwin, allowing different wallet implementations from keypairs to browser wallets. 4 | 5 | ## Architecture 6 | 7 | The architecture follows a client pattern with: 8 | 9 | 1. **SolanaWalletClient Interface**: Defines the contract for all wallet implementations 10 | 2. **BaseSolanaWalletClient**: Base class with common functionality 11 | 3. **Concrete Clients**: Specific implementations for different wallet types 12 | 13 | ## Available Wallet Clients 14 | 15 | ### KeypairClient 16 | 17 | A wallet client that uses a private key stored as a Keypair. This is ideal for server-side applications and scripts. 18 | 19 | ```typescript 20 | import { KeypairClient, SolanaWalletFactory } from 'edwin'; 21 | 22 | // Create from a base58-encoded private key 23 | const client = new KeypairClient(privateKeyBase58); 24 | // OR using the factory 25 | const client = SolanaWalletFactory.fromPrivateKey(privateKeyBase58); 26 | 27 | // Use with Edwin 28 | const edwin = new Edwin({ solanaClient: client }); 29 | ``` 30 | 31 | ### PhantomClient 32 | 33 | A wallet client that integrates with the Phantom wallet in browser environments. 34 | 35 | ```typescript 36 | import { PhantomClient, SolanaWalletFactory } from 'edwin'; 37 | import { createPhantom, Position } from '@phantom/wallet-sdk'; 38 | 39 | // In a React component 40 | async function initializePhantom() { 41 | // Get Phantom instance 42 | const phantom = await createPhantom({ 43 | position: Position.topRight, 44 | namespace: 'phantom-edwin', 45 | }); 46 | 47 | // Connect to wallet 48 | await phantom.solana.connect(); 49 | 50 | // Create client 51 | const client = new PhantomClient(phantom); 52 | // OR using the factory 53 | const client = SolanaWalletFactory.fromPhantom(phantom); 54 | 55 | // Use with Edwin 56 | const edwin = new Edwin({ solanaClient: client }); 57 | } 58 | ``` 59 | 60 | ### PublicKeyClient 61 | 62 | A read-only wallet client that can be used for operations that don't require signing. 63 | 64 | ```typescript 65 | import { PublicKeyClient, SolanaWalletFactory } from 'edwin'; 66 | 67 | // Create from a base58-encoded public key 68 | const client = new PublicKeyClient('GaRBQJKNFT1LM9UPAWHEDb8NkJWmrJHA9kJvgXs1YGbk'); 69 | // OR using the factory 70 | const client = SolanaWalletFactory.fromPublicKey('GaRBQJKNFT1LM9UPAWHEDb8NkJWmrJHA9kJvgXs1YGbk'); 71 | 72 | // Use with Edwin for read-only operations 73 | const edwin = new Edwin({ solanaClient: client }); 74 | ``` 75 | 76 | ## Helper Utility 77 | 78 | You can check if a wallet client supports signing: 79 | 80 | ```typescript 81 | import { canSign } from 'edwin'; 82 | 83 | if (canSign(client)) { 84 | // This client can sign transactions 85 | } else { 86 | // This is a read-only client 87 | } 88 | ``` 89 | 90 | ## Creating Custom Wallet Clients 91 | 92 | You can create clients for other wallets by implementing the `SolanaWalletClient` interface or extending `BaseSolanaWalletClient`. 93 | 94 | ### Example: Custom Wallet Client 95 | 96 | ```typescript 97 | import { BaseSolanaWalletClient } from 'edwin'; 98 | 99 | class CustomWalletClient extends BaseSolanaWalletClient { 100 | private wallet: CustomWalletProvider; 101 | 102 | constructor(provider: CustomWalletProvider) { 103 | super(provider.publicKey); 104 | this.wallet = provider; 105 | } 106 | 107 | // Implement required methods 108 | async signTransaction(transaction) { 109 | return this.wallet.signTransaction(transaction); 110 | } 111 | 112 | async signAllTransactions(transactions) { 113 | return this.wallet.signAllTransactions(transactions); 114 | } 115 | 116 | async signMessage(message) { 117 | return this.wallet.signMessage(message); 118 | } 119 | 120 | async sendTransaction(connection, transaction) { 121 | return this.wallet.sendTransaction(transaction); 122 | } 123 | } 124 | ``` 125 | -------------------------------------------------------------------------------- /src/plugins/storyprotocol/storyProtocolPlugin.ts: -------------------------------------------------------------------------------- 1 | import { EdwinPlugin } from '../../core/classes/edwinPlugin'; 2 | import { EdwinTool, Chain } from '../../core/types'; 3 | import { StoryProtocolService } from './storyProtocolService'; 4 | import { EdwinEVMWallet } from '../../core/wallets'; 5 | import { 6 | RegisterIPAssetParametersSchema, 7 | AttachTermsParametersSchema, 8 | MintLicenseTokenParametersSchema, 9 | RegisterDerivativeParametersSchema, 10 | PayIPAssetParametersSchema, 11 | ClaimRevenueParametersSchema, 12 | RegisterIPAssetParameters, 13 | AttachTermsParameters, 14 | MintLicenseTokenParameters, 15 | RegisterDerivativeParameters, 16 | PayIPAssetParameters, 17 | ClaimRevenueParameters, 18 | } from './parameters'; 19 | 20 | export class StoryProtocolPlugin extends EdwinPlugin { 21 | constructor(wallet: EdwinEVMWallet) { 22 | super('storyprotocol', [new StoryProtocolService(wallet)]); 23 | } 24 | 25 | getTools(): Record { 26 | // Combine public and private tools 27 | return { 28 | ...this.getPublicTools(), 29 | ...this.getPrivateTools(), 30 | }; 31 | } 32 | 33 | getPublicTools(): Record { 34 | // Story Protocol has no public tools 35 | return {}; 36 | } 37 | 38 | getPrivateTools(): Record { 39 | const storyProtocolService = this.toolProviders.find( 40 | provider => provider instanceof StoryProtocolService 41 | ) as StoryProtocolService; 42 | 43 | return { 44 | registerIPAsset: { 45 | name: 'register_ip_asset', 46 | description: 'Register an IP asset on Story Protocol', 47 | schema: RegisterIPAssetParametersSchema.schema, 48 | execute: async (params: RegisterIPAssetParameters) => { 49 | return await storyProtocolService.registerIPAsset(params); 50 | }, 51 | }, 52 | attachTerms: { 53 | name: 'attach_terms', 54 | description: 'Attach terms to an IP asset on Story Protocol', 55 | schema: AttachTermsParametersSchema.schema, 56 | execute: async (params: AttachTermsParameters) => { 57 | return await storyProtocolService.attachTerms(params); 58 | }, 59 | }, 60 | mintLicenseToken: { 61 | name: 'mint_license_token', 62 | description: 'Mint a license token for an IP asset on Story Protocol', 63 | schema: MintLicenseTokenParametersSchema.schema, 64 | execute: async (params: MintLicenseTokenParameters) => { 65 | return await storyProtocolService.mintLicenseToken(params); 66 | }, 67 | }, 68 | registerDerivative: { 69 | name: 'register_derivative', 70 | description: 'Register a derivative IP asset on Story Protocol', 71 | schema: RegisterDerivativeParametersSchema.schema, 72 | execute: async (params: RegisterDerivativeParameters) => { 73 | return await storyProtocolService.registerDerivative(params); 74 | }, 75 | }, 76 | payIPAsset: { 77 | name: 'pay_ip_asset', 78 | description: 'Pay royalty to an IP asset on Story Protocol', 79 | schema: PayIPAssetParametersSchema.schema, 80 | execute: async (params: PayIPAssetParameters) => { 81 | return await storyProtocolService.payIPAsset(params); 82 | }, 83 | }, 84 | claimRevenue: { 85 | name: 'claim_revenue', 86 | description: 'Claim revenue from an IP asset on Story Protocol', 87 | schema: ClaimRevenueParametersSchema.schema, 88 | execute: async (params: ClaimRevenueParameters) => { 89 | return await storyProtocolService.claimRevenue(params); 90 | }, 91 | }, 92 | }; 93 | } 94 | 95 | supportsChain = (chain: Chain) => chain.type === 'evm'; 96 | } 97 | 98 | export const storyprotocol = (wallet: EdwinEVMWallet) => new StoryProtocolPlugin(wallet); 99 | -------------------------------------------------------------------------------- /src/adapters/mcp/index.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import type { Edwin } from '../../index'; 3 | import edwinLogger from '../../utils/logger'; 4 | 5 | export type GetMcpToolsParams = { 6 | edwin: Edwin; 7 | }; 8 | 9 | /** 10 | * Error class for MCP server errors 11 | */ 12 | export class McpServerError extends Error { 13 | public readonly statusCode: number; 14 | public readonly details: unknown; 15 | 16 | constructor(message: string, statusCode: number = 500, details?: unknown) { 17 | super(message); 18 | this.name = 'McpServerError'; 19 | this.statusCode = statusCode; 20 | this.details = details ?? null; 21 | } 22 | } 23 | 24 | /** 25 | * Helper function to log errors 26 | */ 27 | function logError(message: string, error?: unknown): void { 28 | const errorMessage = error instanceof Error ? error.message : String(error); 29 | const fullMessage = error ? `${message}: ${errorMessage}` : message; 30 | 31 | edwinLogger.error(fullMessage); 32 | if (error instanceof Error && error.stack) { 33 | edwinLogger.error(`Stack trace: ${error.stack}`); 34 | } 35 | } 36 | 37 | /** 38 | * Helper function to log debug info 39 | */ 40 | function logDebug(message: string): void { 41 | edwinLogger.debug(message); 42 | } 43 | 44 | /** 45 | * Converts Edwin tools to MCP format 46 | */ 47 | export async function getMcpToolsFromEdwin({ edwin }: GetMcpToolsParams) { 48 | try { 49 | // Get all tools from Edwin 50 | const edwinTools = await edwin.getTools(); 51 | logDebug(`Found ${Object.keys(edwinTools).length} tools from Edwin`); 52 | 53 | // Convert each tool to MCP format 54 | const mcpTools = Object.entries(edwinTools).map(([key, tool]) => { 55 | // Use the tool's name property in uppercase for MCP convention 56 | const mcpToolName = tool.name ? tool.name.toUpperCase() : key.toUpperCase(); 57 | logDebug(`Converting tool: ${mcpToolName}`); 58 | 59 | // Convert Zod schema to parameters for MCP 60 | const parameters = tool.schema instanceof z.ZodObject ? tool.schema.shape : { input: tool.schema }; 61 | 62 | return { 63 | name: mcpToolName, 64 | description: tool.description, 65 | parameters, 66 | execute: async (args: unknown) => { 67 | try { 68 | logDebug(`Executing tool ${key} with params: ${JSON.stringify(args)}`); 69 | // Parse and validate the input using the tool's schema 70 | const validatedArgs = tool.schema.parse(args); 71 | const result = await tool.execute(validatedArgs); 72 | logDebug(`Tool ${key} executed successfully`); 73 | return { 74 | content: [ 75 | { 76 | type: 'text' as const, 77 | text: JSON.stringify(result, null, 2), 78 | }, 79 | ], 80 | }; 81 | } catch (error) { 82 | logError(`Error executing tool ${key}`, error); 83 | const mcpError = 84 | error instanceof McpServerError 85 | ? error 86 | : new McpServerError( 87 | error instanceof Error ? error.message : 'Unknown error occurred', 88 | 500 89 | ); 90 | 91 | return { 92 | isError: true, 93 | content: [ 94 | { 95 | type: 'text' as const, 96 | text: mcpError.message, 97 | }, 98 | ], 99 | }; 100 | } 101 | }, 102 | }; 103 | }); 104 | 105 | return mcpTools; 106 | } catch (error) { 107 | logError('Failed to convert Edwin tools to MCP format', error); 108 | throw error; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Because our project is still in its early stages, we only provide security fixes for the most recent release: 6 | 7 | | Version | Supported | 8 | | ------- | --------- | 9 | | 0.0.x | ✅ | 10 | | < 0.0.1 | ❌ | 11 | 12 | --- 13 | 14 | ## Reporting a Vulnerability 15 | 16 | We take Edwin's security very seriously. If you discover a vulnerability, please let us know by following these steps: 17 | 18 | ### Private Reporting Process 19 | 20 | 1. **Do Not** create a public GitHub issue about the vulnerability. 21 | 2. Send an email to [security@edwin.finance](mailto:security@edwin.finance) with: 22 | - A clear description of the issue 23 | - Steps to reproduce 24 | - Potential impact 25 | - Any proposed mitigations or fixes 26 | 27 | ### What to Expect 28 | 29 | - **Initial Response (within 48 hours):** We will confirm we've received your report. 30 | - **Progress Updates (every five business days):** We'll inform you of any developments. 31 | - **Resolution Timeline (aim: 15 days):** We strive to address critical issues promptly. 32 | - **Disclosure:** We coordinate with you on an appropriate public disclosure timeline. 33 | 34 | --- 35 | 36 | ## Security Best Practices 37 | 38 | ### For Contributors 39 | 40 | #### API Keys and Secrets 41 | 42 | - Never commit passwords, tokens, or other secrets to the repository. 43 | - Use environment variables as outlined in our secrets management documentation. 44 | - Immediately rotate any exposed credentials. 45 | 46 | #### Dependencies 47 | 48 | - Regularly update all dependencies. 49 | - Stay informed of security advisories related to your packages. 50 | - Use `pnpm audit` to detect known vulnerabilities. 51 | 52 | #### Code Review 53 | 54 | - All changes must be made through pull requests. 55 | - Sensitive changes require additional review. 56 | - Always enable branch protection settings on critical branches. 57 | 58 | ### For Users 59 | 60 | #### Environment Setup 61 | 62 | - Adhere to our secrets management guide for secure configuration. 63 | - Use separate API keys for production and development. 64 | - Rotate all credentials periodically. 65 | 66 | #### Model Provider Security 67 | 68 | - Apply rate limiting on API calls where possible. 69 | - Monitor usage to detect unusual behavior. 70 | - Ensure proper authentication for any publicly exposed endpoints. 71 | 72 | #### Platform Integration 73 | 74 | - Use distinct bot tokens for different environments. 75 | - Limit platform API permissions to only what's necessary. 76 | - Conduct regular audits of access and permissions. 77 | 78 | --- 79 | 80 | ## Security Features 81 | 82 | ### Current Implementation 83 | 84 | - Environment-variable-based secrets management. 85 | - Type-safe API implementations. 86 | - Automated dependency updates. 87 | - Security checks integrated into our CI process. 88 | 89 | ### Planned Improvements 90 | 91 | #### Q1 2025 92 | 93 | - Expanded security-focused documentation. 94 | - Automated vulnerability scanning tools. 95 | 96 | --- 97 | 98 | ## Vulnerability Disclosure Policy 99 | 100 | We follow a coordinated disclosure approach: 101 | 102 | 1. The reporter submits the details of the vulnerability. 103 | 2. Our team verifies and assesses the report. 104 | 3. A fix is developed, reviewed, and tested. 105 | 4. We deploy the fix to supported versions. 106 | 5. Public disclosure occurs after 30 days or as agreed upon. 107 | 108 | --- 109 | 110 | ## Recognition 111 | 112 | We appreciate the work of security researchers who help us improve. Anyone who reports a verified vulnerability will: 113 | 114 | - Be acknowledged in our security credits (or remain anonymous upon request). 115 | - Be listed in our security hall of fame. 116 | - Potentially qualify for our upcoming bug bounty program. 117 | 118 | --- 119 | 120 | ## License Considerations 121 | 122 | This project is released under the MIT License, which means: 123 | 124 | - The software is provided "as is" with no warranties. 125 | - Users are responsible for their security measures. 126 | - Contributors grant a perpetual license for all submitted contributions. 127 | 128 | --- 129 | 130 | ## Contact 131 | 132 | - **Security Issues:** [security@edwin.finance](mailto:security@edwin.finance) 133 | - **General Inquiries:** Join our community on Discord 134 | - **Security Updates:** Check our official security advisory page 135 | -------------------------------------------------------------------------------- /src/plugins/cookie/cookieClient.ts: -------------------------------------------------------------------------------- 1 | import { SupportedChain } from '../../core/types'; 2 | import edwinLogger from '../../utils/logger'; 3 | import { AgentParameters } from './parameters'; 4 | 5 | interface AgentResponse { 6 | ok: { 7 | agentName: string; 8 | contracts: { 9 | chain: number; 10 | contractAddress: string; 11 | }[]; 12 | twitterUsernames: string[]; 13 | mindshare: number; 14 | mindshareDeltaPercent: number; 15 | marketCap: number; 16 | marketCapDeltaPercent: number; 17 | price: number; 18 | priceDeltaPercent: number; 19 | liquidity: number; 20 | volume24Hours: number; 21 | volume24HoursDeltaPercent: number; 22 | holdersCount: number; 23 | holdersCountDeltaPercent: number; 24 | averageImpressionsCount: number; 25 | averageImpressionsCountDeltaPercent: number; 26 | averageEngagementsCount: number; 27 | averageEngagementsCountDeltaPercent: number; 28 | followersCount: number; 29 | smartFollowersCount: number; 30 | topTweets: { 31 | tweetUrl: string; 32 | tweetAuthorProfileImageUrl: string; 33 | tweetAuthorDisplayName: string; 34 | smartEngagementPoints: number; 35 | impressionsCount: number; 36 | }[]; 37 | }; 38 | success: boolean; 39 | error: string | null; 40 | } 41 | 42 | interface GetAgentsPagedResponse { 43 | ok: { 44 | data: AgentResponse['ok'][]; 45 | currentPage: number; 46 | totalPages: number; 47 | totalCount: number; 48 | }; 49 | success: boolean; 50 | error: string | null; 51 | } 52 | 53 | export class CookieSwarmClient { 54 | private apiKey: string; 55 | private baseUrl: string; 56 | supportedChains: SupportedChain[] = ['base']; 57 | 58 | constructor(apiKey: string) { 59 | this.apiKey = apiKey; 60 | this.baseUrl = 'https://api.cookie.fun'; 61 | } 62 | 63 | private async fetch(endpoint: string): Promise { 64 | const response = await fetch(`${this.baseUrl}${endpoint}`, { 65 | headers: { 66 | 'x-api-key': this.apiKey, 67 | }, 68 | method: 'GET', 69 | }); 70 | 71 | if (!response.ok) { 72 | edwinLogger.error('API Error:', { 73 | status: response.status, 74 | statusText: response.statusText, 75 | url: response.url, 76 | }); 77 | throw new Error(`API request failed: ${response.statusText}`); 78 | } 79 | 80 | return response.json(); 81 | } 82 | 83 | async getAgentByTwitter(params: AgentParameters): Promise { 84 | // Verify interval is valid 85 | if (!['_3Days', '_7Days'].includes(params.interval)) { 86 | throw new Error('Invalid interval'); 87 | } 88 | // Note the trailing slash after username 89 | const response = await this.fetch( 90 | `/v2/agents/twitterUsername/${params.username}/?interval=${params.interval}` 91 | ); 92 | return JSON.stringify(response); 93 | } 94 | 95 | async getAgentByContract(params: AgentParameters): Promise { 96 | // Verify interval is valid and cast to Interval 97 | if (!['_3Days', '_7Days'].includes(params.interval)) { 98 | throw new Error('Invalid interval'); 99 | } 100 | const response = await this.fetch( 101 | `/v2/agents/contractAddress/${params.contractAddress}?interval=${params.interval}` 102 | ); 103 | return JSON.stringify(response); 104 | } 105 | 106 | async getAgentsPaged(params: AgentParameters): Promise { 107 | // Verify interval is valid and cast to Interval 108 | if (!['_3Days', '_7Days'].includes(params.interval)) { 109 | throw new Error('Invalid interval'); 110 | } 111 | if (params.pageSize && (params.pageSize < 1 || params.pageSize > 25)) { 112 | throw new Error('Page size must be between 1 and 25'); 113 | } 114 | const response = await this.fetch( 115 | `/v2/agents/agentsPaged?interval=${params.interval}&page=${params.page}&pageSize=${params.pageSize}` 116 | ); 117 | return JSON.stringify(response); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /tests/solana_wallet.test.ts: -------------------------------------------------------------------------------- 1 | import { config } from 'dotenv'; 2 | config(); // Load test environment variables from .env file 3 | 4 | import { describe, it, expect, beforeAll } from 'vitest'; 5 | import { SolanaWalletFactory } from '../src/core/wallets/solana_wallet/factory'; 6 | import { SolanaWalletService } from '../src/plugins/solana_wallet/solanaWalletService'; 7 | import { PublicKeyClient } from '../src/core/wallets/solana_wallet/clients/publickey'; 8 | 9 | // Check if Helius API key is available 10 | const hasHeliusKey = Boolean(process.env.HELIUS_KEY); 11 | 12 | // Skip entire test if no Helius API key 13 | const describeIf = hasHeliusKey ? describe : describe.skip; 14 | 15 | // Known Solana wallets with tokens for testing 16 | const TEST_WALLET_ADDRESS = process.env.SOLANA_PUBLIC_KEY as string; 17 | 18 | describeIf('Solana Wallet Token Balances Tests', () => { 19 | let wallet: PublicKeyClient; 20 | let solanaWalletService: SolanaWalletService; 21 | 22 | beforeAll(() => { 23 | // Create a public key wallet for testing 24 | wallet = SolanaWalletFactory.fromPublicKey(TEST_WALLET_ADDRESS as string); 25 | solanaWalletService = new SolanaWalletService(wallet); 26 | console.log('Running Solana wallet tests with Helius API key'); 27 | }); 28 | 29 | describe('Get Wallet Balances', () => { 30 | it('should get all token balances for a wallet', async () => { 31 | console.log(`Getting token balances for wallet: ${TEST_WALLET_ADDRESS}`); 32 | 33 | const result = await solanaWalletService.getSolanaWalletBalances({ 34 | walletAddress: TEST_WALLET_ADDRESS, 35 | }); 36 | 37 | expect(result).toBeDefined(); 38 | expect(Array.isArray(result)).toBe(true); 39 | 40 | // Log the first few tokens for debugging 41 | if (result.length > 0) { 42 | console.log(`Found ${result.length} tokens in wallet`); 43 | console.log('First few tokens:'); 44 | result.slice(0, 3).forEach(token => { 45 | console.log(`- ${token.symbol}: ${token.balance}`); 46 | }); 47 | 48 | // Validate token structure 49 | const firstToken = result[0]; 50 | expect(firstToken.mint).toBeDefined(); 51 | expect(firstToken.symbol).toBeDefined(); 52 | expect(typeof firstToken.balance).toBe('number'); 53 | } else { 54 | console.log('No tokens found in wallet'); 55 | } 56 | }, 30000); // 30 second timeout 57 | }); 58 | 59 | describe('Get Current Wallet Balances', () => { 60 | it('should get all token balances for the current wallet', async () => { 61 | console.log(`Getting token balances for current wallet: ${wallet.getAddress()}`); 62 | 63 | const result = await solanaWalletService.getCurrentSolanaWalletBalances(); 64 | 65 | expect(result).toBeDefined(); 66 | expect(Array.isArray(result)).toBe(true); 67 | 68 | // The current wallet is the same as TEST_WALLET_ADDRESS in this test 69 | // so we expect similar results as the previous test 70 | if (result.length > 0) { 71 | console.log(`Found ${result.length} tokens in current wallet`); 72 | 73 | // Validate token structure 74 | const firstToken = result[0]; 75 | expect(firstToken.mint).toBeDefined(); 76 | expect(firstToken.symbol).toBeDefined(); 77 | expect(typeof firstToken.balance).toBe('number'); 78 | } else { 79 | console.log('No tokens found in current wallet'); 80 | } 81 | }, 30000); // 30 second timeout 82 | }); 83 | 84 | describe('Error Handling', () => { 85 | it('should handle invalid wallet addresses gracefully', async () => { 86 | console.log('Testing with invalid wallet address'); 87 | 88 | try { 89 | await solanaWalletService.getSolanaWalletBalances({ 90 | walletAddress: 'invalid-address', 91 | }); 92 | // If it doesn't throw, the test fails 93 | expect(true).toBe(false); 94 | } catch (error) { 95 | // Expect an error for invalid addresses 96 | expect(error).toBeDefined(); 97 | console.log('Error handling test passed'); 98 | } 99 | }, 30000); // 30 second timeout 100 | }); 101 | }); 102 | -------------------------------------------------------------------------------- /src/plugins/saucerswap/parameters.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { createParameterSchema } from '../../core/utils/createParameterSchema'; 3 | 4 | export const SaucerSwapQuoteParametersSchema = createParameterSchema( 5 | z.object({ 6 | inputTokenId: z 7 | .string() 8 | .min(1) 9 | .describe( 10 | 'The input token ID. Mainnet: use 0.0.1456986 for WHBAR, 0.0.456858 for USDC, 0.0.731861 for SAUCE. Note: Use WHBAR token ID (not "HBAR" string) for quote operations.' 11 | ), 12 | outputTokenId: z 13 | .string() 14 | .min(1) 15 | .describe( 16 | 'The output token ID. Mainnet: use 0.0.1456986 for WHBAR, 0.0.456858 for USDC, 0.0.731861 for SAUCE' 17 | ), 18 | amount: z.number().positive().describe('The amount of input tokens to quote'), 19 | network: z.enum(['mainnet', 'testnet']).optional().default('mainnet').describe('The Hedera network to use'), 20 | }) 21 | ); 22 | 23 | export const SaucerSwapQuoteExactOutputParametersSchema = createParameterSchema( 24 | z.object({ 25 | inputTokenId: z 26 | .string() 27 | .min(1) 28 | .describe( 29 | 'The input token ID. Mainnet: use 0.0.1456986 for WHBAR, 0.0.456858 for USDC, 0.0.731861 for SAUCE. Note: Use WHBAR token ID (not "HBAR" string) for quote operations.' 30 | ), 31 | outputTokenId: z 32 | .string() 33 | .min(1) 34 | .describe( 35 | 'The output token ID. Mainnet: use 0.0.1456986 for WHBAR, 0.0.456858 for USDC, 0.0.731861 for SAUCE' 36 | ), 37 | amountOut: z.number().positive().describe('The exact amount of output tokens to quote'), 38 | network: z.enum(['mainnet', 'testnet']).optional().default('mainnet').describe('The Hedera network to use'), 39 | }) 40 | ); 41 | 42 | export const SaucerSwapExactInputParametersSchema = createParameterSchema( 43 | z.object({ 44 | inputTokenId: z 45 | .string() 46 | .min(1) 47 | .describe( 48 | 'The input token ID. For swaps: use "HBAR" for native HBAR, or token IDs like 0.0.456858 for USDC, 0.0.731861 for SAUCE. Note: For quotes, use WHBAR token ID (0.0.1456986) instead of "HBAR".' 49 | ), 50 | outputTokenId: z 51 | .string() 52 | .min(1) 53 | .describe( 54 | 'The output token ID. Mainnet: use 0.0.456858 for USDC, 0.0.731861 for SAUCE, 0.0.1456986 for WHBAR. For receiving HBAR, use "HBAR".' 55 | ), 56 | amountIn: z.number().positive().describe('The exact amount of input tokens to swap'), 57 | amountOutMinimum: z.number().min(0).describe('The minimum amount of output tokens to receive'), 58 | deadline: z 59 | .number() 60 | .optional() 61 | .describe('Unix timestamp deadline for the swap (defaults to 1 hour from now). Must be in the future.'), 62 | network: z.enum(['mainnet', 'testnet']).optional().default('mainnet').describe('The Hedera network to use'), 63 | }) 64 | ); 65 | 66 | export const SaucerSwapExactOutputParametersSchema = createParameterSchema( 67 | z.object({ 68 | inputTokenId: z 69 | .string() 70 | .min(1) 71 | .describe( 72 | 'The input token ID. For swaps: use "HBAR" for native HBAR, or token IDs like 0.0.456858 for USDC, 0.0.731861 for SAUCE' 73 | ), 74 | outputTokenId: z 75 | .string() 76 | .min(1) 77 | .describe( 78 | 'The output token ID. Mainnet: use 0.0.456858 for USDC, 0.0.731861 for SAUCE, 0.0.1456986 for WHBAR. For receiving HBAR, use "HBAR".' 79 | ), 80 | amountOut: z.number().positive().describe('The exact amount of output tokens to receive'), 81 | amountInMaximum: z.number().positive().describe('The maximum amount of input tokens to spend'), 82 | deadline: z 83 | .number() 84 | .optional() 85 | .describe('Unix timestamp deadline for the swap (defaults to 1 hour from now). Must be in the future.'), 86 | network: z.enum(['mainnet', 'testnet']).optional().default('mainnet').describe('The Hedera network to use'), 87 | }) 88 | ); 89 | 90 | // Export clean parameter types 91 | export type SaucerSwapQuoteParameters = typeof SaucerSwapQuoteParametersSchema.type; 92 | export type SaucerSwapQuoteExactOutputParameters = typeof SaucerSwapQuoteExactOutputParametersSchema.type; 93 | export type SaucerSwapExactInputParameters = typeof SaucerSwapExactInputParametersSchema.type; 94 | export type SaucerSwapExactOutputParameters = typeof SaucerSwapExactOutputParametersSchema.type; 95 | -------------------------------------------------------------------------------- /src/adapters/elizaos/toolParametersGenerator.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { EdwinTool } from '../../core/types'; 3 | 4 | /** 5 | * Base template for extracting tool parameters 6 | */ 7 | export const toolParametersTemplate = ` 8 | You are an AI assistant specialized in extracting specific information from user messages and format it into a structured JSON response. Your task is to extract specific information from user messages and format it into a structured JSON response. Return the JSON object only, nothing else. 9 | 10 | First, review the recent messages from the conversation: 11 | 12 | 13 | {{recentMessages}} 14 | 15 | 16 | Your goal is to extract the following parameters: 17 | {{schemaParameters}} 18 | Provide the final output in JSON format: 19 | 20 | \`\`\`json 21 | {{schemaJson}} 22 | \`\`\` 23 | 24 | Remember to only return the JSON object, nothing else. 25 | `; 26 | 27 | /** 28 | * Generates a tool parameters prompt from a tool and template 29 | * @param tool - The EdwinTool containing the schema 30 | * @returns The populated tool parameters prompt 31 | */ 32 | export function generateToolParametersPrompt(tool: EdwinTool): string { 33 | const schema = tool.schema as z.ZodTypeAny; 34 | 35 | // Generate parameter descriptions 36 | const schemaParameters = generateParameterDescriptions(schema); 37 | 38 | // Generate JSON template 39 | const jsonTemplate = generateJsonTemplate(schema); 40 | 41 | // Replace template placeholders 42 | return toolParametersTemplate 43 | .replace('{{schemaParameters}}', schemaParameters) 44 | .replace('{{schemaJson}}', JSON.stringify(jsonTemplate, null, 2)); 45 | } 46 | 47 | /** 48 | * Generates parameter descriptions from a schema 49 | */ 50 | function generateParameterDescriptions(schema: z.ZodTypeAny): string { 51 | // Only process object schemas 52 | if (schema._def?.typeName !== 'ZodObject') { 53 | return ''; 54 | } 55 | 56 | const shape = schema._def.shape(); 57 | 58 | // Generate a description line for each parameter 59 | const paramLines = Object.entries(shape).map(([key, value]) => { 60 | const isOptional = value.isOptional?.() || false; 61 | const description = value.description || 'No description provided'; 62 | const type = getTypeString(value); 63 | 64 | return `- ${key}${isOptional ? ' (optional)' : ''}: ${description}. Type: ${type}`; 65 | }); 66 | 67 | return paramLines.join('\n'); 68 | } 69 | 70 | /** 71 | * Generates a JSON template from a schema 72 | */ 73 | function generateJsonTemplate(schema: z.ZodTypeAny): Record { 74 | if (schema._def?.typeName !== 'ZodObject') { 75 | return {}; 76 | } 77 | 78 | const shape = schema._def.shape(); 79 | const template: Record = {}; 80 | 81 | Object.entries(shape).forEach(([key, value]) => { 82 | template[key] = getPlaceholderValue(value); 83 | }); 84 | 85 | return template; 86 | } 87 | 88 | /** 89 | * Gets a human-readable type name 90 | */ 91 | function getTypeString(schema: z.ZodTypeAny): string { 92 | const typeName = schema._def?.typeName; 93 | 94 | switch (typeName) { 95 | case 'ZodOptional': 96 | return getTypeString((schema as z.ZodOptional).unwrap()); 97 | case 'ZodString': 98 | return 'string'; 99 | case 'ZodNumber': 100 | return 'number'; 101 | case 'ZodBoolean': 102 | return 'boolean'; 103 | case 'ZodArray': 104 | return 'array'; 105 | case 'ZodObject': 106 | return 'object'; 107 | default: 108 | return 'unknown'; 109 | } 110 | } 111 | 112 | /** 113 | * Creates an appropriate placeholder value for the JSON template 114 | */ 115 | function getPlaceholderValue(schema: z.ZodTypeAny): unknown { 116 | const typeName = schema._def?.typeName; 117 | 118 | switch (typeName) { 119 | case 'ZodOptional': 120 | return getPlaceholderValue((schema as z.ZodOptional).unwrap()); 121 | case 'ZodString': 122 | return ''; 123 | case 'ZodNumber': 124 | return ''; 125 | case 'ZodBoolean': 126 | return ''; 127 | case 'ZodArray': 128 | return ['']; 129 | case 'ZodObject': { 130 | const shape = schema._def.shape(); 131 | const result: Record = {}; 132 | Object.entries(shape).forEach(([key, value]) => { 133 | result[key] = getPlaceholderValue(value); 134 | }); 135 | return result; 136 | } 137 | default: 138 | return ''; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Edwin 2 | 3 | Thank you for your interest in contributing to Edwin! We welcome everyone—from complete beginners to seasoned experts—to help shape this project. 4 | 5 | --- 6 | 7 | ## Contribution License Agreement 8 | 9 | By contributing to Edwin, you accept that your contributions will be released under the MIT License. This implies: 10 | 11 | 1. You grant a perpetual, worldwide, non-exclusive, royalty-free license to use your work. 12 | 2. Your contributions will be publicly available as Free and Open Source Software (FOSS). 13 | 3. You are authorized to submit the work under this license. 14 | 4. You acknowledge that your contributions are recorded and remain accessible indefinitely. 15 | 16 | --- 17 | 18 | ## How to Contribute 19 | 20 | ### For Developers 21 | 22 | 1. **Extend Edwin's Capabilities** 23 | - Create new actions and protocols 24 | - Improve existing modules and components 25 | 26 | 2. **Improve Infrastructure** 27 | - Tackle open issues and submit pull requests 28 | - Update and verify documentation 29 | - Enhance performance 30 | - Refine deployment methods 31 | 32 | 3. **Fork and Branch** 33 | - Fork the repository and branch off from `main`. 34 | - Name the branch with the issue number and a short description (e.g., `9999--add-test-for-bug-123`). 35 | 36 | 4. **Add Tests** 37 | - If you introduce or modify functionality, add the necessary tests. 38 | 39 | 5. **Test and Lint** 40 | - Ensure that the full test suite passes. 41 | - Verify that your code follows linting requirements. 42 | 43 | 6. **Open a Pull Request** 44 | - Finally, open a pull request with a clear description of your changes. 45 | 46 | --- 47 | 48 | ## Style Guides 49 | 50 | ### Git Commit Messages 51 | 52 | - Use present tense (e.g., "Add feature" not "Added feature"). 53 | - Use imperative mood ("Move cursor to..." not "Moves cursor to..."). 54 | - Limit the first line to 72 characters or fewer. 55 | - Reference any relevant issues or pull requests after the first line. 56 | 57 | ### JavaScript Style Guide 58 | 59 | - Follow [JavaScript Standard Style](https://standardjs.com/). 60 | 61 | ### TypeScript Style Guide 62 | 63 | - Follow [TypeScript Standard Style](https://github.com/standard/ts-standard). 64 | 65 | ### Documentation Style Guide 66 | 67 | - Use [Markdown](https://daringfireball.net/projects/markdown/) for all documentation. 68 | 69 | --- 70 | 71 | ## Additional Notes 72 | 73 | ### Issue and Pull Request Labels 74 | 75 | We categorize issues and PRs with labels to maintain clarity: 76 | 77 | - `bug` – Indicates a reported error or glitch 78 | - `enhancement` – Suggests a new feature or improvement 79 | - `Documentation` – Concerns documentation updates or fixes 80 | - `good first issue` – Recommended for new contributors 81 | 82 | --- 83 | 84 | ## Getting Help 85 | 86 | - Join our [Discord](https://discord.gg/QNA55N3KtF) community 87 | - Submit a GitHub issue if you can't find your answer 88 | 89 | --- 90 | 91 | ## Additional Resources 92 | 93 | - [Gitbook](https://docs.edwin.finance/) 94 | 95 | --- 96 | 97 | ### Code of Conduct 98 | 99 | Our full Code of Conduct can be found in the [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) file. 100 | 101 | #### Our Pledge 102 | 103 | We are committed to maintaining an open, welcoming environment. As contributors and maintainers, we pledge to keep our community free from harassment, regardless of age, body size, disability, ethnicity, gender identity, education level, nationality, personal appearance, race, religion, or sexual identity and orientation. 104 | 105 | #### Our Standards 106 | 107 | Positive behaviors include: 108 | 109 | - Using respectful, inclusive language 110 | - Being mindful of different perspectives 111 | - Embracing constructive criticism 112 | - Prioritizing community well-being 113 | - Demonstrating empathy 114 | 115 | Unacceptable behaviors include: 116 | 117 | - Sexualized language or imagery and unwelcome advances 118 | - Trolling, insults, or personal/political attacks 119 | - Harassment in public or private 120 | - Sharing private information without permission 121 | - Other conduct deemed inappropriate for a professional environment 122 | 123 | #### Our Responsibilities 124 | 125 | Project maintainers are responsible for clarifying these standards and will take fair, appropriate action in response to any violations. They may remove, edit, or reject content that does not align with this Code of Conduct and may ban contributors for repeated or severe misconduct. 126 | 127 | #### Scope 128 | 129 | This Code of Conduct applies within all project areas and in public spaces when someone is acting on behalf of the project or its community—such as via official email, social media channels, or at public events. 130 | 131 | Thank you for contributing to Edwin and helping us build the future of DeFAI! 🎉 132 | -------------------------------------------------------------------------------- /src/core/wallets/solana_wallet/clients/phantom/index.ts: -------------------------------------------------------------------------------- 1 | import { Connection, Keypair, PublicKey, Transaction, VersionedTransaction } from '@solana/web3.js'; 2 | import { BaseSolanaWalletClient } from '../../base_client'; 3 | import edwinLogger from '../../../../../utils/logger'; 4 | 5 | /** 6 | * Phantom provider interface 7 | */ 8 | export interface PhantomProvider { 9 | solana: { 10 | publicKey: PublicKey; 11 | signTransaction(transaction: T): Promise; 12 | signAllTransactions(transactions: T[]): Promise; 13 | signMessage(message: Uint8Array): Promise<{ signature: Uint8Array }>; 14 | sendTransaction(transaction: T): Promise; 15 | connect(): Promise<{ publicKey: PublicKey }>; 16 | disconnect(): Promise; 17 | }; 18 | isConnected: boolean; 19 | show(): void; 20 | } 21 | 22 | /** 23 | * Client for interacting with Solana through Phantom wallet 24 | */ 25 | export class PhantomClient extends BaseSolanaWalletClient { 26 | private provider: PhantomProvider; 27 | 28 | constructor(provider: PhantomProvider) { 29 | if (!provider.solana || !provider.solana.publicKey) { 30 | throw new Error('Phantom wallet is not connected'); 31 | } 32 | 33 | super(provider.solana.publicKey); 34 | this.provider = provider; 35 | } 36 | 37 | /** 38 | * Sign a transaction using Phantom wallet 39 | */ 40 | async signTransaction(transaction: T): Promise { 41 | if (!this.provider.solana) { 42 | throw new Error('Phantom wallet is not connected'); 43 | } 44 | 45 | return this.provider.solana.signTransaction(transaction); 46 | } 47 | 48 | /** 49 | * Sign multiple transactions using Phantom wallet 50 | */ 51 | async signAllTransactions(transactions: T[]): Promise { 52 | if (!this.provider.solana) { 53 | throw new Error('Phantom wallet is not connected'); 54 | } 55 | 56 | return this.provider.solana.signAllTransactions(transactions); 57 | } 58 | 59 | /** 60 | * Sign a message using Phantom wallet 61 | */ 62 | async signMessage(message: Uint8Array): Promise { 63 | if (!this.provider.solana) { 64 | throw new Error('Phantom wallet is not connected'); 65 | } 66 | 67 | const { signature } = await this.provider.solana.signMessage(message); 68 | return signature; 69 | } 70 | 71 | /** 72 | * Send a transaction using Phantom wallet 73 | * Note: In Phantom's case, we don't need Connection or additional signers 74 | * as Phantom handles the sending internally 75 | */ 76 | async sendTransaction( 77 | _connection: Connection, 78 | transaction: T, 79 | _signers?: Keypair[] 80 | ): Promise { 81 | if (!this.provider.solana) { 82 | throw new Error('Phantom wallet is not connected'); 83 | } 84 | 85 | // Phantom's sendTransaction expects the transaction to be already signed 86 | // by any required signers other than the wallet's keypair 87 | return this.provider.solana.sendTransaction(transaction); 88 | } 89 | 90 | /** 91 | * Wait for transaction confirmation 92 | */ 93 | async waitForConfirmationGracefully( 94 | connection: Connection, 95 | signature: string, 96 | timeout: number = 120000 97 | ): Promise<{ err: unknown; confirmationStatus?: 'confirmed' | 'finalized' | 'processed' }> { 98 | const startTime = Date.now(); 99 | 100 | while (Date.now() - startTime < timeout) { 101 | // Fetch the status of the transaction 102 | const { value } = await connection.getSignatureStatus(signature, { 103 | searchTransactionHistory: true, 104 | }); 105 | 106 | if (value) { 107 | // Check for transaction error 108 | if (value.err) { 109 | edwinLogger.error(`Transaction failed: ${JSON.stringify(value.err)}`); 110 | return { err: value.err }; 111 | } 112 | 113 | if (value.confirmationStatus === 'confirmed' || value.confirmationStatus === 'finalized') { 114 | return value; // Transaction is confirmed or finalized 115 | } 116 | } 117 | 118 | // Wait for a short interval before retrying 119 | await new Promise(resolve => setTimeout(resolve, 2000)); 120 | } 121 | 122 | const timeoutError = new Error('Transaction confirmation timed out'); 123 | edwinLogger.error('Transaction confirmation timed out'); 124 | return { err: timeoutError }; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /examples/cli-chat-agent/chat-agent.ts: -------------------------------------------------------------------------------- 1 | import { HumanMessage } from '@langchain/core/messages'; 2 | import { MemorySaver } from '@langchain/langgraph'; 3 | import { createReactAgent } from '@langchain/langgraph/prebuilt'; 4 | import { ChatOpenAI } from '@langchain/openai'; 5 | import * as dotenv from 'dotenv'; 6 | import * as readline from 'readline'; 7 | import { Edwin, EdwinConfig } from '../../src/client'; 8 | import { getLangchainToolsFromEdwin } from '../../src/adapters/langchain'; 9 | 10 | dotenv.config(); 11 | 12 | function validateEnvironment(): void { 13 | const missingVars: string[] = []; 14 | const requiredVars = ['OPENAI_API_KEY', 'EVM_PRIVATE_KEY', 'SOLANA_PRIVATE_KEY']; 15 | 16 | requiredVars.forEach(varName => { 17 | if (!process.env[varName]) { 18 | missingVars.push(varName); 19 | } 20 | }); 21 | 22 | if (missingVars.length > 0) { 23 | console.error('Error: Required environment variables are not set'); 24 | missingVars.forEach(varName => { 25 | console.error(`${varName}=your_${varName.toLowerCase()}_here`); 26 | }); 27 | process.exit(1); 28 | } 29 | } 30 | 31 | validateEnvironment(); 32 | 33 | async function initializeAgent() { 34 | try { 35 | const llm = new ChatOpenAI({ 36 | modelName: 'gpt-4o-mini', 37 | temperature: 0.7, 38 | }); 39 | 40 | if (!process.env.EVM_PRIVATE_KEY) { 41 | console.error('Error: EVM private key is not set'); 42 | throw new Error('EVM private key is not set'); 43 | } 44 | 45 | const plugins = ['meteora', 'aave']; 46 | 47 | console.log('Used plugins:', plugins); 48 | 49 | // Configure Edwin wallets and providers 50 | const edwinConfig: EdwinConfig = { 51 | evmPrivateKey: process.env.EVM_PRIVATE_KEY as `0x${string}`, 52 | solanaPrivateKey: process.env.SOLANA_PRIVATE_KEY, 53 | plugins: plugins, 54 | }; 55 | 56 | // Initialize Edwin SDK 57 | const edwin = new Edwin(edwinConfig); 58 | 59 | const tools = await getLangchainToolsFromEdwin({ edwin }); 60 | const memory = new MemorySaver(); 61 | const config = { configurable: { thread_id: 'Edwin SDK Usage' } }; 62 | 63 | const agent = createReactAgent({ 64 | llm, 65 | tools, 66 | checkpointSaver: memory, 67 | messageModifier: ` 68 | - You are an agent that can interact on-chain evm and solana using the tools of Edwin SDK 69 | - If the tool requires fund, you can response to require fund depositing 70 | - If there is any errors, ask the user to try it again 71 | - If the user asks you to do something that you can't do, you must say so and encourage them to implement it themselves using the Edwin SDK and visit https://edwin.finance/ for more information 72 | - Be concise and helpful with your responses`, 73 | }); 74 | 75 | return { agent, config }; 76 | } catch (error) { 77 | console.error('Failed to initialize agent:', error); 78 | throw error; 79 | } 80 | } 81 | 82 | async function runChatMode(agent: any, config: any) { 83 | console.log("Starting chat mode... Type 'exit' to end."); 84 | 85 | const rl = readline.createInterface({ 86 | input: process.stdin, 87 | output: process.stdout, 88 | }); 89 | 90 | const question = (prompt: string): Promise => new Promise(resolve => rl.question(prompt, resolve)); 91 | 92 | try { 93 | while (true) { 94 | const userInput = await question('\nPrompt: '); 95 | 96 | if (userInput.toLowerCase() === 'exit') { 97 | break; 98 | } 99 | 100 | const stream = await agent.stream({ messages: [new HumanMessage(userInput)] }, config); 101 | 102 | for await (const chunk of stream) { 103 | if ('agent' in chunk) { 104 | console.log(chunk.agent.messages[0].content); 105 | } else if ('tools' in chunk) { 106 | console.log(chunk.tools.messages[0].content); 107 | } 108 | console.log('-------------------'); 109 | } 110 | } 111 | } catch (error) { 112 | if (error instanceof Error) { 113 | console.error('Error:', error.message); 114 | } 115 | process.exit(1); 116 | } finally { 117 | rl.close(); 118 | } 119 | } 120 | 121 | async function main() { 122 | try { 123 | console.log('Starting Agent...'); 124 | const { agent, config } = await initializeAgent(); 125 | 126 | await runChatMode(agent, config); 127 | } catch (error) { 128 | if (error instanceof Error) { 129 | console.error('Error:', error.message); 130 | } 131 | process.exit(1); 132 | } 133 | } 134 | 135 | if (require.main === module) { 136 | main().catch(error => { 137 | console.error('Fatal error:', error); 138 | process.exit(1); 139 | }); 140 | } 141 | -------------------------------------------------------------------------------- /tests/silo.test.ts: -------------------------------------------------------------------------------- 1 | import { config } from 'dotenv'; 2 | config(); // Load test environment variables from .env file 3 | 4 | import { describe, it, expect, beforeAll } from 'vitest'; 5 | import { EdwinEVMWallet } from '../src/core/wallets/evm_wallet/evm_wallet'; 6 | import { SiloService } from '../src/plugins/silo/siloService'; 7 | import { SupplyParameters, WithdrawParameters } from '../src/plugins/silo/parameters'; 8 | 9 | // Check if private key is available 10 | const hasPrivateKey = Boolean(process.env.EVM_PRIVATE_KEY); 11 | 12 | const MIN_USDC_REQUIRED = 1.0; // Minimum USDC.e balance required for testing 13 | const SONIC_USDC_ADDRESS = '0x29219dd400f2Bf60E5a23d13Be72B486D4038894' as `0x${string}`; 14 | 15 | // Skip entire test if no private key 16 | let wallet: EdwinEVMWallet | null = null; 17 | let sufficientBalance = false; 18 | 19 | // Check for sufficient balance if we have a private key 20 | if (hasPrivateKey) { 21 | try { 22 | // Create wallet for pre-check only 23 | wallet = new EdwinEVMWallet(process.env.EVM_PRIVATE_KEY as `0x${string}`); 24 | const balance = await wallet.getTokenBalance('sonic', SONIC_USDC_ADDRESS); 25 | const balanceNum = parseFloat(balance); 26 | 27 | sufficientBalance = balanceNum >= MIN_USDC_REQUIRED; 28 | 29 | console.log(`Pre-check: USDC.e Balance on Sonic: ${balance}`); 30 | if (!sufficientBalance) { 31 | console.log( 32 | `Skipping Silo tests: Insufficient Sonic USDC.e balance (${balance}). Need at least ${MIN_USDC_REQUIRED} USDC.e.` 33 | ); 34 | } 35 | } catch (error) { 36 | console.error('Error in pre-check balance:', error); 37 | sufficientBalance = false; 38 | } 39 | } 40 | 41 | // Skip entire test suite if no key or insufficient balance 42 | const shouldRunTests = hasPrivateKey && sufficientBalance; 43 | const describeIf = shouldRunTests ? describe : describe.skip; 44 | 45 | describeIf('Edwin Silo test', () => { 46 | let silo: SiloService; 47 | 48 | beforeAll(() => { 49 | // We already created the wallet in the pre-check 50 | silo = new SiloService(wallet!); 51 | console.log('Running Silo tests with sufficient balance'); 52 | }); 53 | 54 | it('Test supply action for USDC.e', async () => { 55 | expect(silo).toBeDefined(); 56 | 57 | // Use a smaller amount for the test 58 | const testAmount = MIN_USDC_REQUIRED * 0.1; // Using just 10% of min balance 59 | 60 | try { 61 | // Test supply action 62 | const result = await silo.supply({ 63 | chain: 'sonic', 64 | amount: testAmount, 65 | asset: 'USDC.e', 66 | collateralOnly: false, // ERC4626 standard doesn't have collateralOnly flag 67 | }); 68 | 69 | // Check result format 70 | expect(result).toBeDefined(); 71 | 72 | if (result.includes('simulated for testing')) { 73 | console.log('Note: Transaction was simulated due to test environment'); 74 | // This is a simulated response, make sure it has the expected format 75 | expect(result).toContain('Successfully supplied'); 76 | expect(result).toContain(testAmount.toString()); 77 | expect(result).toContain('USDC.e'); 78 | } else { 79 | // Real transaction completed 80 | expect(result).toContain('Successfully supplied'); 81 | expect(result).toContain('transaction signature:'); 82 | } 83 | } catch (error) { 84 | console.error('Supply transaction failed unexpectedly:', error); 85 | throw error; // Fail the test if there's an unexpected error 86 | } 87 | }, 60000); // 60 second timeout 88 | 89 | it('Test withdraw action for USDC.e', async () => { 90 | expect(silo).toBeDefined(); 91 | 92 | // Use a smaller amount for the test 93 | const testAmount = MIN_USDC_REQUIRED * 0.05; // Using just 5% of min balance 94 | 95 | try { 96 | const result = await silo.withdraw({ 97 | chain: 'sonic', 98 | amount: testAmount, 99 | asset: 'USDC.e', 100 | }); 101 | 102 | // Check result format 103 | expect(result).toBeDefined(); 104 | 105 | if (result.includes('simulated for testing')) { 106 | console.log('Note: Withdrawal transaction was simulated due to test environment'); 107 | // This is a simulated response, make sure it has the expected format 108 | expect(result).toContain('Successfully withdrew'); 109 | expect(result).toContain(testAmount.toString()); 110 | expect(result).toContain('USDC.e'); 111 | } else { 112 | // Real transaction completed 113 | expect(result).toContain('Successfully withdrew'); 114 | expect(result).toContain('transaction signature:'); 115 | } 116 | } catch (error) { 117 | console.error('Withdraw transaction failed unexpectedly:', error); 118 | throw error; // Fail the test if there's an unexpected error 119 | } 120 | }, 60000); // 60 second timeout 121 | }); 122 | -------------------------------------------------------------------------------- /src/plugins/solana_wallet/solanaWalletService.ts: -------------------------------------------------------------------------------- 1 | import { EdwinService } from '../../core/classes/edwinToolProvider'; 2 | import { SolanaWalletClient } from '../../core/wallets/solana_wallet'; 3 | import edwinLogger from '../../utils/logger'; 4 | import { SolanaWalletTokenBalanceParameters, SolanaWalletBalancesParameters } from './parameters'; 5 | import { Helius } from 'helius-sdk'; 6 | 7 | interface TokenInfo { 8 | name?: string; 9 | symbol?: string; 10 | balance?: string; 11 | decimals?: number; 12 | ui_amount?: number; 13 | ui_balance?: number; 14 | } 15 | 16 | export class SolanaWalletService extends EdwinService { 17 | private helius: Helius; 18 | 19 | constructor(private wallet: SolanaWalletClient) { 20 | super(); 21 | this.helius = new Helius(process.env.HELIUS_KEY!); 22 | } 23 | 24 | /** 25 | * Get the balance of any Solana wallet 26 | */ 27 | async getSolanaWalletTokenBalance(params: SolanaWalletTokenBalanceParameters): Promise { 28 | edwinLogger.info(`Getting balance for Solana wallet: ${params.walletAddress}`); 29 | 30 | try { 31 | return await this.wallet.getBalanceOfWallet( 32 | params.walletAddress, 33 | params.mintAddress === null ? undefined : params.mintAddress 34 | ); 35 | } catch (error) { 36 | edwinLogger.error('Failed to get Solana wallet balance:', error); 37 | throw error; 38 | } 39 | } 40 | 41 | /** 42 | * Get the balance of the current Solana wallet 43 | */ 44 | async getCurrentSolanaWalletTokenBalance(mintAddress?: string): Promise { 45 | edwinLogger.info('Getting balance for current Solana wallet'); 46 | 47 | try { 48 | // Use getBalanceOfWallet with current wallet address 49 | return await this.wallet.getBalanceOfWallet(this.wallet.getAddress(), mintAddress); 50 | } catch (error) { 51 | edwinLogger.error('Failed to get current Solana wallet balance:', error); 52 | throw error; 53 | } 54 | } 55 | 56 | /** 57 | * Get all token balances for a Solana wallet 58 | */ 59 | async getSolanaWalletBalances(params: SolanaWalletBalancesParameters): Promise< 60 | Array<{ 61 | mint: string; 62 | symbol: string; 63 | balance: number; 64 | }> 65 | > { 66 | edwinLogger.info(`Getting all token balances for Solana wallet: ${params.walletAddress}`); 67 | 68 | try { 69 | // Get SOL balance first 70 | const solBalance = await this.wallet.getBalanceOfWallet(params.walletAddress); 71 | 72 | // Use the Helius API to get all fungible tokens for the wallet 73 | const response = await this.helius.rpc.getAssetsByOwner({ 74 | ownerAddress: params.walletAddress, 75 | page: 1, 76 | limit: 1000, 77 | displayOptions: { 78 | showFungible: true, 79 | }, 80 | }); 81 | 82 | // Start with SOL balance 83 | const balances = [ 84 | { 85 | mint: 'So11111111111111111111111111111111111111112', // Native SOL mint address 86 | symbol: 'SOL', 87 | balance: solBalance, 88 | }, 89 | ]; 90 | 91 | // Filter for fungible tokens only and map to our desired format 92 | const tokenBalances = response.items 93 | .filter(item => item.interface === 'FungibleToken' || item.interface === 'FungibleAsset') 94 | .map(item => { 95 | // Use type assertion to handle the token_info properties 96 | const tokenInfo = (item.token_info as TokenInfo) || {}; 97 | return { 98 | mint: item.id, 99 | symbol: tokenInfo.symbol || 'UNKNOWN', 100 | balance: tokenInfo.balance 101 | ? Number(tokenInfo.balance) / Math.pow(10, tokenInfo.decimals || 0) 102 | : tokenInfo.ui_amount || tokenInfo.ui_balance || 0, 103 | }; 104 | }); 105 | 106 | // Combine SOL and token balances 107 | return [...balances, ...tokenBalances]; 108 | } catch (error) { 109 | edwinLogger.error('Failed to get Solana wallet token balances:', error); 110 | throw error; 111 | } 112 | } 113 | 114 | /** 115 | * Get all token balances for the current Solana wallet 116 | */ 117 | async getCurrentSolanaWalletBalances(): Promise< 118 | Array<{ 119 | mint: string; 120 | symbol: string; 121 | balance: number; 122 | }> 123 | > { 124 | edwinLogger.info('Getting all token balances for current Solana wallet'); 125 | 126 | try { 127 | // Use getSolanaWalletBalances with current wallet address 128 | return await this.getSolanaWalletBalances({ walletAddress: this.wallet.getAddress() }); 129 | } catch (error) { 130 | edwinLogger.error('Failed to get current Solana wallet token balances:', error); 131 | throw error; 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/plugins/dexscreener/dexscreenerPlugin.ts: -------------------------------------------------------------------------------- 1 | import { EdwinPlugin } from '../../core/classes/edwinPlugin'; 2 | import { EdwinTool, Chain } from '../../core/types'; 3 | import { DexScreenerService } from './dexscreenerService'; 4 | import { z } from 'zod'; 5 | import { createParameterSchema } from '../../core/utils/createParameterSchema'; 6 | import { 7 | SearchParametersSchema, 8 | PairParametersSchema, 9 | TokenPairsParametersSchema, 10 | TokensParametersSchema, 11 | TokenOrdersParametersSchema, 12 | SearchParameters, 13 | PairParameters, 14 | TokenPairsParameters, 15 | TokensParameters, 16 | TokenOrdersParameters, 17 | } from './parameters'; 18 | 19 | // Create an empty schema for endpoints without parameters 20 | const EmptyParametersSchema = createParameterSchema(z.object({})); 21 | type EmptyParameters = typeof EmptyParametersSchema.type; 22 | 23 | export class DexScreenerPlugin extends EdwinPlugin { 24 | constructor() { 25 | super('dexscreener', [new DexScreenerService()]); 26 | } 27 | 28 | getTools(): Record { 29 | // Combine public and private tools 30 | return { 31 | ...this.getPublicTools(), 32 | ...this.getPrivateTools(), 33 | }; 34 | } 35 | 36 | getPublicTools(): Record { 37 | const dexscreenerService = this.toolProviders.find( 38 | provider => provider instanceof DexScreenerService 39 | ) as DexScreenerService; 40 | 41 | return { 42 | dexscreenerSearchPairs: { 43 | name: 'dexscreener_search_pairs', 44 | description: 'Search for pairs matching query on DexScreener', 45 | schema: SearchParametersSchema.schema, 46 | execute: async (params: SearchParameters) => { 47 | return await dexscreenerService.searchPairs(params); 48 | }, 49 | }, 50 | dexscreenerGetPair: { 51 | name: 'dexscreener_get_pair', 52 | description: 'Get one or multiple pairs by chain and pair address on DexScreener', 53 | schema: PairParametersSchema.schema, 54 | execute: async (params: PairParameters) => { 55 | return await dexscreenerService.getPair(params); 56 | }, 57 | }, 58 | dexscreenerGetTokenPairs: { 59 | name: 'dexscreener_get_token_pairs', 60 | description: 'Get the pools of a given token address on DexScreener', 61 | schema: TokenPairsParametersSchema.schema, 62 | execute: async (params: TokenPairsParameters) => { 63 | return await dexscreenerService.getTokenPairs(params); 64 | }, 65 | }, 66 | dexscreenerGetTokens: { 67 | name: 'dexscreener_get_tokens', 68 | description: 'Get one or multiple pairs by token address on DexScreener', 69 | schema: TokensParametersSchema.schema, 70 | execute: async (params: TokensParameters) => { 71 | return await dexscreenerService.getTokens(params); 72 | }, 73 | }, 74 | dexscreenerGetLatestTokenProfiles: { 75 | name: 'dexscreener_get_latest_token_profiles', 76 | description: 'Get the latest token profiles on DexScreener', 77 | schema: EmptyParametersSchema.schema, 78 | execute: async (_params: EmptyParameters) => { 79 | return await dexscreenerService.getLatestTokenProfiles(); 80 | }, 81 | }, 82 | dexscreenerGetLatestBoostedTokens: { 83 | name: 'dexscreener_get_latest_boosted_tokens', 84 | description: 'Get the latest boosted tokens on DexScreener', 85 | schema: EmptyParametersSchema.schema, 86 | execute: async (_params: EmptyParameters) => { 87 | return await dexscreenerService.getLatestBoostedTokens(); 88 | }, 89 | }, 90 | dexscreenerGetTopBoostedTokens: { 91 | name: 'dexscreener_get_top_boosted_tokens', 92 | description: 'Get the tokens with most active boosts on DexScreener', 93 | schema: EmptyParametersSchema.schema, 94 | execute: async (_params: EmptyParameters) => { 95 | return await dexscreenerService.getTopBoostedTokens(); 96 | }, 97 | }, 98 | dexscreenerGetTokenOrders: { 99 | name: 'dexscreener_get_token_orders', 100 | description: 'Check orders paid for a token on DexScreener', 101 | schema: TokenOrdersParametersSchema.schema, 102 | execute: async (params: TokenOrdersParameters) => { 103 | return await dexscreenerService.getTokenOrders(params.chainId, params.tokenAddress); 104 | }, 105 | }, 106 | }; 107 | } 108 | 109 | getPrivateTools(): Record { 110 | // DexScreener has no private tools 111 | return {}; 112 | } 113 | 114 | supportsChain = (_chain: Chain) => true; // DexScreener API is chain-agnostic 115 | } 116 | 117 | export const dexscreener = () => new DexScreenerPlugin(); 118 | -------------------------------------------------------------------------------- /src/plugins/hedera_wallet/parameters.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { createParameterSchema } from '../../core/utils/createParameterSchema'; 3 | 4 | // Schema for checking HBAR balance on Hedera 5 | export const HederaWalletBalanceParametersSchema = createParameterSchema( 6 | z.object({ 7 | accountId: z.string().describe('The Hedera account ID to check the HBAR balance of (format: 0.0.123456)'), 8 | }) 9 | ); 10 | 11 | export const CurrentHederaWalletBalanceParametersSchema = createParameterSchema( 12 | z.object({}).describe('No parameters needed - gets HBAR balance for your current Hedera wallet') 13 | ); 14 | 15 | // Schema for getting HTS token balance on Hedera 16 | export const HederaWalletTokenBalanceParametersSchema = createParameterSchema( 17 | z.object({ 18 | accountId: z.string().describe('The Hedera account ID to check HTS token balance of (format: 0.0.123456)'), 19 | tokenId: z 20 | .string() 21 | .describe('The Hedera Token Service (HTS) token ID to check balance for (format: 0.0.123456)'), 22 | }) 23 | ); 24 | 25 | export const CurrentHederaWalletTokenBalanceParametersSchema = createParameterSchema( 26 | z.object({ 27 | tokenId: z 28 | .string() 29 | .describe('The Hedera Token Service (HTS) token ID to check balance for (format: 0.0.123456)'), 30 | }) 31 | ); 32 | 33 | // Schema for getting Hedera account information 34 | export const HederaWalletAccountInfoParametersSchema = createParameterSchema( 35 | z.object({ 36 | accountId: z.string().describe('The Hedera account ID to get information for (format: 0.0.123456)'), 37 | }) 38 | ); 39 | 40 | export const CurrentHederaWalletAccountInfoParametersSchema = createParameterSchema( 41 | z.object({}).describe('No parameters needed - gets account information for your current Hedera wallet') 42 | ); 43 | 44 | // Schema for HBAR transfer on Hedera 45 | export const HederaWalletTransferHbarParametersSchema = createParameterSchema( 46 | z.object({ 47 | toAccountId: z.string().describe('The recipient Hedera account ID (format: 0.0.123456)'), 48 | amount: z.number().positive().describe('Amount of HBAR (Hedera native cryptocurrency) to transfer'), 49 | }) 50 | ); 51 | 52 | // Schema for HTS token transfer on Hedera 53 | export const HederaWalletTransferTokenParametersSchema = createParameterSchema( 54 | z.object({ 55 | toAccountId: z.string().describe('The recipient Hedera account ID (format: 0.0.123456)'), 56 | tokenId: z.string().describe('The Hedera Token Service (HTS) token ID to transfer (format: 0.0.123456)'), 57 | amount: z.number().positive().describe('Amount of HTS tokens to transfer (in human-readable format)'), 58 | }) 59 | ); 60 | 61 | // Schema for HTS token lookup by name/symbol on Hedera 62 | export const HederaWalletTokenLookupParametersSchema = createParameterSchema( 63 | z.object({ 64 | tokenName: z 65 | .string() 66 | .describe('The HTS token name or symbol to lookup on Hedera (e.g., "USDC", "SAUCE", "KARATE")'), 67 | network: z 68 | .enum(['mainnet', 'testnet', 'previewnet']) 69 | .optional() 70 | .describe('The Hedera network to search on (mainnet, testnet, or previewnet - defaults to mainnet)'), 71 | }) 72 | ); 73 | 74 | // Schema for wrapping HBAR to WHBAR 75 | export const HederaWalletWrapHbarParametersSchema = createParameterSchema( 76 | z.object({ 77 | amount: z.number().positive().describe('Amount of HBAR to wrap into WHBAR (wrapped HBAR ERC20 token)'), 78 | network: z 79 | .enum(['mainnet', 'testnet']) 80 | .optional() 81 | .default('mainnet') 82 | .describe('The Hedera network to use (mainnet or testnet - defaults to mainnet)'), 83 | }) 84 | ); 85 | 86 | // Schema for unwrapping WHBAR to HBAR 87 | export const HederaWalletUnwrapWhbarParametersSchema = createParameterSchema( 88 | z.object({ 89 | amount: z.number().positive().describe('Amount of WHBAR to unwrap back into native HBAR'), 90 | network: z 91 | .enum(['mainnet', 'testnet']) 92 | .optional() 93 | .default('mainnet') 94 | .describe('The Hedera network to use (mainnet or testnet - defaults to mainnet)'), 95 | }) 96 | ); 97 | 98 | // Export clean parameter types 99 | export type HederaWalletBalanceParameters = typeof HederaWalletBalanceParametersSchema.type; 100 | export type CurrentHederaWalletBalanceParameters = typeof CurrentHederaWalletBalanceParametersSchema.type; 101 | export type HederaWalletTokenBalanceParameters = typeof HederaWalletTokenBalanceParametersSchema.type; 102 | export type CurrentHederaWalletTokenBalanceParameters = typeof CurrentHederaWalletTokenBalanceParametersSchema.type; 103 | export type HederaWalletAccountInfoParameters = typeof HederaWalletAccountInfoParametersSchema.type; 104 | export type CurrentHederaWalletAccountInfoParameters = typeof CurrentHederaWalletAccountInfoParametersSchema.type; 105 | export type HederaWalletTransferHbarParameters = typeof HederaWalletTransferHbarParametersSchema.type; 106 | export type HederaWalletTransferTokenParameters = typeof HederaWalletTransferTokenParametersSchema.type; 107 | export type HederaWalletTokenLookupParameters = typeof HederaWalletTokenLookupParametersSchema.type; 108 | export type HederaWalletWrapHbarParameters = typeof HederaWalletWrapHbarParametersSchema.type; 109 | export type HederaWalletUnwrapWhbarParameters = typeof HederaWalletUnwrapWhbarParametersSchema.type; 110 | --------------------------------------------------------------------------------