UNPKG

edwin-sdk

Version:

SDK for integrating AI agents with DeFi protocols

343 lines (260 loc) 12.2 kB
# Edwin SDK: Plugin Integration Guide This guide walks you through the process of creating a new plugin for the Edwin SDK. Edwin is a TypeScript library that serves as a bridge between AI agents and DeFi protocols, providing a unified interface for interacting with various blockchain networks and protocols. ## Table of Contents 1. [Plugin Architecture Overview](#plugin-architecture-overview) 2. [Directory and File Structure](#directory-and-file-structure) 3. [Creating a New Plugin](#creating-a-new-plugin) - [Step 1: Create the Plugin Directory](#step-1-create-the-plugin-directory) - [Step 2: Define Parameter Schemas](#step-2-define-parameter-schemas) - [Step 3: Implement the Protocol Service](#step-3-implement-the-protocol-service) - [Step 4: Create the Plugin Class](#step-4-create-the-plugin-class) - [Step 5: Export the Plugin](#step-5-export-the-plugin) 4. [Testing Your Plugin](#testing-your-plugin) 5. [Integrating with the Edwin Client](#integrating-with-the-edwin-client) 6. [Best Practices](#best-practices) 7. [Troubleshooting](#troubleshooting) ## Plugin Architecture Overview The Edwin SDK uses a plugin-based architecture to provide a modular and extensible system for protocol integrations. Each plugin: - Extends the `EdwinPlugin` base class - Provides tools for specific protocols - Defines parameter schemas using Zod - Implements protocol-specific functionality in service classes The main components of the plugin architecture are: 1. **EdwinPlugin**: Base class for all protocol plugins 2. **EdwinTool**: Interface for tools exposed by plugins 3. **EdwinService**: Base class for services that implement protocol interactions 4. **Parameter Schemas**: Zod schemas that define the input parameters for tools ## Directory and File Structure When creating a new plugin, you should follow this directory and file structure: ``` src/ └── plugins/ └── yourplugin/ ├── index.ts # Exports the plugin ├── parameters.ts # Parameter schemas and types ├── yourPluginPlugin.ts # Plugin class implementation ├── yourPluginService.ts # Protocol service implementation └── utils.ts # Optional utility functions ``` ## Creating a New Plugin ### Step 1: Create the Plugin Directory First, create a new directory for your plugin in the `src/plugins` directory: ```bash mkdir -p src/plugins/yourplugin ``` ### Step 2: Define Parameter Schemas Create a `parameters.ts` file to define the parameter schemas for your plugin's tools using Zod: ```typescript // src/plugins/yourplugin/parameters.ts import { z } from 'zod'; import { createParameterSchema } from '../../core/utils/createParameterSchema'; // Define parameter schemas with validation and descriptions export const YourOperationParametersSchema = createParameterSchema( z.object({ chain: z.string().min(1).describe('The blockchain network to use'), asset: z.string().min(1).describe('The asset to interact with'), amount: z.number().positive().describe('The amount to use in the operation'), // Add more parameters as needed }) ); // Export clean parameter types without the "Schema" suffix export type YourOperationParameters = typeof YourOperationParametersSchema.type; ``` ### Step 3: Implement the Protocol Service Create a service class that extends `EdwinService` to implement the protocol-specific functionality: ```typescript // src/plugins/yourplugin/yourPluginService.ts import { EdwinService } from '../../core/classes/edwinToolProvider'; import { EdwinEVMWallet } from '../../core/wallets'; // Or EdwinSolanaWallet import edwinLogger from '../../utils/logger'; import { YourOperationParameters } from './parameters'; import { withRetry } from '../../utils'; export class YourPluginService extends EdwinService { private wallet: EdwinEVMWallet; // Or EdwinSolanaWallet constructor(wallet: EdwinEVMWallet) { // Or EdwinSolanaWallet super(); this.wallet = wallet; } async yourOperation(params: YourOperationParameters): Promise<any> { const { chain, asset, amount } = params; edwinLogger.info(`Calling YourPlugin to perform operation with ${amount} ${asset} on ${chain}`); try { // Implement your protocol-specific logic here // Use this.wallet to interact with the blockchain // Example: Using withRetry for blockchain operations const result = await withRetry(async () => { // Your operation implementation return { success: true, txHash: '0x...' }; }, 'YourPlugin operation'); return result; } catch (error: unknown) { edwinLogger.error('YourPlugin operation error:', error); const message = error instanceof Error ? error.message : String(error); throw new Error(`YourPlugin operation failed: ${message}`); } } } ``` ### Step 4: Create the Plugin Class Create a plugin class that extends `EdwinPlugin` to define the tools your plugin provides: ```typescript // src/plugins/yourplugin/yourPluginPlugin.ts import { EdwinPlugin } from '../../core/classes/edwinPlugin'; import { EdwinTool, Chain } from '../../core/types'; import { YourPluginService } from './yourPluginService'; import { EdwinEVMWallet } from '../../core/wallets'; // Or EdwinSolanaWallet import { YourOperationParameters, YourOperationParametersSchema } from './parameters'; export class YourPlugin extends EdwinPlugin { constructor(wallet: EdwinEVMWallet) { // Or EdwinSolanaWallet super('yourplugin', [new YourPluginService(wallet)]); } getTools(): Record<string, EdwinTool> { const yourPluginService = this.toolProviders.find( provider => provider instanceof YourPluginService ) as YourPluginService; return { yourPluginOperation: { name: 'yourplugin_operation', description: 'Perform an operation using YourPlugin', schema: YourOperationParametersSchema.schema, execute: async (params: YourOperationParameters) => { return await yourPluginService.yourOperation(params); }, }, // Add more tools as needed }; } // Define which chains your plugin supports supportsChain = (chain: Chain) => ['ethereum', 'base', 'avalanche'].includes(chain.name); // For EVM chains // Or for Solana: supportsChain = (chain: Chain) => chain.name === 'solana'; } // Factory function to create a new instance of your plugin export const yourPlugin = (wallet: EdwinEVMWallet) => new YourPlugin(wallet); // Or EdwinSolanaWallet ``` ### Step 5: Export the Plugin Create an `index.ts` file to export your plugin: ```typescript // src/plugins/yourplugin/index.ts export * from './yourPluginPlugin'; export * from './yourPluginService'; ``` Then, update the main plugins index file to include your plugin: ```typescript // src/plugins/index.ts // Add your plugin to the exports export * from './yourplugin'; ``` ## Testing Your Plugin Testing is a crucial part of plugin development. Create test files for your plugin in the `tests` directory: ``` tests/ ├── yourplugin.test.ts └── yourpluginService.test.ts ``` ### Writing Tests Here's an example of how to write tests for your plugin: ```typescript // tests/yourplugin.test.ts import { describe, it, expect } from 'vitest'; import { YourPluginService } from '../src/plugins/yourplugin/yourPluginService'; import { EdwinEVMWallet } from '../src/core/wallets'; // Or EdwinSolanaWallet describe('YourPluginService', () => { let service: YourPluginService; let wallet: EdwinEVMWallet; beforeEach(() => { // Use a real wallet with a test private key wallet = new EdwinEVMWallet(process.env.TEST_PRIVATE_KEY as `0x${string}`); service = new YourPluginService(wallet); }); it('should perform an operation successfully', async () => { // Arrange const params = { chain: 'ethereum', asset: 'eth', amount: 1.0, }; // Act const result = await service.yourOperation(params); // Assert expect(result).toBeDefined(); expect(result.success).toBe(true); }); // Add more tests as needed }); ``` ### Running Tests To run tests for your plugin, use the following command: ```bash pnpm test ``` For testing specific files: ```bash pnpm test tests/plugins/yourplugin ``` ### Testing Best Practices When implementing tests for DeFi protocol integrations: 1. **Maintain two types of tests**: - Mock tests that use test doubles for reliable CI/CD pipeline execution - Integration tests that use properly funded wallets to verify actual protocol interactions 2. **Never replace or remove actual protocol implementations** just to make tests pass. Tests should be fixed by: - Properly configuring test environments with required wallet keys and balances - Adding supplementary mocks where needed while preserving core implementation logic - Using test doubles only for external API calls and network interactions - Maintaining the integrity of protocol-specific business logic 3. **Keep mock implementations strictly in test files** and never add them to the SDK source files themselves, even when using environment-based conditionals. ## Integrating with the Edwin Client To make your plugin accessible from the Edwin client, update the `src/client/edwin.ts` file: 1. Import your plugin: ```typescript // src/client/edwin.ts import { yourPlugin, YourPlugin } from '../plugins/yourplugin'; ``` 2. Add your plugin to the `EdwinPlugins` interface: ```typescript // src/client/edwin.ts interface EdwinPlugins { // Existing plugins yourplugin?: YourPlugin; } ``` 3. Initialize your plugin in the constructor: ```typescript // src/client/edwin.ts constructor(config: EdwinConfig) { // Initialize wallets if (config.evmPrivateKey) { this.wallets.evm = new EdwinEVMWallet(config.evmPrivateKey); } // ... // Initialize plugins if (this.wallets.evm) { // Existing plugins this.plugins.yourplugin = yourPlugin(this.wallets.evm); } // ... } ``` ## Best Practices 1. **Error Handling**: Always use proper error handling and logging in your plugin. 2. **Type Safety**: Use TypeScript types and Zod schemas for type safety. 3. **Retry Mechanism**: Use the `withRetry` utility for blockchain operations that might fail. 4. **Logging**: Use the `edwinLogger` for consistent logging across the SDK. 5. **Parameter Validation**: Validate all input parameters using Zod schemas. 6. **Chain Support**: Clearly define which chains your plugin supports. 7. **Documentation**: Add clear descriptions to your tools and parameters. 8. **Testing**: Write comprehensive tests for your plugin. ## Troubleshooting ### Common Issues 1. **Plugin not loading**: Ensure your plugin is properly exported and imported in the Edwin client. 2. **Parameter validation errors**: Check your Zod schemas for correct validation rules. 3. **Wallet connection issues**: Verify that the wallet is properly initialized and connected. 4. **Transaction failures**: Use the `withRetry` utility and proper error handling. ### Debugging Tips 1. Use `edwinLogger.debug()` for detailed logging during development. 2. Check the transaction simulation before sending it to the blockchain. 3. Verify wallet balances and permissions before performing operations. 4. Test your plugin with mock implementations before using real blockchain interactions. --- By following this guide, you should be able to create a new plugin for the Edwin SDK that integrates seamlessly with the existing architecture. If you encounter any issues or have questions, please refer to the [Edwin SDK documentation](https://docs.edwin.finance), join the [Edwin Discord community](https://discord.com/invite/QNA55N3KtF) for support and discussions, or open an issue on the GitHub repository.