diff --git a/.github/workflows/npm-deploy.yml b/.github/workflows/npm-deploy.yml new file mode 100644 index 0000000..9683956 --- /dev/null +++ b/.github/workflows/npm-deploy.yml @@ -0,0 +1,112 @@ +name: Publish Package + +on: + push: + branches: + - 1.x + workflow_dispatch: + +jobs: + verify_version: + runs-on: ubuntu-latest + outputs: + should_publish: ${{ steps.check.outputs.should_publish }} + version: ${{ steps.check.outputs.version }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Check if package.json version changed + id: check + run: | + echo "Current branch: ${{ github.ref }}" + + # Get current version + CURRENT_VERSION=$(jq -r .version package.json) + echo "Current version: $CURRENT_VERSION" + + # Get previous commit hash + git rev-parse HEAD~1 || git rev-parse HEAD + PREV_COMMIT=$(git rev-parse HEAD~1 2>/dev/null || git rev-parse HEAD) + + # Check if package.json changed + if git diff --name-only HEAD~1 HEAD | grep "package.json"; then + echo "Package.json was changed in this commit" + + # Get previous version if possible + if git show "$PREV_COMMIT:package.json" 2>/dev/null; then + PREV_VERSION=$(git show "$PREV_COMMIT:package.json" | jq -r .version) + echo "Previous version: $PREV_VERSION" + + if [ "$CURRENT_VERSION" != "$PREV_VERSION" ]; then + echo "Version changed from $PREV_VERSION to $CURRENT_VERSION" + echo "should_publish=true" >> $GITHUB_OUTPUT + else + echo "Version unchanged" + echo "should_publish=false" >> $GITHUB_OUTPUT + fi + else + echo "First commit with package.json, will publish" + echo "should_publish=true" >> $GITHUB_OUTPUT + fi + else + echo "Package.json not changed in this commit" + echo "should_publish=false" >> $GITHUB_OUTPUT + fi + + echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT + + publish: + needs: verify_version + if: needs.verify_version.outputs.should_publish == 'true' + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Create Git tag + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git tag -a "v${{ needs.verify_version.outputs.version }}" -m "Release v${{ needs.verify_version.outputs.version }}" + git push origin "v${{ needs.verify_version.outputs.version }}" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + + - name: Install dependencies + run: bun install + + - name: Build package + run: bun run build + + - name: Publish to npm + run: bun publish + env: + NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }} + + create_release: + needs: [verify_version, publish] + if: needs.verify_version.outputs.should_publish == 'true' + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Create GitHub Release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: "v${{ needs.verify_version.outputs.version }}" + release_name: "v${{ needs.verify_version.outputs.version }}" + body: "Release v${{ needs.verify_version.outputs.version }}" + draft: false + prerelease: false diff --git a/.gitignore b/.gitignore index c98e331..ab0378b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules .turbo +dist diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9b4fd09 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Shaw Walters and elizaOS Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index c0c3e79..7fc82a0 100644 --- a/README.md +++ b/README.md @@ -2,348 +2,278 @@ Core Solana blockchain plugin for Eliza OS that provides essential services and actions for token operations, trading, and DeFi integrations. -## Overview +## 🚀 Quick Start -The Solana plugin serves as a foundational component of Eliza OS, bridging Solana blockchain capabilities with the Eliza ecosystem. It provides crucial services for token operations, trading, portfolio management, and DeFi integrations, enabling both automated and user-directed interactions with the Solana blockchain. - -## Features - -### Token Operations - -- **Token Creation**: Deploy new tokens with customizable metadata -- **Token Transfers**: Send and receive tokens securely -- **Balance Management**: Track and manage token balances -- **Portfolio Analytics**: Real-time portfolio valuation and tracking - -### Trading Operations - -- **Token Swaps**: Execute trades between tokens using Jupiter aggregator -- **Order Management**: Place and track token orders -- **Price Monitoring**: Real-time price feeds and historical data -- **Automated Trading**: Configurable trading strategies and automation - -### DeFi Integration - -- **Liquidity Analysis**: Monitor and analyze pool liquidity -- **Market Making**: Automated market making capabilities -- **Yield Optimization**: Smart routing for optimal yields -- **Risk Management**: Advanced risk scoring and monitoring - -### Trust & Security - -- **Trust Scoring**: Dynamic trust score calculation for tokens -- **Risk Assessment**: Real-time risk evaluation for trades -- **Performance Tracking**: Historical performance monitoring -- **Simulation Mode**: Test strategies without real transactions - -## Security Features - -### Access Control - -- **Wallet Management**: Secure wallet key derivation and storage -- **Permission Scoping**: Granular control over trading permissions -- **TEE Integration**: Trusted Execution Environment support -- **Key Protection**: Secure private key handling - -### Risk Management - -- **Trade Limits**: Configurable transaction limits -- **Slippage Protection**: Automatic slippage controls -- **Validation Checks**: Multi-level transaction validation -- **Simulation Support**: Pre-execution transaction simulation - -## Installation +### Installation ```bash -npm install @elizaos/plugin-solana +bun add @elizaos/plugin-solana ``` -## Configuration +### Basic Usage -Configure the plugin by setting the following environment variables: +Add the plugin to your character configuration: ```typescript -const solanaEnvSchema = { - WALLET_SECRET_SALT: string(optional), - WALLET_SECRET_KEY: string, - WALLET_PUBLIC_KEY: string, - SOL_ADDRESS: string, - SLIPPAGE: string, - SOLANA_RPC_URL: string, - HELIUS_API_KEY: string, - BIRDEYE_API_KEY: string, +const character = { + // ... other character config + plugins: [ + "@elizaos/plugin-solana", + // ... other plugins + ], }; ``` -## Usage +## 📋 Prerequisites -### Basic Setup - -```typescript -import { solanaPlugin } from "@elizaos/plugin-solana"; - -// Initialize the plugin -const runtime = await initializeRuntime({ - plugins: [solanaPlugin], -}); -``` +- [ElizaOS](https://github.com/elizaos/eliza) v1.0.0 or higher +- Node.js 23+ or Bun +- Required API credentials (see Configuration) -### Services +## 🔧 Configuration -#### TokenProvider +### Environment Variables -Manages token operations and information retrieval. +Create a `.env` file in your project root: -```typescript -const tokenProvider = new TokenProvider( - tokenAddress, - walletProvider, - cacheManager -); -await tokenProvider.getTokensInWallet(runtime); +```bash +BIRDEYE_API_KEY=your-birdeye-api-key +HELIUS_API_KEY=your-helius-api-key +SLIPPAGE=your-slippage-value +SOLANA_PRIVATE_KEY=your-solana-private-key +SOLANA_PUBLIC_KEY=your-solana-public-key +SOLANA_RPC_URL=your-solana-rpc-url +SOL_ADDRESS=your-sol-address +WALLET_PRIVATE_KEY=your-wallet-private-key +WALLET_PUBLIC_KEY=your-wallet-public-key +WALLET_SECRET_KEY=your-wallet-secret-key +WALLET_SECRET_SALT=your-wallet-secret-salt ``` -#### WalletProvider +### Configuration Options -Handles wallet operations and portfolio management. +The plugin accepts the following configuration options: ```typescript -const walletProvider = new WalletProvider(connection, publicKey); -await walletProvider.getFormattedPortfolio(runtime); +// Example configuration +const config = { + // Add specific configuration options here +}; ``` -#### TrustScoreProvider +## ✨ Features -Evaluates and manages trust scores for tokens and trading activities. +### Core Features -```typescript -const trustScore = await runtime.getProvider("trustScore"); -``` +- **Token Creation**: Deploy new tokens with customizable metadata. +- **Token Transfers**: Send and receive tokens securely. +- **Balance Management**: Track and manage token balances. +- **Portfolio Analytics**: Real-time portfolio valuation and tracking. +- **Token Swaps**: Execute trades between tokens using Jupiter aggregator. +- **Order Management**: Place and track token orders. +- **Price Monitoring**: Real-time price feeds and historical data. +- **Automated Trading**: Configurable trading strategies and automation. +- **Liquidity Analysis**: Monitor and analyze pool liquidity. +- **Market Making**: Automated market making capabilities. +- **Yield Optimization**: Smart routing for optimal yields. +- **Risk Management**: Advanced risk scoring and monitoring. -## Actions +### Actions -### executeSwap +#### transferToken -Executes a token swap using Jupiter aggregator. +Transfers tokens between wallets. ```typescript // Example usage -const result = await runtime.executeAction("EXECUTE_SWAP", { - inputTokenSymbol: "SOL", - outputTokenSymbol: "USDC", - amount: 0.1, +const result = await runtime.executeAction('SEND_TOKEN', { + tokenAddress: 'TokenAddressHere', + recipient: 'RecipientAddressHere', + amount: '1000', }); ``` -### transferToken +- **Parameters**: + - `tokenAddress`: The address of the token to transfer. + - `recipient`: The recipient's wallet address. + - `amount`: The amount of tokens to transfer. -Transfers tokens between wallets. +#### executeSwap + +Executes a token swap using Jupiter aggregator. ```typescript // Example usage -const result = await runtime.executeAction("SEND_TOKEN", { - tokenAddress: "TokenAddressHere", - recipient: "RecipientAddressHere", - amount: "1000", +const result = await runtime.executeAction('EXECUTE_SWAP', { + inputTokenSymbol: 'SOL', + outputTokenSymbol: 'USDC', + amount: 0.1, }); ``` -### transferSol +- **Parameters**: + - `inputTokenSymbol`: The symbol of the token to swap from. + - `outputTokenSymbol`: The symbol of the token to swap to. + - `amount`: The amount of the input token to swap. -Transfers SOL between wallets. - -```typescript -// Example usage -const result = await runtime.executeAction("SEND_SOL", { - recipient: "RecipientAddressHere", - amount: "1000", -}); -``` +### Services -### takeOrder +#### SolanaService -Places a buy order based on conviction level. +Handles interactions with the Solana blockchain, facilitating token operations, trading, and DeFi integrations. -```typescript -// Example usage -const result = await runtime.executeAction("TAKE_ORDER", { - ticker: "SOL", - contractAddress: "ContractAddressHere", -}); -``` +### Providers -### pumpfun +#### walletProvider -Creates and buys tokens on pump.fun. +Provides wallet information and manages portfolio data for Solana. ```typescript -// Example usage -const result = await runtime.executeAction("CREATE_AND_BUY_TOKEN", { - tokenMetadata: { - name: "TokenName", - symbol: "SYMBOL", - description: "Token description", - image_description: "Image description", - }, - buyAmountSol: 0.1, -}); +const walletProvider = new WalletProvider(connection, publicKey); +await walletProvider.getFormattedPortfolio(runtime); ``` -### fomo +- **Description**: This provider dynamically retrieves and manages wallet data, ensuring up-to-date portfolio information is available for operations. + +## 📖 Usage Examples -Creates and buys tokens on fomo.fund. +### Basic Example ```typescript -// Example usage -const result = await runtime.executeAction("CREATE_AND_BUY_TOKEN", { - tokenMetadata: { - name: "TokenName", - symbol: "SYMBOL", - description: "Token description", - image_description: "Image description", - }, - buyAmountSol: 0.1, - requiredLiquidity: 1000, +import { solanaPlugin } from '@elizaos/plugin-solana'; + +// Initialize the plugin +const runtime = await initializeRuntime({ + plugins: [solanaPlugin], }); ``` -### executeSwapForDAO - -Executes token swaps for DAO operations. +### Advanced Usage ```typescript -// Example usage -const result = await runtime.executeAction("EXECUTE_SWAP_DAO", { - inputTokenSymbol: "SOL", - outputTokenSymbol: "USDC", - amount: 0.1, -}); +// More complex usage examples +const tokenProvider = new TokenProvider(tokenAddress, walletProvider, cacheManager); +await tokenProvider.getTokensInWallet(runtime); ``` -## Performance Optimization - -1. **Cache Management** +## 🛠️ Development - - Implement token data caching - - Configure cache TTL settings - - Monitor cache hit rates +### Building -2. **RPC Optimization** +```bash +# Install dependencies +bun install - - Use connection pooling - - Implement request batching - - Monitor RPC usage +# Build the plugin +bun run build -3. **Transaction Management** - - Optimize transaction bundling - - Implement retry strategies - - Monitor transaction success rates +# Run tests +bun test +``` -## System Requirements +### Testing -- Node.js 16.x or higher -- Solana CLI tools (optional) -- Minimum 4GB RAM recommended -- Stable internet connection -- Access to Solana RPC endpoint +```bash +# Run unit tests +bun test -## Troubleshooting +# Run integration tests +bun test:integration -### Common Issues +# Run with coverage +bun test:coverage +``` -1. **Wallet Connection Failures** +### Local Development +1. Clone the repository: ```bash -Error: Failed to connect to wallet +git clone https://github.com/elizaos-plugins/plugin-solana.git +cd plugin-solana ``` -- Verify RPC endpoint is accessible -- Check wallet configuration settings -- Ensure proper network selection +2. Install dependencies: +```bash +bun install +``` -2. **Transaction Errors** +3. Build the plugin: +```bash +bun run build +``` +4. Link for local development: ```bash -Error: Transaction simulation failed +bun link ``` -- Check account balances -- Verify transaction parameters -- Ensure proper fee configuration +## 🤝 Contributing -3. **Price Feed Issues** +Contributions are welcome! Please see our [Contributing Guidelines](CONTRIBUTING.md) for details. -```bash -Error: Unable to fetch price data -``` +### Development Workflow -- Verify API key configuration -- Check network connectivity -- Ensure price feed service status +1. Fork the repository +2. Create your feature branch (`git checkout -b feature/amazing-feature`) +3. Commit your changes (`git commit -m 'Add some amazing feature'`) +4. Push to the branch (`git push origin feature/amazing-feature`) +5. Open a Pull Request -## Safety & Security +## 🐛 Troubleshooting -### Best Practices +### Common Issues -1. **Environment Variables** +#### Issue: Plugin not loading +**Solution**: Ensure the plugin is properly added to your character's `plugins` array and all required environment variables are set. - - Store sensitive keys in environment variables - - Use .env.example for non-sensitive defaults - - Never commit real credentials to version control +#### Issue: API authentication errors +**Solution**: Verify your API credentials are correct and have the necessary permissions. -2. **Transaction Limits** +#### Issue: Rate limiting +**Solution**: The plugin includes built-in rate limiting. If you're hitting limits, consider adjusting the request frequency in your configuration. - - Set maximum transaction amounts - - Implement daily trading limits - - Configure per-token restrictions +## 📚 API Reference -3. **Monitoring** +### Actions - - Track failed transaction attempts - - Monitor unusual trading patterns - - Log security-relevant events +Detailed documentation for each action: -4. **Recovery** - - Implement transaction rollback mechanisms - - Maintain backup RPC endpoints - - Document recovery procedures +#### transferToken +- **Parameters**: `tokenAddress`, `recipient`, `amount` +- **Returns**: Transaction result -## Performance Optimization +#### executeSwap +- **Parameters**: `inputTokenSymbol`, `outputTokenSymbol`, `amount` +- **Returns**: Swap result -1. **Cache Management** +### Services - - Implement token data caching - - Configure cache TTL settings - - Monitor cache hit rates +Detailed documentation for each service: -2. **RPC Optimization** +#### SolanaService +- **Methods**: Handles blockchain interactions - - Use connection pooling - - Implement request batching - - Monitor RPC usage +## 🔒 Security -3. **Transaction Management** - - Optimize transaction bundling - - Implement retry strategies - - Monitor transaction success rates +- Store all sensitive credentials in environment variables +- Never commit `.env` files to version control +- Regularly rotate API keys and tokens +- Follow the principle of least privilege for API permissions -## Support +## 📄 License -For issues and feature requests, please: +This plugin is part of the ElizaOS project. See the [LICENSE](LICENSE) file for details. -1. Check the troubleshooting guide above -2. Review existing GitHub issues -3. Submit a new issue with: - - System information - - Error logs - - Steps to reproduce - - Transaction IDs (if applicable) +## 🆘 Support -## Contributing +- 📧 Email: support@elizaos.ai +- 💬 Discord: [ElizaOS Discord](https://discord.gg/elizaos) +- 📚 Documentation: [ElizaOS Docs](https://eliza.how) +- 🐛 Issues: [GitHub Issues](https://github.com/elizaos-plugins/plugin-solana/issues) -Contributions are welcome! Please see the [CONTRIBUTING.md](CONTRIBUTING.md) file for more information. +## 🙏 Acknowledgments -## Credits +Special thanks to the ElizaOS team and all contributors to this plugin. This plugin integrates with and builds upon several key technologies: @@ -357,11 +287,6 @@ This plugin integrates with and builds upon several key technologies: - [FOMO](https://fomo.fund/) - Token creation and trading - [Pump.fun](https://pump.fun/) - Token creation and trading -Special thanks to: - -- The Solana ecosystem and all the open-source contributors who make these integrations possible. -- The Eliza community for their contributions and feedback. - For more information about Solana blockchain capabilities: - [Solana Documentation](https://docs.solana.com/) @@ -369,6 +294,6 @@ For more information about Solana blockchain capabilities: - [Solana Network Dashboard](https://solscan.io/) - [Solana GitHub Repository](https://github.com/solana-labs/solana) -## License +--- -This plugin is part of the Eliza project. See the main project repository for license information. +Made with ❤️ by the ElizaOS community \ No newline at end of file diff --git a/__tests__/actions/swap.test.ts b/__tests__/actions/swap.test.ts index f38f806..4aa5ec9 100644 --- a/__tests__/actions/swap.test.ts +++ b/__tests__/actions/swap.test.ts @@ -1,22 +1,22 @@ -import { describe, it, expect, vi } from 'vitest'; +import { describe, expect, it, vi } from 'vitest'; describe('Swap Action', () => { - describe('validate', () => { - it('should handle swap message validation', async () => { - const mockMessage = { - content: 'Swap 1 SOL to USDC', - metadata: { - fromToken: 'SOL', - toToken: 'USDC', - amount: '1' - } - }; + describe('validate', () => { + it('should handle swap message validation', async () => { + const mockMessage = { + content: 'Swap 1 SOL to USDC', + metadata: { + fromToken: 'SOL', + toToken: 'USDC', + amount: '1', + }, + }; - // Basic test to ensure message structure - expect(mockMessage.metadata).toBeDefined(); - expect(mockMessage.metadata.fromToken).toBe('SOL'); - expect(mockMessage.metadata.toToken).toBe('USDC'); - expect(mockMessage.metadata.amount).toBe('1'); - }); + // Basic test to ensure message structure + expect(mockMessage.metadata).toBeDefined(); + expect(mockMessage.metadata.fromToken).toBe('SOL'); + expect(mockMessage.metadata.toToken).toBe('USDC'); + expect(mockMessage.metadata.amount).toBe('1'); }); + }); }); diff --git a/__tests__/evaluators/trust.test.ts b/__tests__/evaluators/trust.test.ts deleted file mode 100644 index ff7cb8b..0000000 --- a/__tests__/evaluators/trust.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { describe, it, expect, vi } from 'vitest'; -import { trustEvaluator } from '../../src/evaluators/trust'; - -// Mock the core module -vi.mock('@elizaos/core', () => ({ - generateTrueOrFalse: vi.fn().mockResolvedValue(false), - ModelClass: { - SMALL: 'small' - }, - settings: { - MAIN_WALLET_ADDRESS: 'test-wallet-address' - }, - booleanFooter: 'Answer with Yes or No.', - elizaLogger: { - log: vi.fn(), - debug: vi.fn(), - info: vi.fn(), - error: vi.fn() - }, - composeContext: vi.fn().mockReturnValue({ - state: {}, - template: '' - }) -})); - -describe('Trust Evaluator', () => { - it('should handle non-trust messages', async () => { - const mockRuntime = { - getSetting: vi.fn(), - composeState: vi.fn().mockResolvedValue({ - agentId: 'test-agent', - roomId: 'test-room' - }), - getLogger: vi.fn().mockReturnValue({ - debug: vi.fn(), - info: vi.fn(), - log: vi.fn(), - error: vi.fn() - }) - }; - - const mockMessage = { - content: 'Hello world', - metadata: {} - }; - - const mockState = { - agentId: 'test-agent', - roomId: 'test-room' - }; - - const result = await trustEvaluator.handler(mockRuntime, mockMessage, mockState); - expect(result).toBeDefined(); - }); -}); diff --git a/__tests__/providers/token-security.test.ts b/__tests__/providers/token-security.test.ts deleted file mode 100644 index d350953..0000000 --- a/__tests__/providers/token-security.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { describe, it, expect, vi } from 'vitest'; -import { TokenProvider } from '../../src/providers/token'; -import { WalletProvider } from '../../src/providers/wallet'; -import { ICacheManager } from '@elizaos/core'; - -describe('Token Security', () => { - it('should handle empty security data gracefully', async () => { - const mockCacheManager = { - get: vi.fn().mockResolvedValue(null), - set: vi.fn(), - delete: vi.fn(), - clear: vi.fn(), - has: vi.fn(), - }; - - const mockWalletProvider = new WalletProvider(mockCacheManager); - const tokenProvider = new TokenProvider( - 'So11111111111111111111111111111111111111112', - mockWalletProvider, - mockCacheManager - ); - - global.fetch = vi.fn().mockResolvedValueOnce({ - ok: true, - json: () => Promise.resolve({ - success: true, - data: {} - }) - }); - - const result = await tokenProvider.fetchTokenSecurity(); - expect(result).toBeDefined(); - expect(result.ownerBalance).toBe(undefined); - expect(result.creatorBalance).toBe(undefined); - }); -}); diff --git a/__tests__/token.test.ts b/__tests__/token.test.ts deleted file mode 100644 index 5d1a5e2..0000000 --- a/__tests__/token.test.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { describe, it, expect, beforeEach, vi, afterEach } from "vitest"; -import { TokenProvider } from "../src/providers/token.ts"; - -// Mock NodeCache -vi.mock("node-cache", () => { - return { - default: vi.fn().mockImplementation(() => ({ - set: vi.fn(), - get: vi.fn().mockReturnValue(null), - })), - }; -}); - -// Mock path module -vi.mock("path", async () => { - const actual = await vi.importActual("path"); - return { - ...(actual as any), - join: vi.fn().mockImplementation((...args) => args.join("/")), - }; -}); - -// Mock the WalletProvider -const mockWalletProvider = { - fetchPortfolioValue: vi.fn(), -}; - -// Mock the ICacheManager -const mockCacheManager = { - get: vi.fn().mockResolvedValue(null), - set: vi.fn(), -}; - -// Mock fetch globally -const mockFetch = vi.fn(); -global.fetch = mockFetch; - -describe("TokenProvider", () => { - let tokenProvider: TokenProvider; - const TEST_TOKEN_ADDRESS = "2weMjPLLybRMMva1fM3U31goWWrCpF59CHWNhnCJ9Vyh"; - - beforeEach(() => { - vi.clearAllMocks(); - mockCacheManager.get.mockResolvedValue(null); - - // Create new instance of TokenProvider with mocked dependencies - tokenProvider = new TokenProvider( - TEST_TOKEN_ADDRESS, - mockWalletProvider as any, - mockCacheManager as any - ); - }); - - afterEach(() => { - vi.clearAllTimers(); - }); - - describe("Cache Management", () => { - it("should use cached data when available", async () => { - const mockData = { test: "data" }; - mockCacheManager.get.mockResolvedValueOnce(mockData); - - const result = await (tokenProvider as any).getCachedData( - "test-key" - ); - - expect(result).toEqual(mockData); - expect(mockCacheManager.get).toHaveBeenCalledTimes(1); - }); - - it("should write data to both caches", async () => { - const testData = { test: "data" }; - - await (tokenProvider as any).setCachedData("test-key", testData); - - expect(mockCacheManager.set).toHaveBeenCalledWith( - expect.stringContaining("test-key"), - testData, - expect.any(Object) - ); - }); - }); - - describe("Wallet Integration", () => { - it("should fetch tokens in wallet", async () => { - const mockItems = [ - { symbol: "SOL", address: "address1" }, - { symbol: "BTC", address: "address2" }, - ]; - - mockWalletProvider.fetchPortfolioValue.mockResolvedValueOnce({ - items: mockItems, - }); - - const result = await tokenProvider.getTokensInWallet({} as any); - - expect(result).toEqual(mockItems); - expect( - mockWalletProvider.fetchPortfolioValue - ).toHaveBeenCalledTimes(1); - }); - - it("should find token in wallet by symbol", async () => { - const mockItems = [ - { symbol: "SOL", address: "address1" }, - { symbol: "BTC", address: "address2" }, - ]; - - mockWalletProvider.fetchPortfolioValue.mockResolvedValueOnce({ - items: mockItems, - }); - - const result = await tokenProvider.getTokenFromWallet( - {} as any, - "SOL" - ); - - expect(result).toBe("address1"); - }); - - it("should return null for token not in wallet", async () => { - mockWalletProvider.fetchPortfolioValue.mockResolvedValueOnce({ - items: [], - }); - - const result = await tokenProvider.getTokenFromWallet( - {} as any, - "NONEXISTENT" - ); - - expect(result).toBeNull(); - }); - }); -}); diff --git a/biome.json b/biome.json deleted file mode 100644 index 818716a..0000000 --- a/biome.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json", - "organizeImports": { - "enabled": false - }, - "linter": { - "enabled": true, - "rules": { - "recommended": true, - "correctness": { - "noUnusedVariables": "error" - }, - "suspicious": { - "noExplicitAny": "error" - }, - "style": { - "useConst": "error", - "useImportType": "off" - } - } - }, - "formatter": { - "enabled": true, - "indentStyle": "space", - "indentWidth": 4, - "lineWidth": 100 - }, - "javascript": { - "formatter": { - "quoteStyle": "single", - "trailingCommas": "es5" - } - }, - "files": { - "ignore": [ - "dist/**/*", - "extra/**/*", - "node_modules/**/*" - ] - } -} \ No newline at end of file diff --git a/dist/index.d.ts b/dist/index.d.ts deleted file mode 100644 index 7a9fc95..0000000 --- a/dist/index.d.ts +++ /dev/null @@ -1,505 +0,0 @@ -import { Provider, ICacheManager, IAgentRuntime, Memory, Evaluator, Plugin } from '@elizaos/core'; -import { Connection, PublicKey } from '@solana/web3.js'; -import { TrustScoreDatabase, TokenPerformance, RecommenderMetrics, TradePerformance } from '@elizaos/plugin-trustdb'; - -interface TokenSecurityData { - ownerBalance: string; - creatorBalance: string; - ownerPercentage: number; - creatorPercentage: number; - top10HolderBalance: string; - top10HolderPercent: number; -} -interface TokenCodex { - id: string; - address: string; - cmcId: number; - decimals: number; - name: string; - symbol: string; - totalSupply: string; - circulatingSupply: string; - imageThumbUrl: string; - blueCheckmark: boolean; - isScam: boolean; -} -interface TokenTradeData { - address: string; - holder: number; - market: number; - last_trade_unix_time: number; - last_trade_human_time: string; - price: number; - history_30m_price: number; - price_change_30m_percent: number; - history_1h_price: number; - price_change_1h_percent: number; - history_2h_price: number; - price_change_2h_percent: number; - history_4h_price: number; - price_change_4h_percent: number; - history_6h_price: number; - price_change_6h_percent: number; - history_8h_price: number; - price_change_8h_percent: number; - history_12h_price: number; - price_change_12h_percent: number; - history_24h_price: number; - price_change_24h_percent: number; - unique_wallet_30m: number; - unique_wallet_history_30m: number; - unique_wallet_30m_change_percent: number; - unique_wallet_1h: number; - unique_wallet_history_1h: number; - unique_wallet_1h_change_percent: number; - unique_wallet_2h: number; - unique_wallet_history_2h: number; - unique_wallet_2h_change_percent: number; - unique_wallet_4h: number; - unique_wallet_history_4h: number; - unique_wallet_4h_change_percent: number; - unique_wallet_8h: number; - unique_wallet_history_8h: number | null; - unique_wallet_8h_change_percent: number | null; - unique_wallet_24h: number; - unique_wallet_history_24h: number | null; - unique_wallet_24h_change_percent: number | null; - trade_30m: number; - trade_history_30m: number; - trade_30m_change_percent: number; - sell_30m: number; - sell_history_30m: number; - sell_30m_change_percent: number; - buy_30m: number; - buy_history_30m: number; - buy_30m_change_percent: number; - volume_30m: number; - volume_30m_usd: number; - volume_history_30m: number; - volume_history_30m_usd: number; - volume_30m_change_percent: number; - volume_buy_30m: number; - volume_buy_30m_usd: number; - volume_buy_history_30m: number; - volume_buy_history_30m_usd: number; - volume_buy_30m_change_percent: number; - volume_sell_30m: number; - volume_sell_30m_usd: number; - volume_sell_history_30m: number; - volume_sell_history_30m_usd: number; - volume_sell_30m_change_percent: number; - trade_1h: number; - trade_history_1h: number; - trade_1h_change_percent: number; - sell_1h: number; - sell_history_1h: number; - sell_1h_change_percent: number; - buy_1h: number; - buy_history_1h: number; - buy_1h_change_percent: number; - volume_1h: number; - volume_1h_usd: number; - volume_history_1h: number; - volume_history_1h_usd: number; - volume_1h_change_percent: number; - volume_buy_1h: number; - volume_buy_1h_usd: number; - volume_buy_history_1h: number; - volume_buy_history_1h_usd: number; - volume_buy_1h_change_percent: number; - volume_sell_1h: number; - volume_sell_1h_usd: number; - volume_sell_history_1h: number; - volume_sell_history_1h_usd: number; - volume_sell_1h_change_percent: number; - trade_2h: number; - trade_history_2h: number; - trade_2h_change_percent: number; - sell_2h: number; - sell_history_2h: number; - sell_2h_change_percent: number; - buy_2h: number; - buy_history_2h: number; - buy_2h_change_percent: number; - volume_2h: number; - volume_2h_usd: number; - volume_history_2h: number; - volume_history_2h_usd: number; - volume_2h_change_percent: number; - volume_buy_2h: number; - volume_buy_2h_usd: number; - volume_buy_history_2h: number; - volume_buy_history_2h_usd: number; - volume_buy_2h_change_percent: number; - volume_sell_2h: number; - volume_sell_2h_usd: number; - volume_sell_history_2h: number; - volume_sell_history_2h_usd: number; - volume_sell_2h_change_percent: number; - trade_4h: number; - trade_history_4h: number; - trade_4h_change_percent: number; - sell_4h: number; - sell_history_4h: number; - sell_4h_change_percent: number; - buy_4h: number; - buy_history_4h: number; - buy_4h_change_percent: number; - volume_4h: number; - volume_4h_usd: number; - volume_history_4h: number; - volume_history_4h_usd: number; - volume_4h_change_percent: number; - volume_buy_4h: number; - volume_buy_4h_usd: number; - volume_buy_history_4h: number; - volume_buy_history_4h_usd: number; - volume_buy_4h_change_percent: number; - volume_sell_4h: number; - volume_sell_4h_usd: number; - volume_sell_history_4h: number; - volume_sell_history_4h_usd: number; - volume_sell_4h_change_percent: number; - trade_8h: number; - trade_history_8h: number | null; - trade_8h_change_percent: number | null; - sell_8h: number; - sell_history_8h: number | null; - sell_8h_change_percent: number | null; - buy_8h: number; - buy_history_8h: number | null; - buy_8h_change_percent: number | null; - volume_8h: number; - volume_8h_usd: number; - volume_history_8h: number; - volume_history_8h_usd: number; - volume_8h_change_percent: number | null; - volume_buy_8h: number; - volume_buy_8h_usd: number; - volume_buy_history_8h: number; - volume_buy_history_8h_usd: number; - volume_buy_8h_change_percent: number | null; - volume_sell_8h: number; - volume_sell_8h_usd: number; - volume_sell_history_8h: number; - volume_sell_history_8h_usd: number; - volume_sell_8h_change_percent: number | null; - trade_24h: number; - trade_history_24h: number; - trade_24h_change_percent: number | null; - sell_24h: number; - sell_history_24h: number; - sell_24h_change_percent: number | null; - buy_24h: number; - buy_history_24h: number; - buy_24h_change_percent: number | null; - volume_24h: number; - volume_24h_usd: number; - volume_history_24h: number; - volume_history_24h_usd: number; - volume_24h_change_percent: number | null; - volume_buy_24h: number; - volume_buy_24h_usd: number; - volume_buy_history_24h: number; - volume_buy_history_24h_usd: number; - volume_buy_24h_change_percent: number | null; - volume_sell_24h: number; - volume_sell_24h_usd: number; - volume_sell_history_24h: number; - volume_sell_history_24h_usd: number; - volume_sell_24h_change_percent: number | null; -} -interface HolderData { - address: string; - balance: string; -} -interface ProcessedTokenData { - security: TokenSecurityData; - tradeData: TokenTradeData; - holderDistributionTrend: string; - highValueHolders: Array<{ - holderAddress: string; - balanceUsd: string; - }>; - recentTrades: boolean; - highSupplyHoldersCount: number; - dexScreenerData: DexScreenerData; - isDexScreenerListed: boolean; - isDexScreenerPaid: boolean; - tokenCodex: TokenCodex; -} -interface DexScreenerPair { - chainId: string; - dexId: string; - url: string; - pairAddress: string; - baseToken: { - address: string; - name: string; - symbol: string; - }; - quoteToken: { - address: string; - name: string; - symbol: string; - }; - priceNative: string; - priceUsd: string; - txns: { - m5: { - buys: number; - sells: number; - }; - h1: { - buys: number; - sells: number; - }; - h6: { - buys: number; - sells: number; - }; - h24: { - buys: number; - sells: number; - }; - }; - volume: { - h24: number; - h6: number; - h1: number; - m5: number; - }; - priceChange: { - m5: number; - h1: number; - h6: number; - h24: number; - }; - liquidity: { - usd: number; - base: number; - quote: number; - }; - fdv: number; - marketCap: number; - pairCreatedAt: number; - info: { - imageUrl: string; - websites: { - label: string; - url: string; - }[]; - socials: { - type: string; - url: string; - }[]; - }; - boosts: { - active: number; - }; -} -interface DexScreenerData { - schemaVersion: string; - pairs: DexScreenerPair[]; -} -interface Prices$1 { - solana: { - usd: string; - }; - bitcoin: { - usd: string; - }; - ethereum: { - usd: string; - }; -} -interface CalculatedBuyAmounts { - none: 0; - low: number; - medium: number; - high: number; -} - -interface Item { - name: string; - address: string; - symbol: string; - decimals: number; - balance: string; - uiAmount: string; - priceUsd: string; - valueUsd: string; - valueSol?: string; -} -interface WalletPortfolio { - totalUsd: string; - totalSol?: string; - items: Array; -} -interface Prices { - solana: { - usd: string; - }; - bitcoin: { - usd: string; - }; - ethereum: { - usd: string; - }; -} -declare class WalletProvider { - private connection; - private walletPublicKey; - private cache; - constructor(connection: Connection, walletPublicKey: PublicKey); - private fetchWithRetry; - fetchPortfolioValue(runtime: any): Promise; - fetchPortfolioValueCodex(runtime: any): Promise; - fetchPrices(runtime: any): Promise; - formatPortfolio(runtime: any, portfolio: WalletPortfolio, prices: Prices): string; - getFormattedPortfolio(runtime: any): Promise; - private getTokenAccounts; -} -declare const walletProvider: Provider; - -declare class TokenProvider { - private tokenAddress; - private walletProvider; - private cacheManager; - private cache; - private cacheKey; - private NETWORK_ID; - private GRAPHQL_ENDPOINT; - constructor(tokenAddress: string, walletProvider: WalletProvider, cacheManager: ICacheManager); - private readFromCache; - private writeToCache; - private getCachedData; - private setCachedData; - private fetchWithRetry; - getTokensInWallet(runtime: IAgentRuntime): Promise; - getTokenFromWallet(runtime: IAgentRuntime, tokenSymbol: string): Promise; - fetchTokenCodex(): Promise; - fetchPrices(): Promise; - calculateBuyAmounts(): Promise; - fetchTokenSecurity(): Promise; - fetchTokenTradeData(): Promise; - fetchDexScreenerData(): Promise; - searchDexScreenerData(symbol: string): Promise; - getHighestLiquidityPair(dexData: DexScreenerData): DexScreenerPair | null; - analyzeHolderDistribution(tradeData: TokenTradeData): Promise; - fetchHolderList(): Promise; - filterHighValueHolders(tradeData: TokenTradeData): Promise>; - checkRecentTrades(tradeData: TokenTradeData): Promise; - countHighSupplyHolders(securityData: TokenSecurityData): Promise; - getProcessedTokenData(): Promise; - shouldTradeToken(): Promise; - formatTokenData(data: ProcessedTokenData): string; - getFormattedTokenReport(): Promise; -} -declare const tokenProvider: Provider; - -interface TradeData { - buy_amount: number; - is_simulation: boolean; -} -interface sellDetails { - sell_amount: number; - sell_recommender_id: string | null; -} -interface RecommenderData { - recommenderId: string; - trustScore: number; - riskScore: number; - consistencyScore: number; - recommenderMetrics: RecommenderMetrics; -} -interface TokenRecommendationSummary { - tokenAddress: string; - averageTrustScore: number; - averageRiskScore: number; - averageConsistencyScore: number; - recommenders: RecommenderData[]; -} -declare class TrustScoreManager { - private tokenProvider; - private trustScoreDb; - private simulationSellingService; - private connection; - private baseMint; - private DECAY_RATE; - private MAX_DECAY_DAYS; - private backend; - private backendToken; - constructor(runtime: IAgentRuntime, tokenProvider: TokenProvider, trustScoreDb: TrustScoreDatabase); - getRecommenederBalance(recommenderWallet: string): Promise; - /** - * Generates and saves trust score based on processed token data and user recommendations. - * @param tokenAddress The address of the token to analyze. - * @param recommenderId The UUID of the recommender. - * @returns An object containing TokenPerformance and RecommenderMetrics. - */ - generateTrustScore(tokenAddress: string, recommenderId: string, recommenderWallet: string): Promise<{ - tokenPerformance: TokenPerformance; - recommenderMetrics: RecommenderMetrics; - }>; - updateRecommenderMetrics(recommenderId: string, tokenPerformance: TokenPerformance, recommenderWallet: string): Promise; - calculateTrustScore(tokenPerformance: TokenPerformance, recommenderMetrics: RecommenderMetrics): number; - calculateOverallRiskScore(tokenPerformance: TokenPerformance, recommenderMetrics: RecommenderMetrics): number; - calculateRiskScore(tokenPerformance: TokenPerformance): number; - calculateConsistencyScore(tokenPerformance: TokenPerformance, recommenderMetrics: RecommenderMetrics): number; - suspiciousVolume(tokenAddress: string): Promise; - sustainedGrowth(tokenAddress: string): Promise; - isRapidDump(tokenAddress: string): Promise; - checkTrustScore(tokenAddress: string): Promise; - /** - * Creates a TradePerformance object based on token data and recommender. - * @param tokenAddress The address of the token. - * @param recommenderId The UUID of the recommender. - * @param data ProcessedTokenData. - * @returns TradePerformance object. - */ - createTradePerformance(runtime: IAgentRuntime, tokenAddress: string, recommenderId: string, data: TradeData): Promise; - delay(ms: number): Promise; - createTradeInBe(tokenAddress: string, recommenderId: string, data: TradeData, retries?: number, delayMs?: number): Promise; - /** - * Updates a trade with sell details. - * @param tokenAddress The address of the token. - * @param recommenderId The UUID of the recommender. - * @param buyTimeStamp The timestamp when the buy occurred. - * @param sellDetails An object containing sell-related details. - * @param isSimulation Whether the trade is a simulation. If true, updates in simulation_trade; otherwise, in trade. - * @returns boolean indicating success. - */ - updateSellDetails(runtime: IAgentRuntime, tokenAddress: string, recommenderId: string, sellTimeStamp: string, sellDetails: sellDetails, isSimulation: boolean): Promise<{ - sell_price: number; - sell_timeStamp: string; - sell_amount: number; - received_sol: number; - sell_value_usd: number; - profit_usd: number; - profit_percent: number; - sell_market_cap: number; - market_cap_change: number; - sell_liquidity: number; - liquidity_change: number; - rapidDump: boolean; - sell_recommender_id: string; - }>; - getRecommendations(startDate: Date, endDate: Date): Promise>; -} -declare const trustScoreProvider: Provider; - -declare const formatRecommendations: (recommendations: Memory[]) => string; -declare const trustEvaluator: Evaluator; - -declare function getTokenBalance(connection: Connection, walletPublicKey: PublicKey, tokenMintAddress: PublicKey): Promise; -declare function getTokenBalances(connection: Connection, walletPublicKey: PublicKey): Promise<{ - [tokenName: string]: number; -}>; - -declare const solanaPlugin: Plugin; - -export { type Item, TokenProvider, TrustScoreManager, WalletProvider, solanaPlugin as default, formatRecommendations, getTokenBalance, getTokenBalances, solanaPlugin, tokenProvider, trustEvaluator, trustScoreProvider, walletProvider }; diff --git a/dist/index.js b/dist/index.js deleted file mode 100644 index 4b75c40..0000000 --- a/dist/index.js +++ /dev/null @@ -1,4324 +0,0 @@ -// src/providers/token.ts -import { - elizaLogger as elizaLogger3, - settings -} from "@elizaos/core"; -import NodeCache2 from "node-cache"; -import * as path from "path"; - -// src/bignumber.ts -import BigNumber from "bignumber.js"; -function toBN(value) { - return new BigNumber(value); -} - -// src/providers/wallet.ts -import { - elizaLogger as elizaLogger2 -} from "@elizaos/core"; -import { Connection, PublicKey as PublicKey2 } from "@solana/web3.js"; -import BigNumber2 from "bignumber.js"; -import NodeCache from "node-cache"; - -// src/keypairUtils.ts -import { Keypair, PublicKey } from "@solana/web3.js"; -import { DeriveKeyProvider, TEEMode } from "@elizaos/plugin-tee"; -import bs58 from "bs58"; -import { elizaLogger } from "@elizaos/core"; -async function getWalletKey(runtime, requirePrivateKey = true) { - const teeMode = runtime.getSetting("TEE_MODE") || TEEMode.OFF; - if (teeMode !== TEEMode.OFF) { - const walletSecretSalt = runtime.getSetting("WALLET_SECRET_SALT"); - if (!walletSecretSalt) { - throw new Error( - "WALLET_SECRET_SALT required when TEE_MODE is enabled" - ); - } - const deriveKeyProvider = new DeriveKeyProvider(teeMode); - const deriveKeyResult = await deriveKeyProvider.deriveEd25519Keypair( - walletSecretSalt, - "solana", - runtime.agentId - ); - return requirePrivateKey ? { keypair: deriveKeyResult.keypair } : { publicKey: deriveKeyResult.keypair.publicKey }; - } - if (requirePrivateKey) { - const privateKeyString = runtime.getSetting("SOLANA_PRIVATE_KEY") ?? runtime.getSetting("WALLET_PRIVATE_KEY"); - if (!privateKeyString) { - throw new Error("Private key not found in settings"); - } - try { - const secretKey = bs58.decode(privateKeyString); - return { keypair: Keypair.fromSecretKey(secretKey) }; - } catch (e) { - elizaLogger.log("Error decoding base58 private key:", e); - try { - elizaLogger.log("Try decoding base64 instead"); - const secretKey = Uint8Array.from( - Buffer.from(privateKeyString, "base64") - ); - return { keypair: Keypair.fromSecretKey(secretKey) }; - } catch (e2) { - elizaLogger.error("Error decoding private key: ", e2); - throw new Error("Invalid private key format"); - } - } - } else { - const publicKeyString = runtime.getSetting("SOLANA_PUBLIC_KEY") ?? runtime.getSetting("WALLET_PUBLIC_KEY"); - if (!publicKeyString) { - throw new Error("Public key not found in settings"); - } - return { publicKey: new PublicKey(publicKeyString) }; - } -} - -// src/providers/wallet.ts -var PROVIDER_CONFIG = { - BIRDEYE_API: "https://public-api.birdeye.so", - MAX_RETRIES: 3, - RETRY_DELAY: 2e3, - DEFAULT_RPC: "https://api.mainnet-beta.solana.com", - GRAPHQL_ENDPOINT: "https://graph.codex.io/graphql", - TOKEN_ADDRESSES: { - SOL: "So11111111111111111111111111111111111111112", - BTC: "3NZ9JMVBmGAqocybic2c7LQCJScmgsAZ6vQqTDzcqmJh", - ETH: "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs" - } -}; -var WalletProvider = class { - constructor(connection3, walletPublicKey) { - this.connection = connection3; - this.walletPublicKey = walletPublicKey; - this.cache = new NodeCache({ stdTTL: 300 }); - } - cache; - async fetchWithRetry(runtime, url, options = {}) { - let lastError; - for (let i = 0; i < PROVIDER_CONFIG.MAX_RETRIES; i++) { - try { - const response = await fetch(url, { - ...options, - headers: { - Accept: "application/json", - "x-chain": "solana", - "X-API-KEY": runtime.getSetting("BIRDEYE_API_KEY", "") || "", - ...options.headers - } - }); - if (!response.ok) { - const errorText = await response.text(); - throw new Error( - `HTTP error! status: ${response.status}, message: ${errorText}` - ); - } - const data = await response.json(); - return data; - } catch (error) { - elizaLogger2.error(`Attempt ${i + 1} failed:`, error); - lastError = error; - if (i < PROVIDER_CONFIG.MAX_RETRIES - 1) { - const delay = PROVIDER_CONFIG.RETRY_DELAY * Math.pow(2, i); - await new Promise((resolve) => setTimeout(resolve, delay)); - continue; - } - } - } - elizaLogger2.error( - "All attempts failed. Throwing the last error:", - lastError - ); - throw lastError; - } - async fetchPortfolioValue(runtime) { - try { - const cacheKey = `portfolio-${this.walletPublicKey.toBase58()}`; - const cachedValue = this.cache.get(cacheKey); - if (cachedValue) { - elizaLogger2.log("Cache hit for fetchPortfolioValue"); - return cachedValue; - } - elizaLogger2.log("Cache miss for fetchPortfolioValue"); - const birdeyeApiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (birdeyeApiKey) { - const walletData = await this.fetchWithRetry( - runtime, - `${PROVIDER_CONFIG.BIRDEYE_API}/v1/wallet/token_list?wallet=${this.walletPublicKey.toBase58()}` - ); - if (walletData?.success && walletData?.data) { - const data = walletData.data; - const totalUsd = new BigNumber2(data.totalUsd.toString()); - const prices = await this.fetchPrices(runtime); - const solPriceInUSD = new BigNumber2( - prices.solana.usd.toString() - ); - const items2 = data.items.map((item) => ({ - ...item, - valueSol: new BigNumber2(item.valueUsd || 0).div(solPriceInUSD).toFixed(6), - name: item.name || "Unknown", - symbol: item.symbol || "Unknown", - priceUsd: item.priceUsd || "0", - valueUsd: item.valueUsd || "0" - })); - const portfolio2 = { - totalUsd: totalUsd.toString(), - totalSol: totalUsd.div(solPriceInUSD).toFixed(6), - items: items2.sort( - (a, b) => new BigNumber2(b.valueUsd).minus(new BigNumber2(a.valueUsd)).toNumber() - ) - }; - this.cache.set(cacheKey, portfolio2); - return portfolio2; - } - } - const accounts = await this.getTokenAccounts( - this.walletPublicKey.toBase58() - ); - const items = accounts.map((acc) => ({ - name: "Unknown", - address: acc.account.data.parsed.info.mint, - symbol: "Unknown", - decimals: acc.account.data.parsed.info.tokenAmount.decimals, - balance: acc.account.data.parsed.info.tokenAmount.amount, - uiAmount: acc.account.data.parsed.info.tokenAmount.uiAmount.toString(), - priceUsd: "0", - valueUsd: "0", - valueSol: "0" - })); - const portfolio = { - totalUsd: "0", - totalSol: "0", - items - }; - this.cache.set(cacheKey, portfolio); - return portfolio; - } catch (error) { - elizaLogger2.error("Error fetching portfolio:", error); - throw error; - } - } - async fetchPortfolioValueCodex(runtime) { - try { - const cacheKey = `portfolio-${this.walletPublicKey.toBase58()}`; - const cachedValue = await this.cache.get(cacheKey); - if (cachedValue) { - elizaLogger2.log("Cache hit for fetchPortfolioValue"); - return cachedValue; - } - elizaLogger2.log("Cache miss for fetchPortfolioValue"); - const query = ` - query Balances($walletId: String!, $cursor: String) { - balances(input: { walletId: $walletId, cursor: $cursor }) { - cursor - items { - walletId - tokenId - balance - shiftedBalance - } - } - } - `; - const variables = { - walletId: `${this.walletPublicKey.toBase58()}:${1399811149}`, - cursor: null - }; - const response = await fetch(PROVIDER_CONFIG.GRAPHQL_ENDPOINT, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: runtime.getSetting("CODEX_API_KEY", "") || "" - }, - body: JSON.stringify({ - query, - variables - }) - }).then((res) => res.json()); - const data = response.data?.data?.balances?.items; - if (!data || data.length === 0) { - elizaLogger2.error("No portfolio data available", data); - throw new Error("No portfolio data available"); - } - const prices = await this.fetchPrices(runtime); - const solPriceInUSD = new BigNumber2(prices.solana.usd.toString()); - const items = data.map((item) => { - return { - name: "Unknown", - address: item.tokenId.split(":")[0], - symbol: item.tokenId.split(":")[0], - decimals: 6, - balance: item.balance, - uiAmount: item.shiftedBalance.toString(), - priceUsd: "", - valueUsd: "", - valueSol: "" - }; - }); - const totalUsd = items.reduce( - (sum, item) => sum.plus(new BigNumber2(item.valueUsd)), - new BigNumber2(0) - ); - const totalSol = totalUsd.div(solPriceInUSD); - const portfolio = { - totalUsd: totalUsd.toFixed(6), - totalSol: totalSol.toFixed(6), - items: items.sort( - (a, b) => new BigNumber2(b.valueUsd).minus(new BigNumber2(a.valueUsd)).toNumber() - ) - }; - await this.cache.set(cacheKey, portfolio, 60 * 1e3); - return portfolio; - } catch (error) { - elizaLogger2.error("Error fetching portfolio:", error); - throw error; - } - } - async fetchPrices(runtime) { - try { - const cacheKey = "prices"; - const cachedValue = this.cache.get(cacheKey); - if (cachedValue) { - elizaLogger2.log("Cache hit for fetchPrices"); - return cachedValue; - } - elizaLogger2.log("Cache miss for fetchPrices"); - const { SOL, BTC, ETH } = PROVIDER_CONFIG.TOKEN_ADDRESSES; - const tokens = [SOL, BTC, ETH]; - const prices = { - solana: { usd: "0" }, - bitcoin: { usd: "0" }, - ethereum: { usd: "0" } - }; - for (const token of tokens) { - const response = await this.fetchWithRetry( - runtime, - `${PROVIDER_CONFIG.BIRDEYE_API}/defi/price?address=${token}`, - { - headers: { - "x-chain": "solana" - } - } - ); - if (response?.data?.value) { - const price = response.data.value.toString(); - prices[token === SOL ? "solana" : token === BTC ? "bitcoin" : "ethereum"].usd = price; - } else { - elizaLogger2.warn( - `No price data available for token: ${token}` - ); - } - } - this.cache.set(cacheKey, prices); - return prices; - } catch (error) { - elizaLogger2.error("Error fetching prices:", error); - throw error; - } - } - formatPortfolio(runtime, portfolio, prices) { - let output = `${runtime.character.description} -`; - output += `Wallet Address: ${this.walletPublicKey.toBase58()} - -`; - const totalUsdFormatted = new BigNumber2(portfolio.totalUsd).toFixed(2); - const totalSolFormatted = portfolio.totalSol; - output += `Total Value: $${totalUsdFormatted} (${totalSolFormatted} SOL) - -`; - output += "Token Balances:\n"; - const nonZeroItems = portfolio.items.filter( - (item) => new BigNumber2(item.uiAmount).isGreaterThan(0) - ); - if (nonZeroItems.length === 0) { - output += "No tokens found with non-zero balance\n"; - } else { - for (const item of nonZeroItems) { - const valueUsd = new BigNumber2(item.valueUsd).toFixed(2); - output += `${item.name} (${item.symbol}): ${new BigNumber2( - item.uiAmount - ).toFixed(6)} ($${valueUsd} | ${item.valueSol} SOL) -`; - } - } - output += "\nMarket Prices:\n"; - output += `SOL: $${new BigNumber2(prices.solana.usd).toFixed(2)} -`; - output += `BTC: $${new BigNumber2(prices.bitcoin.usd).toFixed(2)} -`; - output += `ETH: $${new BigNumber2(prices.ethereum.usd).toFixed(2)} -`; - return output; - } - async getFormattedPortfolio(runtime) { - try { - const [portfolio, prices] = await Promise.all([ - this.fetchPortfolioValue(runtime), - this.fetchPrices(runtime) - ]); - return this.formatPortfolio(runtime, portfolio, prices); - } catch (error) { - elizaLogger2.error("Error generating portfolio report:", error); - return "Unable to fetch wallet information. Please try again later."; - } - } - async getTokenAccounts(walletAddress) { - try { - const accounts = await this.connection.getParsedTokenAccountsByOwner( - new PublicKey2(walletAddress), - { - programId: new PublicKey2( - "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" - ) - } - ); - return accounts.value; - } catch (error) { - elizaLogger2.error("Error fetching token accounts:", error); - return []; - } - } -}; -var walletProvider = { - get: async (runtime, _message, _state) => { - try { - const { publicKey } = await getWalletKey(runtime, false); - const connection3 = new Connection( - runtime.getSetting("SOLANA_RPC_URL") || PROVIDER_CONFIG.DEFAULT_RPC - ); - const provider = new WalletProvider(connection3, publicKey); - return await provider.getFormattedPortfolio(runtime); - } catch (error) { - elizaLogger2.error("Error in wallet provider:", error); - return null; - } - } -}; - -// src/providers/token.ts -import { Connection as Connection2 } from "@solana/web3.js"; -var PROVIDER_CONFIG2 = { - BIRDEYE_API: "https://public-api.birdeye.so", - MAX_RETRIES: 3, - RETRY_DELAY: 2e3, - DEFAULT_RPC: "https://api.mainnet-beta.solana.com", - TOKEN_ADDRESSES: { - SOL: "So11111111111111111111111111111111111111112", - BTC: "qfnqNqs3nCAHjnyCgLRDbBtq4p2MtHZxw8YjSyYhPoL", - ETH: "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", - Example: "2weMjPLLybRMMva1fM3U31goWWrCpF59CHWNhnCJ9Vyh" - }, - TOKEN_SECURITY_ENDPOINT: "/defi/token_security?address=", - TOKEN_TRADE_DATA_ENDPOINT: "/defi/v3/token/trade-data/single?address=", - DEX_SCREENER_API: "https://api.dexscreener.com/latest/dex/tokens/", - MAIN_WALLET: "" -}; -var TokenProvider = class { - constructor(tokenAddress2, walletProvider2, cacheManager) { - this.tokenAddress = tokenAddress2; - this.walletProvider = walletProvider2; - this.cacheManager = cacheManager; - this.cache = new NodeCache2({ stdTTL: 300 }); - } - cache; - cacheKey = "solana/tokens"; - NETWORK_ID = 1399811149; - GRAPHQL_ENDPOINT = "https://graph.codex.io/graphql"; - async readFromCache(key) { - const cached = await this.cacheManager.get( - path.join(this.cacheKey, key) - ); - return cached; - } - async writeToCache(key, data) { - await this.cacheManager.set(path.join(this.cacheKey, key), data, { - expires: Date.now() + 5 * 60 * 1e3 - }); - } - async getCachedData(key) { - const cachedData = this.cache.get(key); - if (cachedData) { - return cachedData; - } - const fileCachedData = await this.readFromCache(key); - if (fileCachedData) { - this.cache.set(key, fileCachedData); - return fileCachedData; - } - return null; - } - async setCachedData(cacheKey, data) { - this.cache.set(cacheKey, data); - await this.writeToCache(cacheKey, data); - } - async fetchWithRetry(url, options = {}) { - let lastError; - for (let i = 0; i < PROVIDER_CONFIG2.MAX_RETRIES; i++) { - try { - const response = await fetch(url, { - ...options, - headers: { - Accept: "application/json", - "x-chain": "solana", - "X-API-KEY": settings.BIRDEYE_API_KEY || "", - ...options.headers - } - }); - if (!response.ok) { - const errorText = await response.text(); - throw new Error( - `HTTP error! status: ${response.status}, message: ${errorText}` - ); - } - const data = await response.json(); - return data; - } catch (error) { - elizaLogger3.error(`Attempt ${i + 1} failed:`, error); - lastError = error; - if (i < PROVIDER_CONFIG2.MAX_RETRIES - 1) { - const delay = PROVIDER_CONFIG2.RETRY_DELAY * Math.pow(2, i); - elizaLogger3.log(`Waiting ${delay}ms before retrying...`); - await new Promise((resolve) => setTimeout(resolve, delay)); - continue; - } - } - } - elizaLogger3.error( - "All attempts failed. Throwing the last error:", - lastError - ); - throw lastError; - } - async getTokensInWallet(runtime) { - const walletInfo = await this.walletProvider.fetchPortfolioValue(runtime); - const items = walletInfo.items; - return items; - } - // check if the token symbol is in the wallet - async getTokenFromWallet(runtime, tokenSymbol) { - try { - const items = await this.getTokensInWallet(runtime); - const token = items.find((item) => item.symbol === tokenSymbol); - if (token) { - return token.address; - } else { - return null; - } - } catch (error) { - elizaLogger3.error("Error checking token in wallet:", error); - return null; - } - } - async fetchTokenCodex() { - try { - const cacheKey = `token_${this.tokenAddress}`; - const cachedData = await this.getCachedData(cacheKey); - if (cachedData) { - elizaLogger3.log( - `Returning cached token data for ${this.tokenAddress}.` - ); - return cachedData; - } - const query = ` - query Token($address: String!, $networkId: Int!) { - token(input: { address: $address, networkId: $networkId }) { - id - address - cmcId - decimals - name - symbol - totalSupply - isScam - info { - circulatingSupply - imageThumbUrl - } - explorerData { - blueCheckmark - description - tokenType - } - } - } - `; - const variables = { - address: this.tokenAddress, - networkId: this.NETWORK_ID - // Replace with your network ID - }; - const response = await fetch(this.GRAPHQL_ENDPOINT, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: settings.CODEX_API_KEY - }, - body: JSON.stringify({ - query, - variables - }) - }).then((res) => res.json()); - const token = response.data?.data?.token; - if (!token) { - throw new Error(`No data returned for token ${tokenAddress}`); - } - this.setCachedData(cacheKey, token); - return { - id: token.id, - address: token.address, - cmcId: token.cmcId, - decimals: token.decimals, - name: token.name, - symbol: token.symbol, - totalSupply: token.totalSupply, - circulatingSupply: token.info?.circulatingSupply, - imageThumbUrl: token.info?.imageThumbUrl, - blueCheckmark: token.explorerData?.blueCheckmark, - isScam: token.isScam ? true : false - }; - } catch (error) { - elizaLogger3.error( - "Error fetching token data from Codex:", - error.message - ); - return {}; - } - } - async fetchPrices() { - try { - const cacheKey = "prices"; - const cachedData = await this.getCachedData(cacheKey); - if (cachedData) { - elizaLogger3.log("Returning cached prices."); - return cachedData; - } - const { SOL, BTC, ETH } = PROVIDER_CONFIG2.TOKEN_ADDRESSES; - const tokens = [SOL, BTC, ETH]; - const prices = { - solana: { usd: "0" }, - bitcoin: { usd: "0" }, - ethereum: { usd: "0" } - }; - for (const token of tokens) { - const response = await this.fetchWithRetry( - `${PROVIDER_CONFIG2.BIRDEYE_API}/defi/price?address=${token}`, - { - headers: { - "x-chain": "solana" - } - } - ); - if (response?.data?.value) { - const price = response.data.value.toString(); - prices[token === SOL ? "solana" : token === BTC ? "bitcoin" : "ethereum"].usd = price; - } else { - elizaLogger3.warn( - `No price data available for token: ${token}` - ); - } - } - this.setCachedData(cacheKey, prices); - return prices; - } catch (error) { - elizaLogger3.error("Error fetching prices:", error); - throw error; - } - } - async calculateBuyAmounts() { - const dexScreenerData = await this.fetchDexScreenerData(); - const prices = await this.fetchPrices(); - const solPrice = toBN(prices.solana.usd); - if (!dexScreenerData || dexScreenerData.pairs.length === 0) { - return { none: 0, low: 0, medium: 0, high: 0 }; - } - const pair = dexScreenerData.pairs[0]; - const { liquidity, marketCap } = pair; - if (!liquidity || !marketCap) { - return { none: 0, low: 0, medium: 0, high: 0 }; - } - if (liquidity.usd === 0) { - return { none: 0, low: 0, medium: 0, high: 0 }; - } - if (marketCap < 1e5) { - return { none: 0, low: 0, medium: 0, high: 0 }; - } - const impactPercentages = { - LOW: 0.01, - // 1% of liquidity - MEDIUM: 0.05, - // 5% of liquidity - HIGH: 0.1 - // 10% of liquidity - }; - const lowBuyAmountUSD = liquidity.usd * impactPercentages.LOW; - const mediumBuyAmountUSD = liquidity.usd * impactPercentages.MEDIUM; - const highBuyAmountUSD = liquidity.usd * impactPercentages.HIGH; - const lowBuyAmountSOL = toBN(lowBuyAmountUSD).div(solPrice).toNumber(); - const mediumBuyAmountSOL = toBN(mediumBuyAmountUSD).div(solPrice).toNumber(); - const highBuyAmountSOL = toBN(highBuyAmountUSD).div(solPrice).toNumber(); - return { - none: 0, - low: lowBuyAmountSOL, - medium: mediumBuyAmountSOL, - high: highBuyAmountSOL - }; - } - async fetchTokenSecurity() { - const cacheKey = `tokenSecurity_${this.tokenAddress}`; - const cachedData = await this.getCachedData(cacheKey); - if (cachedData) { - elizaLogger3.log( - `Returning cached token security data for ${this.tokenAddress}.` - ); - return cachedData; - } - const url = `${PROVIDER_CONFIG2.BIRDEYE_API}${PROVIDER_CONFIG2.TOKEN_SECURITY_ENDPOINT}${this.tokenAddress}`; - const data = await this.fetchWithRetry(url); - if (!data?.success || !data?.data) { - throw new Error("No token security data available"); - } - const security = { - ownerBalance: data.data.ownerBalance, - creatorBalance: data.data.creatorBalance, - ownerPercentage: data.data.ownerPercentage, - creatorPercentage: data.data.creatorPercentage, - top10HolderBalance: data.data.top10HolderBalance, - top10HolderPercent: data.data.top10HolderPercent - }; - this.setCachedData(cacheKey, security); - elizaLogger3.log(`Token security data cached for ${this.tokenAddress}.`); - return security; - } - async fetchTokenTradeData() { - const cacheKey = `tokenTradeData_${this.tokenAddress}`; - const cachedData = await this.getCachedData(cacheKey); - if (cachedData) { - elizaLogger3.log( - `Returning cached token trade data for ${this.tokenAddress}.` - ); - return cachedData; - } - const url = `${PROVIDER_CONFIG2.BIRDEYE_API}${PROVIDER_CONFIG2.TOKEN_TRADE_DATA_ENDPOINT}${this.tokenAddress}`; - const options = { - method: "GET", - headers: { - accept: "application/json", - "X-API-KEY": settings.BIRDEYE_API_KEY || "" - } - }; - const data = await fetch(url, options).then((res) => res.json()).catch((err) => elizaLogger3.error(err)); - if (!data?.success || !data?.data) { - throw new Error("No token trade data available"); - } - const tradeData = { - address: data.data.address, - holder: data.data.holder, - market: data.data.market, - last_trade_unix_time: data.data.last_trade_unix_time, - last_trade_human_time: data.data.last_trade_human_time, - price: data.data.price, - history_30m_price: data.data.history_30m_price, - price_change_30m_percent: data.data.price_change_30m_percent, - history_1h_price: data.data.history_1h_price, - price_change_1h_percent: data.data.price_change_1h_percent, - history_2h_price: data.data.history_2h_price, - price_change_2h_percent: data.data.price_change_2h_percent, - history_4h_price: data.data.history_4h_price, - price_change_4h_percent: data.data.price_change_4h_percent, - history_6h_price: data.data.history_6h_price, - price_change_6h_percent: data.data.price_change_6h_percent, - history_8h_price: data.data.history_8h_price, - price_change_8h_percent: data.data.price_change_8h_percent, - history_12h_price: data.data.history_12h_price, - price_change_12h_percent: data.data.price_change_12h_percent, - history_24h_price: data.data.history_24h_price, - price_change_24h_percent: data.data.price_change_24h_percent, - unique_wallet_30m: data.data.unique_wallet_30m, - unique_wallet_history_30m: data.data.unique_wallet_history_30m, - unique_wallet_30m_change_percent: data.data.unique_wallet_30m_change_percent, - unique_wallet_1h: data.data.unique_wallet_1h, - unique_wallet_history_1h: data.data.unique_wallet_history_1h, - unique_wallet_1h_change_percent: data.data.unique_wallet_1h_change_percent, - unique_wallet_2h: data.data.unique_wallet_2h, - unique_wallet_history_2h: data.data.unique_wallet_history_2h, - unique_wallet_2h_change_percent: data.data.unique_wallet_2h_change_percent, - unique_wallet_4h: data.data.unique_wallet_4h, - unique_wallet_history_4h: data.data.unique_wallet_history_4h, - unique_wallet_4h_change_percent: data.data.unique_wallet_4h_change_percent, - unique_wallet_8h: data.data.unique_wallet_8h, - unique_wallet_history_8h: data.data.unique_wallet_history_8h, - unique_wallet_8h_change_percent: data.data.unique_wallet_8h_change_percent, - unique_wallet_24h: data.data.unique_wallet_24h, - unique_wallet_history_24h: data.data.unique_wallet_history_24h, - unique_wallet_24h_change_percent: data.data.unique_wallet_24h_change_percent, - trade_30m: data.data.trade_30m, - trade_history_30m: data.data.trade_history_30m, - trade_30m_change_percent: data.data.trade_30m_change_percent, - sell_30m: data.data.sell_30m, - sell_history_30m: data.data.sell_history_30m, - sell_30m_change_percent: data.data.sell_30m_change_percent, - buy_30m: data.data.buy_30m, - buy_history_30m: data.data.buy_history_30m, - buy_30m_change_percent: data.data.buy_30m_change_percent, - volume_30m: data.data.volume_30m, - volume_30m_usd: data.data.volume_30m_usd, - volume_history_30m: data.data.volume_history_30m, - volume_history_30m_usd: data.data.volume_history_30m_usd, - volume_30m_change_percent: data.data.volume_30m_change_percent, - volume_buy_30m: data.data.volume_buy_30m, - volume_buy_30m_usd: data.data.volume_buy_30m_usd, - volume_buy_history_30m: data.data.volume_buy_history_30m, - volume_buy_history_30m_usd: data.data.volume_buy_history_30m_usd, - volume_buy_30m_change_percent: data.data.volume_buy_30m_change_percent, - volume_sell_30m: data.data.volume_sell_30m, - volume_sell_30m_usd: data.data.volume_sell_30m_usd, - volume_sell_history_30m: data.data.volume_sell_history_30m, - volume_sell_history_30m_usd: data.data.volume_sell_history_30m_usd, - volume_sell_30m_change_percent: data.data.volume_sell_30m_change_percent, - trade_1h: data.data.trade_1h, - trade_history_1h: data.data.trade_history_1h, - trade_1h_change_percent: data.data.trade_1h_change_percent, - sell_1h: data.data.sell_1h, - sell_history_1h: data.data.sell_history_1h, - sell_1h_change_percent: data.data.sell_1h_change_percent, - buy_1h: data.data.buy_1h, - buy_history_1h: data.data.buy_history_1h, - buy_1h_change_percent: data.data.buy_1h_change_percent, - volume_1h: data.data.volume_1h, - volume_1h_usd: data.data.volume_1h_usd, - volume_history_1h: data.data.volume_history_1h, - volume_history_1h_usd: data.data.volume_history_1h_usd, - volume_1h_change_percent: data.data.volume_1h_change_percent, - volume_buy_1h: data.data.volume_buy_1h, - volume_buy_1h_usd: data.data.volume_buy_1h_usd, - volume_buy_history_1h: data.data.volume_buy_history_1h, - volume_buy_history_1h_usd: data.data.volume_buy_history_1h_usd, - volume_buy_1h_change_percent: data.data.volume_buy_1h_change_percent, - volume_sell_1h: data.data.volume_sell_1h, - volume_sell_1h_usd: data.data.volume_sell_1h_usd, - volume_sell_history_1h: data.data.volume_sell_history_1h, - volume_sell_history_1h_usd: data.data.volume_sell_history_1h_usd, - volume_sell_1h_change_percent: data.data.volume_sell_1h_change_percent, - trade_2h: data.data.trade_2h, - trade_history_2h: data.data.trade_history_2h, - trade_2h_change_percent: data.data.trade_2h_change_percent, - sell_2h: data.data.sell_2h, - sell_history_2h: data.data.sell_history_2h, - sell_2h_change_percent: data.data.sell_2h_change_percent, - buy_2h: data.data.buy_2h, - buy_history_2h: data.data.buy_history_2h, - buy_2h_change_percent: data.data.buy_2h_change_percent, - volume_2h: data.data.volume_2h, - volume_2h_usd: data.data.volume_2h_usd, - volume_history_2h: data.data.volume_history_2h, - volume_history_2h_usd: data.data.volume_history_2h_usd, - volume_2h_change_percent: data.data.volume_2h_change_percent, - volume_buy_2h: data.data.volume_buy_2h, - volume_buy_2h_usd: data.data.volume_buy_2h_usd, - volume_buy_history_2h: data.data.volume_buy_history_2h, - volume_buy_history_2h_usd: data.data.volume_buy_history_2h_usd, - volume_buy_2h_change_percent: data.data.volume_buy_2h_change_percent, - volume_sell_2h: data.data.volume_sell_2h, - volume_sell_2h_usd: data.data.volume_sell_2h_usd, - volume_sell_history_2h: data.data.volume_sell_history_2h, - volume_sell_history_2h_usd: data.data.volume_sell_history_2h_usd, - volume_sell_2h_change_percent: data.data.volume_sell_2h_change_percent, - trade_4h: data.data.trade_4h, - trade_history_4h: data.data.trade_history_4h, - trade_4h_change_percent: data.data.trade_4h_change_percent, - sell_4h: data.data.sell_4h, - sell_history_4h: data.data.sell_history_4h, - sell_4h_change_percent: data.data.sell_4h_change_percent, - buy_4h: data.data.buy_4h, - buy_history_4h: data.data.buy_history_4h, - buy_4h_change_percent: data.data.buy_4h_change_percent, - volume_4h: data.data.volume_4h, - volume_4h_usd: data.data.volume_4h_usd, - volume_history_4h: data.data.volume_history_4h, - volume_history_4h_usd: data.data.volume_history_4h_usd, - volume_4h_change_percent: data.data.volume_4h_change_percent, - volume_buy_4h: data.data.volume_buy_4h, - volume_buy_4h_usd: data.data.volume_buy_4h_usd, - volume_buy_history_4h: data.data.volume_buy_history_4h, - volume_buy_history_4h_usd: data.data.volume_buy_history_4h_usd, - volume_buy_4h_change_percent: data.data.volume_buy_4h_change_percent, - volume_sell_4h: data.data.volume_sell_4h, - volume_sell_4h_usd: data.data.volume_sell_4h_usd, - volume_sell_history_4h: data.data.volume_sell_history_4h, - volume_sell_history_4h_usd: data.data.volume_sell_history_4h_usd, - volume_sell_4h_change_percent: data.data.volume_sell_4h_change_percent, - trade_8h: data.data.trade_8h, - trade_history_8h: data.data.trade_history_8h, - trade_8h_change_percent: data.data.trade_8h_change_percent, - sell_8h: data.data.sell_8h, - sell_history_8h: data.data.sell_history_8h, - sell_8h_change_percent: data.data.sell_8h_change_percent, - buy_8h: data.data.buy_8h, - buy_history_8h: data.data.buy_history_8h, - buy_8h_change_percent: data.data.buy_8h_change_percent, - volume_8h: data.data.volume_8h, - volume_8h_usd: data.data.volume_8h_usd, - volume_history_8h: data.data.volume_history_8h, - volume_history_8h_usd: data.data.volume_history_8h_usd, - volume_8h_change_percent: data.data.volume_8h_change_percent, - volume_buy_8h: data.data.volume_buy_8h, - volume_buy_8h_usd: data.data.volume_buy_8h_usd, - volume_buy_history_8h: data.data.volume_buy_history_8h, - volume_buy_history_8h_usd: data.data.volume_buy_history_8h_usd, - volume_buy_8h_change_percent: data.data.volume_buy_8h_change_percent, - volume_sell_8h: data.data.volume_sell_8h, - volume_sell_8h_usd: data.data.volume_sell_8h_usd, - volume_sell_history_8h: data.data.volume_sell_history_8h, - volume_sell_history_8h_usd: data.data.volume_sell_history_8h_usd, - volume_sell_8h_change_percent: data.data.volume_sell_8h_change_percent, - trade_24h: data.data.trade_24h, - trade_history_24h: data.data.trade_history_24h, - trade_24h_change_percent: data.data.trade_24h_change_percent, - sell_24h: data.data.sell_24h, - sell_history_24h: data.data.sell_history_24h, - sell_24h_change_percent: data.data.sell_24h_change_percent, - buy_24h: data.data.buy_24h, - buy_history_24h: data.data.buy_history_24h, - buy_24h_change_percent: data.data.buy_24h_change_percent, - volume_24h: data.data.volume_24h, - volume_24h_usd: data.data.volume_24h_usd, - volume_history_24h: data.data.volume_history_24h, - volume_history_24h_usd: data.data.volume_history_24h_usd, - volume_24h_change_percent: data.data.volume_24h_change_percent, - volume_buy_24h: data.data.volume_buy_24h, - volume_buy_24h_usd: data.data.volume_buy_24h_usd, - volume_buy_history_24h: data.data.volume_buy_history_24h, - volume_buy_history_24h_usd: data.data.volume_buy_history_24h_usd, - volume_buy_24h_change_percent: data.data.volume_buy_24h_change_percent, - volume_sell_24h: data.data.volume_sell_24h, - volume_sell_24h_usd: data.data.volume_sell_24h_usd, - volume_sell_history_24h: data.data.volume_sell_history_24h, - volume_sell_history_24h_usd: data.data.volume_sell_history_24h_usd, - volume_sell_24h_change_percent: data.data.volume_sell_24h_change_percent - }; - this.setCachedData(cacheKey, tradeData); - return tradeData; - } - async fetchDexScreenerData() { - const cacheKey = `dexScreenerData_${this.tokenAddress}`; - const cachedData = await this.getCachedData(cacheKey); - if (cachedData) { - elizaLogger3.log("Returning cached DexScreener data."); - return cachedData; - } - const url = `https://api.dexscreener.com/latest/dex/search?q=${this.tokenAddress}`; - try { - elizaLogger3.log( - `Fetching DexScreener data for token: ${this.tokenAddress}` - ); - const data = await fetch(url).then((res) => res.json()).catch((err) => { - elizaLogger3.error(err); - }); - if (!data || !data.pairs) { - throw new Error("No DexScreener data available"); - } - const dexData = { - schemaVersion: data.schemaVersion, - pairs: data.pairs - }; - this.setCachedData(cacheKey, dexData); - return dexData; - } catch (error) { - elizaLogger3.error(`Error fetching DexScreener data:`, error); - return { - schemaVersion: "1.0.0", - pairs: [] - }; - } - } - async searchDexScreenerData(symbol) { - const cacheKey = `dexScreenerData_search_${symbol}`; - const cachedData = await this.getCachedData(cacheKey); - if (cachedData) { - elizaLogger3.log("Returning cached search DexScreener data."); - return this.getHighestLiquidityPair(cachedData); - } - const url = `https://api.dexscreener.com/latest/dex/search?q=${symbol}`; - try { - elizaLogger3.log(`Fetching DexScreener data for symbol: ${symbol}`); - const data = await fetch(url).then((res) => res.json()).catch((err) => { - elizaLogger3.error(err); - return null; - }); - if (!data || !data.pairs || data.pairs.length === 0) { - throw new Error("No DexScreener data available"); - } - const dexData = { - schemaVersion: data.schemaVersion, - pairs: data.pairs - }; - this.setCachedData(cacheKey, dexData); - return this.getHighestLiquidityPair(dexData); - } catch (error) { - elizaLogger3.error(`Error fetching DexScreener data:`, error); - return null; - } - } - getHighestLiquidityPair(dexData) { - if (dexData.pairs.length === 0) { - return null; - } - return dexData.pairs.sort((a, b) => { - const liquidityDiff = b.liquidity.usd - a.liquidity.usd; - if (liquidityDiff !== 0) { - return liquidityDiff; - } - return b.marketCap - a.marketCap; - })[0]; - } - async analyzeHolderDistribution(tradeData) { - const intervals = [ - { - period: "30m", - change: tradeData.unique_wallet_30m_change_percent - }, - { period: "1h", change: tradeData.unique_wallet_1h_change_percent }, - { period: "2h", change: tradeData.unique_wallet_2h_change_percent }, - { period: "4h", change: tradeData.unique_wallet_4h_change_percent }, - { period: "8h", change: tradeData.unique_wallet_8h_change_percent }, - { - period: "24h", - change: tradeData.unique_wallet_24h_change_percent - } - ]; - const validChanges = intervals.map((interval) => interval.change).filter( - (change) => change !== null && change !== void 0 - ); - if (validChanges.length === 0) { - return "stable"; - } - const averageChange = validChanges.reduce((acc, curr) => acc + curr, 0) / validChanges.length; - const increaseThreshold = 10; - const decreaseThreshold = -10; - if (averageChange > increaseThreshold) { - return "increasing"; - } else if (averageChange < decreaseThreshold) { - return "decreasing"; - } else { - return "stable"; - } - } - async fetchHolderList() { - const cacheKey = `holderList_${this.tokenAddress}`; - const cachedData = await this.getCachedData(cacheKey); - if (cachedData) { - elizaLogger3.log("Returning cached holder list."); - return cachedData; - } - const allHoldersMap = /* @__PURE__ */ new Map(); - let page = 1; - const limit = 1e3; - let cursor; - const url = `https://mainnet.helius-rpc.com/?api-key=${settings.HELIUS_API_KEY || ""}`; - elizaLogger3.log({ url }); - try { - while (true) { - const params = { - limit, - displayOptions: {}, - mint: this.tokenAddress, - cursor - }; - if (cursor != void 0) { - params.cursor = cursor; - } - elizaLogger3.log(`Fetching holders - Page ${page}`); - if (page > 2) { - break; - } - const response = await fetch(url, { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({ - jsonrpc: "2.0", - id: "helius-test", - method: "getTokenAccounts", - params - }) - }); - const data = await response.json(); - if (!data || !data.result || !data.result.token_accounts || data.result.token_accounts.length === 0) { - elizaLogger3.log( - `No more holders found. Total pages fetched: ${page - 1}` - ); - break; - } - elizaLogger3.log( - `Processing ${data.result.token_accounts.length} holders from page ${page}` - ); - data.result.token_accounts.forEach((account) => { - const owner = account.owner; - const balance = Number.parseFloat(account.amount); - if (allHoldersMap.has(owner)) { - allHoldersMap.set( - owner, - allHoldersMap.get(owner) + balance - ); - } else { - allHoldersMap.set(owner, balance); - } - }); - cursor = data.result.cursor; - page++; - } - const holders = Array.from( - allHoldersMap.entries() - ).map(([address, balance]) => ({ - address, - balance: balance.toString() - })); - elizaLogger3.log(`Total unique holders fetched: ${holders.length}`); - this.setCachedData(cacheKey, holders); - return holders; - } catch (error) { - elizaLogger3.error("Error fetching holder list from Helius:", error); - throw new Error("Failed to fetch holder list from Helius."); - } - } - async filterHighValueHolders(tradeData) { - const holdersData = await this.fetchHolderList(); - const tokenPriceUsd = toBN(tradeData.price); - const highValueHolders = holdersData.filter((holder) => { - const balanceUsd = toBN(holder.balance).multipliedBy( - tokenPriceUsd - ); - return balanceUsd.isGreaterThan(5); - }).map((holder) => ({ - holderAddress: holder.address, - balanceUsd: toBN(holder.balance).multipliedBy(tokenPriceUsd).toFixed(2) - })); - return highValueHolders; - } - async checkRecentTrades(tradeData) { - return toBN(tradeData.volume_24h_usd).isGreaterThan(0); - } - async countHighSupplyHolders(securityData) { - try { - const ownerBalance = toBN(securityData.ownerBalance); - const totalSupply = ownerBalance.plus(securityData.creatorBalance); - const highSupplyHolders = await this.fetchHolderList(); - const highSupplyHoldersCount = highSupplyHolders.filter( - (holder) => { - const balance = toBN(holder.balance); - return balance.dividedBy(totalSupply).isGreaterThan(0.02); - } - ).length; - return highSupplyHoldersCount; - } catch (error) { - elizaLogger3.error("Error counting high supply holders:", error); - return 0; - } - } - async getProcessedTokenData() { - try { - elizaLogger3.log( - `Fetching security data for token: ${this.tokenAddress}` - ); - const security = await this.fetchTokenSecurity(); - const tokenCodex = await this.fetchTokenCodex(); - elizaLogger3.log( - `Fetching trade data for token: ${this.tokenAddress}` - ); - const tradeData = await this.fetchTokenTradeData(); - elizaLogger3.log( - `Fetching DexScreener data for token: ${this.tokenAddress}` - ); - const dexData = await this.fetchDexScreenerData(); - elizaLogger3.log( - `Analyzing holder distribution for token: ${this.tokenAddress}` - ); - const holderDistributionTrend = await this.analyzeHolderDistribution(tradeData); - elizaLogger3.log( - `Filtering high-value holders for token: ${this.tokenAddress}` - ); - const highValueHolders = await this.filterHighValueHolders(tradeData); - elizaLogger3.log( - `Checking recent trades for token: ${this.tokenAddress}` - ); - const recentTrades = await this.checkRecentTrades(tradeData); - elizaLogger3.log( - `Counting high-supply holders for token: ${this.tokenAddress}` - ); - const highSupplyHoldersCount = await this.countHighSupplyHolders(security); - elizaLogger3.log( - `Determining DexScreener listing status for token: ${this.tokenAddress}` - ); - const isDexScreenerListed = dexData.pairs.length > 0; - const isDexScreenerPaid = dexData.pairs.some( - (pair) => pair.boosts && pair.boosts.active > 0 - ); - const processedData = { - security, - tradeData, - holderDistributionTrend, - highValueHolders, - recentTrades, - highSupplyHoldersCount, - dexScreenerData: dexData, - isDexScreenerListed, - isDexScreenerPaid, - tokenCodex - }; - return processedData; - } catch (error) { - elizaLogger3.error("Error processing token data:", error); - throw error; - } - } - async shouldTradeToken() { - try { - const tokenData = await this.getProcessedTokenData(); - const { tradeData, security, dexScreenerData } = tokenData; - const { ownerBalance, creatorBalance } = security; - const { liquidity, marketCap } = dexScreenerData.pairs[0]; - const liquidityUsd = toBN(liquidity.usd); - const marketCapUsd = toBN(marketCap); - const totalSupply = toBN(ownerBalance).plus(creatorBalance); - const _ownerPercentage = toBN(ownerBalance).dividedBy(totalSupply); - const _creatorPercentage = toBN(creatorBalance).dividedBy(totalSupply); - const top10HolderPercent = toBN(tradeData.volume_24h_usd).dividedBy( - totalSupply - ); - const priceChange24hPercent = toBN( - tradeData.price_change_24h_percent - ); - const priceChange12hPercent = toBN( - tradeData.price_change_12h_percent - ); - const uniqueWallet24h = tradeData.unique_wallet_24h; - const volume24hUsd = toBN(tradeData.volume_24h_usd); - const volume24hUsdThreshold = 1e3; - const priceChange24hPercentThreshold = 10; - const priceChange12hPercentThreshold = 5; - const top10HolderPercentThreshold = 0.05; - const uniqueWallet24hThreshold = 100; - const isTop10Holder = top10HolderPercent.gte( - top10HolderPercentThreshold - ); - const isVolume24h = volume24hUsd.gte(volume24hUsdThreshold); - const isPriceChange24h = priceChange24hPercent.gte( - priceChange24hPercentThreshold - ); - const isPriceChange12h = priceChange12hPercent.gte( - priceChange12hPercentThreshold - ); - const isUniqueWallet24h = uniqueWallet24h >= uniqueWallet24hThreshold; - const isLiquidityTooLow = liquidityUsd.lt(1e3); - const isMarketCapTooLow = marketCapUsd.lt(1e5); - return isTop10Holder || isVolume24h || isPriceChange24h || isPriceChange12h || isUniqueWallet24h || isLiquidityTooLow || isMarketCapTooLow; - } catch (error) { - elizaLogger3.error("Error processing token data:", error); - throw error; - } - } - formatTokenData(data) { - let output = `**Token Security and Trade Report** -`; - output += `Token Address: ${this.tokenAddress} - -`; - output += `**Ownership Distribution:** -`; - output += `- Owner Balance: ${data.security.ownerBalance} -`; - output += `- Creator Balance: ${data.security.creatorBalance} -`; - output += `- Owner Percentage: ${data.security.ownerPercentage}% -`; - output += `- Creator Percentage: ${data.security.creatorPercentage}% -`; - output += `- Top 10 Holders Balance: ${data.security.top10HolderBalance} -`; - output += `- Top 10 Holders Percentage: ${data.security.top10HolderPercent}% - -`; - output += `**Trade Data:** -`; - output += `- Holders: ${data.tradeData.holder} -`; - output += `- Unique Wallets (24h): ${data.tradeData.unique_wallet_24h} -`; - output += `- Price Change (24h): ${data.tradeData.price_change_24h_percent}% -`; - output += `- Price Change (12h): ${data.tradeData.price_change_12h_percent}% -`; - output += `- Volume (24h USD): $${toBN(data.tradeData.volume_24h_usd).toFixed(2)} -`; - output += `- Current Price: $${toBN(data.tradeData.price).toFixed(2)} - -`; - output += `**Holder Distribution Trend:** ${data.holderDistributionTrend} - -`; - output += `**High-Value Holders (>$5 USD):** -`; - if (data.highValueHolders.length === 0) { - output += `- No high-value holders found or data not available. -`; - } else { - data.highValueHolders.forEach((holder) => { - output += `- ${holder.holderAddress}: $${holder.balanceUsd} -`; - }); - } - output += ` -`; - output += `**Recent Trades (Last 24h):** ${data.recentTrades ? "Yes" : "No"} - -`; - output += `**Holders with >2% Supply:** ${data.highSupplyHoldersCount} - -`; - output += `**DexScreener Listing:** ${data.isDexScreenerListed ? "Yes" : "No"} -`; - if (data.isDexScreenerListed) { - output += `- Listing Type: ${data.isDexScreenerPaid ? "Paid" : "Free"} -`; - output += `- Number of DexPairs: ${data.dexScreenerData.pairs.length} - -`; - output += `**DexScreener Pairs:** -`; - data.dexScreenerData.pairs.forEach((pair, index) => { - output += ` -**Pair ${index + 1}:** -`; - output += `- DEX: ${pair.dexId} -`; - output += `- URL: ${pair.url} -`; - output += `- Price USD: $${toBN(pair.priceUsd).toFixed(6)} -`; - output += `- Volume (24h USD): $${toBN(pair.volume.h24).toFixed(2)} -`; - output += `- Boosts Active: ${pair.boosts && pair.boosts.active} -`; - output += `- Liquidity USD: $${toBN(pair.liquidity.usd).toFixed(2)} -`; - }); - } - output += ` -`; - elizaLogger3.log("Formatted token data:", output); - return output; - } - async getFormattedTokenReport() { - try { - elizaLogger3.log("Generating formatted token report..."); - const processedData = await this.getProcessedTokenData(); - return this.formatTokenData(processedData); - } catch (error) { - elizaLogger3.error("Error generating token report:", error); - return "Unable to fetch token information. Please try again later."; - } - } -}; -var tokenAddress = PROVIDER_CONFIG2.TOKEN_ADDRESSES.Example; -var connection = new Connection2(PROVIDER_CONFIG2.DEFAULT_RPC); -var tokenProvider = { - get: async (runtime, _message, _state) => { - try { - const { publicKey } = await getWalletKey(runtime, false); - const walletProvider2 = new WalletProvider(connection, publicKey); - const provider = new TokenProvider( - tokenAddress, - walletProvider2, - runtime.cacheManager - ); - return provider.getFormattedTokenReport(); - } catch (error) { - elizaLogger3.error("Error fetching token data:", error); - return "Unable to fetch token information. Please try again later."; - } - } -}; - -// src/providers/trustScoreProvider.ts -import { - elizaLogger as elizaLogger5, - settings as settings2 -} from "@elizaos/core"; -import { - TrustScoreDatabase -} from "@elizaos/plugin-trustdb"; -import { getAssociatedTokenAddress } from "@solana/spl-token"; -import { Connection as Connection4, PublicKey as PublicKey4 } from "@solana/web3.js"; - -// ../../node_modules/uuid/dist/esm-node/stringify.js -var byteToHex = []; -for (let i = 0; i < 256; ++i) { - byteToHex.push((i + 256).toString(16).slice(1)); -} -function unsafeStringify(arr, offset = 0) { - return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase(); -} - -// ../../node_modules/uuid/dist/esm-node/rng.js -import crypto from "node:crypto"; -var rnds8Pool = new Uint8Array(256); -var poolPtr = rnds8Pool.length; -function rng() { - if (poolPtr > rnds8Pool.length - 16) { - crypto.randomFillSync(rnds8Pool); - poolPtr = 0; - } - return rnds8Pool.slice(poolPtr, poolPtr += 16); -} - -// ../../node_modules/uuid/dist/esm-node/native.js -import crypto2 from "node:crypto"; -var native_default = { - randomUUID: crypto2.randomUUID -}; - -// ../../node_modules/uuid/dist/esm-node/v4.js -function v4(options, buf, offset) { - if (native_default.randomUUID && !buf && !options) { - return native_default.randomUUID(); - } - options = options || {}; - const rnds = options.random || (options.rng || rng)(); - rnds[6] = rnds[6] & 15 | 64; - rnds[8] = rnds[8] & 63 | 128; - if (buf) { - offset = offset || 0; - for (let i = 0; i < 16; ++i) { - buf[offset + i] = rnds[i]; - } - return buf; - } - return unsafeStringify(rnds); -} -var v4_default = v4; - -// src/providers/simulationSellingService.ts -import { Connection as Connection3, PublicKey as PublicKey3 } from "@solana/web3.js"; -import { elizaLogger as elizaLogger4 } from "@elizaos/core"; -import * as amqp from "amqplib"; -var SimulationSellingService = class { - trustScoreDb; - walletProvider; - connection; - baseMint; - DECAY_RATE = 0.95; - MAX_DECAY_DAYS = 30; - backend; - backendToken; - amqpConnection; - amqpChannel; - sonarBe; - sonarBeToken; - runtime; - runningProcesses = /* @__PURE__ */ new Set(); - constructor(runtime, trustScoreDb) { - this.trustScoreDb = trustScoreDb; - this.connection = new Connection3(runtime.getSetting("SOLANA_RPC_URL")); - this.baseMint = new PublicKey3( - runtime.getSetting("BASE_MINT") || "So11111111111111111111111111111111111111112" - ); - this.backend = runtime.getSetting("BACKEND_URL"); - this.backendToken = runtime.getSetting("BACKEND_TOKEN"); - this.initializeRabbitMQ(runtime.getSetting("AMQP_URL")); - this.sonarBe = runtime.getSetting("SONAR_BE"); - this.sonarBeToken = runtime.getSetting("SONAR_BE_TOKEN"); - this.runtime = runtime; - this.initializeWalletProvider(); - } - /** - * Initializes the RabbitMQ connection and starts consuming messages. - * @param amqpUrl The RabbitMQ server URL. - */ - async initializeRabbitMQ(amqpUrl) { - try { - this.amqpConnection = await amqp.connect(amqpUrl); - this.amqpChannel = await this.amqpConnection.createChannel(); - elizaLogger4.log("Connected to RabbitMQ"); - this.consumeMessages(); - } catch (error) { - elizaLogger4.error("Failed to connect to RabbitMQ:", error); - } - } - /** - * Sets up the consumer for the specified RabbitMQ queue. - */ - async consumeMessages() { - const queue = "process_eliza_simulation"; - await this.amqpChannel.assertQueue(queue, { durable: true }); - this.amqpChannel.consume( - queue, - (msg) => { - if (msg !== null) { - const content = msg.content.toString(); - this.processMessage(content); - this.amqpChannel.ack(msg); - } - }, - { noAck: false } - ); - elizaLogger4.log(`Listening for messages on queue: ${queue}`); - } - /** - * Processes incoming messages from RabbitMQ. - * @param message The message content as a string. - */ - async processMessage(message) { - try { - const { tokenAddress: tokenAddress2, amount, sell_recommender_id } = JSON.parse(message); - elizaLogger4.log( - `Received message for token ${tokenAddress2} to sell ${amount}` - ); - const decision = { - tokenPerformance: await this.trustScoreDb.getTokenPerformance(tokenAddress2), - amountToSell: amount, - sell_recommender_id - }; - await this.executeSellDecision(decision); - this.runningProcesses.delete(tokenAddress2); - } catch (error) { - elizaLogger4.error("Error processing message:", error); - } - } - /** - * Executes a single sell decision. - * @param decision The sell decision containing token performance and amount to sell. - */ - async executeSellDecision(decision) { - const { tokenPerformance, amountToSell, sell_recommender_id } = decision; - const tokenAddress2 = tokenPerformance.tokenAddress; - try { - elizaLogger4.log( - `Executing sell for token ${tokenPerformance.symbol}: ${amountToSell}` - ); - const sellDetails = { - sell_amount: amountToSell, - sell_recommender_id - // Adjust if necessary - }; - const sellTimeStamp = (/* @__PURE__ */ new Date()).toISOString(); - const tokenProvider2 = new TokenProvider( - tokenAddress2, - this.walletProvider, - this.runtime.cacheManager - ); - const sellDetailsData = await this.updateSellDetails( - tokenAddress2, - sell_recommender_id, - sellTimeStamp, - sellDetails, - true, - // isSimulation - tokenProvider2 - ); - elizaLogger4.log( - "Sell order executed successfully", - sellDetailsData - ); - const balance = this.trustScoreDb.getTokenBalance(tokenAddress2); - if (balance === 0) { - this.runningProcesses.delete(tokenAddress2); - } - await this.stopProcessInTheSonarBackend(tokenAddress2); - } catch (error) { - elizaLogger4.error( - `Error executing sell for token ${tokenAddress2}:`, - error - ); - } - } - /** - * Derives the public key based on the TEE (Trusted Execution Environment) mode and initializes the wallet provider. - * If TEE mode is enabled, derives a keypair using the DeriveKeyProvider with the wallet secret salt and agent ID. - * If TEE mode is disabled, uses the provided Solana public key or wallet public key from settings. - */ - async initializeWalletProvider() { - const { publicKey } = await getWalletKey(this.runtime, false); - this.walletProvider = new WalletProvider(this.connection, publicKey); - } - async startService() { - elizaLogger4.log("Starting SellingService..."); - await this.startListeners(); - } - async startListeners() { - elizaLogger4.log("Scanning for token performances..."); - const tokenPerformances = await this.trustScoreDb.getAllTokenPerformancesWithBalance(); - await this.processTokenPerformances(tokenPerformances); - } - processTokenPerformances(tokenPerformances) { - elizaLogger4.log("Deciding when to sell and how much..."); - const runningProcesses = this.runningProcesses; - tokenPerformances = tokenPerformances.filter( - (tp) => !runningProcesses.has(tp.tokenAddress) - ); - tokenPerformances.forEach(async (tokenPerformance) => { - const tokenProvider2 = new TokenProvider( - tokenPerformance.tokenAddress, - this.walletProvider, - this.runtime.cacheManager - ); - const tokenRecommendations = this.trustScoreDb.getRecommendationsByToken( - tokenPerformance.tokenAddress - ); - const tokenRecommendation = tokenRecommendations[0]; - const balance = tokenPerformance.balance; - const sell_recommender_id = tokenRecommendation.recommenderId; - const tokenAddress2 = tokenPerformance.tokenAddress; - const process = await this.startProcessInTheSonarBackend( - tokenAddress2, - balance, - true, - sell_recommender_id, - tokenPerformance.initialMarketCap - ); - if (process) { - this.runningProcesses.add(tokenAddress2); - } - }); - } - processTokenPerformance(tokenAddress2, recommenderId) { - try { - const runningProcesses = this.runningProcesses; - if (runningProcesses.has(tokenAddress2)) { - elizaLogger4.log( - `Token ${tokenAddress2} is already being processed` - ); - return; - } - const tokenPerformance = this.trustScoreDb.getTokenPerformance(tokenAddress2); - const tokenProvider2 = new TokenProvider( - tokenPerformance.tokenAddress, - this.walletProvider, - this.runtime.cacheManager - ); - const balance = tokenPerformance.balance; - const sell_recommender_id = recommenderId; - const process = this.startProcessInTheSonarBackend( - tokenAddress2, - balance, - true, - sell_recommender_id, - tokenPerformance.initialMarketCap - ); - if (process) { - this.runningProcesses.add(tokenAddress2); - } - } catch (error) { - elizaLogger4.error( - `Error getting token performance for token ${tokenAddress2}:`, - error - ); - } - } - async startProcessInTheSonarBackend(tokenAddress2, balance, isSimulation, sell_recommender_id, initial_mc) { - try { - const message = JSON.stringify({ - tokenAddress: tokenAddress2, - balance, - isSimulation, - initial_mc, - sell_recommender_id - }); - const response = await fetch( - `${this.sonarBe}/elizaos-sol/startProcess`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - "x-api-key": `${this.sonarBeToken}` - }, - body: message - } - ); - if (!response.ok) { - elizaLogger4.error( - `Failed to send message to process token ${tokenAddress2}` - ); - return; - } - const result = await response.json(); - elizaLogger4.log("Received response:", result); - elizaLogger4.log(`Sent message to process token ${tokenAddress2}`); - return result; - } catch (error) { - elizaLogger4.error( - `Error sending message to process token ${tokenAddress2}:`, - error - ); - return null; - } - } - stopProcessInTheSonarBackend(tokenAddress2) { - try { - return fetch(`${this.sonarBe}/elizaos-sol/stopProcess`, { - method: "POST", - headers: { - "Content-Type": "application/json", - "x-api-key": `${this.sonarBeToken}` - }, - body: JSON.stringify({ tokenAddress: tokenAddress2 }) - }); - } catch (error) { - elizaLogger4.error( - `Error stopping process for token ${tokenAddress2}:`, - error - ); - } - } - async updateSellDetails(tokenAddress2, recommenderId, sellTimeStamp, sellDetails, isSimulation, tokenProvider2) { - const recommender = await this.trustScoreDb.getOrCreateRecommenderWithTelegramId( - recommenderId - ); - const processedData = await tokenProvider2.getProcessedTokenData(); - const prices = await this.walletProvider.fetchPrices(null); - const solPrice = prices.solana.usd; - const sellSol = sellDetails.sell_amount / Number.parseFloat(solPrice); - const sell_value_usd = sellDetails.sell_amount * processedData.tradeData.price; - const trade = await this.trustScoreDb.getLatestTradePerformance( - tokenAddress2, - recommender.id, - isSimulation - ); - const buyTimeStamp = trade.buy_timeStamp; - const marketCap = processedData.dexScreenerData.pairs[0]?.marketCap || 0; - const liquidity = processedData.dexScreenerData.pairs[0]?.liquidity.usd || 0; - const sell_price = processedData.tradeData.price; - const profit_usd = sell_value_usd - trade.buy_value_usd; - const profit_percent = profit_usd / trade.buy_value_usd * 100; - const market_cap_change = marketCap - trade.buy_market_cap; - const liquidity_change = liquidity - trade.buy_liquidity; - const isRapidDump = await this.isRapidDump(tokenAddress2, tokenProvider2); - const sellDetailsData = { - sell_price, - sell_timeStamp: sellTimeStamp, - sell_amount: sellDetails.sell_amount, - received_sol: sellSol, - sell_value_usd, - profit_usd, - profit_percent, - sell_market_cap: marketCap, - market_cap_change, - sell_liquidity: liquidity, - liquidity_change, - rapidDump: isRapidDump, - sell_recommender_id: sellDetails.sell_recommender_id || null - }; - this.trustScoreDb.updateTradePerformanceOnSell( - tokenAddress2, - recommender.id, - buyTimeStamp, - sellDetailsData, - isSimulation - ); - const oldBalance = this.trustScoreDb.getTokenBalance(tokenAddress2); - const tokenBalance = oldBalance - sellDetails.sell_amount; - this.trustScoreDb.updateTokenBalance(tokenAddress2, tokenBalance); - const hash = Math.random().toString(36).substring(7); - const transaction = { - tokenAddress: tokenAddress2, - type: "sell", - transactionHash: hash, - amount: sellDetails.sell_amount, - price: processedData.tradeData.price, - isSimulation: true, - timestamp: (/* @__PURE__ */ new Date()).toISOString() - }; - this.trustScoreDb.addTransaction(transaction); - this.updateTradeInBe( - tokenAddress2, - recommender.id, - recommender.telegramId, - sellDetailsData, - tokenBalance - ); - return sellDetailsData; - } - async isRapidDump(tokenAddress2, tokenProvider2) { - const processedData = await tokenProvider2.getProcessedTokenData(); - elizaLogger4.log( - `Fetched processed token data for token: ${tokenAddress2}` - ); - return processedData.tradeData.trade_24h_change_percent < -50; - } - async delay(ms) { - return new Promise((resolve) => setTimeout(resolve, ms)); - } - async updateTradeInBe(tokenAddress2, recommenderId, username, data, balanceLeft, retries = 3, delayMs = 2e3) { - for (let attempt = 1; attempt <= retries; attempt++) { - try { - await fetch( - `${this.backend}/api/updaters/updateTradePerformance`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${this.backendToken}` - }, - body: JSON.stringify({ - tokenAddress: tokenAddress2, - tradeData: data, - recommenderId, - username, - isSimulation: true, - balanceLeft - }) - } - ); - return; - } catch (error) { - elizaLogger4.error( - `Attempt ${attempt} failed: Error creating trade in backend`, - error - ); - if (attempt < retries) { - elizaLogger4.log(`Retrying in ${delayMs} ms...`); - await this.delay(delayMs); - } else { - elizaLogger4.error("All attempts failed."); - } - } - } - } -}; - -// src/providers/trustScoreProvider.ts -var Wallet = settings2.MAIN_WALLET_ADDRESS; -var TrustScoreManager = class { - tokenProvider; - trustScoreDb; - simulationSellingService; - connection; - baseMint; - DECAY_RATE = 0.95; - MAX_DECAY_DAYS = 30; - backend; - backendToken; - constructor(runtime, tokenProvider2, trustScoreDb) { - this.tokenProvider = tokenProvider2; - this.trustScoreDb = trustScoreDb; - this.connection = new Connection4(runtime.getSetting("SOLANA_RPC_URL")); - this.baseMint = new PublicKey4( - runtime.getSetting("BASE_MINT") || "So11111111111111111111111111111111111111112" - ); - this.backend = runtime.getSetting("BACKEND_URL"); - this.backendToken = runtime.getSetting("BACKEND_TOKEN"); - this.simulationSellingService = new SimulationSellingService( - runtime, - this.trustScoreDb - ); - } - //getRecommenederBalance - async getRecommenederBalance(recommenderWallet) { - try { - const tokenAta = await getAssociatedTokenAddress( - new PublicKey4(recommenderWallet), - this.baseMint - ); - const tokenBalInfo = await this.connection.getTokenAccountBalance(tokenAta); - const tokenBalance = tokenBalInfo.value.amount; - const balance = Number.parseFloat(tokenBalance); - return balance; - } catch (error) { - elizaLogger5.error("Error fetching balance", error); - return 0; - } - } - /** - * Generates and saves trust score based on processed token data and user recommendations. - * @param tokenAddress The address of the token to analyze. - * @param recommenderId The UUID of the recommender. - * @returns An object containing TokenPerformance and RecommenderMetrics. - */ - async generateTrustScore(tokenAddress2, recommenderId, recommenderWallet) { - const processedData = await this.tokenProvider.getProcessedTokenData(); - elizaLogger5.log( - `Fetched processed token data for token: ${tokenAddress2}` - ); - const recommenderMetrics = await this.trustScoreDb.getRecommenderMetrics(recommenderId); - const isRapidDump = await this.isRapidDump(tokenAddress2); - const sustainedGrowth = await this.sustainedGrowth(tokenAddress2); - const suspiciousVolume = await this.suspiciousVolume(tokenAddress2); - const balance = await this.getRecommenederBalance(recommenderWallet); - const virtualConfidence = balance / 1e6; - const lastActive = recommenderMetrics.lastActiveDate; - const now = /* @__PURE__ */ new Date(); - const inactiveDays = Math.floor( - (now.getTime() - lastActive.getTime()) / (1e3 * 60 * 60 * 24) - ); - const decayFactor = Math.pow( - this.DECAY_RATE, - Math.min(inactiveDays, this.MAX_DECAY_DAYS) - ); - const decayedScore = recommenderMetrics.trustScore * decayFactor; - const validationTrustScore = this.trustScoreDb.calculateValidationTrust(tokenAddress2); - return { - tokenPerformance: { - tokenAddress: processedData.dexScreenerData.pairs[0]?.baseToken.address || "", - priceChange24h: processedData.tradeData.price_change_24h_percent, - volumeChange24h: processedData.tradeData.volume_24h, - trade_24h_change: processedData.tradeData.trade_24h_change_percent, - liquidity: processedData.dexScreenerData.pairs[0]?.liquidity.usd || 0, - liquidityChange24h: 0, - holderChange24h: processedData.tradeData.unique_wallet_24h_change_percent, - rugPull: false, - isScam: processedData.tokenCodex.isScam, - marketCapChange24h: 0, - sustainedGrowth, - rapidDump: isRapidDump, - suspiciousVolume, - validationTrust: validationTrustScore, - balance, - initialMarketCap: processedData.dexScreenerData.pairs[0]?.marketCap || 0, - lastUpdated: /* @__PURE__ */ new Date(), - symbol: "" - }, - recommenderMetrics: { - recommenderId, - trustScore: recommenderMetrics.trustScore, - totalRecommendations: recommenderMetrics.totalRecommendations, - successfulRecs: recommenderMetrics.successfulRecs, - avgTokenPerformance: recommenderMetrics.avgTokenPerformance, - riskScore: recommenderMetrics.riskScore, - consistencyScore: recommenderMetrics.consistencyScore, - virtualConfidence, - lastActiveDate: now, - trustDecay: decayedScore, - lastUpdated: /* @__PURE__ */ new Date() - } - }; - } - async updateRecommenderMetrics(recommenderId, tokenPerformance, recommenderWallet) { - const recommenderMetrics = await this.trustScoreDb.getRecommenderMetrics(recommenderId); - const totalRecommendations = recommenderMetrics.totalRecommendations + 1; - const successfulRecs = tokenPerformance.rugPull ? recommenderMetrics.successfulRecs : recommenderMetrics.successfulRecs + 1; - const avgTokenPerformance = (recommenderMetrics.avgTokenPerformance * recommenderMetrics.totalRecommendations + tokenPerformance.priceChange24h) / totalRecommendations; - const overallTrustScore = this.calculateTrustScore( - tokenPerformance, - recommenderMetrics - ); - const riskScore = this.calculateOverallRiskScore( - tokenPerformance, - recommenderMetrics - ); - const consistencyScore = this.calculateConsistencyScore( - tokenPerformance, - recommenderMetrics - ); - const balance = await this.getRecommenederBalance(recommenderWallet); - const virtualConfidence = balance / 1e6; - const lastActive = recommenderMetrics.lastActiveDate; - const now = /* @__PURE__ */ new Date(); - const inactiveDays = Math.floor( - (now.getTime() - lastActive.getTime()) / (1e3 * 60 * 60 * 24) - ); - const decayFactor = Math.pow( - this.DECAY_RATE, - Math.min(inactiveDays, this.MAX_DECAY_DAYS) - ); - const decayedScore = recommenderMetrics.trustScore * decayFactor; - const newRecommenderMetrics = { - recommenderId, - trustScore: overallTrustScore, - totalRecommendations, - successfulRecs, - avgTokenPerformance, - riskScore, - consistencyScore, - virtualConfidence, - lastActiveDate: /* @__PURE__ */ new Date(), - trustDecay: decayedScore, - lastUpdated: /* @__PURE__ */ new Date() - }; - await this.trustScoreDb.updateRecommenderMetrics(newRecommenderMetrics); - } - calculateTrustScore(tokenPerformance, recommenderMetrics) { - const riskScore = this.calculateRiskScore(tokenPerformance); - const consistencyScore = this.calculateConsistencyScore( - tokenPerformance, - recommenderMetrics - ); - return (riskScore + consistencyScore) / 2; - } - calculateOverallRiskScore(tokenPerformance, recommenderMetrics) { - const riskScore = this.calculateRiskScore(tokenPerformance); - const consistencyScore = this.calculateConsistencyScore( - tokenPerformance, - recommenderMetrics - ); - return (riskScore + consistencyScore) / 2; - } - calculateRiskScore(tokenPerformance) { - let riskScore = 0; - if (tokenPerformance.rugPull) { - riskScore += 10; - } - if (tokenPerformance.isScam) { - riskScore += 10; - } - if (tokenPerformance.rapidDump) { - riskScore += 5; - } - if (tokenPerformance.suspiciousVolume) { - riskScore += 5; - } - return riskScore; - } - calculateConsistencyScore(tokenPerformance, recommenderMetrics) { - const avgTokenPerformance = recommenderMetrics.avgTokenPerformance; - const priceChange24h = tokenPerformance.priceChange24h; - return Math.abs(priceChange24h - avgTokenPerformance); - } - async suspiciousVolume(tokenAddress2) { - const processedData = await this.tokenProvider.getProcessedTokenData(); - const unique_wallet_24h = processedData.tradeData.unique_wallet_24h; - const volume_24h = processedData.tradeData.volume_24h; - const suspiciousVolume = unique_wallet_24h / volume_24h > 0.5; - elizaLogger5.log( - `Fetched processed token data for token: ${tokenAddress2}` - ); - return suspiciousVolume; - } - async sustainedGrowth(tokenAddress2) { - const processedData = await this.tokenProvider.getProcessedTokenData(); - elizaLogger5.log( - `Fetched processed token data for token: ${tokenAddress2}` - ); - return processedData.tradeData.volume_24h_change_percent > 50; - } - async isRapidDump(tokenAddress2) { - const processedData = await this.tokenProvider.getProcessedTokenData(); - elizaLogger5.log( - `Fetched processed token data for token: ${tokenAddress2}` - ); - return processedData.tradeData.trade_24h_change_percent < -50; - } - async checkTrustScore(tokenAddress2) { - const processedData = await this.tokenProvider.getProcessedTokenData(); - elizaLogger5.log( - `Fetched processed token data for token: ${tokenAddress2}` - ); - return { - ownerBalance: processedData.security.ownerBalance, - creatorBalance: processedData.security.creatorBalance, - ownerPercentage: processedData.security.ownerPercentage, - creatorPercentage: processedData.security.creatorPercentage, - top10HolderBalance: processedData.security.top10HolderBalance, - top10HolderPercent: processedData.security.top10HolderPercent - }; - } - /** - * Creates a TradePerformance object based on token data and recommender. - * @param tokenAddress The address of the token. - * @param recommenderId The UUID of the recommender. - * @param data ProcessedTokenData. - * @returns TradePerformance object. - */ - async createTradePerformance(runtime, tokenAddress2, recommenderId, data) { - const recommender = await this.trustScoreDb.getOrCreateRecommenderWithTelegramId( - recommenderId - ); - const processedData = await this.tokenProvider.getProcessedTokenData(); - const wallet = new WalletProvider( - this.connection, - new PublicKey4(Wallet) - ); - let tokensBalance = 0; - const prices = await wallet.fetchPrices(runtime); - const solPrice = prices.solana.usd; - const buySol = data.buy_amount / Number.parseFloat(solPrice); - const buy_value_usd = data.buy_amount * processedData.tradeData.price; - const token = await this.tokenProvider.fetchTokenTradeData(); - const tokenCodex = await this.tokenProvider.fetchTokenCodex(); - const tokenPrice = token.price; - tokensBalance = buy_value_usd / tokenPrice; - const creationData = { - token_address: tokenAddress2, - recommender_id: recommender.id, - buy_price: processedData.tradeData.price, - sell_price: 0, - buy_timeStamp: (/* @__PURE__ */ new Date()).toISOString(), - sell_timeStamp: "", - buy_amount: data.buy_amount, - sell_amount: 0, - buy_sol: buySol, - received_sol: 0, - buy_value_usd, - sell_value_usd: 0, - profit_usd: 0, - profit_percent: 0, - buy_market_cap: processedData.dexScreenerData.pairs[0]?.marketCap || 0, - sell_market_cap: 0, - market_cap_change: 0, - buy_liquidity: processedData.dexScreenerData.pairs[0]?.liquidity.usd || 0, - sell_liquidity: 0, - liquidity_change: 0, - last_updated: (/* @__PURE__ */ new Date()).toISOString(), - rapidDump: false - }; - this.trustScoreDb.addTradePerformance(creationData, data.is_simulation); - const tokenUUId = v4_default(); - const tokenRecommendation = { - id: tokenUUId, - recommenderId, - tokenAddress: tokenAddress2, - timestamp: /* @__PURE__ */ new Date(), - initialMarketCap: processedData.dexScreenerData.pairs[0]?.marketCap || 0, - initialLiquidity: processedData.dexScreenerData.pairs[0]?.liquidity?.usd || 0, - initialPrice: processedData.tradeData.price - }; - this.trustScoreDb.addTokenRecommendation(tokenRecommendation); - this.trustScoreDb.upsertTokenPerformance({ - tokenAddress: tokenAddress2, - symbol: processedData.tokenCodex.symbol, - priceChange24h: processedData.tradeData.price_change_24h_percent, - volumeChange24h: processedData.tradeData.volume_24h, - trade_24h_change: processedData.tradeData.trade_24h_change_percent, - liquidity: processedData.dexScreenerData.pairs[0]?.liquidity.usd || 0, - liquidityChange24h: 0, - holderChange24h: processedData.tradeData.unique_wallet_24h_change_percent, - rugPull: false, - isScam: tokenCodex.isScam, - marketCapChange24h: 0, - sustainedGrowth: false, - rapidDump: false, - suspiciousVolume: false, - validationTrust: 0, - balance: tokensBalance, - initialMarketCap: processedData.dexScreenerData.pairs[0]?.marketCap || 0, - lastUpdated: /* @__PURE__ */ new Date() - }); - if (data.is_simulation) { - this.trustScoreDb.updateTokenBalance(tokenAddress2, tokensBalance); - const hash = Math.random().toString(36).substring(7); - const transaction = { - tokenAddress: tokenAddress2, - type: "buy", - transactionHash: hash, - amount: data.buy_amount, - price: processedData.tradeData.price, - isSimulation: true, - timestamp: (/* @__PURE__ */ new Date()).toISOString() - }; - this.trustScoreDb.addTransaction(transaction); - } - this.simulationSellingService.processTokenPerformance( - tokenAddress2, - recommenderId - ); - this.createTradeInBe(tokenAddress2, recommenderId, data); - return creationData; - } - async delay(ms) { - return new Promise((resolve) => setTimeout(resolve, ms)); - } - async createTradeInBe(tokenAddress2, recommenderId, data, retries = 3, delayMs = 2e3) { - for (let attempt = 1; attempt <= retries; attempt++) { - try { - await fetch( - `${this.backend}/api/updaters/createTradePerformance`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${this.backendToken}` - }, - body: JSON.stringify({ - tokenAddress: tokenAddress2, - tradeData: data, - recommenderId - }) - } - ); - return; - } catch (error) { - elizaLogger5.error( - `Attempt ${attempt} failed: Error creating trade in backend`, - error - ); - if (attempt < retries) { - elizaLogger5.log(`Retrying in ${delayMs} ms...`); - await this.delay(delayMs); - } else { - elizaLogger5.error("All attempts failed."); - } - } - } - } - /** - * Updates a trade with sell details. - * @param tokenAddress The address of the token. - * @param recommenderId The UUID of the recommender. - * @param buyTimeStamp The timestamp when the buy occurred. - * @param sellDetails An object containing sell-related details. - * @param isSimulation Whether the trade is a simulation. If true, updates in simulation_trade; otherwise, in trade. - * @returns boolean indicating success. - */ - async updateSellDetails(runtime, tokenAddress2, recommenderId, sellTimeStamp, sellDetails, isSimulation) { - const recommender = await this.trustScoreDb.getOrCreateRecommenderWithTelegramId( - recommenderId - ); - const processedData = await this.tokenProvider.getProcessedTokenData(); - const wallet = new WalletProvider( - this.connection, - new PublicKey4(Wallet) - ); - const prices = await wallet.fetchPrices(runtime); - const solPrice = prices.solana.usd; - const sellSol = sellDetails.sell_amount / Number.parseFloat(solPrice); - const sell_value_usd = sellDetails.sell_amount * processedData.tradeData.price; - const trade = await this.trustScoreDb.getLatestTradePerformance( - tokenAddress2, - recommender.id, - isSimulation - ); - const buyTimeStamp = trade.buy_timeStamp; - const marketCap = processedData.dexScreenerData.pairs[0]?.marketCap || 0; - const liquidity = processedData.dexScreenerData.pairs[0]?.liquidity.usd || 0; - const sell_price = processedData.tradeData.price; - const profit_usd = sell_value_usd - trade.buy_value_usd; - const profit_percent = profit_usd / trade.buy_value_usd * 100; - const market_cap_change = marketCap - trade.buy_market_cap; - const liquidity_change = liquidity - trade.buy_liquidity; - const isRapidDump = await this.isRapidDump(tokenAddress2); - const sellDetailsData = { - sell_price, - sell_timeStamp: sellTimeStamp, - sell_amount: sellDetails.sell_amount, - received_sol: sellSol, - sell_value_usd, - profit_usd, - profit_percent, - sell_market_cap: marketCap, - market_cap_change, - sell_liquidity: liquidity, - liquidity_change, - rapidDump: isRapidDump, - sell_recommender_id: sellDetails.sell_recommender_id || null - }; - this.trustScoreDb.updateTradePerformanceOnSell( - tokenAddress2, - recommender.id, - buyTimeStamp, - sellDetailsData, - isSimulation - ); - if (isSimulation) { - const oldBalance = this.trustScoreDb.getTokenBalance(tokenAddress2); - const tokenBalance = oldBalance - sellDetails.sell_amount; - this.trustScoreDb.updateTokenBalance(tokenAddress2, tokenBalance); - const hash = Math.random().toString(36).substring(7); - const transaction = { - tokenAddress: tokenAddress2, - type: "sell", - transactionHash: hash, - amount: sellDetails.sell_amount, - price: processedData.tradeData.price, - isSimulation: true, - timestamp: (/* @__PURE__ */ new Date()).toISOString() - }; - this.trustScoreDb.addTransaction(transaction); - } - return sellDetailsData; - } - // get all recommendations - async getRecommendations(startDate, endDate) { - const recommendations = this.trustScoreDb.getRecommendationsByDateRange( - startDate, - endDate - ); - const groupedRecommendations = recommendations.reduce( - (acc, recommendation) => { - const { tokenAddress: tokenAddress2 } = recommendation; - if (!acc[tokenAddress2]) acc[tokenAddress2] = []; - acc[tokenAddress2].push(recommendation); - return acc; - }, - {} - ); - const result = Object.keys(groupedRecommendations).map( - (tokenAddress2) => { - const tokenRecommendations = groupedRecommendations[tokenAddress2]; - let totalTrustScore = 0; - let totalRiskScore = 0; - let totalConsistencyScore = 0; - const recommenderData = []; - tokenRecommendations.forEach((recommendation) => { - const tokenPerformance = this.trustScoreDb.getTokenPerformance( - recommendation.tokenAddress - ); - const recommenderMetrics = this.trustScoreDb.getRecommenderMetrics( - recommendation.recommenderId - ); - const trustScore = this.calculateTrustScore( - tokenPerformance, - recommenderMetrics - ); - const consistencyScore = this.calculateConsistencyScore( - tokenPerformance, - recommenderMetrics - ); - const riskScore = this.calculateRiskScore(tokenPerformance); - totalTrustScore += trustScore; - totalRiskScore += riskScore; - totalConsistencyScore += consistencyScore; - recommenderData.push({ - recommenderId: recommendation.recommenderId, - trustScore, - riskScore, - consistencyScore, - recommenderMetrics - }); - }); - const averageTrustScore = totalTrustScore / tokenRecommendations.length; - const averageRiskScore = totalRiskScore / tokenRecommendations.length; - const averageConsistencyScore = totalConsistencyScore / tokenRecommendations.length; - return { - tokenAddress: tokenAddress2, - averageTrustScore, - averageRiskScore, - averageConsistencyScore, - recommenders: recommenderData - }; - } - ); - result.sort((a, b) => b.averageTrustScore - a.averageTrustScore); - return result; - } -}; -var trustScoreProvider = { - async get(runtime, message, _state) { - try { - if (runtime.getSetting("POSTGRES_URL")) { - elizaLogger5.warn( - "skipping trust evaluator because db is postgres" - ); - return ""; - } - const trustScoreDb = new TrustScoreDatabase( - runtime.databaseAdapter.db - ); - const userId = message.userId; - if (!userId) { - elizaLogger5.error("User ID is missing from the message"); - return ""; - } - const recommenderMetrics = await trustScoreDb.getRecommenderMetrics(userId); - if (!recommenderMetrics) { - elizaLogger5.error( - "No recommender metrics found for user:", - userId - ); - return ""; - } - const trustScore = recommenderMetrics.trustScore; - const user = await runtime.databaseAdapter.getAccountById(userId); - const trustScoreString = `${user.name}'s trust score: ${trustScore.toFixed(2)}`; - return trustScoreString; - } catch (error) { - elizaLogger5.error("Error in trust score provider:", error.message); - return `Failed to fetch trust score: ${error instanceof Error ? error.message : "Unknown error"}`; - } - } -}; - -// src/evaluators/trust.ts -import { - booleanFooter, - composeContext, - elizaLogger as elizaLogger6, - generateObjectArray, - generateTrueOrFalse, - MemoryManager, - ModelClass -} from "@elizaos/core"; -import { TrustScoreDatabase as TrustScoreDatabase2 } from "@elizaos/plugin-trustdb"; -import { Connection as Connection5 } from "@solana/web3.js"; -var shouldProcessTemplate = `# Task: Decide if the recent messages should be processed for token recommendations. - - Look for messages that: - - Mention specific token tickers or contract addresses - - Contain words related to buying, selling, or trading tokens - - Express opinions or convictions about tokens - - Based on the following conversation, should the messages be processed for recommendations? YES or NO - - {{recentMessages}} - - Should the messages be processed for recommendations? ` + booleanFooter; -var formatRecommendations = (recommendations) => { - const messageStrings = recommendations.reverse().map((rec) => `${rec.content?.content}`); - const finalMessageStrings = messageStrings.join("\n"); - return finalMessageStrings; -}; -var recommendationTemplate = `TASK: Extract recommendations to buy or sell memecoins from the conversation as an array of objects in JSON format. - - Memecoins usually have a ticker and a contract address. Additionally, recommenders may make recommendations with some amount of conviction. The amount of conviction in their recommendation can be none, low, medium, or high. Recommenders can make recommendations to buy, not buy, sell and not sell. - -# START OF EXAMPLES -These are an examples of the expected output of this task: -{{evaluationExamples}} -# END OF EXAMPLES - -# INSTRUCTIONS - -Extract any new recommendations from the conversation that are not already present in the list of known recommendations below: -{{recentRecommendations}} - -- Include the recommender's username -- Try not to include already-known recommendations. If you think a recommendation is already known, but you're not sure, respond with alreadyKnown: true. -- Set the conviction to 'none', 'low', 'medium' or 'high' -- Set the recommendation type to 'buy', 'dont_buy', 'sell', or 'dont_sell' -- Include the contract address and/or ticker if available - -Recent Messages: -{{recentMessages}} - -Response should be a JSON object array inside a JSON markdown block. Correct response format: -\`\`\`json -[ - { - "recommender": string, - "ticker": string | null, - "contractAddress": string | null, - "type": enum, - "conviction": enum, - "alreadyKnown": boolean - }, - ... -] -\`\`\``; -async function handler(runtime, message) { - elizaLogger6.log("Evaluating for trust"); - const state = await runtime.composeState(message); - if (runtime.getSetting("POSTGRES_URL")) { - elizaLogger6.warn("skipping trust evaluator because db is postgres"); - return []; - } - const { agentId, roomId } = state; - const shouldProcessContext = composeContext({ - state, - template: shouldProcessTemplate - }); - const shouldProcess = await generateTrueOrFalse({ - context: shouldProcessContext, - modelClass: ModelClass.SMALL, - runtime - }); - if (!shouldProcess) { - elizaLogger6.log("Skipping process"); - return []; - } - elizaLogger6.log("Processing recommendations"); - const recommendationsManager = new MemoryManager({ - runtime, - tableName: "recommendations" - }); - const recentRecommendations = await recommendationsManager.getMemories({ - roomId, - count: 20 - }); - const context = composeContext({ - state: { - ...state, - recentRecommendations: formatRecommendations(recentRecommendations) - }, - template: recommendationTemplate - }); - const recommendations = await generateObjectArray({ - runtime, - context, - modelClass: ModelClass.LARGE - }); - elizaLogger6.log("recommendations", recommendations); - if (!recommendations) { - return []; - } - const filteredRecommendations = recommendations.filter((rec) => { - return !rec.alreadyKnown && (rec.ticker || rec.contractAddress) && rec.recommender && rec.conviction && rec.recommender.trim() !== ""; - }); - const { publicKey } = await getWalletKey(runtime, false); - for (const rec of filteredRecommendations) { - const walletProvider2 = new WalletProvider( - new Connection5( - runtime.getSetting("SOLANA_RPC_URL") || "https://api.mainnet-beta.solana.com" - ), - publicKey - ); - const tokenProvider2 = new TokenProvider( - rec.contractAddress, - walletProvider2, - runtime.cacheManager - ); - if (!rec.contractAddress) { - const tokenAddress2 = await tokenProvider2.getTokenFromWallet( - runtime, - rec.ticker - ); - rec.contractAddress = tokenAddress2; - if (!tokenAddress2) { - const result = await tokenProvider2.searchDexScreenerData( - rec.ticker - ); - const tokenAddress3 = result?.baseToken?.address; - rec.contractAddress = tokenAddress3; - if (!tokenAddress3) { - elizaLogger6.warn("Could not find contract address for token"); - continue; - } - } - } - const trustScoreDb = new TrustScoreDatabase2(runtime.databaseAdapter.db); - const trustScoreManager = new TrustScoreManager( - runtime, - tokenProvider2, - trustScoreDb - ); - const participants = await runtime.databaseAdapter.getParticipantsForRoom( - message.roomId - ); - const user = participants.find(async (actor) => { - const user2 = await runtime.databaseAdapter.getAccountById(actor); - return user2.name.toLowerCase().trim() === rec.recommender.toLowerCase().trim(); - }); - if (!user) { - elizaLogger6.warn("Could not find user: ", rec.recommender); - continue; - } - const account = await runtime.databaseAdapter.getAccountById(user); - const userId = account.id; - const recMemory = { - userId, - agentId, - content: { text: JSON.stringify(rec) }, - roomId, - createdAt: Date.now() - }; - await recommendationsManager.createMemory(recMemory, true); - elizaLogger6.log("recommendationsManager", rec); - const buyAmounts = await tokenProvider2.calculateBuyAmounts(); - let buyAmount = buyAmounts[rec.conviction.toLowerCase().trim()]; - if (!buyAmount) { - buyAmount = 10; - } - const shouldTrade = await tokenProvider2.shouldTradeToken(); - if (!shouldTrade) { - elizaLogger6.warn( - "There might be a problem with the token, not trading" - ); - continue; - } - switch (rec.type) { - case "buy": - await trustScoreManager.createTradePerformance( - runtime, - rec.contractAddress, - userId, - { - buy_amount: rec.buyAmount, - is_simulation: true - } - ); - break; - case "sell": - case "dont_sell": - case "dont_buy": - elizaLogger6.warn("Not implemented"); - break; - } - } - return filteredRecommendations; -} -var trustEvaluator = { - name: "EXTRACT_RECOMMENDATIONS", - similes: [ - "GET_RECOMMENDATIONS", - "EXTRACT_TOKEN_RECS", - "EXTRACT_MEMECOIN_RECS" - ], - alwaysRun: true, - validate: async (runtime, message) => { - if (message.content.text.length < 5) { - return false; - } - return message.userId !== message.agentId; - }, - description: "Extract recommendations to buy or sell memecoins/tokens from the conversation, including details like ticker, contract address, conviction level, and recommender username.", - handler, - examples: [ - { - context: `Actors in the scene: -{{user1}}: Experienced DeFi degen. Constantly chasing high yield farms. -{{user2}}: New to DeFi, learning the ropes. - -Recommendations about the actors: -None`, - messages: [ - { - user: "{{user1}}", - content: { - text: "Yo, have you checked out $SOLARUG? Dope new yield aggregator on Solana." - } - }, - { - user: "{{user2}}", - content: { - text: "Nah, I'm still trying to wrap my head around how yield farming even works haha. Is it risky?" - } - }, - { - user: "{{user1}}", - content: { - text: "I mean, there's always risk in DeFi, but the $SOLARUG devs seem legit. Threw a few sol into the FCweoTfJ128jGgNEXgdfTXdEZVk58Bz9trCemr6sXNx9 vault, farming's been smooth so far." - } - } - ], - outcome: `\`\`\`json -[ - { - "recommender": "{{user1}}", - "ticker": "SOLARUG", - "contractAddress": "FCweoTfJ128jGgNEXgdfTXdEZVk58Bz9trCemr6sXNx9", - "type": "buy", - "conviction": "medium", - "alreadyKnown": false - } -] -\`\`\`` - }, - { - context: `Actors in the scene: -{{user1}}: Solana maximalist. Believes Solana will flip Ethereum. -{{user2}}: Multichain proponent. Holds both SOL and ETH. - -Recommendations about the actors: -{{user1}} has previously promoted $COPETOKEN and $SOYLENT.`, - messages: [ - { - user: "{{user1}}", - content: { - text: "If you're not long $SOLVAULT at 7tRzKud6FBVFEhYqZS3CuQ2orLRM21bdisGykL5Sr4Dx, you're missing out. This will be the blackhole of Solana liquidity." - } - }, - { - user: "{{user2}}", - content: { - text: "Idk man, feels like there's a new 'vault' or 'reserve' token every week on Sol. What happened to $COPETOKEN and $SOYLENT that you were shilling before?" - } - }, - { - user: "{{user1}}", - content: { - text: "$COPETOKEN and $SOYLENT had their time, I took profits near the top. But $SOLVAULT is different, it has actual utility. Do what you want, but don't say I didn't warn you when this 50x's and you're left holding your $ETH bags." - } - } - ], - outcome: `\`\`\`json -[ - { - "recommender": "{{user1}}", - "ticker": "COPETOKEN", - "contractAddress": null, - "type": "sell", - "conviction": "low", - "alreadyKnown": true - }, - { - "recommender": "{{user1}}", - "ticker": "SOYLENT", - "contractAddress": null, - "type": "sell", - "conviction": "low", - "alreadyKnown": true - }, - { - "recommender": "{{user1}}", - "ticker": "SOLVAULT", - "contractAddress": "7tRzKud6FBVFEhYqZS3CuQ2orLRM21bdisGykL5Sr4Dx", - "type": "buy", - "conviction": "high", - "alreadyKnown": false - } -] -\`\`\`` - }, - { - context: `Actors in the scene: -{{user1}}: Self-proclaimed Solana alpha caller. Allegedly has insider info. -{{user2}}: Degen gambler. Will ape into any hyped token. - -Recommendations about the actors: -None`, - messages: [ - { - user: "{{user1}}", - content: { - text: "I normally don't do this, but I like you anon, so I'll let you in on some alpha. $ROULETTE at 48vV5y4DRH1Adr1bpvSgFWYCjLLPtHYBqUSwNc2cmCK2 is going to absolutely send it soon. You didn't hear it from me \u{1F910}" - } - }, - { - user: "{{user2}}", - content: { - text: "Oh shit, insider info from the alpha god himself? Say no more, I'm aping in hard." - } - } - ], - outcome: `\`\`\`json -[ - { - "recommender": "{{user1}}", - "ticker": "ROULETTE", - "contractAddress": "48vV5y4DRH1Adr1bpvSgFWYCjLLPtHYBqUSwNc2cmCK2", - "type": "buy", - "conviction": "high", - "alreadyKnown": false - } -] -\`\`\`` - }, - { - context: `Actors in the scene: -{{user1}}: NFT collector and trader. Bullish on Solana NFTs. -{{user2}}: Only invests based on fundamentals. Sees all NFTs as worthless JPEGs. - -Recommendations about the actors: -None -`, - messages: [ - { - user: "{{user1}}", - content: { - text: "GM. I'm heavily accumulating $PIXELAPE, the token for the Pixel Ape Yacht Club NFT collection. 10x is inevitable." - } - }, - { - user: "{{user2}}", - content: { - text: "NFTs are a scam bro. There's no underlying value. You're essentially trading worthless JPEGs." - } - }, - { - user: "{{user1}}", - content: { - text: "Fun staying poor \u{1F921} $PIXELAPE is about to moon and you'll be left behind." - } - }, - { - user: "{{user2}}", - content: { - text: "Whatever man, I'm not touching that shit with a ten foot pole. Have fun holding your bags." - } - }, - { - user: "{{user1}}", - content: { - text: "Don't need luck where I'm going \u{1F60E} Once $PIXELAPE at 3hAKKmR6XyBooQBPezCbUMhrmcyTkt38sRJm2thKytWc takes off, you'll change your tune." - } - } - ], - outcome: `\`\`\`json -[ - { - "recommender": "{{user1}}", - "ticker": "PIXELAPE", - "contractAddress": "3hAKKmR6XyBooQBPezCbUMhrmcyTkt38sRJm2thKytWc", - "type": "buy", - "conviction": "high", - "alreadyKnown": false - } -] -\`\`\`` - }, - { - context: `Actors in the scene: -{{user1}}: Contrarian investor. Bets against hyped projects. -{{user2}}: Trend follower. Buys tokens that are currently popular. - -Recommendations about the actors: -None`, - messages: [ - { - user: "{{user2}}", - content: { - text: "$SAMOYED is the talk of CT right now. Making serious moves. Might have to get a bag." - } - }, - { - user: "{{user1}}", - content: { - text: "Whenever a token is the 'talk of CT', that's my cue to short it. $SAMOYED is going to dump hard, mark my words." - } - }, - { - user: "{{user2}}", - content: { - text: "Idk man, the hype seems real this time. 5TQwHyZbedaH4Pcthj1Hxf5GqcigL6qWuB7YEsBtqvhr chart looks bullish af." - } - }, - { - user: "{{user1}}", - content: { - text: "Hype is always real until it isn't. I'm taking out a fat short position here. Don't say I didn't warn you when this crashes 90% and you're left holding the flaming bags." - } - } - ], - outcome: `\`\`\`json -[ - { - "recommender": "{{user2}}", - "ticker": "SAMOYED", - "contractAddress": "5TQwHyZbedaH4Pcthj1Hxf5GqcigL6qWuB7YEsBtqvhr", - "type": "buy", - "conviction": "medium", - "alreadyKnown": false - }, - { - "recommender": "{{user1}}", - "ticker": "SAMOYED", - "contractAddress": "5TQwHyZbedaH4Pcthj1Hxf5GqcigL6qWuB7YEsBtqvhr", - "type": "dont_buy", - "conviction": "high", - "alreadyKnown": false - } -] -\`\`\`` - } - ] -}; - -// src/actions/transfer.ts -import { - getAssociatedTokenAddressSync, - createTransferInstruction -} from "@solana/spl-token"; -import { elizaLogger as elizaLogger7, settings as settings3 } from "@elizaos/core"; -import { - Connection as Connection6, - PublicKey as PublicKey5, - TransactionMessage, - VersionedTransaction -} from "@solana/web3.js"; -import { - ModelClass as ModelClass2 -} from "@elizaos/core"; -import { composeContext as composeContext2 } from "@elizaos/core"; -import { generateObjectDeprecated } from "@elizaos/core"; -function isTransferContent(runtime, content) { - elizaLogger7.log("Content for transfer", content); - return typeof content.tokenAddress === "string" && typeof content.recipient === "string" && (typeof content.amount === "string" || typeof content.amount === "number"); -} -var transferTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined. - -Example response: -\`\`\`json -{ - "tokenAddress": "BieefG47jAHCGZBxi2q87RDuHyGZyYC3vAzxpyu8pump", - "recipient": "9jW8FPr6BSSsemWPV22UUCzSqkVdTp6HTyPqeqyuBbCa", - "amount": "1000" -} -\`\`\` - -{{recentMessages}} - -Extract the following information about the requested token transfer: -- Token contract address -- Recipient wallet address -- Amount to transfer - -If no token address is mentioned, respond with null. -`; -var transfer_default = { - name: "SEND_TOKEN", - similes: ["TRANSFER_TOKEN", "TRANSFER_TOKENS", "SEND_TOKENS", "PAY_TOKEN", "PAY_TOKENS", "PAY"], - validate: async (runtime, message) => { - elizaLogger7.log("Validating token transfer from user:", message.userId); - return true; - }, - description: "Transfer SPL tokens from agent's wallet to another address", - handler: async (runtime, message, state, _options, callback) => { - elizaLogger7.log("Starting SEND_TOKEN handler..."); - if (!state) { - state = await runtime.composeState(message); - } else { - state = await runtime.updateRecentMessageState(state); - } - const transferContext = composeContext2({ - state, - template: transferTemplate - }); - const content = await generateObjectDeprecated({ - runtime, - context: transferContext, - modelClass: ModelClass2.LARGE - }); - if (!isTransferContent(runtime, content)) { - if (callback) { - callback({ - text: "Token address needed to send the token.", - content: { error: "Invalid transfer content" } - }); - } - return false; - } - try { - const { keypair: senderKeypair } = await getWalletKey(runtime, true); - const connection3 = new Connection6(settings3.SOLANA_RPC_URL); - const mintPubkey = new PublicKey5(content.tokenAddress); - const recipientPubkey = new PublicKey5(content.recipient); - const mintInfo = await connection3.getParsedAccountInfo(mintPubkey); - const decimals = mintInfo.value?.data?.parsed?.info?.decimals ?? 9; - const adjustedAmount = BigInt(Number(content.amount) * Math.pow(10, decimals)); - const senderATA = getAssociatedTokenAddressSync(mintPubkey, senderKeypair.publicKey); - const recipientATA = getAssociatedTokenAddressSync(mintPubkey, recipientPubkey); - const instructions = []; - const recipientATAInfo = await connection3.getAccountInfo(recipientATA); - if (!recipientATAInfo) { - const { createAssociatedTokenAccountInstruction } = await import("@solana/spl-token"); - instructions.push( - createAssociatedTokenAccountInstruction( - senderKeypair.publicKey, - recipientATA, - recipientPubkey, - mintPubkey - ) - ); - } - instructions.push( - createTransferInstruction( - senderATA, - recipientATA, - senderKeypair.publicKey, - adjustedAmount - ) - ); - const messageV0 = new TransactionMessage({ - payerKey: senderKeypair.publicKey, - recentBlockhash: (await connection3.getLatestBlockhash()).blockhash, - instructions - }).compileToV0Message(); - const transaction = new VersionedTransaction(messageV0); - transaction.sign([senderKeypair]); - const signature = await connection3.sendTransaction(transaction); - if (callback) { - callback({ - text: `Sent ${content.amount} tokens to ${content.recipient} -Transaction hash: ${signature}`, - content: { - success: true, - signature, - amount: content.amount, - recipient: content.recipient - } - }); - } - return true; - } catch (error) { - elizaLogger7.error("Error during token transfer:", error); - if (callback) { - callback({ - text: `Issue with the transfer: ${error.message}`, - content: { error: error.message } - }); - } - return false; - } - }, - examples: [ - [ - { - user: "{{user1}}", - content: { - text: "Send 69 EZSIS BieefG47jAHCGZBxi2q87RDuHyGZyYC3vAzxpyu8pump to 9jW8FPr6BSSsemWPV22UUCzSqkVdTp6HTyPqeqyuBbCa" - } - }, - { - user: "{{user2}}", - content: { - text: "Sending the tokens now...", - action: "SEND_TOKEN" - } - } - ] - ] -}; - -// src/actions/transfer_sol.ts -import { elizaLogger as elizaLogger8, settings as settings4 } from "@elizaos/core"; -import { - Connection as Connection7, - PublicKey as PublicKey6, - SystemProgram, - TransactionMessage as TransactionMessage2, - VersionedTransaction as VersionedTransaction2 -} from "@solana/web3.js"; -import { - ModelClass as ModelClass3 -} from "@elizaos/core"; -import { composeContext as composeContext3 } from "@elizaos/core"; -import { generateObjectDeprecated as generateObjectDeprecated2 } from "@elizaos/core"; -function isSolTransferContent(content) { - return typeof content.recipient === "string" && typeof content.amount === "number"; -} -var solTransferTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined. - -Example response: -\`\`\`json -{ - "recipient": "9jW8FPr6BSSsemWPV22UUCzSqkVdTp6HTyPqeqyuBbCa", - "amount": 1.5 -} -\`\`\` - -{{recentMessages}} - -Extract the following information about the requested SOL transfer: -- Recipient wallet address -- Amount of SOL to transfer -`; -var transfer_sol_default = { - name: "SEND_SOL", - similes: ["TRANSFER_SOL", "PAY_SOL", "TRANSACT_SOL"], - validate: async (runtime, message) => { - elizaLogger8.log("Validating SOL transfer from user:", message.userId); - return true; - }, - description: "Transfer native SOL from agent's wallet to specified address", - handler: async (runtime, message, state, _options, callback) => { - elizaLogger8.log("Starting SEND_SOL handler..."); - if (!state) { - state = await runtime.composeState(message); - } else { - state = await runtime.updateRecentMessageState(state); - } - const transferContext = composeContext3({ - state, - template: solTransferTemplate - }); - const content = await generateObjectDeprecated2({ - runtime, - context: transferContext, - modelClass: ModelClass3.LARGE - }); - if (!isSolTransferContent(content)) { - if (callback) { - callback({ - text: "Need an address and the amount of SOL to send.", - content: { error: "Invalid transfer content" } - }); - } - return false; - } - try { - const { keypair: senderKeypair } = await getWalletKey(runtime, true); - const connection3 = new Connection7(settings4.SOLANA_RPC_URL); - const recipientPubkey = new PublicKey6(content.recipient); - const lamports = content.amount * 1e9; - const instruction = SystemProgram.transfer({ - fromPubkey: senderKeypair.publicKey, - toPubkey: recipientPubkey, - lamports - }); - const messageV0 = new TransactionMessage2({ - payerKey: senderKeypair.publicKey, - recentBlockhash: (await connection3.getLatestBlockhash()).blockhash, - instructions: [instruction] - }).compileToV0Message(); - const transaction = new VersionedTransaction2(messageV0); - transaction.sign([senderKeypair]); - const signature = await connection3.sendTransaction(transaction); - if (callback) { - callback({ - text: `Sent ${content.amount} SOL. Transaction hash: ${signature}`, - content: { - success: true, - signature, - amount: content.amount, - recipient: content.recipient - } - }); - } - return true; - } catch (error) { - elizaLogger8.error("Error during SOL transfer:", error); - if (callback) { - callback({ - text: `Problem with the SOL transfer: ${error.message}`, - content: { error: error.message } - }); - } - return false; - } - }, - examples: [ - [ - { - user: "{{user1}}", - content: { - text: "Send 1.5 SOL to 9jW8FPr6BSSsemWPV22UUCzSqkVdTp6HTyPqeqyuBbCa" - } - }, - { - user: "{{user2}}", - content: { - text: "Sure thing, SOL on its way.", - action: "SEND_SOL" - } - } - ] - ] -}; - -// src/providers/tokenUtils.ts -import { getAccount, getAssociatedTokenAddress as getAssociatedTokenAddress2 } from "@solana/spl-token"; -import { PublicKey as PublicKey7 } from "@solana/web3.js"; -import { elizaLogger as elizaLogger9 } from "@elizaos/core"; -async function getTokenBalance(connection3, walletPublicKey, tokenMintAddress) { - const tokenAccountAddress = await getAssociatedTokenAddress2( - tokenMintAddress, - walletPublicKey - ); - try { - const tokenAccount = await getAccount(connection3, tokenAccountAddress); - const tokenAmount = tokenAccount.amount; - return tokenAmount; - } catch (error) { - elizaLogger9.error( - `Error retrieving balance for token: ${tokenMintAddress.toBase58()}`, - error - ); - return 0; - } -} -async function getTokenBalances(connection3, walletPublicKey) { - const tokenBalances = {}; - const tokenMintAddresses = [ - new PublicKey7("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"), - // USDC - new PublicKey7("So11111111111111111111111111111111111111112") - // SOL - // Add more token mint addresses as needed - ]; - for (const mintAddress of tokenMintAddresses) { - const tokenName = getTokenName(mintAddress); - const balance = await getTokenBalance( - connection3, - walletPublicKey, - mintAddress - ); - tokenBalances[tokenName] = balance; - } - return tokenBalances; -} -function getTokenName(mintAddress) { - const tokenNameMap = { - EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v: "USDC", - So11111111111111111111111111111111111111112: "SOL" - // Add more token mint addresses and their corresponding names - }; - return tokenNameMap[mintAddress.toBase58()] || "Unknown Token"; -} - -// src/actions/swap.ts -import { - composeContext as composeContext4, - generateObjectDeprecated as generateObjectDeprecated3, - ModelClass as ModelClass4, - settings as settings6, - elizaLogger as elizaLogger11 -} from "@elizaos/core"; -import { Connection as Connection9, VersionedTransaction as VersionedTransaction4 } from "@solana/web3.js"; -import BigNumber3 from "bignumber.js"; - -// src/actions/swapUtils.ts -import { getAssociatedTokenAddress as getAssociatedTokenAddress3 } from "@solana/spl-token"; -import { - Connection as Connection8, - PublicKey as PublicKey8, - VersionedTransaction as VersionedTransaction3 -} from "@solana/web3.js"; -import { settings as settings5, elizaLogger as elizaLogger10 } from "@elizaos/core"; -var solAddress = settings5.SOL_ADDRESS; -var SLIPPAGE = settings5.SLIPPAGE; -var connection2 = new Connection8( - settings5.SOLANA_RPC_URL || "https://api.mainnet-beta.solana.com" -); -async function getTokenDecimals(connection3, mintAddress) { - const mintPublicKey = new PublicKey8(mintAddress); - const tokenAccountInfo = await connection3.getParsedAccountInfo(mintPublicKey); - if (tokenAccountInfo.value && typeof tokenAccountInfo.value.data === "object" && "parsed" in tokenAccountInfo.value.data) { - const parsedInfo = tokenAccountInfo.value.data.parsed?.info; - if (parsedInfo && typeof parsedInfo.decimals === "number") { - return parsedInfo.decimals; - } - } - throw new Error("Unable to fetch token decimals"); -} -async function getQuote(connection3, baseToken, outputToken, amount) { - const decimals = await getTokenDecimals(connection3, baseToken); - const adjustedAmount = amount * 10 ** decimals; - const quoteResponse = await fetch( - `https://quote-api.jup.ag/v6/quote?inputMint=${baseToken}&outputMint=${outputToken}&amount=${adjustedAmount}&slippageBps=50` - ); - const swapTransaction = await quoteResponse.json(); - const swapTransactionBuf = Buffer.from(swapTransaction, "base64"); - return new Uint8Array(swapTransactionBuf); -} - -// src/actions/swap.ts -async function swapToken(connection3, walletPublicKey, inputTokenCA, outputTokenCA, amount) { - try { - const decimals = inputTokenCA === settings6.SOL_ADDRESS ? new BigNumber3(9) : new BigNumber3( - await getTokenDecimals(connection3, inputTokenCA) - ); - elizaLogger11.log("Decimals:", decimals.toString()); - const amountBN = new BigNumber3(amount); - const adjustedAmount = amountBN.multipliedBy( - new BigNumber3(10).pow(decimals) - ); - elizaLogger11.log("Fetching quote with params:", { - inputMint: inputTokenCA, - outputMint: outputTokenCA, - amount: adjustedAmount - }); - const quoteResponse = await fetch( - `https://quote-api.jup.ag/v6/quote?inputMint=${inputTokenCA}&outputMint=${outputTokenCA}&amount=${adjustedAmount}&dynamicSlippage=true&maxAccounts=64` - ); - const quoteData = await quoteResponse.json(); - if (!quoteData || quoteData.error) { - elizaLogger11.error("Quote error:", quoteData); - throw new Error( - `Failed to get quote: ${quoteData?.error || "Unknown error"}` - ); - } - elizaLogger11.log("Quote received:", quoteData); - const swapRequestBody = { - quoteResponse: quoteData, - userPublicKey: walletPublicKey.toBase58(), - dynamicComputeUnitLimit: true, - dynamicSlippage: true, - priorityLevelWithMaxLamports: { - maxLamports: 4e6, - priorityLevel: "veryHigh" - } - }; - elizaLogger11.log("Requesting swap with body:", swapRequestBody); - const swapResponse = await fetch("https://quote-api.jup.ag/v6/swap", { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify(swapRequestBody) - }); - const swapData = await swapResponse.json(); - if (!swapData || !swapData.swapTransaction) { - elizaLogger11.error("Swap error:", swapData); - throw new Error( - `Failed to get swap transaction: ${swapData?.error || "No swap transaction returned"}` - ); - } - elizaLogger11.log("Swap transaction received"); - return swapData; - } catch (error) { - elizaLogger11.error("Error in swapToken:", error); - throw error; - } -} -var swapTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined. - -Example response: -\`\`\`json -{ - "inputTokenSymbol": "SOL", - "outputTokenSymbol": "USDC", - "inputTokenCA": "So11111111111111111111111111111111111111112", - "outputTokenCA": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", - "amount": 1.5 -} -\`\`\` - -{{recentMessages}} - -Given the recent messages and wallet information below: - -{{walletInfo}} - -Extract the following information about the requested token swap: -- Input token symbol (the token being sold) -- Output token symbol (the token being bought) -- Input token contract address if provided -- Output token contract address if provided -- Amount to swap - -Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined. The result should be a valid JSON object with the following schema: -\`\`\`json -{ - "inputTokenSymbol": string | null, - "outputTokenSymbol": string | null, - "inputTokenCA": string | null, - "outputTokenCA": string | null, - "amount": number | string | null -} -\`\`\``; -async function getTokensInWallet(runtime) { - const { publicKey } = await getWalletKey(runtime, false); - const walletProvider2 = new WalletProvider( - new Connection9("https://api.mainnet-beta.solana.com"), - publicKey - ); - const walletInfo = await walletProvider2.fetchPortfolioValue(runtime); - const items = walletInfo.items; - return items; -} -async function getTokenFromWallet(runtime, tokenSymbol) { - try { - const items = await getTokensInWallet(runtime); - const token = items.find((item) => item.symbol === tokenSymbol); - if (token) { - return token.address; - } else { - return null; - } - } catch (error) { - elizaLogger11.error("Error checking token in wallet:", error); - return null; - } -} -var executeSwap = { - name: "EXECUTE_SWAP", - similes: ["SWAP_TOKENS", "TOKEN_SWAP", "TRADE_TOKENS", "EXCHANGE_TOKENS"], - validate: async (runtime, message) => { - elizaLogger11.log("Message:", message); - return true; - }, - description: "Perform a token swap.", - handler: async (runtime, message, state, _options, callback) => { - if (!state) { - state = await runtime.composeState(message); - } else { - state = await runtime.updateRecentMessageState(state); - } - const walletInfo = await walletProvider.get(runtime, message, state); - state.walletInfo = walletInfo; - const swapContext = composeContext4({ - state, - template: swapTemplate - }); - const response = await generateObjectDeprecated3({ - runtime, - context: swapContext, - modelClass: ModelClass4.LARGE - }); - elizaLogger11.log("Response:", response); - if (response.inputTokenSymbol?.toUpperCase() === "SOL") { - response.inputTokenCA = settings6.SOL_ADDRESS; - } - if (response.outputTokenSymbol?.toUpperCase() === "SOL") { - response.outputTokenCA = settings6.SOL_ADDRESS; - } - if (!response.inputTokenCA && response.inputTokenSymbol) { - elizaLogger11.log( - `Attempting to resolve CA for input token symbol: ${response.inputTokenSymbol}` - ); - response.inputTokenCA = await getTokenFromWallet( - runtime, - response.inputTokenSymbol - ); - if (response.inputTokenCA) { - elizaLogger11.log( - `Resolved inputTokenCA: ${response.inputTokenCA}` - ); - } else { - elizaLogger11.log( - "No contract addresses provided, skipping swap" - ); - const responseMsg = { - text: "I need the contract addresses to perform the swap" - }; - callback?.(responseMsg); - return true; - } - } - if (!response.outputTokenCA && response.outputTokenSymbol) { - elizaLogger11.log( - `Attempting to resolve CA for output token symbol: ${response.outputTokenSymbol}` - ); - response.outputTokenCA = await getTokenFromWallet( - runtime, - response.outputTokenSymbol - ); - if (response.outputTokenCA) { - elizaLogger11.log( - `Resolved outputTokenCA: ${response.outputTokenCA}` - ); - } else { - elizaLogger11.log( - "No contract addresses provided, skipping swap" - ); - const responseMsg = { - text: "I need the contract addresses to perform the swap" - }; - callback?.(responseMsg); - return true; - } - } - if (!response.amount) { - elizaLogger11.log("No amount provided, skipping swap"); - const responseMsg = { - text: "I need the amount to perform the swap" - }; - callback?.(responseMsg); - return true; - } - if (!response.amount) { - elizaLogger11.log("Amount is not a number, skipping swap"); - const responseMsg = { - text: "The amount must be a number" - }; - callback?.(responseMsg); - return true; - } - try { - const connection3 = new Connection9( - "https://api.mainnet-beta.solana.com" - ); - const { publicKey: walletPublicKey } = await getWalletKey( - runtime, - false - ); - elizaLogger11.log("Wallet Public Key:", walletPublicKey); - elizaLogger11.log("inputTokenSymbol:", response.inputTokenCA); - elizaLogger11.log("outputTokenSymbol:", response.outputTokenCA); - elizaLogger11.log("amount:", response.amount); - const swapResult = await swapToken( - connection3, - walletPublicKey, - response.inputTokenCA, - response.outputTokenCA, - response.amount - ); - elizaLogger11.log("Deserializing transaction..."); - const transactionBuf = Buffer.from( - swapResult.swapTransaction, - "base64" - ); - const transaction = VersionedTransaction4.deserialize(transactionBuf); - elizaLogger11.log("Preparing to sign transaction..."); - elizaLogger11.log("Creating keypair..."); - const { keypair } = await getWalletKey(runtime, true); - if (keypair.publicKey.toBase58() !== walletPublicKey.toBase58()) { - throw new Error( - "Generated public key doesn't match expected public key" - ); - } - elizaLogger11.log("Signing transaction..."); - transaction.sign([keypair]); - elizaLogger11.log("Sending transaction..."); - const latestBlockhash = await connection3.getLatestBlockhash(); - const txid = await connection3.sendTransaction(transaction, { - skipPreflight: false, - maxRetries: 3, - preflightCommitment: "confirmed" - }); - elizaLogger11.log("Transaction sent:", txid); - const confirmation = await connection3.confirmTransaction( - { - signature: txid, - blockhash: latestBlockhash.blockhash, - lastValidBlockHeight: latestBlockhash.lastValidBlockHeight - }, - "confirmed" - ); - if (confirmation.value.err) { - throw new Error( - `Transaction failed: ${confirmation.value.err}` - ); - } - if (confirmation.value.err) { - throw new Error( - `Transaction failed: ${confirmation.value.err}` - ); - } - elizaLogger11.log("Swap completed successfully!"); - elizaLogger11.log(`Transaction ID: ${txid}`); - const responseMsg = { - text: `Swap completed successfully! Transaction ID: ${txid}` - }; - callback?.(responseMsg); - return true; - } catch (error) { - elizaLogger11.error("Error during token swap:", error); - return false; - } - }, - examples: [ - [ - { - user: "{{user1}}", - content: { - inputTokenSymbol: "SOL", - outputTokenSymbol: "USDC", - amount: 0.1 - } - }, - { - user: "{{user2}}", - content: { - text: "Swapping 0.1 SOL for USDC...", - action: "TOKEN_SWAP" - } - }, - { - user: "{{user2}}", - content: { - text: "Swap completed successfully! Transaction ID: ..." - } - } - ] - // Add more examples as needed - ] -}; - -// src/actions/takeOrder.ts -import { - ModelClass as ModelClass5, - composeContext as composeContext5, - generateText -} from "@elizaos/core"; -var take_order = { - name: "TAKE_ORDER", - similes: ["BUY_ORDER", "PLACE_ORDER"], - description: "Records a buy order based on the user's conviction level.", - examples: [], - validate: async (runtime, message) => { - const text = message.content.text; - const tickerRegex = /\b[A-Z]{1,5}\b/g; - return tickerRegex.test(text); - }, - handler: async (runtime, message) => { - const _text = message.content.text; - const userId = message.userId; - const template = ` - -// CLAUDE TODO: Put the usual conversation context here - -Ticker is: {{ticker}} -Contract address is: {{contractAddress}} - -Determine if the user is trying to shill the ticker. if they are, respond with empty conviction, ticker and contractAddress. - -// CLAUDE TODO: output a JSON block with the following fields: -// - reasoning: string -// - conviction: negative, low, medium, high -// - ticker: string (extract from CA so we have context) -// - contractAddress: string -`; - let ticker, contractAddress; - if (!ticker || !contractAddress) { - return { - text: "Ticker and CA?" - }; - } - const state = await runtime.composeState(message); - const context = composeContext5({ - state: { - ...state, - ticker, - contractAddress - }, - template - }); - const convictionResponse = await generateText({ - runtime, - context, - modelClass: ModelClass5.LARGE - }); - const convictionResponseJson = JSON.parse(convictionResponse); - const conviction = convictionResponseJson.conviction; - let buyAmount = 0; - if (conviction === "low") { - buyAmount = 20; - } else if (conviction === "medium") { - buyAmount = 50; - } else if (conviction === "high") { - buyAmount = 100; - } - const currentPrice = 100; - const order = { - userId, - ticker: ticker || "", - contractAddress, - timestamp: (/* @__PURE__ */ new Date()).toISOString(), - buyAmount, - price: currentPrice - }; - const orderBookPath = runtime.getSetting("orderBookPath") ?? "solana/orderBook.json"; - const orderBook = []; - const cachedOrderBook = await runtime.cacheManager.get(orderBookPath); - if (cachedOrderBook) { - orderBook.push(...cachedOrderBook); - } - orderBook.push(order); - await runtime.cacheManager.set(orderBookPath, orderBook); - return { - text: `Recorded a ${conviction} conviction buy order for ${ticker} (${contractAddress}) with an amount of ${buyAmount} at the price of ${currentPrice}.` - }; - } -}; -var takeOrder_default = take_order; - -// src/actions/pumpfun.ts -import { generateImage, elizaLogger as elizaLogger12 } from "@elizaos/core"; -import { Connection as Connection10, Keypair as Keypair2 } from "@solana/web3.js"; -import { VersionedTransaction as VersionedTransaction5 } from "@solana/web3.js"; -import { Fomo } from "fomo-sdk-solana"; -import { getAssociatedTokenAddressSync as getAssociatedTokenAddressSync2 } from "@solana/spl-token"; -import bs582 from "bs58"; -import { - settings as settings7, - ModelClass as ModelClass6, - generateObject, - composeContext as composeContext6 -} from "@elizaos/core"; -function isCreateAndBuyContentForFomo(content) { - elizaLogger12.log("Content for create & buy", content); - return typeof content.tokenMetadata === "object" && content.tokenMetadata !== null && typeof content.tokenMetadata.name === "string" && typeof content.tokenMetadata.symbol === "string" && typeof content.tokenMetadata.description === "string" && typeof content.tokenMetadata.image_description === "string" && (typeof content.buyAmountSol === "string" || typeof content.buyAmountSol === "number") && typeof content.requiredLiquidity === "number"; -} -var createAndBuyToken = async ({ - deployer, - mint, - tokenMetadata, - buyAmountSol, - priorityFee, - requiredLiquidity = 85, - allowOffCurve, - commitment = "confirmed", - fomo, - connection: connection3 -}) => { - const { transaction: versionedTx } = await fomo.createToken( - deployer.publicKey, - tokenMetadata.name, - tokenMetadata.symbol, - tokenMetadata.uri, - priorityFee, - bs582.encode(mint.secretKey), - requiredLiquidity, - Number(buyAmountSol) / 10 ** 9 - ); - const { blockhash, lastValidBlockHeight } = await connection3.getLatestBlockhash(); - versionedTx.message.recentBlockhash = blockhash; - versionedTx.sign([mint]); - const serializedTransaction = versionedTx.serialize(); - const serializedTransactionBase64 = Buffer.from( - serializedTransaction - ).toString("base64"); - const deserializedTx = VersionedTransaction5.deserialize( - Buffer.from(serializedTransactionBase64, "base64") - ); - const txid = await connection3.sendTransaction(deserializedTx, { - skipPreflight: false, - maxRetries: 3, - preflightCommitment: "confirmed" - }); - elizaLogger12.log("Transaction sent:", txid); - const confirmation = await connection3.confirmTransaction( - { - signature: txid, - blockhash, - lastValidBlockHeight - }, - commitment - ); - if (!confirmation.value.err) { - elizaLogger12.log( - "Success:", - `https://fomo.fund/token/${mint.publicKey.toBase58()}` - ); - const ata = getAssociatedTokenAddressSync2( - mint.publicKey, - deployer.publicKey, - allowOffCurve - ); - const balance = await connection3.getTokenAccountBalance( - ata, - "processed" - ); - const amount = balance.value.uiAmount; - if (amount === null) { - elizaLogger12.log( - `${deployer.publicKey.toBase58()}:`, - "No Account Found" - ); - } else { - elizaLogger12.log(`${deployer.publicKey.toBase58()}:`, amount); - } - return { - success: true, - ca: mint.publicKey.toBase58(), - creator: deployer.publicKey.toBase58() - }; - } else { - elizaLogger12.log("Create and Buy failed"); - return { - success: false, - ca: mint.publicKey.toBase58(), - error: confirmation.value.err || "Transaction failed" - }; - } -}; -var promptConfirmation = async () => { - return true; -}; -var fomoTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined. - -Example response: -\`\`\`json -{ - "tokenMetadata": { - "name": "Test Token", - "symbol": "TEST", - "description": "A test token", - "image_description": "create an image of a rabbit" - }, - "buyAmountSol": "0.00069", - "requiredLiquidity": "85" -} -\`\`\` - -{{recentMessages}} - -Given the recent messages, extract or generate (come up with if not included) the following information about the requested token creation: -- Token name -- Token symbol -- Token description -- Token image description -- Amount of SOL to buy - -Respond with a JSON markdown block containing only the extracted values.`; -var pumpfun_default = { - name: "CREATE_AND_BUY_TOKEN", - similes: ["CREATE_AND_PURCHASE_TOKEN", "DEPLOY_AND_BUY_TOKEN"], - validate: async (_runtime, _message) => { - return true; - }, - description: "Create a new token and buy a specified amount using SOL. Requires deployer private key, token metadata, buy amount in SOL, priority fee, and allowOffCurve flag.", - handler: async (runtime, message, state, _options, callback) => { - elizaLogger12.log("Starting CREATE_AND_BUY_TOKEN handler..."); - if (!state) { - state = await runtime.composeState(message); - } else { - state = await runtime.updateRecentMessageState(state); - } - const walletInfo = await walletProvider.get(runtime, message, state); - state.walletInfo = walletInfo; - const pumpContext = composeContext6({ - state, - template: fomoTemplate - }); - const content = await generateObject({ - runtime, - context: pumpContext, - modelClass: ModelClass6.LARGE - }); - if (!isCreateAndBuyContentForFomo(content)) { - elizaLogger12.error( - "Invalid content for CREATE_AND_BUY_TOKEN action." - ); - return false; - } - const { tokenMetadata, buyAmountSol, requiredLiquidity } = content; - const imageResult = await generateImage( - { - prompt: `logo for ${tokenMetadata.name} (${tokenMetadata.symbol}) token - ${tokenMetadata.description}`, - width: 256, - height: 256, - count: 1 - }, - runtime - ); - const imageBuffer = Buffer.from(imageResult.data[0], "base64"); - const formData = new FormData(); - const blob = new Blob([imageBuffer], { type: "image/png" }); - formData.append("file", blob, `${tokenMetadata.name}.png`); - formData.append("name", tokenMetadata.name); - formData.append("symbol", tokenMetadata.symbol); - formData.append("description", tokenMetadata.description); - const metadataResponse = await fetch("https://pump.fun/api/ipfs", { - method: "POST", - body: formData - }); - const metadataResponseJSON = await metadataResponse.json(); - const fullTokenMetadata = { - name: tokenMetadata.name, - symbol: tokenMetadata.symbol, - uri: metadataResponseJSON.metadataUri - }; - const priorityFee = { - unitLimit: 1e8, - unitPrice: 1e5 - }; - const slippage = "2000"; - try { - const privateKeyString = runtime.getSetting("SOLANA_PRIVATE_KEY") ?? runtime.getSetting("WALLET_PRIVATE_KEY"); - const secretKey = bs582.decode(privateKeyString); - const deployerKeypair = Keypair2.fromSecretKey(secretKey); - const mintKeypair = Keypair2.generate(); - elizaLogger12.log( - `Generated mint address: ${mintKeypair.publicKey.toBase58()}` - ); - const connection3 = new Connection10(settings7.SOLANA_RPC_URL, { - commitment: "confirmed", - confirmTransactionInitialTimeout: 5e5, - wsEndpoint: settings7.SOLANA_RPC_URL.replace("https", "wss") - }); - const sdk = new Fomo(connection3, "devnet", deployerKeypair); - const createAndBuyConfirmation = await promptConfirmation(); - if (!createAndBuyConfirmation) { - elizaLogger12.log("Create and buy token canceled by user"); - return false; - } - const lamports = Math.floor(Number(buyAmountSol) * 1e9); - elizaLogger12.log("Executing create and buy transaction..."); - const result = await createAndBuyToken({ - deployer: deployerKeypair, - mint: mintKeypair, - tokenMetadata: fullTokenMetadata, - buyAmountSol: BigInt(lamports), - priorityFee: priorityFee.unitPrice, - requiredLiquidity: Number(requiredLiquidity), - allowOffCurve: false, - fomo: sdk, - connection: connection3, - slippage - }); - if (callback) { - if (result.success) { - callback({ - text: `Token ${tokenMetadata.name} (${tokenMetadata.symbol}) created successfully! -URL: https://fomo.fund/token/${result.ca} -Creator: ${result.creator} -View at: https://fomo.fund/token/${result.ca}`, - content: { - tokenInfo: { - symbol: tokenMetadata.symbol, - address: result.ca, - creator: result.creator, - name: tokenMetadata.name, - description: tokenMetadata.description, - timestamp: Date.now() - } - } - }); - } else { - callback({ - text: `Failed to create token: ${result.error} -Attempted mint address: ${result.ca}`, - content: { - error: result.error, - mintAddress: result.ca - } - }); - } - } - const successMessage = `Token created and purchased successfully! View at: https://fomo.fund/token/${mintKeypair.publicKey.toBase58()}`; - elizaLogger12.log(successMessage); - return result.success; - } catch (error) { - if (callback) { - callback({ - text: `Error during token creation: ${error.message}`, - content: { error: error.message } - }); - } - return false; - } - }, - examples: [ - [ - { - user: "{{user1}}", - content: { - text: "Create a new token called GLITCHIZA with symbol GLITCHIZA and generate a description about it on fomo.fund. Also come up with a description for it to use for image generation .buy 0.00069 SOL worth." - } - }, - { - user: "{{user2}}", - content: { - text: "Token GLITCHIZA (GLITCHIZA) created successfully on fomo.fund!\nURL: https://fomo.fund/token/673247855e8012181f941f84\nCreator: Anonymous\nView at: https://fomo.fund/token/673247855e8012181f941f84", - action: "CREATE_AND_BUY_TOKEN", - content: { - tokenInfo: { - symbol: "GLITCHIZA", - address: "EugPwuZ8oUMWsYHeBGERWvELfLGFmA1taDtmY8uMeX6r", - creator: "9jW8FPr6BSSsemWPV22UUCzSqkVdTp6HTyPqeqyuBbCa", - name: "GLITCHIZA", - description: "A GLITCHIZA token" - } - } - } - } - ] - ] -}; - -// src/actions/fomo.ts -import { generateImage as generateImage2, elizaLogger as elizaLogger13 } from "@elizaos/core"; -import { - Connection as Connection11, - Keypair as Keypair3, - VersionedTransaction as VersionedTransaction6 -} from "@solana/web3.js"; -import { Fomo as Fomo2 } from "fomo-sdk-solana"; -import { getAssociatedTokenAddressSync as getAssociatedTokenAddressSync3 } from "@solana/spl-token"; -import bs583 from "bs58"; -import { - settings as settings8, - ModelClass as ModelClass7, - generateObject as generateObject2, - composeContext as composeContext7 -} from "@elizaos/core"; -function isCreateAndBuyContentForFomo2(content) { - elizaLogger13.log("Content for create & buy", content); - return typeof content.tokenMetadata === "object" && content.tokenMetadata !== null && typeof content.tokenMetadata.name === "string" && typeof content.tokenMetadata.symbol === "string" && typeof content.tokenMetadata.description === "string" && typeof content.tokenMetadata.image_description === "string" && (typeof content.buyAmountSol === "string" || typeof content.buyAmountSol === "number") && typeof content.requiredLiquidity === "number"; -} -var createAndBuyToken2 = async ({ - deployer, - mint, - tokenMetadata, - buyAmountSol, - priorityFee, - requiredLiquidity = 85, - allowOffCurve, - commitment = "confirmed", - fomo, - connection: connection3 -}) => { - const { transaction: versionedTx } = await fomo.createToken( - deployer.publicKey, - tokenMetadata.name, - tokenMetadata.symbol, - tokenMetadata.uri, - priorityFee, - bs583.encode(mint.secretKey), - requiredLiquidity, - Number(buyAmountSol) / 10 ** 9 - ); - const { blockhash, lastValidBlockHeight } = await connection3.getLatestBlockhash(); - versionedTx.message.recentBlockhash = blockhash; - versionedTx.sign([mint]); - const serializedTransaction = versionedTx.serialize(); - const serializedTransactionBase64 = Buffer.from( - serializedTransaction - ).toString("base64"); - const deserializedTx = VersionedTransaction6.deserialize( - Buffer.from(serializedTransactionBase64, "base64") - ); - const txid = await connection3.sendTransaction(deserializedTx, { - skipPreflight: false, - maxRetries: 3, - preflightCommitment: "confirmed" - }); - elizaLogger13.log("Transaction sent:", txid); - const confirmation = await connection3.confirmTransaction( - { - signature: txid, - blockhash, - lastValidBlockHeight - }, - commitment - ); - if (!confirmation.value.err) { - elizaLogger13.log( - "Success:", - `https://fomo.fund/token/${mint.publicKey.toBase58()}` - ); - const ata = getAssociatedTokenAddressSync3( - mint.publicKey, - deployer.publicKey, - allowOffCurve - ); - const balance = await connection3.getTokenAccountBalance( - ata, - "processed" - ); - const amount = balance.value.uiAmount; - if (amount === null) { - elizaLogger13.log( - `${deployer.publicKey.toBase58()}:`, - "No Account Found" - ); - } else { - elizaLogger13.log(`${deployer.publicKey.toBase58()}:`, amount); - } - return { - success: true, - ca: mint.publicKey.toBase58(), - creator: deployer.publicKey.toBase58() - }; - } else { - elizaLogger13.log("Create and Buy failed"); - return { - success: false, - ca: mint.publicKey.toBase58(), - error: confirmation.value.err || "Transaction failed" - }; - } -}; -var promptConfirmation2 = async () => { - return true; -}; -var fomoTemplate2 = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined. - -Example response: -\`\`\`json -{ - "tokenMetadata": { - "name": "Test Token", - "symbol": "TEST", - "description": "A test token", - "image_description": "create an image of a rabbit" - }, - "buyAmountSol": "0.00069", - "requiredLiquidity": "85" -} -\`\`\` - -{{recentMessages}} - -Given the recent messages, extract or generate (come up with if not included) the following information about the requested token creation: -- Token name -- Token symbol -- Token description -- Token image description -- Amount of SOL to buy - -Respond with a JSON markdown block containing only the extracted values.`; -var fomo_default = { - name: "CREATE_AND_BUY_TOKEN", - similes: ["CREATE_AND_PURCHASE_TOKEN", "DEPLOY_AND_BUY_TOKEN"], - validate: async (_runtime, _message) => { - return true; - }, - description: "Create a new token and buy a specified amount using SOL. Requires deployer private key, token metadata, buy amount in SOL, priority fee, and allowOffCurve flag.", - handler: async (runtime, message, state, _options, callback) => { - elizaLogger13.log("Starting CREATE_AND_BUY_TOKEN handler..."); - if (!state) { - state = await runtime.composeState(message); - } else { - state = await runtime.updateRecentMessageState(state); - } - const walletInfo = await walletProvider.get(runtime, message, state); - state.walletInfo = walletInfo; - const pumpContext = composeContext7({ - state, - template: fomoTemplate2 - }); - const content = await generateObject2({ - runtime, - context: pumpContext, - modelClass: ModelClass7.LARGE - }); - if (!isCreateAndBuyContentForFomo2(content)) { - elizaLogger13.error( - "Invalid content for CREATE_AND_BUY_TOKEN action." - ); - return false; - } - const { tokenMetadata, buyAmountSol, requiredLiquidity } = content; - const imageResult = await generateImage2( - { - prompt: `logo for ${tokenMetadata.name} (${tokenMetadata.symbol}) token - ${tokenMetadata.description}`, - width: 256, - height: 256, - count: 1 - }, - runtime - ); - const imageBuffer = Buffer.from(imageResult.data[0], "base64"); - const formData = new FormData(); - const blob = new Blob([imageBuffer], { type: "image/png" }); - formData.append("file", blob, `${tokenMetadata.name}.png`); - formData.append("name", tokenMetadata.name); - formData.append("symbol", tokenMetadata.symbol); - formData.append("description", tokenMetadata.description); - const metadataResponse = await fetch("https://pump.fun/api/ipfs", { - method: "POST", - body: formData - }); - const metadataResponseJSON = await metadataResponse.json(); - const fullTokenMetadata = { - name: tokenMetadata.name, - symbol: tokenMetadata.symbol, - uri: metadataResponseJSON.metadataUri - }; - const priorityFee = { - unitLimit: 1e8, - unitPrice: 1e5 - }; - const slippage = "2000"; - try { - const privateKeyString = runtime.getSetting("SOLANA_PRIVATE_KEY") ?? runtime.getSetting("WALLET_PRIVATE_KEY"); - const secretKey = bs583.decode(privateKeyString); - const deployerKeypair = Keypair3.fromSecretKey(secretKey); - const mintKeypair = Keypair3.generate(); - elizaLogger13.log( - `Generated mint address: ${mintKeypair.publicKey.toBase58()}` - ); - const connection3 = new Connection11(settings8.SOLANA_RPC_URL, { - commitment: "confirmed", - confirmTransactionInitialTimeout: 5e5, - // 120 seconds - wsEndpoint: settings8.SOLANA_RPC_URL.replace("https", "wss") - }); - const sdk = new Fomo2(connection3, "devnet", deployerKeypair); - const createAndBuyConfirmation = await promptConfirmation2(); - if (!createAndBuyConfirmation) { - elizaLogger13.log("Create and buy token canceled by user"); - return false; - } - const lamports = Math.floor(Number(buyAmountSol) * 1e9); - elizaLogger13.log("Executing create and buy transaction..."); - const result = await createAndBuyToken2({ - deployer: deployerKeypair, - mint: mintKeypair, - tokenMetadata: fullTokenMetadata, - buyAmountSol: BigInt(lamports), - priorityFee: priorityFee.unitPrice, - requiredLiquidity: Number(requiredLiquidity), - allowOffCurve: false, - fomo: sdk, - connection: connection3, - slippage - }); - if (callback) { - if (result.success) { - callback({ - text: `Token ${tokenMetadata.name} (${tokenMetadata.symbol}) created successfully! -URL: https://fomo.fund/token/${result.ca} -Creator: ${result.creator} -View at: https://fomo.fund/token/${result.ca}`, - content: { - tokenInfo: { - symbol: tokenMetadata.symbol, - address: result.ca, - creator: result.creator, - name: tokenMetadata.name, - description: tokenMetadata.description, - timestamp: Date.now() - } - } - }); - } else { - callback({ - text: `Failed to create token: ${result.error} -Attempted mint address: ${result.ca}`, - content: { - error: result.error, - mintAddress: result.ca - } - }); - } - } - const successMessage = `Token created and purchased successfully! View at: https://fomo.fund/token/${mintKeypair.publicKey.toBase58()}`; - elizaLogger13.log(successMessage); - return result.success; - } catch (error) { - if (callback) { - callback({ - text: `Error during token creation: ${error.message}`, - content: { error: error.message } - }); - } - return false; - } - }, - examples: [ - [ - { - user: "{{user1}}", - content: { - text: "Create a new token called GLITCHIZA with symbol GLITCHIZA and generate a description about it on fomo.fund. Also come up with a description for it to use for image generation .buy 0.00069 SOL worth." - } - }, - { - user: "{{user2}}", - content: { - text: "Token GLITCHIZA (GLITCHIZA) created successfully on fomo.fund!\nURL: https://fomo.fund/token/673247855e8012181f941f84\nCreator: Anonymous\nView at: https://fomo.fund/token/673247855e8012181f941f84", - action: "CREATE_AND_BUY_TOKEN", - content: { - tokenInfo: { - symbol: "GLITCHIZA", - address: "EugPwuZ8oUMWsYHeBGERWvELfLGFmA1taDtmY8uMeX6r", - creator: "9jW8FPr6BSSsemWPV22UUCzSqkVdTp6HTyPqeqyuBbCa", - name: "GLITCHIZA", - description: "A GLITCHIZA token" - } - } - } - } - ] - ] -}; - -// src/actions/swapDao.ts -import { - elizaLogger as elizaLogger14 -} from "@elizaos/core"; -import { Connection as Connection12, PublicKey as PublicKey9, Transaction } from "@solana/web3.js"; -async function invokeSwapDao(connection3, authority, statePDA, walletPDA, instructionData) { - const discriminator = new Uint8Array([ - 25, - 143, - 207, - 190, - 174, - 228, - 130, - 107 - ]); - const combinedData = new Uint8Array( - discriminator.length + instructionData.length - ); - combinedData.set(discriminator, 0); - combinedData.set(instructionData, discriminator.length); - const transaction = new Transaction().add({ - programId: new PublicKey9("PROGRAM_ID"), - keys: [ - { pubkey: authority.publicKey, isSigner: true, isWritable: true }, - { pubkey: statePDA, isSigner: false, isWritable: true }, - { pubkey: walletPDA, isSigner: false, isWritable: true } - ], - data: Buffer.from(combinedData) - }); - const signature = await connection3.sendTransaction(transaction, [ - authority - ]); - await connection3.confirmTransaction(signature); - return signature; -} -async function promptConfirmation3() { - const confirmSwap = window.confirm("Confirm the token swap?"); - return confirmSwap; -} -var executeSwapForDAO = { - name: "EXECUTE_SWAP_DAO", - similes: ["SWAP_TOKENS_DAO", "TOKEN_SWAP_DAO"], - validate: async (runtime, message) => { - elizaLogger14.log("Message:", message); - return true; - }, - description: "Perform a DAO token swap using execute_invoke.", - handler: async (runtime, message) => { - const { inputToken, outputToken, amount } = message.content; - try { - const connection3 = new Connection12( - runtime.getSetting("SOLANA_RPC_URL") - ); - const { keypair: authority } = await getWalletKey(runtime, true); - const daoMint = new PublicKey9(runtime.getSetting("DAO_MINT")); - const [statePDA] = await PublicKey9.findProgramAddress( - [Buffer.from("state"), daoMint.toBuffer()], - authority.publicKey - ); - const [walletPDA] = await PublicKey9.findProgramAddress( - [Buffer.from("wallet"), daoMint.toBuffer()], - authority.publicKey - ); - const quoteData = await getQuote( - connection3, - inputToken, - outputToken, - amount - ); - elizaLogger14.log("Swap Quote:", quoteData); - const confirmSwap = await promptConfirmation3(); - if (!confirmSwap) { - elizaLogger14.log("Swap canceled by user"); - return false; - } - const instructionData = Buffer.from( - JSON.stringify({ - quote: quoteData.data, - userPublicKey: authority.publicKey.toString(), - wrapAndUnwrapSol: true - }) - ); - const txid = await invokeSwapDao( - connection3, - authority, - statePDA, - walletPDA, - instructionData - ); - elizaLogger14.log("DAO Swap completed successfully!"); - elizaLogger14.log(`Transaction ID: ${txid}`); - return true; - } catch (error) { - elizaLogger14.error("Error during DAO token swap:", error); - return false; - } - }, - examples: [ - [ - { - user: "{{user1}}", - content: { - inputTokenSymbol: "SOL", - outputTokenSymbol: "USDC", - inputToken: "So11111111111111111111111111111111111111112", - outputToken: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", - amount: 0.1 - } - }, - { - user: "{{user2}}", - content: { - text: "Swapping 0.1 SOL for USDC using DAO...", - action: "TOKEN_SWAP_DAO" - } - }, - { - user: "{{user2}}", - content: { - text: "DAO Swap completed successfully! Transaction ID: ..." - } - } - ] - ] -}; - -// src/index.ts -var solanaPlugin = { - name: "solana", - description: "Solana Plugin for Eliza", - actions: [ - transfer_default, - transfer_sol_default, - executeSwap, - pumpfun_default, - fomo_default, - executeSwapForDAO, - takeOrder_default - ], - evaluators: [trustEvaluator], - providers: [walletProvider, trustScoreProvider] -}; -var index_default = solanaPlugin; -export { - TokenProvider, - TrustScoreManager, - WalletProvider, - index_default as default, - formatRecommendations, - getTokenBalance, - getTokenBalances, - solanaPlugin, - tokenProvider, - trustEvaluator, - trustScoreProvider, - walletProvider -}; -//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/index.js.map b/dist/index.js.map deleted file mode 100644 index 403d431..0000000 --- a/dist/index.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../src/providers/token.ts","../src/bignumber.ts","../src/providers/wallet.ts","../src/keypairUtils.ts","../src/providers/trustScoreProvider.ts","../../../node_modules/uuid/dist/esm-node/stringify.js","../../../node_modules/uuid/dist/esm-node/rng.js","../../../node_modules/uuid/dist/esm-node/native.js","../../../node_modules/uuid/dist/esm-node/v4.js","../src/providers/simulationSellingService.ts","../src/evaluators/trust.ts","../src/actions/transfer.ts","../src/actions/transfer_sol.ts","../src/providers/tokenUtils.ts","../src/actions/swap.ts","../src/actions/swapUtils.ts","../src/actions/takeOrder.ts","../src/actions/pumpfun.ts","../src/actions/fomo.ts","../src/actions/swapDao.ts","../src/index.ts"],"sourcesContent":["import {\n type IAgentRuntime,\n type Memory,\n type Provider,\n type State,\n elizaLogger,\n type ICacheManager,\n settings,\n} from \"@elizaos/core\";\nimport type {\n DexScreenerData,\n DexScreenerPair,\n HolderData,\n ProcessedTokenData,\n TokenSecurityData,\n TokenTradeData,\n CalculatedBuyAmounts,\n Prices,\n TokenCodex,\n} from \"../types/token.ts\";\nimport NodeCache from \"node-cache\";\nimport * as path from \"path\";\nimport { toBN } from \"../bignumber.ts\";\nimport { WalletProvider, type Item } from \"./wallet.ts\";\nimport { Connection } from \"@solana/web3.js\";\nimport { getWalletKey } from \"../keypairUtils.ts\";\n\nconst PROVIDER_CONFIG = {\n BIRDEYE_API: \"https://public-api.birdeye.so\",\n MAX_RETRIES: 3,\n RETRY_DELAY: 2000,\n DEFAULT_RPC: \"https://api.mainnet-beta.solana.com\",\n TOKEN_ADDRESSES: {\n SOL: \"So11111111111111111111111111111111111111112\",\n BTC: \"qfnqNqs3nCAHjnyCgLRDbBtq4p2MtHZxw8YjSyYhPoL\",\n ETH: \"7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs\",\n Example: \"2weMjPLLybRMMva1fM3U31goWWrCpF59CHWNhnCJ9Vyh\",\n },\n TOKEN_SECURITY_ENDPOINT: \"/defi/token_security?address=\",\n TOKEN_TRADE_DATA_ENDPOINT: \"/defi/v3/token/trade-data/single?address=\",\n DEX_SCREENER_API: \"https://api.dexscreener.com/latest/dex/tokens/\",\n MAIN_WALLET: \"\",\n};\n\nexport class TokenProvider {\n private cache: NodeCache;\n private cacheKey = \"solana/tokens\";\n private NETWORK_ID = 1399811149;\n private GRAPHQL_ENDPOINT = \"https://graph.codex.io/graphql\";\n\n constructor(\n // private connection: Connection,\n private tokenAddress: string,\n private walletProvider: WalletProvider,\n private cacheManager: ICacheManager\n ) {\n this.cache = new NodeCache({ stdTTL: 300 }); // 5 minutes cache\n }\n\n private async readFromCache(key: string): Promise {\n const cached = await this.cacheManager.get(\n path.join(this.cacheKey, key)\n );\n return cached;\n }\n\n private async writeToCache(key: string, data: T): Promise {\n await this.cacheManager.set(path.join(this.cacheKey, key), data, {\n expires: Date.now() + 5 * 60 * 1000,\n });\n }\n\n private async getCachedData(key: string): Promise {\n // Check in-memory cache first\n const cachedData = this.cache.get(key);\n if (cachedData) {\n return cachedData;\n }\n\n // Check file-based cache\n const fileCachedData = await this.readFromCache(key);\n if (fileCachedData) {\n // Populate in-memory cache\n this.cache.set(key, fileCachedData);\n return fileCachedData;\n }\n\n return null;\n }\n\n private async setCachedData(cacheKey: string, data: T): Promise {\n // Set in-memory cache\n this.cache.set(cacheKey, data);\n\n // Write to file-based cache\n await this.writeToCache(cacheKey, data);\n }\n\n private async fetchWithRetry(\n url: string,\n options: RequestInit = {}\n ): Promise {\n let lastError: Error;\n\n for (let i = 0; i < PROVIDER_CONFIG.MAX_RETRIES; i++) {\n try {\n const response = await fetch(url, {\n ...options,\n headers: {\n Accept: \"application/json\",\n \"x-chain\": \"solana\",\n \"X-API-KEY\": settings.BIRDEYE_API_KEY || \"\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(\n `HTTP error! status: ${response.status}, message: ${errorText}`\n );\n }\n\n const data = await response.json();\n return data;\n } catch (error) {\n elizaLogger.error(`Attempt ${i + 1} failed:`, error);\n lastError = error as Error;\n if (i < PROVIDER_CONFIG.MAX_RETRIES - 1) {\n const delay = PROVIDER_CONFIG.RETRY_DELAY * Math.pow(2, i);\n elizaLogger.log(`Waiting ${delay}ms before retrying...`);\n await new Promise((resolve) => setTimeout(resolve, delay));\n continue;\n }\n }\n }\n\n elizaLogger.error(\n \"All attempts failed. Throwing the last error:\",\n lastError\n );\n throw lastError;\n }\n\n async getTokensInWallet(runtime: IAgentRuntime): Promise {\n const walletInfo =\n await this.walletProvider.fetchPortfolioValue(runtime);\n const items = walletInfo.items;\n return items;\n }\n\n // check if the token symbol is in the wallet\n async getTokenFromWallet(runtime: IAgentRuntime, tokenSymbol: string) {\n try {\n const items = await this.getTokensInWallet(runtime);\n const token = items.find((item) => item.symbol === tokenSymbol);\n\n if (token) {\n return token.address;\n } else {\n return null;\n }\n } catch (error) {\n elizaLogger.error(\"Error checking token in wallet:\", error);\n return null;\n }\n }\n\n async fetchTokenCodex(): Promise {\n try {\n const cacheKey = `token_${this.tokenAddress}`;\n const cachedData = await this.getCachedData(cacheKey);\n if (cachedData) {\n elizaLogger.log(\n `Returning cached token data for ${this.tokenAddress}.`\n );\n return cachedData;\n }\n const query = `\n query Token($address: String!, $networkId: Int!) {\n token(input: { address: $address, networkId: $networkId }) {\n id\n address\n cmcId\n decimals\n name\n symbol\n totalSupply\n isScam\n info {\n circulatingSupply\n imageThumbUrl\n }\n explorerData {\n blueCheckmark\n description\n tokenType\n }\n }\n }\n `;\n\n const variables = {\n address: this.tokenAddress,\n networkId: this.NETWORK_ID, // Replace with your network ID\n };\n\n const response = await fetch(this.GRAPHQL_ENDPOINT, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: settings.CODEX_API_KEY,\n },\n body: JSON.stringify({\n query,\n variables,\n }),\n }).then((res) => res.json());\n\n const token = response.data?.data?.token;\n\n if (!token) {\n throw new Error(`No data returned for token ${tokenAddress}`);\n }\n\n this.setCachedData(cacheKey, token);\n\n return {\n id: token.id,\n address: token.address,\n cmcId: token.cmcId,\n decimals: token.decimals,\n name: token.name,\n symbol: token.symbol,\n totalSupply: token.totalSupply,\n circulatingSupply: token.info?.circulatingSupply,\n imageThumbUrl: token.info?.imageThumbUrl,\n blueCheckmark: token.explorerData?.blueCheckmark,\n isScam: token.isScam ? true : false,\n };\n } catch (error) {\n elizaLogger.error(\n \"Error fetching token data from Codex:\",\n error.message\n );\n return {} as TokenCodex;\n }\n }\n\n async fetchPrices(): Promise {\n try {\n const cacheKey = \"prices\";\n const cachedData = await this.getCachedData(cacheKey);\n if (cachedData) {\n elizaLogger.log(\"Returning cached prices.\");\n return cachedData;\n }\n const { SOL, BTC, ETH } = PROVIDER_CONFIG.TOKEN_ADDRESSES;\n const tokens = [SOL, BTC, ETH];\n const prices: Prices = {\n solana: { usd: \"0\" },\n bitcoin: { usd: \"0\" },\n ethereum: { usd: \"0\" },\n };\n\n for (const token of tokens) {\n const response = await this.fetchWithRetry(\n `${PROVIDER_CONFIG.BIRDEYE_API}/defi/price?address=${token}`,\n {\n headers: {\n \"x-chain\": \"solana\",\n },\n }\n );\n\n if (response?.data?.value) {\n const price = response.data.value.toString();\n prices[\n token === SOL\n ? \"solana\"\n : token === BTC\n ? \"bitcoin\"\n : \"ethereum\"\n ].usd = price;\n } else {\n elizaLogger.warn(\n `No price data available for token: ${token}`\n );\n }\n }\n this.setCachedData(cacheKey, prices);\n return prices;\n } catch (error) {\n elizaLogger.error(\"Error fetching prices:\", error);\n throw error;\n }\n }\n async calculateBuyAmounts(): Promise {\n const dexScreenerData = await this.fetchDexScreenerData();\n const prices = await this.fetchPrices();\n const solPrice = toBN(prices.solana.usd);\n\n if (!dexScreenerData || dexScreenerData.pairs.length === 0) {\n return { none: 0, low: 0, medium: 0, high: 0 };\n }\n\n // Get the first pair\n const pair = dexScreenerData.pairs[0];\n const { liquidity, marketCap } = pair;\n if (!liquidity || !marketCap) {\n return { none: 0, low: 0, medium: 0, high: 0 };\n }\n\n if (liquidity.usd === 0) {\n return { none: 0, low: 0, medium: 0, high: 0 };\n }\n if (marketCap < 100000) {\n return { none: 0, low: 0, medium: 0, high: 0 };\n }\n\n // impact percentages based on liquidity\n const impactPercentages = {\n LOW: 0.01, // 1% of liquidity\n MEDIUM: 0.05, // 5% of liquidity\n HIGH: 0.1, // 10% of liquidity\n };\n\n // Calculate buy amounts in USD\n const lowBuyAmountUSD = liquidity.usd * impactPercentages.LOW;\n const mediumBuyAmountUSD = liquidity.usd * impactPercentages.MEDIUM;\n const highBuyAmountUSD = liquidity.usd * impactPercentages.HIGH;\n\n // Convert each buy amount to SOL\n const lowBuyAmountSOL = toBN(lowBuyAmountUSD).div(solPrice).toNumber();\n const mediumBuyAmountSOL = toBN(mediumBuyAmountUSD)\n .div(solPrice)\n .toNumber();\n const highBuyAmountSOL = toBN(highBuyAmountUSD)\n .div(solPrice)\n .toNumber();\n\n return {\n none: 0,\n low: lowBuyAmountSOL,\n medium: mediumBuyAmountSOL,\n high: highBuyAmountSOL,\n };\n }\n\n async fetchTokenSecurity(): Promise {\n const cacheKey = `tokenSecurity_${this.tokenAddress}`;\n const cachedData =\n await this.getCachedData(cacheKey);\n if (cachedData) {\n elizaLogger.log(\n `Returning cached token security data for ${this.tokenAddress}.`\n );\n return cachedData;\n }\n const url = `${PROVIDER_CONFIG.BIRDEYE_API}${PROVIDER_CONFIG.TOKEN_SECURITY_ENDPOINT}${this.tokenAddress}`;\n const data = await this.fetchWithRetry(url);\n\n if (!data?.success || !data?.data) {\n throw new Error(\"No token security data available\");\n }\n\n const security: TokenSecurityData = {\n ownerBalance: data.data.ownerBalance,\n creatorBalance: data.data.creatorBalance,\n ownerPercentage: data.data.ownerPercentage,\n creatorPercentage: data.data.creatorPercentage,\n top10HolderBalance: data.data.top10HolderBalance,\n top10HolderPercent: data.data.top10HolderPercent,\n };\n this.setCachedData(cacheKey, security);\n elizaLogger.log(`Token security data cached for ${this.tokenAddress}.`);\n\n return security;\n }\n\n async fetchTokenTradeData(): Promise {\n const cacheKey = `tokenTradeData_${this.tokenAddress}`;\n const cachedData = await this.getCachedData(cacheKey);\n if (cachedData) {\n elizaLogger.log(\n `Returning cached token trade data for ${this.tokenAddress}.`\n );\n return cachedData;\n }\n\n const url = `${PROVIDER_CONFIG.BIRDEYE_API}${PROVIDER_CONFIG.TOKEN_TRADE_DATA_ENDPOINT}${this.tokenAddress}`;\n const options = {\n method: \"GET\",\n headers: {\n accept: \"application/json\",\n \"X-API-KEY\": settings.BIRDEYE_API_KEY || \"\",\n },\n };\n\n const data = await fetch(url, options)\n .then((res) => res.json())\n .catch((err) => elizaLogger.error(err));\n\n if (!data?.success || !data?.data) {\n throw new Error(\"No token trade data available\");\n }\n\n const tradeData: TokenTradeData = {\n address: data.data.address,\n holder: data.data.holder,\n market: data.data.market,\n last_trade_unix_time: data.data.last_trade_unix_time,\n last_trade_human_time: data.data.last_trade_human_time,\n price: data.data.price,\n history_30m_price: data.data.history_30m_price,\n price_change_30m_percent: data.data.price_change_30m_percent,\n history_1h_price: data.data.history_1h_price,\n price_change_1h_percent: data.data.price_change_1h_percent,\n history_2h_price: data.data.history_2h_price,\n price_change_2h_percent: data.data.price_change_2h_percent,\n history_4h_price: data.data.history_4h_price,\n price_change_4h_percent: data.data.price_change_4h_percent,\n history_6h_price: data.data.history_6h_price,\n price_change_6h_percent: data.data.price_change_6h_percent,\n history_8h_price: data.data.history_8h_price,\n price_change_8h_percent: data.data.price_change_8h_percent,\n history_12h_price: data.data.history_12h_price,\n price_change_12h_percent: data.data.price_change_12h_percent,\n history_24h_price: data.data.history_24h_price,\n price_change_24h_percent: data.data.price_change_24h_percent,\n unique_wallet_30m: data.data.unique_wallet_30m,\n unique_wallet_history_30m: data.data.unique_wallet_history_30m,\n unique_wallet_30m_change_percent:\n data.data.unique_wallet_30m_change_percent,\n unique_wallet_1h: data.data.unique_wallet_1h,\n unique_wallet_history_1h: data.data.unique_wallet_history_1h,\n unique_wallet_1h_change_percent:\n data.data.unique_wallet_1h_change_percent,\n unique_wallet_2h: data.data.unique_wallet_2h,\n unique_wallet_history_2h: data.data.unique_wallet_history_2h,\n unique_wallet_2h_change_percent:\n data.data.unique_wallet_2h_change_percent,\n unique_wallet_4h: data.data.unique_wallet_4h,\n unique_wallet_history_4h: data.data.unique_wallet_history_4h,\n unique_wallet_4h_change_percent:\n data.data.unique_wallet_4h_change_percent,\n unique_wallet_8h: data.data.unique_wallet_8h,\n unique_wallet_history_8h: data.data.unique_wallet_history_8h,\n unique_wallet_8h_change_percent:\n data.data.unique_wallet_8h_change_percent,\n unique_wallet_24h: data.data.unique_wallet_24h,\n unique_wallet_history_24h: data.data.unique_wallet_history_24h,\n unique_wallet_24h_change_percent:\n data.data.unique_wallet_24h_change_percent,\n trade_30m: data.data.trade_30m,\n trade_history_30m: data.data.trade_history_30m,\n trade_30m_change_percent: data.data.trade_30m_change_percent,\n sell_30m: data.data.sell_30m,\n sell_history_30m: data.data.sell_history_30m,\n sell_30m_change_percent: data.data.sell_30m_change_percent,\n buy_30m: data.data.buy_30m,\n buy_history_30m: data.data.buy_history_30m,\n buy_30m_change_percent: data.data.buy_30m_change_percent,\n volume_30m: data.data.volume_30m,\n volume_30m_usd: data.data.volume_30m_usd,\n volume_history_30m: data.data.volume_history_30m,\n volume_history_30m_usd: data.data.volume_history_30m_usd,\n volume_30m_change_percent: data.data.volume_30m_change_percent,\n volume_buy_30m: data.data.volume_buy_30m,\n volume_buy_30m_usd: data.data.volume_buy_30m_usd,\n volume_buy_history_30m: data.data.volume_buy_history_30m,\n volume_buy_history_30m_usd: data.data.volume_buy_history_30m_usd,\n volume_buy_30m_change_percent:\n data.data.volume_buy_30m_change_percent,\n volume_sell_30m: data.data.volume_sell_30m,\n volume_sell_30m_usd: data.data.volume_sell_30m_usd,\n volume_sell_history_30m: data.data.volume_sell_history_30m,\n volume_sell_history_30m_usd: data.data.volume_sell_history_30m_usd,\n volume_sell_30m_change_percent:\n data.data.volume_sell_30m_change_percent,\n trade_1h: data.data.trade_1h,\n trade_history_1h: data.data.trade_history_1h,\n trade_1h_change_percent: data.data.trade_1h_change_percent,\n sell_1h: data.data.sell_1h,\n sell_history_1h: data.data.sell_history_1h,\n sell_1h_change_percent: data.data.sell_1h_change_percent,\n buy_1h: data.data.buy_1h,\n buy_history_1h: data.data.buy_history_1h,\n buy_1h_change_percent: data.data.buy_1h_change_percent,\n volume_1h: data.data.volume_1h,\n volume_1h_usd: data.data.volume_1h_usd,\n volume_history_1h: data.data.volume_history_1h,\n volume_history_1h_usd: data.data.volume_history_1h_usd,\n volume_1h_change_percent: data.data.volume_1h_change_percent,\n volume_buy_1h: data.data.volume_buy_1h,\n volume_buy_1h_usd: data.data.volume_buy_1h_usd,\n volume_buy_history_1h: data.data.volume_buy_history_1h,\n volume_buy_history_1h_usd: data.data.volume_buy_history_1h_usd,\n volume_buy_1h_change_percent:\n data.data.volume_buy_1h_change_percent,\n volume_sell_1h: data.data.volume_sell_1h,\n volume_sell_1h_usd: data.data.volume_sell_1h_usd,\n volume_sell_history_1h: data.data.volume_sell_history_1h,\n volume_sell_history_1h_usd: data.data.volume_sell_history_1h_usd,\n volume_sell_1h_change_percent:\n data.data.volume_sell_1h_change_percent,\n trade_2h: data.data.trade_2h,\n trade_history_2h: data.data.trade_history_2h,\n trade_2h_change_percent: data.data.trade_2h_change_percent,\n sell_2h: data.data.sell_2h,\n sell_history_2h: data.data.sell_history_2h,\n sell_2h_change_percent: data.data.sell_2h_change_percent,\n buy_2h: data.data.buy_2h,\n buy_history_2h: data.data.buy_history_2h,\n buy_2h_change_percent: data.data.buy_2h_change_percent,\n volume_2h: data.data.volume_2h,\n volume_2h_usd: data.data.volume_2h_usd,\n volume_history_2h: data.data.volume_history_2h,\n volume_history_2h_usd: data.data.volume_history_2h_usd,\n volume_2h_change_percent: data.data.volume_2h_change_percent,\n volume_buy_2h: data.data.volume_buy_2h,\n volume_buy_2h_usd: data.data.volume_buy_2h_usd,\n volume_buy_history_2h: data.data.volume_buy_history_2h,\n volume_buy_history_2h_usd: data.data.volume_buy_history_2h_usd,\n volume_buy_2h_change_percent:\n data.data.volume_buy_2h_change_percent,\n volume_sell_2h: data.data.volume_sell_2h,\n volume_sell_2h_usd: data.data.volume_sell_2h_usd,\n volume_sell_history_2h: data.data.volume_sell_history_2h,\n volume_sell_history_2h_usd: data.data.volume_sell_history_2h_usd,\n volume_sell_2h_change_percent:\n data.data.volume_sell_2h_change_percent,\n trade_4h: data.data.trade_4h,\n trade_history_4h: data.data.trade_history_4h,\n trade_4h_change_percent: data.data.trade_4h_change_percent,\n sell_4h: data.data.sell_4h,\n sell_history_4h: data.data.sell_history_4h,\n sell_4h_change_percent: data.data.sell_4h_change_percent,\n buy_4h: data.data.buy_4h,\n buy_history_4h: data.data.buy_history_4h,\n buy_4h_change_percent: data.data.buy_4h_change_percent,\n volume_4h: data.data.volume_4h,\n volume_4h_usd: data.data.volume_4h_usd,\n volume_history_4h: data.data.volume_history_4h,\n volume_history_4h_usd: data.data.volume_history_4h_usd,\n volume_4h_change_percent: data.data.volume_4h_change_percent,\n volume_buy_4h: data.data.volume_buy_4h,\n volume_buy_4h_usd: data.data.volume_buy_4h_usd,\n volume_buy_history_4h: data.data.volume_buy_history_4h,\n volume_buy_history_4h_usd: data.data.volume_buy_history_4h_usd,\n volume_buy_4h_change_percent:\n data.data.volume_buy_4h_change_percent,\n volume_sell_4h: data.data.volume_sell_4h,\n volume_sell_4h_usd: data.data.volume_sell_4h_usd,\n volume_sell_history_4h: data.data.volume_sell_history_4h,\n volume_sell_history_4h_usd: data.data.volume_sell_history_4h_usd,\n volume_sell_4h_change_percent:\n data.data.volume_sell_4h_change_percent,\n trade_8h: data.data.trade_8h,\n trade_history_8h: data.data.trade_history_8h,\n trade_8h_change_percent: data.data.trade_8h_change_percent,\n sell_8h: data.data.sell_8h,\n sell_history_8h: data.data.sell_history_8h,\n sell_8h_change_percent: data.data.sell_8h_change_percent,\n buy_8h: data.data.buy_8h,\n buy_history_8h: data.data.buy_history_8h,\n buy_8h_change_percent: data.data.buy_8h_change_percent,\n volume_8h: data.data.volume_8h,\n volume_8h_usd: data.data.volume_8h_usd,\n volume_history_8h: data.data.volume_history_8h,\n volume_history_8h_usd: data.data.volume_history_8h_usd,\n volume_8h_change_percent: data.data.volume_8h_change_percent,\n volume_buy_8h: data.data.volume_buy_8h,\n volume_buy_8h_usd: data.data.volume_buy_8h_usd,\n volume_buy_history_8h: data.data.volume_buy_history_8h,\n volume_buy_history_8h_usd: data.data.volume_buy_history_8h_usd,\n volume_buy_8h_change_percent:\n data.data.volume_buy_8h_change_percent,\n volume_sell_8h: data.data.volume_sell_8h,\n volume_sell_8h_usd: data.data.volume_sell_8h_usd,\n volume_sell_history_8h: data.data.volume_sell_history_8h,\n volume_sell_history_8h_usd: data.data.volume_sell_history_8h_usd,\n volume_sell_8h_change_percent:\n data.data.volume_sell_8h_change_percent,\n trade_24h: data.data.trade_24h,\n trade_history_24h: data.data.trade_history_24h,\n trade_24h_change_percent: data.data.trade_24h_change_percent,\n sell_24h: data.data.sell_24h,\n sell_history_24h: data.data.sell_history_24h,\n sell_24h_change_percent: data.data.sell_24h_change_percent,\n buy_24h: data.data.buy_24h,\n buy_history_24h: data.data.buy_history_24h,\n buy_24h_change_percent: data.data.buy_24h_change_percent,\n volume_24h: data.data.volume_24h,\n volume_24h_usd: data.data.volume_24h_usd,\n volume_history_24h: data.data.volume_history_24h,\n volume_history_24h_usd: data.data.volume_history_24h_usd,\n volume_24h_change_percent: data.data.volume_24h_change_percent,\n volume_buy_24h: data.data.volume_buy_24h,\n volume_buy_24h_usd: data.data.volume_buy_24h_usd,\n volume_buy_history_24h: data.data.volume_buy_history_24h,\n volume_buy_history_24h_usd: data.data.volume_buy_history_24h_usd,\n volume_buy_24h_change_percent:\n data.data.volume_buy_24h_change_percent,\n volume_sell_24h: data.data.volume_sell_24h,\n volume_sell_24h_usd: data.data.volume_sell_24h_usd,\n volume_sell_history_24h: data.data.volume_sell_history_24h,\n volume_sell_history_24h_usd: data.data.volume_sell_history_24h_usd,\n volume_sell_24h_change_percent:\n data.data.volume_sell_24h_change_percent,\n };\n this.setCachedData(cacheKey, tradeData);\n return tradeData;\n }\n\n async fetchDexScreenerData(): Promise {\n const cacheKey = `dexScreenerData_${this.tokenAddress}`;\n const cachedData = await this.getCachedData(cacheKey);\n if (cachedData) {\n elizaLogger.log(\"Returning cached DexScreener data.\");\n return cachedData;\n }\n\n const url = `https://api.dexscreener.com/latest/dex/search?q=${this.tokenAddress}`;\n try {\n elizaLogger.log(\n `Fetching DexScreener data for token: ${this.tokenAddress}`\n );\n const data = await fetch(url)\n .then((res) => res.json())\n .catch((err) => {\n elizaLogger.error(err);\n });\n\n if (!data || !data.pairs) {\n throw new Error(\"No DexScreener data available\");\n }\n\n const dexData: DexScreenerData = {\n schemaVersion: data.schemaVersion,\n pairs: data.pairs,\n };\n\n // Cache the result\n this.setCachedData(cacheKey, dexData);\n\n return dexData;\n } catch (error) {\n elizaLogger.error(`Error fetching DexScreener data:`, error);\n return {\n schemaVersion: \"1.0.0\",\n pairs: [],\n };\n }\n }\n\n async searchDexScreenerData(\n symbol: string\n ): Promise {\n const cacheKey = `dexScreenerData_search_${symbol}`;\n const cachedData = await this.getCachedData(cacheKey);\n if (cachedData) {\n elizaLogger.log(\"Returning cached search DexScreener data.\");\n return this.getHighestLiquidityPair(cachedData);\n }\n\n const url = `https://api.dexscreener.com/latest/dex/search?q=${symbol}`;\n try {\n elizaLogger.log(`Fetching DexScreener data for symbol: ${symbol}`);\n const data = await fetch(url)\n .then((res) => res.json())\n .catch((err) => {\n elizaLogger.error(err);\n return null;\n });\n\n if (!data || !data.pairs || data.pairs.length === 0) {\n throw new Error(\"No DexScreener data available\");\n }\n\n const dexData: DexScreenerData = {\n schemaVersion: data.schemaVersion,\n pairs: data.pairs,\n };\n\n // Cache the result\n this.setCachedData(cacheKey, dexData);\n\n // Return the pair with the highest liquidity and market cap\n return this.getHighestLiquidityPair(dexData);\n } catch (error) {\n elizaLogger.error(`Error fetching DexScreener data:`, error);\n return null;\n }\n }\n getHighestLiquidityPair(dexData: DexScreenerData): DexScreenerPair | null {\n if (dexData.pairs.length === 0) {\n return null;\n }\n\n // Sort pairs by both liquidity and market cap to get the highest one\n return dexData.pairs.sort((a, b) => {\n const liquidityDiff = b.liquidity.usd - a.liquidity.usd;\n if (liquidityDiff !== 0) {\n return liquidityDiff; // Higher liquidity comes first\n }\n return b.marketCap - a.marketCap; // If liquidity is equal, higher market cap comes first\n })[0];\n }\n\n async analyzeHolderDistribution(\n tradeData: TokenTradeData\n ): Promise {\n // Define the time intervals to consider (e.g., 30m, 1h, 2h)\n const intervals = [\n {\n period: \"30m\",\n change: tradeData.unique_wallet_30m_change_percent,\n },\n { period: \"1h\", change: tradeData.unique_wallet_1h_change_percent },\n { period: \"2h\", change: tradeData.unique_wallet_2h_change_percent },\n { period: \"4h\", change: tradeData.unique_wallet_4h_change_percent },\n { period: \"8h\", change: tradeData.unique_wallet_8h_change_percent },\n {\n period: \"24h\",\n change: tradeData.unique_wallet_24h_change_percent,\n },\n ];\n\n // Calculate the average change percentage\n const validChanges = intervals\n .map((interval) => interval.change)\n .filter(\n (change) => change !== null && change !== undefined\n ) as number[];\n\n if (validChanges.length === 0) {\n return \"stable\";\n }\n\n const averageChange =\n validChanges.reduce((acc, curr) => acc + curr, 0) /\n validChanges.length;\n\n const increaseThreshold = 10; // e.g., average change > 10%\n const decreaseThreshold = -10; // e.g., average change < -10%\n\n if (averageChange > increaseThreshold) {\n return \"increasing\";\n } else if (averageChange < decreaseThreshold) {\n return \"decreasing\";\n } else {\n return \"stable\";\n }\n }\n\n async fetchHolderList(): Promise {\n const cacheKey = `holderList_${this.tokenAddress}`;\n const cachedData = await this.getCachedData(cacheKey);\n if (cachedData) {\n elizaLogger.log(\"Returning cached holder list.\");\n return cachedData;\n }\n\n const allHoldersMap = new Map();\n let page = 1;\n const limit = 1000;\n let cursor;\n //HELIOUS_API_KEY needs to be added\n const url = `https://mainnet.helius-rpc.com/?api-key=${settings.HELIUS_API_KEY || \"\"}`;\n elizaLogger.log({ url });\n\n try {\n while (true) {\n const params = {\n limit: limit,\n displayOptions: {},\n mint: this.tokenAddress,\n cursor: cursor,\n };\n if (cursor != undefined) {\n params.cursor = cursor;\n }\n elizaLogger.log(`Fetching holders - Page ${page}`);\n if (page > 2) {\n break;\n }\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n jsonrpc: \"2.0\",\n id: \"helius-test\",\n method: \"getTokenAccounts\",\n params: params,\n }),\n });\n\n const data = await response.json();\n\n if (\n !data ||\n !data.result ||\n !data.result.token_accounts ||\n data.result.token_accounts.length === 0\n ) {\n elizaLogger.log(\n `No more holders found. Total pages fetched: ${page - 1}`\n );\n break;\n }\n\n elizaLogger.log(\n `Processing ${data.result.token_accounts.length} holders from page ${page}`\n );\n\n data.result.token_accounts.forEach((account: any) => {\n const owner = account.owner;\n const balance = Number.parseFloat(account.amount);\n\n if (allHoldersMap.has(owner)) {\n allHoldersMap.set(\n owner,\n allHoldersMap.get(owner)! + balance\n );\n } else {\n allHoldersMap.set(owner, balance);\n }\n });\n cursor = data.result.cursor;\n page++;\n }\n\n const holders: HolderData[] = Array.from(\n allHoldersMap.entries()\n ).map(([address, balance]) => ({\n address,\n balance: balance.toString(),\n }));\n\n elizaLogger.log(`Total unique holders fetched: ${holders.length}`);\n\n // Cache the result\n this.setCachedData(cacheKey, holders);\n\n return holders;\n } catch (error) {\n elizaLogger.error(\"Error fetching holder list from Helius:\", error);\n throw new Error(\"Failed to fetch holder list from Helius.\");\n }\n }\n\n async filterHighValueHolders(\n tradeData: TokenTradeData\n ): Promise> {\n const holdersData = await this.fetchHolderList();\n\n const tokenPriceUsd = toBN(tradeData.price);\n\n const highValueHolders = holdersData\n .filter((holder) => {\n const balanceUsd = toBN(holder.balance).multipliedBy(\n tokenPriceUsd\n );\n return balanceUsd.isGreaterThan(5);\n })\n .map((holder) => ({\n holderAddress: holder.address,\n balanceUsd: toBN(holder.balance)\n .multipliedBy(tokenPriceUsd)\n .toFixed(2),\n }));\n\n return highValueHolders;\n }\n\n async checkRecentTrades(tradeData: TokenTradeData): Promise {\n return toBN(tradeData.volume_24h_usd).isGreaterThan(0);\n }\n\n async countHighSupplyHolders(\n securityData: TokenSecurityData\n ): Promise {\n try {\n const ownerBalance = toBN(securityData.ownerBalance);\n const totalSupply = ownerBalance.plus(securityData.creatorBalance);\n\n const highSupplyHolders = await this.fetchHolderList();\n const highSupplyHoldersCount = highSupplyHolders.filter(\n (holder) => {\n const balance = toBN(holder.balance);\n return balance.dividedBy(totalSupply).isGreaterThan(0.02);\n }\n ).length;\n return highSupplyHoldersCount;\n } catch (error) {\n elizaLogger.error(\"Error counting high supply holders:\", error);\n return 0;\n }\n }\n\n async getProcessedTokenData(): Promise {\n try {\n elizaLogger.log(\n `Fetching security data for token: ${this.tokenAddress}`\n );\n const security = await this.fetchTokenSecurity();\n\n const tokenCodex = await this.fetchTokenCodex();\n\n elizaLogger.log(\n `Fetching trade data for token: ${this.tokenAddress}`\n );\n const tradeData = await this.fetchTokenTradeData();\n\n elizaLogger.log(\n `Fetching DexScreener data for token: ${this.tokenAddress}`\n );\n const dexData = await this.fetchDexScreenerData();\n\n elizaLogger.log(\n `Analyzing holder distribution for token: ${this.tokenAddress}`\n );\n const holderDistributionTrend =\n await this.analyzeHolderDistribution(tradeData);\n\n elizaLogger.log(\n `Filtering high-value holders for token: ${this.tokenAddress}`\n );\n const highValueHolders =\n await this.filterHighValueHolders(tradeData);\n\n elizaLogger.log(\n `Checking recent trades for token: ${this.tokenAddress}`\n );\n const recentTrades = await this.checkRecentTrades(tradeData);\n\n elizaLogger.log(\n `Counting high-supply holders for token: ${this.tokenAddress}`\n );\n const highSupplyHoldersCount =\n await this.countHighSupplyHolders(security);\n\n elizaLogger.log(\n `Determining DexScreener listing status for token: ${this.tokenAddress}`\n );\n const isDexScreenerListed = dexData.pairs.length > 0;\n const isDexScreenerPaid = dexData.pairs.some(\n (pair) => pair.boosts && pair.boosts.active > 0\n );\n\n const processedData: ProcessedTokenData = {\n security,\n tradeData,\n holderDistributionTrend,\n highValueHolders,\n recentTrades,\n highSupplyHoldersCount,\n dexScreenerData: dexData,\n isDexScreenerListed,\n isDexScreenerPaid,\n tokenCodex,\n };\n\n // elizaLogger.log(\"Processed token data:\", processedData);\n return processedData;\n } catch (error) {\n elizaLogger.error(\"Error processing token data:\", error);\n throw error;\n }\n }\n\n async shouldTradeToken(): Promise {\n try {\n const tokenData = await this.getProcessedTokenData();\n const { tradeData, security, dexScreenerData } = tokenData;\n const { ownerBalance, creatorBalance } = security;\n const { liquidity, marketCap } = dexScreenerData.pairs[0];\n const liquidityUsd = toBN(liquidity.usd);\n const marketCapUsd = toBN(marketCap);\n const totalSupply = toBN(ownerBalance).plus(creatorBalance);\n const _ownerPercentage = toBN(ownerBalance).dividedBy(totalSupply);\n const _creatorPercentage =\n toBN(creatorBalance).dividedBy(totalSupply);\n const top10HolderPercent = toBN(tradeData.volume_24h_usd).dividedBy(\n totalSupply\n );\n const priceChange24hPercent = toBN(\n tradeData.price_change_24h_percent\n );\n const priceChange12hPercent = toBN(\n tradeData.price_change_12h_percent\n );\n const uniqueWallet24h = tradeData.unique_wallet_24h;\n const volume24hUsd = toBN(tradeData.volume_24h_usd);\n const volume24hUsdThreshold = 1000;\n const priceChange24hPercentThreshold = 10;\n const priceChange12hPercentThreshold = 5;\n const top10HolderPercentThreshold = 0.05;\n const uniqueWallet24hThreshold = 100;\n const isTop10Holder = top10HolderPercent.gte(\n top10HolderPercentThreshold\n );\n const isVolume24h = volume24hUsd.gte(volume24hUsdThreshold);\n const isPriceChange24h = priceChange24hPercent.gte(\n priceChange24hPercentThreshold\n );\n const isPriceChange12h = priceChange12hPercent.gte(\n priceChange12hPercentThreshold\n );\n const isUniqueWallet24h =\n uniqueWallet24h >= uniqueWallet24hThreshold;\n const isLiquidityTooLow = liquidityUsd.lt(1000);\n const isMarketCapTooLow = marketCapUsd.lt(100000);\n return (\n isTop10Holder ||\n isVolume24h ||\n isPriceChange24h ||\n isPriceChange12h ||\n isUniqueWallet24h ||\n isLiquidityTooLow ||\n isMarketCapTooLow\n );\n } catch (error) {\n elizaLogger.error(\"Error processing token data:\", error);\n throw error;\n }\n }\n\n formatTokenData(data: ProcessedTokenData): string {\n let output = `**Token Security and Trade Report**\\n`;\n output += `Token Address: ${this.tokenAddress}\\n\\n`;\n\n // Security Data\n output += `**Ownership Distribution:**\\n`;\n output += `- Owner Balance: ${data.security.ownerBalance}\\n`;\n output += `- Creator Balance: ${data.security.creatorBalance}\\n`;\n output += `- Owner Percentage: ${data.security.ownerPercentage}%\\n`;\n output += `- Creator Percentage: ${data.security.creatorPercentage}%\\n`;\n output += `- Top 10 Holders Balance: ${data.security.top10HolderBalance}\\n`;\n output += `- Top 10 Holders Percentage: ${data.security.top10HolderPercent}%\\n\\n`;\n\n // Trade Data\n output += `**Trade Data:**\\n`;\n output += `- Holders: ${data.tradeData.holder}\\n`;\n output += `- Unique Wallets (24h): ${data.tradeData.unique_wallet_24h}\\n`;\n output += `- Price Change (24h): ${data.tradeData.price_change_24h_percent}%\\n`;\n output += `- Price Change (12h): ${data.tradeData.price_change_12h_percent}%\\n`;\n output += `- Volume (24h USD): $${toBN(data.tradeData.volume_24h_usd).toFixed(2)}\\n`;\n output += `- Current Price: $${toBN(data.tradeData.price).toFixed(2)}\\n\\n`;\n\n // Holder Distribution Trend\n output += `**Holder Distribution Trend:** ${data.holderDistributionTrend}\\n\\n`;\n\n // High-Value Holders\n output += `**High-Value Holders (>$5 USD):**\\n`;\n if (data.highValueHolders.length === 0) {\n output += `- No high-value holders found or data not available.\\n`;\n } else {\n data.highValueHolders.forEach((holder) => {\n output += `- ${holder.holderAddress}: $${holder.balanceUsd}\\n`;\n });\n }\n output += `\\n`;\n\n // Recent Trades\n output += `**Recent Trades (Last 24h):** ${data.recentTrades ? \"Yes\" : \"No\"}\\n\\n`;\n\n // High-Supply Holders\n output += `**Holders with >2% Supply:** ${data.highSupplyHoldersCount}\\n\\n`;\n\n // DexScreener Status\n output += `**DexScreener Listing:** ${data.isDexScreenerListed ? \"Yes\" : \"No\"}\\n`;\n if (data.isDexScreenerListed) {\n output += `- Listing Type: ${data.isDexScreenerPaid ? \"Paid\" : \"Free\"}\\n`;\n output += `- Number of DexPairs: ${data.dexScreenerData.pairs.length}\\n\\n`;\n output += `**DexScreener Pairs:**\\n`;\n data.dexScreenerData.pairs.forEach((pair, index) => {\n output += `\\n**Pair ${index + 1}:**\\n`;\n output += `- DEX: ${pair.dexId}\\n`;\n output += `- URL: ${pair.url}\\n`;\n output += `- Price USD: $${toBN(pair.priceUsd).toFixed(6)}\\n`;\n output += `- Volume (24h USD): $${toBN(pair.volume.h24).toFixed(2)}\\n`;\n output += `- Boosts Active: ${pair.boosts && pair.boosts.active}\\n`;\n output += `- Liquidity USD: $${toBN(pair.liquidity.usd).toFixed(2)}\\n`;\n });\n }\n output += `\\n`;\n\n elizaLogger.log(\"Formatted token data:\", output);\n return output;\n }\n\n async getFormattedTokenReport(): Promise {\n try {\n elizaLogger.log(\"Generating formatted token report...\");\n const processedData = await this.getProcessedTokenData();\n return this.formatTokenData(processedData);\n } catch (error) {\n elizaLogger.error(\"Error generating token report:\", error);\n return \"Unable to fetch token information. Please try again later.\";\n }\n }\n}\n\nconst tokenAddress = PROVIDER_CONFIG.TOKEN_ADDRESSES.Example;\n\nconst connection = new Connection(PROVIDER_CONFIG.DEFAULT_RPC);\nconst tokenProvider: Provider = {\n get: async (\n runtime: IAgentRuntime,\n _message: Memory,\n _state?: State\n ): Promise => {\n try {\n const { publicKey } = await getWalletKey(runtime, false);\n\n const walletProvider = new WalletProvider(connection, publicKey);\n\n const provider = new TokenProvider(\n tokenAddress,\n walletProvider,\n runtime.cacheManager\n );\n\n return provider.getFormattedTokenReport();\n } catch (error) {\n elizaLogger.error(\"Error fetching token data:\", error);\n return \"Unable to fetch token information. Please try again later.\";\n }\n },\n};\n\nexport { tokenProvider };\n","import BigNumber from \"bignumber.js\";\n\n// Re-export BigNumber constructor\nexport const BN = BigNumber;\n\n// Helper function to create new BigNumber instances\nexport function toBN(value: string | number | BigNumber): BigNumber {\n return new BigNumber(value);\n}\n","import {\n type IAgentRuntime,\n type Memory,\n type Provider,\n type State,\n elizaLogger,\n} from \"@elizaos/core\";\nimport { Connection, PublicKey } from \"@solana/web3.js\";\nimport BigNumber from \"bignumber.js\";\nimport NodeCache from \"node-cache\";\nimport { getWalletKey } from \"../keypairUtils\";\n\n// Provider configuration\nconst PROVIDER_CONFIG = {\n BIRDEYE_API: \"https://public-api.birdeye.so\",\n MAX_RETRIES: 3,\n RETRY_DELAY: 2000,\n DEFAULT_RPC: \"https://api.mainnet-beta.solana.com\",\n GRAPHQL_ENDPOINT: \"https://graph.codex.io/graphql\",\n TOKEN_ADDRESSES: {\n SOL: \"So11111111111111111111111111111111111111112\",\n BTC: \"3NZ9JMVBmGAqocybic2c7LQCJScmgsAZ6vQqTDzcqmJh\",\n ETH: \"7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs\",\n },\n};\n\nexport interface Item {\n name: string;\n address: string;\n symbol: string;\n decimals: number;\n balance: string;\n uiAmount: string;\n priceUsd: string;\n valueUsd: string;\n valueSol?: string;\n}\n\ninterface WalletPortfolio {\n totalUsd: string;\n totalSol?: string;\n items: Array;\n}\n\ninterface _BirdEyePriceData {\n data: {\n [key: string]: {\n price: number;\n priceChange24h: number;\n };\n };\n}\n\ninterface Prices {\n solana: { usd: string };\n bitcoin: { usd: string };\n ethereum: { usd: string };\n}\n\nexport class WalletProvider {\n private cache: NodeCache;\n\n constructor(\n private connection: Connection,\n private walletPublicKey: PublicKey\n ) {\n this.cache = new NodeCache({ stdTTL: 300 }); // Cache TTL set to 5 minutes\n }\n\n private async fetchWithRetry(\n runtime,\n url: string,\n options: RequestInit = {}\n ): Promise {\n let lastError: Error;\n\n for (let i = 0; i < PROVIDER_CONFIG.MAX_RETRIES; i++) {\n try {\n const response = await fetch(url, {\n ...options,\n headers: {\n Accept: \"application/json\",\n \"x-chain\": \"solana\",\n \"X-API-KEY\":\n runtime.getSetting(\"BIRDEYE_API_KEY\", \"\") || \"\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(\n `HTTP error! status: ${response.status}, message: ${errorText}`\n );\n }\n\n const data = await response.json();\n return data;\n } catch (error) {\n elizaLogger.error(`Attempt ${i + 1} failed:`, error);\n lastError = error;\n if (i < PROVIDER_CONFIG.MAX_RETRIES - 1) {\n const delay = PROVIDER_CONFIG.RETRY_DELAY * Math.pow(2, i);\n await new Promise((resolve) => setTimeout(resolve, delay));\n continue;\n }\n }\n }\n\n elizaLogger.error(\n \"All attempts failed. Throwing the last error:\",\n lastError\n );\n throw lastError;\n }\n\n async fetchPortfolioValue(runtime): Promise {\n try {\n const cacheKey = `portfolio-${this.walletPublicKey.toBase58()}`;\n const cachedValue = this.cache.get(cacheKey);\n\n if (cachedValue) {\n elizaLogger.log(\"Cache hit for fetchPortfolioValue\");\n return cachedValue;\n }\n elizaLogger.log(\"Cache miss for fetchPortfolioValue\");\n\n // Check if Birdeye API key is available\n const birdeyeApiKey = runtime.getSetting(\"BIRDEYE_API_KEY\");\n\n if (birdeyeApiKey) {\n // Existing Birdeye API logic\n const walletData = await this.fetchWithRetry(\n runtime,\n `${PROVIDER_CONFIG.BIRDEYE_API}/v1/wallet/token_list?wallet=${this.walletPublicKey.toBase58()}`\n );\n\n if (walletData?.success && walletData?.data) {\n const data = walletData.data;\n const totalUsd = new BigNumber(data.totalUsd.toString());\n const prices = await this.fetchPrices(runtime);\n const solPriceInUSD = new BigNumber(\n prices.solana.usd.toString()\n );\n\n const items = data.items.map((item: any) => ({\n ...item,\n valueSol: new BigNumber(item.valueUsd || 0)\n .div(solPriceInUSD)\n .toFixed(6),\n name: item.name || \"Unknown\",\n symbol: item.symbol || \"Unknown\",\n priceUsd: item.priceUsd || \"0\",\n valueUsd: item.valueUsd || \"0\",\n }));\n\n const portfolio = {\n totalUsd: totalUsd.toString(),\n totalSol: totalUsd.div(solPriceInUSD).toFixed(6),\n items: items.sort((a, b) =>\n new BigNumber(b.valueUsd)\n .minus(new BigNumber(a.valueUsd))\n .toNumber()\n ),\n };\n\n this.cache.set(cacheKey, portfolio);\n return portfolio;\n }\n }\n\n // Fallback to basic token account info if no Birdeye API key or API call fails\n const accounts = await this.getTokenAccounts(\n this.walletPublicKey.toBase58()\n );\n\n const items = accounts.map((acc) => ({\n name: \"Unknown\",\n address: acc.account.data.parsed.info.mint,\n symbol: \"Unknown\",\n decimals: acc.account.data.parsed.info.tokenAmount.decimals,\n balance: acc.account.data.parsed.info.tokenAmount.amount,\n uiAmount:\n acc.account.data.parsed.info.tokenAmount.uiAmount.toString(),\n priceUsd: \"0\",\n valueUsd: \"0\",\n valueSol: \"0\",\n }));\n\n const portfolio = {\n totalUsd: \"0\",\n totalSol: \"0\",\n items,\n };\n\n this.cache.set(cacheKey, portfolio);\n return portfolio;\n } catch (error) {\n elizaLogger.error(\"Error fetching portfolio:\", error);\n throw error;\n }\n }\n\n async fetchPortfolioValueCodex(runtime): Promise {\n try {\n const cacheKey = `portfolio-${this.walletPublicKey.toBase58()}`;\n const cachedValue = await this.cache.get(cacheKey);\n\n if (cachedValue) {\n elizaLogger.log(\"Cache hit for fetchPortfolioValue\");\n return cachedValue;\n }\n elizaLogger.log(\"Cache miss for fetchPortfolioValue\");\n\n const query = `\n query Balances($walletId: String!, $cursor: String) {\n balances(input: { walletId: $walletId, cursor: $cursor }) {\n cursor\n items {\n walletId\n tokenId\n balance\n shiftedBalance\n }\n }\n }\n `;\n\n const variables = {\n walletId: `${this.walletPublicKey.toBase58()}:${1399811149}`,\n cursor: null,\n };\n\n const response = await fetch(PROVIDER_CONFIG.GRAPHQL_ENDPOINT, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization:\n runtime.getSetting(\"CODEX_API_KEY\", \"\") || \"\",\n },\n body: JSON.stringify({\n query,\n variables,\n }),\n }).then((res) => res.json());\n\n const data = response.data?.data?.balances?.items;\n\n if (!data || data.length === 0) {\n elizaLogger.error(\"No portfolio data available\", data);\n throw new Error(\"No portfolio data available\");\n }\n\n // Fetch token prices\n const prices = await this.fetchPrices(runtime);\n const solPriceInUSD = new BigNumber(prices.solana.usd.toString());\n\n // Reformat items\n const items: Item[] = data.map((item: any) => {\n return {\n name: \"Unknown\",\n address: item.tokenId.split(\":\")[0],\n symbol: item.tokenId.split(\":\")[0],\n decimals: 6,\n balance: item.balance,\n uiAmount: item.shiftedBalance.toString(),\n priceUsd: \"\",\n valueUsd: \"\",\n valueSol: \"\",\n };\n });\n\n // Calculate total portfolio value\n const totalUsd = items.reduce(\n (sum, item) => sum.plus(new BigNumber(item.valueUsd)),\n new BigNumber(0)\n );\n\n const totalSol = totalUsd.div(solPriceInUSD);\n\n const portfolio: WalletPortfolio = {\n totalUsd: totalUsd.toFixed(6),\n totalSol: totalSol.toFixed(6),\n items: items.sort((a, b) =>\n new BigNumber(b.valueUsd)\n .minus(new BigNumber(a.valueUsd))\n .toNumber()\n ),\n };\n\n // Cache the portfolio for future requests\n await this.cache.set(cacheKey, portfolio, 60 * 1000); // Cache for 1 minute\n\n return portfolio;\n } catch (error) {\n elizaLogger.error(\"Error fetching portfolio:\", error);\n throw error;\n }\n }\n\n async fetchPrices(runtime): Promise {\n try {\n const cacheKey = \"prices\";\n const cachedValue = this.cache.get(cacheKey);\n\n if (cachedValue) {\n elizaLogger.log(\"Cache hit for fetchPrices\");\n return cachedValue;\n }\n elizaLogger.log(\"Cache miss for fetchPrices\");\n\n const { SOL, BTC, ETH } = PROVIDER_CONFIG.TOKEN_ADDRESSES;\n const tokens = [SOL, BTC, ETH];\n const prices: Prices = {\n solana: { usd: \"0\" },\n bitcoin: { usd: \"0\" },\n ethereum: { usd: \"0\" },\n };\n\n for (const token of tokens) {\n const response = await this.fetchWithRetry(\n runtime,\n `${PROVIDER_CONFIG.BIRDEYE_API}/defi/price?address=${token}`,\n {\n headers: {\n \"x-chain\": \"solana\",\n },\n }\n );\n\n if (response?.data?.value) {\n const price = response.data.value.toString();\n prices[\n token === SOL\n ? \"solana\"\n : token === BTC\n ? \"bitcoin\"\n : \"ethereum\"\n ].usd = price;\n } else {\n elizaLogger.warn(\n `No price data available for token: ${token}`\n );\n }\n }\n\n this.cache.set(cacheKey, prices);\n return prices;\n } catch (error) {\n elizaLogger.error(\"Error fetching prices:\", error);\n throw error;\n }\n }\n\n formatPortfolio(\n runtime,\n portfolio: WalletPortfolio,\n prices: Prices\n ): string {\n let output = `${runtime.character.description}\\n`;\n output += `Wallet Address: ${this.walletPublicKey.toBase58()}\\n\\n`;\n\n const totalUsdFormatted = new BigNumber(portfolio.totalUsd).toFixed(2);\n const totalSolFormatted = portfolio.totalSol;\n\n output += `Total Value: $${totalUsdFormatted} (${totalSolFormatted} SOL)\\n\\n`;\n output += \"Token Balances:\\n\";\n\n const nonZeroItems = portfolio.items.filter((item) =>\n new BigNumber(item.uiAmount).isGreaterThan(0)\n );\n\n if (nonZeroItems.length === 0) {\n output += \"No tokens found with non-zero balance\\n\";\n } else {\n for (const item of nonZeroItems) {\n const valueUsd = new BigNumber(item.valueUsd).toFixed(2);\n output += `${item.name} (${item.symbol}): ${new BigNumber(\n item.uiAmount\n ).toFixed(6)} ($${valueUsd} | ${item.valueSol} SOL)\\n`;\n }\n }\n\n output += \"\\nMarket Prices:\\n\";\n output += `SOL: $${new BigNumber(prices.solana.usd).toFixed(2)}\\n`;\n output += `BTC: $${new BigNumber(prices.bitcoin.usd).toFixed(2)}\\n`;\n output += `ETH: $${new BigNumber(prices.ethereum.usd).toFixed(2)}\\n`;\n\n return output;\n }\n\n async getFormattedPortfolio(runtime): Promise {\n try {\n const [portfolio, prices] = await Promise.all([\n this.fetchPortfolioValue(runtime),\n this.fetchPrices(runtime),\n ]);\n\n return this.formatPortfolio(runtime, portfolio, prices);\n } catch (error) {\n elizaLogger.error(\"Error generating portfolio report:\", error);\n return \"Unable to fetch wallet information. Please try again later.\";\n }\n }\n\n private async getTokenAccounts(walletAddress: string) {\n try {\n const accounts =\n await this.connection.getParsedTokenAccountsByOwner(\n new PublicKey(walletAddress),\n {\n programId: new PublicKey(\n \"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA\"\n ),\n }\n );\n return accounts.value;\n } catch (error) {\n elizaLogger.error(\"Error fetching token accounts:\", error);\n return [];\n }\n }\n}\n\nconst walletProvider: Provider = {\n get: async (\n runtime: IAgentRuntime,\n _message: Memory,\n _state?: State\n ): Promise => {\n try {\n const { publicKey } = await getWalletKey(runtime, false);\n\n const connection = new Connection(\n runtime.getSetting(\"SOLANA_RPC_URL\") ||\n PROVIDER_CONFIG.DEFAULT_RPC\n );\n\n const provider = new WalletProvider(connection, publicKey);\n\n return await provider.getFormattedPortfolio(runtime);\n } catch (error) {\n elizaLogger.error(\"Error in wallet provider:\", error);\n return null;\n }\n },\n};\n\n// Module exports\nexport { walletProvider };\n","import { Keypair, PublicKey } from \"@solana/web3.js\";\nimport { DeriveKeyProvider, TEEMode } from \"@elizaos/plugin-tee\";\nimport bs58 from \"bs58\";\nimport { type IAgentRuntime, elizaLogger } from \"@elizaos/core\";\n\nexport interface KeypairResult {\n keypair?: Keypair;\n publicKey?: PublicKey;\n}\n\n/**\n * Gets either a keypair or public key based on TEE mode and runtime settings\n * @param runtime The agent runtime\n * @param requirePrivateKey Whether to return a full keypair (true) or just public key (false)\n * @returns KeypairResult containing either keypair or public key\n */\nexport async function getWalletKey(\n runtime: IAgentRuntime,\n requirePrivateKey = true\n): Promise {\n const teeMode = runtime.getSetting(\"TEE_MODE\") || TEEMode.OFF;\n\n if (teeMode !== TEEMode.OFF) {\n const walletSecretSalt = runtime.getSetting(\"WALLET_SECRET_SALT\");\n if (!walletSecretSalt) {\n throw new Error(\n \"WALLET_SECRET_SALT required when TEE_MODE is enabled\"\n );\n }\n\n const deriveKeyProvider = new DeriveKeyProvider(teeMode);\n const deriveKeyResult = await deriveKeyProvider.deriveEd25519Keypair(\n walletSecretSalt,\n \"solana\",\n runtime.agentId\n );\n\n return requirePrivateKey\n ? { keypair: deriveKeyResult.keypair }\n : { publicKey: deriveKeyResult.keypair.publicKey };\n }\n\n // TEE mode is OFF\n if (requirePrivateKey) {\n const privateKeyString =\n runtime.getSetting(\"SOLANA_PRIVATE_KEY\") ??\n runtime.getSetting(\"WALLET_PRIVATE_KEY\");\n\n if (!privateKeyString) {\n throw new Error(\"Private key not found in settings\");\n }\n\n try {\n // First try base58\n const secretKey = bs58.decode(privateKeyString);\n return { keypair: Keypair.fromSecretKey(secretKey) };\n } catch (e) {\n elizaLogger.log(\"Error decoding base58 private key:\", e);\n try {\n // Then try base64\n elizaLogger.log(\"Try decoding base64 instead\");\n const secretKey = Uint8Array.from(\n Buffer.from(privateKeyString, \"base64\")\n );\n return { keypair: Keypair.fromSecretKey(secretKey) };\n } catch (e2) {\n elizaLogger.error(\"Error decoding private key: \", e2);\n throw new Error(\"Invalid private key format\");\n }\n }\n } else {\n const publicKeyString =\n runtime.getSetting(\"SOLANA_PUBLIC_KEY\") ??\n runtime.getSetting(\"WALLET_PUBLIC_KEY\");\n\n if (!publicKeyString) {\n throw new Error(\"Public key not found in settings\");\n }\n\n return { publicKey: new PublicKey(publicKeyString) };\n }\n}\n","import {\n elizaLogger,\n type IAgentRuntime,\n type Memory,\n type Provider,\n settings,\n type State,\n} from \"@elizaos/core\";\nimport {\n type RecommenderMetrics,\n type TokenPerformance,\n type TokenRecommendation,\n type TradePerformance,\n TrustScoreDatabase,\n} from \"@elizaos/plugin-trustdb\";\nimport { getAssociatedTokenAddress } from \"@solana/spl-token\";\nimport { Connection, PublicKey } from \"@solana/web3.js\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport type { ProcessedTokenData, TokenSecurityData } from \"../types/token.ts\";\nimport { SimulationSellingService } from \"./simulationSellingService.ts\";\nimport type { TokenProvider } from \"./token.ts\";\nimport { WalletProvider } from \"./wallet.ts\";\n\nconst Wallet = settings.MAIN_WALLET_ADDRESS;\ninterface TradeData {\n buy_amount: number;\n is_simulation: boolean;\n}\ninterface sellDetails {\n sell_amount: number;\n sell_recommender_id: string | null;\n}\ninterface _RecommendationGroup {\n recommendation: any;\n trustScore: number;\n}\n\ninterface RecommenderData {\n recommenderId: string;\n trustScore: number;\n riskScore: number;\n consistencyScore: number;\n recommenderMetrics: RecommenderMetrics;\n}\n\ninterface TokenRecommendationSummary {\n tokenAddress: string;\n averageTrustScore: number;\n averageRiskScore: number;\n averageConsistencyScore: number;\n recommenders: RecommenderData[];\n}\nexport class TrustScoreManager {\n private tokenProvider: TokenProvider;\n private trustScoreDb: TrustScoreDatabase;\n private simulationSellingService: SimulationSellingService;\n private connection: Connection;\n private baseMint: PublicKey;\n private DECAY_RATE = 0.95;\n private MAX_DECAY_DAYS = 30;\n private backend;\n private backendToken;\n constructor(\n runtime: IAgentRuntime,\n tokenProvider: TokenProvider,\n trustScoreDb: TrustScoreDatabase\n ) {\n this.tokenProvider = tokenProvider;\n this.trustScoreDb = trustScoreDb;\n this.connection = new Connection(runtime.getSetting(\"SOLANA_RPC_URL\"));\n this.baseMint = new PublicKey(\n runtime.getSetting(\"BASE_MINT\") ||\n \"So11111111111111111111111111111111111111112\"\n );\n this.backend = runtime.getSetting(\"BACKEND_URL\");\n this.backendToken = runtime.getSetting(\"BACKEND_TOKEN\");\n this.simulationSellingService = new SimulationSellingService(\n runtime,\n this.trustScoreDb\n );\n }\n\n //getRecommenederBalance\n async getRecommenederBalance(recommenderWallet: string): Promise {\n try {\n const tokenAta = await getAssociatedTokenAddress(\n new PublicKey(recommenderWallet),\n this.baseMint\n );\n const tokenBalInfo =\n await this.connection.getTokenAccountBalance(tokenAta);\n const tokenBalance = tokenBalInfo.value.amount;\n const balance = Number.parseFloat(tokenBalance);\n return balance;\n } catch (error) {\n elizaLogger.error(\"Error fetching balance\", error);\n return 0;\n }\n }\n\n /**\n * Generates and saves trust score based on processed token data and user recommendations.\n * @param tokenAddress The address of the token to analyze.\n * @param recommenderId The UUID of the recommender.\n * @returns An object containing TokenPerformance and RecommenderMetrics.\n */\n async generateTrustScore(\n tokenAddress: string,\n recommenderId: string,\n recommenderWallet: string\n ): Promise<{\n tokenPerformance: TokenPerformance;\n recommenderMetrics: RecommenderMetrics;\n }> {\n const processedData: ProcessedTokenData =\n await this.tokenProvider.getProcessedTokenData();\n elizaLogger.log(\n `Fetched processed token data for token: ${tokenAddress}`\n );\n\n const recommenderMetrics =\n await this.trustScoreDb.getRecommenderMetrics(recommenderId);\n\n const isRapidDump = await this.isRapidDump(tokenAddress);\n const sustainedGrowth = await this.sustainedGrowth(tokenAddress);\n const suspiciousVolume = await this.suspiciousVolume(tokenAddress);\n const balance = await this.getRecommenederBalance(recommenderWallet);\n const virtualConfidence = balance / 1000000; // TODO: create formula to calculate virtual confidence based on user balance\n const lastActive = recommenderMetrics.lastActiveDate;\n const now = new Date();\n const inactiveDays = Math.floor(\n (now.getTime() - lastActive.getTime()) / (1000 * 60 * 60 * 24)\n );\n const decayFactor = Math.pow(\n this.DECAY_RATE,\n Math.min(inactiveDays, this.MAX_DECAY_DAYS)\n );\n const decayedScore = recommenderMetrics.trustScore * decayFactor;\n const validationTrustScore =\n this.trustScoreDb.calculateValidationTrust(tokenAddress);\n\n return {\n tokenPerformance: {\n tokenAddress:\n processedData.dexScreenerData.pairs[0]?.baseToken.address ||\n \"\",\n priceChange24h:\n processedData.tradeData.price_change_24h_percent,\n volumeChange24h: processedData.tradeData.volume_24h,\n trade_24h_change:\n processedData.tradeData.trade_24h_change_percent,\n liquidity:\n processedData.dexScreenerData.pairs[0]?.liquidity.usd || 0,\n liquidityChange24h: 0,\n holderChange24h:\n processedData.tradeData.unique_wallet_24h_change_percent,\n rugPull: false,\n isScam: processedData.tokenCodex.isScam,\n marketCapChange24h: 0,\n sustainedGrowth: sustainedGrowth,\n rapidDump: isRapidDump,\n suspiciousVolume: suspiciousVolume,\n validationTrust: validationTrustScore,\n balance: balance,\n initialMarketCap:\n processedData.dexScreenerData.pairs[0]?.marketCap || 0,\n lastUpdated: new Date(),\n symbol: \"\",\n },\n recommenderMetrics: {\n recommenderId: recommenderId,\n trustScore: recommenderMetrics.trustScore,\n totalRecommendations: recommenderMetrics.totalRecommendations,\n successfulRecs: recommenderMetrics.successfulRecs,\n avgTokenPerformance: recommenderMetrics.avgTokenPerformance,\n riskScore: recommenderMetrics.riskScore,\n consistencyScore: recommenderMetrics.consistencyScore,\n virtualConfidence: virtualConfidence,\n lastActiveDate: now,\n trustDecay: decayedScore,\n lastUpdated: new Date(),\n },\n };\n }\n\n async updateRecommenderMetrics(\n recommenderId: string,\n tokenPerformance: TokenPerformance,\n recommenderWallet: string\n ): Promise {\n const recommenderMetrics =\n await this.trustScoreDb.getRecommenderMetrics(recommenderId);\n\n const totalRecommendations =\n recommenderMetrics.totalRecommendations + 1;\n const successfulRecs = tokenPerformance.rugPull\n ? recommenderMetrics.successfulRecs\n : recommenderMetrics.successfulRecs + 1;\n const avgTokenPerformance =\n (recommenderMetrics.avgTokenPerformance *\n recommenderMetrics.totalRecommendations +\n tokenPerformance.priceChange24h) /\n totalRecommendations;\n\n const overallTrustScore = this.calculateTrustScore(\n tokenPerformance,\n recommenderMetrics\n );\n const riskScore = this.calculateOverallRiskScore(\n tokenPerformance,\n recommenderMetrics\n );\n const consistencyScore = this.calculateConsistencyScore(\n tokenPerformance,\n recommenderMetrics\n );\n\n const balance = await this.getRecommenederBalance(recommenderWallet);\n const virtualConfidence = balance / 1000000; // TODO: create formula to calculate virtual confidence based on user balance\n const lastActive = recommenderMetrics.lastActiveDate;\n const now = new Date();\n const inactiveDays = Math.floor(\n (now.getTime() - lastActive.getTime()) / (1000 * 60 * 60 * 24)\n );\n const decayFactor = Math.pow(\n this.DECAY_RATE,\n Math.min(inactiveDays, this.MAX_DECAY_DAYS)\n );\n const decayedScore = recommenderMetrics.trustScore * decayFactor;\n\n const newRecommenderMetrics: RecommenderMetrics = {\n recommenderId: recommenderId,\n trustScore: overallTrustScore,\n totalRecommendations: totalRecommendations,\n successfulRecs: successfulRecs,\n avgTokenPerformance: avgTokenPerformance,\n riskScore: riskScore,\n consistencyScore: consistencyScore,\n virtualConfidence: virtualConfidence,\n lastActiveDate: new Date(),\n trustDecay: decayedScore,\n lastUpdated: new Date(),\n };\n\n await this.trustScoreDb.updateRecommenderMetrics(newRecommenderMetrics);\n }\n\n calculateTrustScore(\n tokenPerformance: TokenPerformance,\n recommenderMetrics: RecommenderMetrics\n ): number {\n const riskScore = this.calculateRiskScore(tokenPerformance);\n const consistencyScore = this.calculateConsistencyScore(\n tokenPerformance,\n recommenderMetrics\n );\n\n return (riskScore + consistencyScore) / 2;\n }\n\n calculateOverallRiskScore(\n tokenPerformance: TokenPerformance,\n recommenderMetrics: RecommenderMetrics\n ) {\n const riskScore = this.calculateRiskScore(tokenPerformance);\n const consistencyScore = this.calculateConsistencyScore(\n tokenPerformance,\n recommenderMetrics\n );\n\n return (riskScore + consistencyScore) / 2;\n }\n\n calculateRiskScore(tokenPerformance: TokenPerformance): number {\n let riskScore = 0;\n if (tokenPerformance.rugPull) {\n riskScore += 10;\n }\n if (tokenPerformance.isScam) {\n riskScore += 10;\n }\n if (tokenPerformance.rapidDump) {\n riskScore += 5;\n }\n if (tokenPerformance.suspiciousVolume) {\n riskScore += 5;\n }\n return riskScore;\n }\n\n calculateConsistencyScore(\n tokenPerformance: TokenPerformance,\n recommenderMetrics: RecommenderMetrics\n ): number {\n const avgTokenPerformance = recommenderMetrics.avgTokenPerformance;\n const priceChange24h = tokenPerformance.priceChange24h;\n\n return Math.abs(priceChange24h - avgTokenPerformance);\n }\n\n async suspiciousVolume(tokenAddress: string): Promise {\n const processedData: ProcessedTokenData =\n await this.tokenProvider.getProcessedTokenData();\n const unique_wallet_24h = processedData.tradeData.unique_wallet_24h;\n const volume_24h = processedData.tradeData.volume_24h;\n const suspiciousVolume = unique_wallet_24h / volume_24h > 0.5;\n elizaLogger.log(\n `Fetched processed token data for token: ${tokenAddress}`\n );\n return suspiciousVolume;\n }\n\n async sustainedGrowth(tokenAddress: string): Promise {\n const processedData: ProcessedTokenData =\n await this.tokenProvider.getProcessedTokenData();\n elizaLogger.log(\n `Fetched processed token data for token: ${tokenAddress}`\n );\n\n return processedData.tradeData.volume_24h_change_percent > 50;\n }\n\n async isRapidDump(tokenAddress: string): Promise {\n const processedData: ProcessedTokenData =\n await this.tokenProvider.getProcessedTokenData();\n elizaLogger.log(\n `Fetched processed token data for token: ${tokenAddress}`\n );\n\n return processedData.tradeData.trade_24h_change_percent < -50;\n }\n\n async checkTrustScore(tokenAddress: string): Promise {\n const processedData: ProcessedTokenData =\n await this.tokenProvider.getProcessedTokenData();\n elizaLogger.log(\n `Fetched processed token data for token: ${tokenAddress}`\n );\n\n return {\n ownerBalance: processedData.security.ownerBalance,\n creatorBalance: processedData.security.creatorBalance,\n ownerPercentage: processedData.security.ownerPercentage,\n creatorPercentage: processedData.security.creatorPercentage,\n top10HolderBalance: processedData.security.top10HolderBalance,\n top10HolderPercent: processedData.security.top10HolderPercent,\n };\n }\n\n /**\n * Creates a TradePerformance object based on token data and recommender.\n * @param tokenAddress The address of the token.\n * @param recommenderId The UUID of the recommender.\n * @param data ProcessedTokenData.\n * @returns TradePerformance object.\n */\n async createTradePerformance(\n runtime: IAgentRuntime,\n tokenAddress: string,\n recommenderId: string,\n data: TradeData\n ): Promise {\n const recommender =\n await this.trustScoreDb.getOrCreateRecommenderWithTelegramId(\n recommenderId\n );\n const processedData: ProcessedTokenData =\n await this.tokenProvider.getProcessedTokenData();\n const wallet = new WalletProvider(\n this.connection,\n new PublicKey(Wallet!)\n );\n\n let tokensBalance = 0;\n const prices = await wallet.fetchPrices(runtime);\n const solPrice = prices.solana.usd;\n const buySol = data.buy_amount / Number.parseFloat(solPrice);\n const buy_value_usd = data.buy_amount * processedData.tradeData.price;\n const token = await this.tokenProvider.fetchTokenTradeData();\n const tokenCodex = await this.tokenProvider.fetchTokenCodex();\n const tokenPrice = token.price;\n tokensBalance = buy_value_usd / tokenPrice;\n\n const creationData = {\n token_address: tokenAddress,\n recommender_id: recommender.id,\n buy_price: processedData.tradeData.price,\n sell_price: 0,\n buy_timeStamp: new Date().toISOString(),\n sell_timeStamp: \"\",\n buy_amount: data.buy_amount,\n sell_amount: 0,\n buy_sol: buySol,\n received_sol: 0,\n buy_value_usd: buy_value_usd,\n sell_value_usd: 0,\n profit_usd: 0,\n profit_percent: 0,\n buy_market_cap:\n processedData.dexScreenerData.pairs[0]?.marketCap || 0,\n sell_market_cap: 0,\n market_cap_change: 0,\n buy_liquidity:\n processedData.dexScreenerData.pairs[0]?.liquidity.usd || 0,\n sell_liquidity: 0,\n liquidity_change: 0,\n last_updated: new Date().toISOString(),\n rapidDump: false,\n };\n this.trustScoreDb.addTradePerformance(creationData, data.is_simulation);\n // generate unique uuid for each TokenRecommendation\n const tokenUUId = uuidv4();\n const tokenRecommendation: TokenRecommendation = {\n id: tokenUUId,\n recommenderId: recommenderId,\n tokenAddress: tokenAddress,\n timestamp: new Date(),\n initialMarketCap:\n processedData.dexScreenerData.pairs[0]?.marketCap || 0,\n initialLiquidity:\n processedData.dexScreenerData.pairs[0]?.liquidity?.usd || 0,\n initialPrice: processedData.tradeData.price,\n };\n this.trustScoreDb.addTokenRecommendation(tokenRecommendation);\n\n this.trustScoreDb.upsertTokenPerformance({\n tokenAddress: tokenAddress,\n symbol: processedData.tokenCodex.symbol,\n priceChange24h: processedData.tradeData.price_change_24h_percent,\n volumeChange24h: processedData.tradeData.volume_24h,\n trade_24h_change: processedData.tradeData.trade_24h_change_percent,\n liquidity:\n processedData.dexScreenerData.pairs[0]?.liquidity.usd || 0,\n liquidityChange24h: 0,\n holderChange24h:\n processedData.tradeData.unique_wallet_24h_change_percent,\n rugPull: false,\n isScam: tokenCodex.isScam,\n marketCapChange24h: 0,\n sustainedGrowth: false,\n rapidDump: false,\n suspiciousVolume: false,\n validationTrust: 0,\n balance: tokensBalance,\n initialMarketCap:\n processedData.dexScreenerData.pairs[0]?.marketCap || 0,\n lastUpdated: new Date(),\n });\n\n if (data.is_simulation) {\n // If the trade is a simulation update the balance\n this.trustScoreDb.updateTokenBalance(tokenAddress, tokensBalance);\n // generate some random hash for simulations\n const hash = Math.random().toString(36).substring(7);\n const transaction = {\n tokenAddress: tokenAddress,\n type: \"buy\" as \"buy\" | \"sell\",\n transactionHash: hash,\n amount: data.buy_amount,\n price: processedData.tradeData.price,\n isSimulation: true,\n timestamp: new Date().toISOString(),\n };\n this.trustScoreDb.addTransaction(transaction);\n }\n this.simulationSellingService.processTokenPerformance(\n tokenAddress,\n recommenderId\n );\n // api call to update trade performance\n this.createTradeInBe(tokenAddress, recommenderId, data);\n return creationData;\n }\n\n async delay(ms: number) {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n async createTradeInBe(\n tokenAddress: string,\n recommenderId: string,\n data: TradeData,\n retries = 3,\n delayMs = 2000\n ) {\n for (let attempt = 1; attempt <= retries; attempt++) {\n try {\n await fetch(\n `${this.backend}/api/updaters/createTradePerformance`,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.backendToken}`,\n },\n body: JSON.stringify({\n tokenAddress: tokenAddress,\n tradeData: data,\n recommenderId: recommenderId,\n }),\n }\n );\n // If the request is successful, exit the loop\n return;\n } catch (error) {\n elizaLogger.error(\n `Attempt ${attempt} failed: Error creating trade in backend`,\n error\n );\n if (attempt < retries) {\n elizaLogger.log(`Retrying in ${delayMs} ms...`);\n await this.delay(delayMs); // Wait for the specified delay before retrying\n } else {\n elizaLogger.error(\"All attempts failed.\");\n }\n }\n }\n }\n\n /**\n * Updates a trade with sell details.\n * @param tokenAddress The address of the token.\n * @param recommenderId The UUID of the recommender.\n * @param buyTimeStamp The timestamp when the buy occurred.\n * @param sellDetails An object containing sell-related details.\n * @param isSimulation Whether the trade is a simulation. If true, updates in simulation_trade; otherwise, in trade.\n * @returns boolean indicating success.\n */\n\n async updateSellDetails(\n runtime: IAgentRuntime,\n tokenAddress: string,\n recommenderId: string,\n sellTimeStamp: string,\n sellDetails: sellDetails,\n isSimulation: boolean\n ) {\n const recommender =\n await this.trustScoreDb.getOrCreateRecommenderWithTelegramId(\n recommenderId\n );\n const processedData: ProcessedTokenData =\n await this.tokenProvider.getProcessedTokenData();\n const wallet = new WalletProvider(\n this.connection,\n new PublicKey(Wallet!)\n );\n const prices = await wallet.fetchPrices(runtime);\n const solPrice = prices.solana.usd;\n const sellSol = sellDetails.sell_amount / Number.parseFloat(solPrice);\n const sell_value_usd =\n sellDetails.sell_amount * processedData.tradeData.price;\n const trade = await this.trustScoreDb.getLatestTradePerformance(\n tokenAddress,\n recommender.id,\n isSimulation\n );\n const buyTimeStamp = trade.buy_timeStamp;\n const marketCap =\n processedData.dexScreenerData.pairs[0]?.marketCap || 0;\n const liquidity =\n processedData.dexScreenerData.pairs[0]?.liquidity.usd || 0;\n const sell_price = processedData.tradeData.price;\n const profit_usd = sell_value_usd - trade.buy_value_usd;\n const profit_percent = (profit_usd / trade.buy_value_usd) * 100;\n\n const market_cap_change = marketCap - trade.buy_market_cap;\n const liquidity_change = liquidity - trade.buy_liquidity;\n\n const isRapidDump = await this.isRapidDump(tokenAddress);\n\n const sellDetailsData = {\n sell_price: sell_price,\n sell_timeStamp: sellTimeStamp,\n sell_amount: sellDetails.sell_amount,\n received_sol: sellSol,\n sell_value_usd: sell_value_usd,\n profit_usd: profit_usd,\n profit_percent: profit_percent,\n sell_market_cap: marketCap,\n market_cap_change: market_cap_change,\n sell_liquidity: liquidity,\n liquidity_change: liquidity_change,\n rapidDump: isRapidDump,\n sell_recommender_id: sellDetails.sell_recommender_id || null,\n };\n this.trustScoreDb.updateTradePerformanceOnSell(\n tokenAddress,\n recommender.id,\n buyTimeStamp,\n sellDetailsData,\n isSimulation\n );\n if (isSimulation) {\n // If the trade is a simulation update the balance\n const oldBalance = this.trustScoreDb.getTokenBalance(tokenAddress);\n const tokenBalance = oldBalance - sellDetails.sell_amount;\n this.trustScoreDb.updateTokenBalance(tokenAddress, tokenBalance);\n // generate some random hash for simulations\n const hash = Math.random().toString(36).substring(7);\n const transaction = {\n tokenAddress: tokenAddress,\n type: \"sell\" as \"buy\" | \"sell\",\n transactionHash: hash,\n amount: sellDetails.sell_amount,\n price: processedData.tradeData.price,\n isSimulation: true,\n timestamp: new Date().toISOString(),\n };\n this.trustScoreDb.addTransaction(transaction);\n }\n\n return sellDetailsData;\n }\n\n // get all recommendations\n async getRecommendations(\n startDate: Date,\n endDate: Date\n ): Promise> {\n const recommendations = this.trustScoreDb.getRecommendationsByDateRange(\n startDate,\n endDate\n );\n\n // Group recommendations by tokenAddress\n const groupedRecommendations = recommendations.reduce(\n (acc, recommendation) => {\n const { tokenAddress } = recommendation;\n if (!acc[tokenAddress]) acc[tokenAddress] = [];\n acc[tokenAddress].push(recommendation);\n return acc;\n },\n {} as Record>\n );\n\n const result = Object.keys(groupedRecommendations).map(\n (tokenAddress) => {\n const tokenRecommendations =\n groupedRecommendations[tokenAddress];\n\n // Initialize variables to compute averages\n let totalTrustScore = 0;\n let totalRiskScore = 0;\n let totalConsistencyScore = 0;\n const recommenderData = [];\n\n tokenRecommendations.forEach((recommendation) => {\n const tokenPerformance =\n this.trustScoreDb.getTokenPerformance(\n recommendation.tokenAddress\n );\n const recommenderMetrics =\n this.trustScoreDb.getRecommenderMetrics(\n recommendation.recommenderId\n );\n\n const trustScore = this.calculateTrustScore(\n tokenPerformance,\n recommenderMetrics\n );\n const consistencyScore = this.calculateConsistencyScore(\n tokenPerformance,\n recommenderMetrics\n );\n const riskScore = this.calculateRiskScore(tokenPerformance);\n\n // Accumulate scores for averaging\n totalTrustScore += trustScore;\n totalRiskScore += riskScore;\n totalConsistencyScore += consistencyScore;\n\n recommenderData.push({\n recommenderId: recommendation.recommenderId,\n trustScore,\n riskScore,\n consistencyScore,\n recommenderMetrics,\n });\n });\n\n // Calculate averages for this token\n const averageTrustScore =\n totalTrustScore / tokenRecommendations.length;\n const averageRiskScore =\n totalRiskScore / tokenRecommendations.length;\n const averageConsistencyScore =\n totalConsistencyScore / tokenRecommendations.length;\n\n return {\n tokenAddress,\n averageTrustScore,\n averageRiskScore,\n averageConsistencyScore,\n recommenders: recommenderData,\n };\n }\n );\n\n // Sort recommendations by the highest average trust score\n result.sort((a, b) => b.averageTrustScore - a.averageTrustScore);\n\n return result;\n }\n}\n\nexport const trustScoreProvider: Provider = {\n async get(\n runtime: IAgentRuntime,\n message: Memory,\n _state?: State\n ): Promise {\n try {\n // if the database type is postgres, we don't want to run this evaluator because it relies on sql queries that are currently specific to sqlite. This check can be removed once the trust score provider is updated to work with postgres.\n if (runtime.getSetting(\"POSTGRES_URL\")) {\n elizaLogger.warn(\n \"skipping trust evaluator because db is postgres\"\n );\n return \"\";\n }\n\n const trustScoreDb = new TrustScoreDatabase(\n runtime.databaseAdapter.db\n );\n\n // Get the user ID from the message\n const userId = message.userId;\n\n if (!userId) {\n elizaLogger.error(\"User ID is missing from the message\");\n return \"\";\n }\n\n // Get the recommender metrics for the user\n const recommenderMetrics =\n await trustScoreDb.getRecommenderMetrics(userId);\n\n if (!recommenderMetrics) {\n elizaLogger.error(\n \"No recommender metrics found for user:\",\n userId\n );\n return \"\";\n }\n\n // Compute the trust score\n const trustScore = recommenderMetrics.trustScore;\n\n const user = await runtime.databaseAdapter.getAccountById(userId);\n\n // Format the trust score string\n const trustScoreString = `${user.name}'s trust score: ${trustScore.toFixed(2)}`;\n\n return trustScoreString;\n } catch (error) {\n elizaLogger.error(\"Error in trust score provider:\", error.message);\n return `Failed to fetch trust score: ${error instanceof Error ? error.message : \"Unknown error\"}`;\n }\n },\n};\n","import validate from './validate.js';\n\n/**\n * Convert array of 16 byte values to UUID string format of the form:\n * XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX\n */\nconst byteToHex = [];\nfor (let i = 0; i < 256; ++i) {\n byteToHex.push((i + 0x100).toString(16).slice(1));\n}\nexport function unsafeStringify(arr, offset = 0) {\n // Note: Be careful editing this code! It's been tuned for performance\n // and works in ways you may not expect. See https://github.com/uuidjs/uuid/pull/434\n //\n // Note to future-self: No, you can't remove the `toLowerCase()` call.\n // REF: https://github.com/uuidjs/uuid/pull/677#issuecomment-1757351351\n return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + '-' + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + '-' + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + '-' + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + '-' + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();\n}\nfunction stringify(arr, offset = 0) {\n const uuid = unsafeStringify(arr, offset);\n // Consistency check for valid UUID. If this throws, it's likely due to one\n // of the following:\n // - One or more input array values don't map to a hex octet (leading to\n // \"undefined\" in the uuid)\n // - Invalid input values for the RFC `version` or `variant` fields\n if (!validate(uuid)) {\n throw TypeError('Stringified UUID is invalid');\n }\n return uuid;\n}\nexport default stringify;","import crypto from 'node:crypto';\nconst rnds8Pool = new Uint8Array(256); // # of random values to pre-allocate\nlet poolPtr = rnds8Pool.length;\nexport default function rng() {\n if (poolPtr > rnds8Pool.length - 16) {\n crypto.randomFillSync(rnds8Pool);\n poolPtr = 0;\n }\n return rnds8Pool.slice(poolPtr, poolPtr += 16);\n}","import crypto from 'node:crypto';\nexport default {\n randomUUID: crypto.randomUUID\n};","import native from './native.js';\nimport rng from './rng.js';\nimport { unsafeStringify } from './stringify.js';\nfunction v4(options, buf, offset) {\n if (native.randomUUID && !buf && !options) {\n return native.randomUUID();\n }\n options = options || {};\n const rnds = options.random || (options.rng || rng)();\n\n // Per 4.4, set bits for version and `clock_seq_hi_and_reserved`\n rnds[6] = rnds[6] & 0x0f | 0x40;\n rnds[8] = rnds[8] & 0x3f | 0x80;\n\n // Copy bytes to buffer, if provided\n if (buf) {\n offset = offset || 0;\n for (let i = 0; i < 16; ++i) {\n buf[offset + i] = rnds[i];\n }\n return buf;\n }\n return unsafeStringify(rnds);\n}\nexport default v4;","import type {\n TrustScoreDatabase,\n TokenPerformance,\n // TradePerformance,\n TokenRecommendation,\n} from \"@elizaos/plugin-trustdb\";\nimport { Connection, PublicKey } from \"@solana/web3.js\";\n// Assuming TokenProvider and IAgentRuntime are available\nimport { TokenProvider } from \"./token.ts\";\n// import { settings } from \"@elizaos/core\";\nimport { type IAgentRuntime, elizaLogger } from \"@elizaos/core\";\nimport { WalletProvider } from \"./wallet.ts\";\nimport * as amqp from \"amqplib\";\nimport type { ProcessedTokenData } from \"../types/token.ts\";\nimport { getWalletKey } from \"../keypairUtils.ts\";\n\ninterface SellDetails {\n sell_amount: number;\n sell_recommender_id: string | null;\n}\n\nexport class SimulationSellingService {\n private trustScoreDb: TrustScoreDatabase;\n private walletProvider: WalletProvider;\n private connection: Connection;\n private baseMint: PublicKey;\n private DECAY_RATE = 0.95;\n private MAX_DECAY_DAYS = 30;\n private backend: string;\n private backendToken: string;\n private amqpConnection: amqp.Connection;\n private amqpChannel: amqp.Channel;\n private sonarBe: string;\n private sonarBeToken: string;\n private runtime: IAgentRuntime;\n\n private runningProcesses: Set = new Set();\n\n constructor(runtime: IAgentRuntime, trustScoreDb: TrustScoreDatabase) {\n this.trustScoreDb = trustScoreDb;\n\n this.connection = new Connection(runtime.getSetting(\"SOLANA_RPC_URL\"));\n this.baseMint = new PublicKey(\n runtime.getSetting(\"BASE_MINT\") ||\n \"So11111111111111111111111111111111111111112\"\n );\n this.backend = runtime.getSetting(\"BACKEND_URL\");\n this.backendToken = runtime.getSetting(\"BACKEND_TOKEN\");\n this.initializeRabbitMQ(runtime.getSetting(\"AMQP_URL\"));\n this.sonarBe = runtime.getSetting(\"SONAR_BE\");\n this.sonarBeToken = runtime.getSetting(\"SONAR_BE_TOKEN\");\n this.runtime = runtime;\n this.initializeWalletProvider();\n }\n /**\n * Initializes the RabbitMQ connection and starts consuming messages.\n * @param amqpUrl The RabbitMQ server URL.\n */\n private async initializeRabbitMQ(amqpUrl: string) {\n try {\n this.amqpConnection = await amqp.connect(amqpUrl);\n this.amqpChannel = await this.amqpConnection.createChannel();\n elizaLogger.log(\"Connected to RabbitMQ\");\n // Start consuming messages\n this.consumeMessages();\n } catch (error) {\n elizaLogger.error(\"Failed to connect to RabbitMQ:\", error);\n }\n }\n\n /**\n * Sets up the consumer for the specified RabbitMQ queue.\n */\n private async consumeMessages() {\n const queue = \"process_eliza_simulation\";\n await this.amqpChannel.assertQueue(queue, { durable: true });\n this.amqpChannel.consume(\n queue,\n (msg) => {\n if (msg !== null) {\n const content = msg.content.toString();\n this.processMessage(content);\n this.amqpChannel.ack(msg);\n }\n },\n { noAck: false }\n );\n elizaLogger.log(`Listening for messages on queue: ${queue}`);\n }\n\n /**\n * Processes incoming messages from RabbitMQ.\n * @param message The message content as a string.\n */\n private async processMessage(message: string) {\n try {\n const { tokenAddress, amount, sell_recommender_id } =\n JSON.parse(message);\n elizaLogger.log(\n `Received message for token ${tokenAddress} to sell ${amount}`\n );\n\n const decision: SellDecision = {\n tokenPerformance:\n await this.trustScoreDb.getTokenPerformance(tokenAddress),\n amountToSell: amount,\n sell_recommender_id: sell_recommender_id,\n };\n\n // Execute the sell\n await this.executeSellDecision(decision);\n\n // Remove from running processes after completion\n this.runningProcesses.delete(tokenAddress);\n } catch (error) {\n elizaLogger.error(\"Error processing message:\", error);\n }\n }\n\n /**\n * Executes a single sell decision.\n * @param decision The sell decision containing token performance and amount to sell.\n */\n private async executeSellDecision(decision: SellDecision) {\n const { tokenPerformance, amountToSell, sell_recommender_id } =\n decision;\n const tokenAddress = tokenPerformance.tokenAddress;\n\n try {\n elizaLogger.log(\n `Executing sell for token ${tokenPerformance.symbol}: ${amountToSell}`\n );\n\n // Update the sell details\n const sellDetails: SellDetails = {\n sell_amount: amountToSell,\n sell_recommender_id: sell_recommender_id, // Adjust if necessary\n };\n const sellTimeStamp = new Date().toISOString();\n const tokenProvider = new TokenProvider(\n tokenAddress,\n this.walletProvider,\n this.runtime.cacheManager\n );\n\n // Update sell details in the database\n const sellDetailsData = await this.updateSellDetails(\n tokenAddress,\n sell_recommender_id,\n sellTimeStamp,\n sellDetails,\n true, // isSimulation\n tokenProvider\n );\n\n elizaLogger.log(\n \"Sell order executed successfully\",\n sellDetailsData\n );\n\n // check if balance is zero and remove token from running processes\n const balance = this.trustScoreDb.getTokenBalance(tokenAddress);\n if (balance === 0) {\n this.runningProcesses.delete(tokenAddress);\n }\n // stop the process in the sonar backend\n await this.stopProcessInTheSonarBackend(tokenAddress);\n } catch (error) {\n elizaLogger.error(\n `Error executing sell for token ${tokenAddress}:`,\n error\n );\n }\n }\n\n /**\n * Derives the public key based on the TEE (Trusted Execution Environment) mode and initializes the wallet provider.\n * If TEE mode is enabled, derives a keypair using the DeriveKeyProvider with the wallet secret salt and agent ID.\n * If TEE mode is disabled, uses the provided Solana public key or wallet public key from settings.\n */\n private async initializeWalletProvider(): Promise {\n const { publicKey } = await getWalletKey(this.runtime, false);\n\n this.walletProvider = new WalletProvider(this.connection, publicKey);\n }\n\n public async startService() {\n // starting the service\n elizaLogger.log(\"Starting SellingService...\");\n await this.startListeners();\n }\n\n public async startListeners() {\n // scanning recommendations and selling\n elizaLogger.log(\"Scanning for token performances...\");\n const tokenPerformances =\n await this.trustScoreDb.getAllTokenPerformancesWithBalance();\n\n await this.processTokenPerformances(tokenPerformances);\n }\n\n private processTokenPerformances(tokenPerformances: TokenPerformance[]) {\n // To Do: logic when to sell and how much\n elizaLogger.log(\"Deciding when to sell and how much...\");\n const runningProcesses = this.runningProcesses;\n // remove running processes from tokenPerformances\n tokenPerformances = tokenPerformances.filter(\n (tp) => !runningProcesses.has(tp.tokenAddress)\n );\n\n // start the process in the sonar backend\n tokenPerformances.forEach(async (tokenPerformance) => {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const tokenProvider = new TokenProvider(\n tokenPerformance.tokenAddress,\n this.walletProvider,\n this.runtime.cacheManager\n );\n // const shouldTrade = await tokenProvider.shouldTradeToken();\n // if (shouldTrade) {\n const tokenRecommendations: TokenRecommendation[] =\n this.trustScoreDb.getRecommendationsByToken(\n tokenPerformance.tokenAddress\n );\n const tokenRecommendation: TokenRecommendation =\n tokenRecommendations[0];\n const balance = tokenPerformance.balance;\n const sell_recommender_id = tokenRecommendation.recommenderId;\n const tokenAddress = tokenPerformance.tokenAddress;\n const process = await this.startProcessInTheSonarBackend(\n tokenAddress,\n balance,\n true,\n sell_recommender_id,\n tokenPerformance.initialMarketCap\n );\n if (process) {\n this.runningProcesses.add(tokenAddress);\n }\n // }\n });\n }\n\n public processTokenPerformance(\n tokenAddress: string,\n recommenderId: string\n ) {\n try {\n const runningProcesses = this.runningProcesses;\n // check if token is already being processed\n if (runningProcesses.has(tokenAddress)) {\n elizaLogger.log(\n `Token ${tokenAddress} is already being processed`\n );\n return;\n }\n const tokenPerformance =\n this.trustScoreDb.getTokenPerformance(tokenAddress);\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const tokenProvider = new TokenProvider(\n tokenPerformance.tokenAddress,\n this.walletProvider,\n this.runtime.cacheManager\n );\n const balance = tokenPerformance.balance;\n const sell_recommender_id = recommenderId;\n const process = this.startProcessInTheSonarBackend(\n tokenAddress,\n balance,\n true,\n sell_recommender_id,\n tokenPerformance.initialMarketCap\n );\n if (process) {\n this.runningProcesses.add(tokenAddress);\n }\n } catch (error) {\n elizaLogger.error(\n `Error getting token performance for token ${tokenAddress}:`,\n error\n );\n }\n }\n\n private async startProcessInTheSonarBackend(\n tokenAddress: string,\n balance: number,\n isSimulation: boolean,\n sell_recommender_id: string,\n initial_mc: number\n ) {\n try {\n const message = JSON.stringify({\n tokenAddress,\n balance,\n isSimulation,\n initial_mc,\n sell_recommender_id,\n });\n const response = await fetch(\n `${this.sonarBe}/elizaos-sol/startProcess`,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": `${this.sonarBeToken}`,\n },\n body: message,\n }\n );\n\n if (!response.ok) {\n elizaLogger.error(\n `Failed to send message to process token ${tokenAddress}`\n );\n return;\n }\n\n const result = await response.json();\n elizaLogger.log(\"Received response:\", result);\n elizaLogger.log(`Sent message to process token ${tokenAddress}`);\n\n return result;\n } catch (error) {\n elizaLogger.error(\n `Error sending message to process token ${tokenAddress}:`,\n error\n );\n return null;\n }\n }\n\n private stopProcessInTheSonarBackend(tokenAddress: string) {\n try {\n return fetch(`${this.sonarBe}/elizaos-sol/stopProcess`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": `${this.sonarBeToken}`,\n },\n body: JSON.stringify({ tokenAddress }),\n });\n } catch (error) {\n elizaLogger.error(\n `Error stopping process for token ${tokenAddress}:`,\n error\n );\n }\n }\n\n async updateSellDetails(\n tokenAddress: string,\n recommenderId: string,\n sellTimeStamp: string,\n sellDetails: SellDetails,\n isSimulation: boolean,\n tokenProvider: TokenProvider\n ) {\n const recommender =\n await this.trustScoreDb.getOrCreateRecommenderWithTelegramId(\n recommenderId\n );\n const processedData: ProcessedTokenData =\n await tokenProvider.getProcessedTokenData();\n const prices = await this.walletProvider.fetchPrices(null);\n const solPrice = prices.solana.usd;\n const sellSol = sellDetails.sell_amount / Number.parseFloat(solPrice);\n const sell_value_usd =\n sellDetails.sell_amount * processedData.tradeData.price;\n const trade = await this.trustScoreDb.getLatestTradePerformance(\n tokenAddress,\n recommender.id,\n isSimulation\n );\n const buyTimeStamp = trade.buy_timeStamp;\n const marketCap =\n processedData.dexScreenerData.pairs[0]?.marketCap || 0;\n const liquidity =\n processedData.dexScreenerData.pairs[0]?.liquidity.usd || 0;\n const sell_price = processedData.tradeData.price;\n const profit_usd = sell_value_usd - trade.buy_value_usd;\n const profit_percent = (profit_usd / trade.buy_value_usd) * 100;\n\n const market_cap_change = marketCap - trade.buy_market_cap;\n const liquidity_change = liquidity - trade.buy_liquidity;\n\n const isRapidDump = await this.isRapidDump(tokenAddress, tokenProvider);\n\n const sellDetailsData = {\n sell_price: sell_price,\n sell_timeStamp: sellTimeStamp,\n sell_amount: sellDetails.sell_amount,\n received_sol: sellSol,\n sell_value_usd: sell_value_usd,\n profit_usd: profit_usd,\n profit_percent: profit_percent,\n sell_market_cap: marketCap,\n market_cap_change: market_cap_change,\n sell_liquidity: liquidity,\n liquidity_change: liquidity_change,\n rapidDump: isRapidDump,\n sell_recommender_id: sellDetails.sell_recommender_id || null,\n };\n this.trustScoreDb.updateTradePerformanceOnSell(\n tokenAddress,\n recommender.id,\n buyTimeStamp,\n sellDetailsData,\n isSimulation\n );\n\n // If the trade is a simulation update the balance\n const oldBalance = this.trustScoreDb.getTokenBalance(tokenAddress);\n const tokenBalance = oldBalance - sellDetails.sell_amount;\n this.trustScoreDb.updateTokenBalance(tokenAddress, tokenBalance);\n // generate some random hash for simulations\n const hash = Math.random().toString(36).substring(7);\n const transaction = {\n tokenAddress: tokenAddress,\n type: \"sell\" as \"buy\" | \"sell\",\n transactionHash: hash,\n amount: sellDetails.sell_amount,\n price: processedData.tradeData.price,\n isSimulation: true,\n timestamp: new Date().toISOString(),\n };\n this.trustScoreDb.addTransaction(transaction);\n this.updateTradeInBe(\n tokenAddress,\n recommender.id,\n recommender.telegramId,\n sellDetailsData,\n tokenBalance\n );\n\n return sellDetailsData;\n }\n async isRapidDump(\n tokenAddress: string,\n tokenProvider: TokenProvider\n ): Promise {\n const processedData: ProcessedTokenData =\n await tokenProvider.getProcessedTokenData();\n elizaLogger.log(\n `Fetched processed token data for token: ${tokenAddress}`\n );\n\n return processedData.tradeData.trade_24h_change_percent < -50;\n }\n\n async delay(ms: number) {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n async updateTradeInBe(\n tokenAddress: string,\n recommenderId: string,\n username: string,\n data: SellDetails,\n balanceLeft: number,\n retries = 3,\n delayMs = 2000\n ) {\n for (let attempt = 1; attempt <= retries; attempt++) {\n try {\n await fetch(\n `${this.backend}/api/updaters/updateTradePerformance`,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.backendToken}`,\n },\n body: JSON.stringify({\n tokenAddress: tokenAddress,\n tradeData: data,\n recommenderId: recommenderId,\n username: username,\n isSimulation: true,\n balanceLeft: balanceLeft,\n }),\n }\n );\n // If the request is successful, exit the loop\n return;\n } catch (error) {\n elizaLogger.error(\n `Attempt ${attempt} failed: Error creating trade in backend`,\n error\n );\n if (attempt < retries) {\n elizaLogger.log(`Retrying in ${delayMs} ms...`);\n await this.delay(delayMs); // Wait for the specified delay before retrying\n } else {\n elizaLogger.error(\"All attempts failed.\");\n }\n }\n }\n }\n}\n\n// SellDecision interface\ninterface SellDecision {\n tokenPerformance: TokenPerformance;\n amountToSell: number;\n sell_recommender_id: string | null;\n}\n","import {\n type ActionExample,\n booleanFooter,\n composeContext,\n type Content,\n elizaLogger,\n type Evaluator,\n generateObjectArray,\n generateTrueOrFalse,\n type IAgentRuntime,\n type Memory,\n MemoryManager,\n ModelClass,\n} from \"@elizaos/core\";\nimport { TrustScoreDatabase } from \"@elizaos/plugin-trustdb\";\nimport { Connection } from \"@solana/web3.js\";\nimport { getWalletKey } from \"../keypairUtils.ts\";\nimport { TokenProvider } from \"../providers/token.ts\";\nimport { TrustScoreManager } from \"../providers/trustScoreProvider.ts\";\nimport { WalletProvider } from \"../providers/wallet.ts\";\n\nconst shouldProcessTemplate =\n `# Task: Decide if the recent messages should be processed for token recommendations.\n\n Look for messages that:\n - Mention specific token tickers or contract addresses\n - Contain words related to buying, selling, or trading tokens\n - Express opinions or convictions about tokens\n\n Based on the following conversation, should the messages be processed for recommendations? YES or NO\n\n {{recentMessages}}\n\n Should the messages be processed for recommendations? ` + booleanFooter;\n\nexport const formatRecommendations = (recommendations: Memory[]) => {\n const messageStrings = recommendations\n .reverse()\n .map((rec: Memory) => `${(rec.content as Content)?.content}`);\n const finalMessageStrings = messageStrings.join(\"\\n\");\n return finalMessageStrings;\n};\n\nconst recommendationTemplate = `TASK: Extract recommendations to buy or sell memecoins from the conversation as an array of objects in JSON format.\n\n Memecoins usually have a ticker and a contract address. Additionally, recommenders may make recommendations with some amount of conviction. The amount of conviction in their recommendation can be none, low, medium, or high. Recommenders can make recommendations to buy, not buy, sell and not sell.\n\n# START OF EXAMPLES\nThese are an examples of the expected output of this task:\n{{evaluationExamples}}\n# END OF EXAMPLES\n\n# INSTRUCTIONS\n\nExtract any new recommendations from the conversation that are not already present in the list of known recommendations below:\n{{recentRecommendations}}\n\n- Include the recommender's username\n- Try not to include already-known recommendations. If you think a recommendation is already known, but you're not sure, respond with alreadyKnown: true.\n- Set the conviction to 'none', 'low', 'medium' or 'high'\n- Set the recommendation type to 'buy', 'dont_buy', 'sell', or 'dont_sell'\n- Include the contract address and/or ticker if available\n\nRecent Messages:\n{{recentMessages}}\n\nResponse should be a JSON object array inside a JSON markdown block. Correct response format:\n\\`\\`\\`json\n[\n {\n \"recommender\": string,\n \"ticker\": string | null,\n \"contractAddress\": string | null,\n \"type\": enum,\n \"conviction\": enum,\n \"alreadyKnown\": boolean\n },\n ...\n]\n\\`\\`\\``;\n\nasync function handler(runtime: IAgentRuntime, message: Memory) {\n elizaLogger.log(\"Evaluating for trust\");\n const state = await runtime.composeState(message);\n\n // if the database type is postgres, we don't want to run this because it relies on sql queries that are currently specific to sqlite. This check can be removed once the trust score provider is updated to work with postgres.\n if (runtime.getSetting(\"POSTGRES_URL\")) {\n elizaLogger.warn(\"skipping trust evaluator because db is postgres\");\n return [];\n }\n\n const { agentId, roomId } = state;\n\n // Check if we should process the messages\n const shouldProcessContext = composeContext({\n state,\n template: shouldProcessTemplate,\n });\n\n const shouldProcess = await generateTrueOrFalse({\n context: shouldProcessContext,\n modelClass: ModelClass.SMALL,\n runtime,\n });\n\n if (!shouldProcess) {\n elizaLogger.log(\"Skipping process\");\n return [];\n }\n\n elizaLogger.log(\"Processing recommendations\");\n\n // Get recent recommendations\n const recommendationsManager = new MemoryManager({\n runtime,\n tableName: \"recommendations\",\n });\n\n const recentRecommendations = await recommendationsManager.getMemories({\n roomId,\n count: 20,\n });\n\n const context = composeContext({\n state: {\n ...state,\n recentRecommendations: formatRecommendations(recentRecommendations),\n },\n template: recommendationTemplate,\n });\n\n const recommendations = await generateObjectArray({\n runtime,\n context,\n modelClass: ModelClass.LARGE,\n });\n\n elizaLogger.log(\"recommendations\", recommendations);\n\n if (!recommendations) {\n return [];\n }\n\n // If the recommendation is already known or corrupted, remove it\n const filteredRecommendations = recommendations.filter((rec) => {\n return (\n !rec.alreadyKnown &&\n (rec.ticker || rec.contractAddress) &&\n rec.recommender &&\n rec.conviction &&\n rec.recommender.trim() !== \"\"\n );\n });\n\n const { publicKey } = await getWalletKey(runtime, false);\n\n for (const rec of filteredRecommendations) {\n // create the wallet provider and token provider\n const walletProvider = new WalletProvider(\n new Connection(\n runtime.getSetting(\"SOLANA_RPC_URL\") ||\n \"https://api.mainnet-beta.solana.com\"\n ),\n publicKey\n );\n const tokenProvider = new TokenProvider(\n rec.contractAddress,\n walletProvider,\n runtime.cacheManager\n );\n\n // TODO: Check to make sure the contract address is valid, it's the right one, etc\n\n //\n if (!rec.contractAddress) {\n const tokenAddress = await tokenProvider.getTokenFromWallet(\n runtime,\n rec.ticker\n );\n rec.contractAddress = tokenAddress;\n if (!tokenAddress) {\n // try to search for the symbol and return the contract address with they highest liquidity and market cap\n const result = await tokenProvider.searchDexScreenerData(\n rec.ticker\n );\n const tokenAddress = result?.baseToken?.address;\n rec.contractAddress = tokenAddress;\n if (!tokenAddress) {\n elizaLogger.warn(\"Could not find contract address for token\");\n continue;\n }\n }\n }\n\n // create the trust score manager\n\n const trustScoreDb = new TrustScoreDatabase(runtime.databaseAdapter.db);\n const trustScoreManager = new TrustScoreManager(\n runtime,\n tokenProvider,\n trustScoreDb\n );\n\n // get actors from the database\n const participants =\n await runtime.databaseAdapter.getParticipantsForRoom(\n message.roomId\n );\n\n // find the first user Id from a user with the username that we extracted\n const user = participants.find(async (actor) => {\n const user = await runtime.databaseAdapter.getAccountById(actor);\n return (\n user.name.toLowerCase().trim() ===\n rec.recommender.toLowerCase().trim()\n );\n });\n\n if (!user) {\n elizaLogger.warn(\"Could not find user: \", rec.recommender);\n continue;\n }\n\n const account = await runtime.databaseAdapter.getAccountById(user);\n const userId = account.id;\n\n const recMemory = {\n userId,\n agentId,\n content: { text: JSON.stringify(rec) },\n roomId,\n createdAt: Date.now(),\n };\n\n await recommendationsManager.createMemory(recMemory, true);\n\n elizaLogger.log(\"recommendationsManager\", rec);\n\n // - from here we just need to make sure code is right\n\n // buy, dont buy, sell, dont sell\n\n const buyAmounts = await tokenProvider.calculateBuyAmounts();\n\n let buyAmount = buyAmounts[rec.conviction.toLowerCase().trim()];\n if (!buyAmount) {\n // handle annoying cases\n // for now just put in 10 sol\n buyAmount = 10;\n }\n\n // TODO: is this is a buy, sell, dont buy, or dont sell?\n const shouldTrade = await tokenProvider.shouldTradeToken();\n\n if (!shouldTrade) {\n elizaLogger.warn(\n \"There might be a problem with the token, not trading\"\n );\n continue;\n }\n\n switch (rec.type) {\n case \"buy\":\n // for now, lets just assume buy only, but we should implement\n await trustScoreManager.createTradePerformance(\n runtime,\n rec.contractAddress,\n userId,\n {\n buy_amount: rec.buyAmount,\n is_simulation: true,\n }\n );\n break;\n case \"sell\":\n case \"dont_sell\":\n case \"dont_buy\":\n elizaLogger.warn(\"Not implemented\");\n break;\n }\n }\n\n return filteredRecommendations;\n}\n\nexport const trustEvaluator: Evaluator = {\n name: \"EXTRACT_RECOMMENDATIONS\",\n similes: [\n \"GET_RECOMMENDATIONS\",\n \"EXTRACT_TOKEN_RECS\",\n \"EXTRACT_MEMECOIN_RECS\",\n ],\n alwaysRun: true,\n validate: async (\n runtime: IAgentRuntime,\n message: Memory\n ): Promise => {\n if (message.content.text.length < 5) {\n return false;\n }\n\n return message.userId !== message.agentId;\n },\n description:\n \"Extract recommendations to buy or sell memecoins/tokens from the conversation, including details like ticker, contract address, conviction level, and recommender username.\",\n handler,\n examples: [\n {\n context: `Actors in the scene:\n{{user1}}: Experienced DeFi degen. Constantly chasing high yield farms.\n{{user2}}: New to DeFi, learning the ropes.\n\nRecommendations about the actors:\nNone`,\n messages: [\n {\n user: \"{{user1}}\",\n content: {\n text: \"Yo, have you checked out $SOLARUG? Dope new yield aggregator on Solana.\",\n },\n },\n {\n user: \"{{user2}}\",\n content: {\n text: \"Nah, I'm still trying to wrap my head around how yield farming even works haha. Is it risky?\",\n },\n },\n {\n user: \"{{user1}}\",\n content: {\n text: \"I mean, there's always risk in DeFi, but the $SOLARUG devs seem legit. Threw a few sol into the FCweoTfJ128jGgNEXgdfTXdEZVk58Bz9trCemr6sXNx9 vault, farming's been smooth so far.\",\n },\n },\n ] as ActionExample[],\n outcome: `\\`\\`\\`json\n[\n {\n \"recommender\": \"{{user1}}\",\n \"ticker\": \"SOLARUG\",\n \"contractAddress\": \"FCweoTfJ128jGgNEXgdfTXdEZVk58Bz9trCemr6sXNx9\",\n \"type\": \"buy\",\n \"conviction\": \"medium\",\n \"alreadyKnown\": false\n }\n]\n\\`\\`\\``,\n },\n\n {\n context: `Actors in the scene:\n{{user1}}: Solana maximalist. Believes Solana will flip Ethereum.\n{{user2}}: Multichain proponent. Holds both SOL and ETH.\n\nRecommendations about the actors:\n{{user1}} has previously promoted $COPETOKEN and $SOYLENT.`,\n messages: [\n {\n user: \"{{user1}}\",\n content: {\n text: \"If you're not long $SOLVAULT at 7tRzKud6FBVFEhYqZS3CuQ2orLRM21bdisGykL5Sr4Dx, you're missing out. This will be the blackhole of Solana liquidity.\",\n },\n },\n {\n user: \"{{user2}}\",\n content: {\n text: \"Idk man, feels like there's a new 'vault' or 'reserve' token every week on Sol. What happened to $COPETOKEN and $SOYLENT that you were shilling before?\",\n },\n },\n {\n user: \"{{user1}}\",\n content: {\n text: \"$COPETOKEN and $SOYLENT had their time, I took profits near the top. But $SOLVAULT is different, it has actual utility. Do what you want, but don't say I didn't warn you when this 50x's and you're left holding your $ETH bags.\",\n },\n },\n ] as ActionExample[],\n outcome: `\\`\\`\\`json\n[\n {\n \"recommender\": \"{{user1}}\",\n \"ticker\": \"COPETOKEN\",\n \"contractAddress\": null,\n \"type\": \"sell\",\n \"conviction\": \"low\",\n \"alreadyKnown\": true\n },\n {\n \"recommender\": \"{{user1}}\",\n \"ticker\": \"SOYLENT\",\n \"contractAddress\": null,\n \"type\": \"sell\",\n \"conviction\": \"low\",\n \"alreadyKnown\": true\n },\n {\n \"recommender\": \"{{user1}}\",\n \"ticker\": \"SOLVAULT\",\n \"contractAddress\": \"7tRzKud6FBVFEhYqZS3CuQ2orLRM21bdisGykL5Sr4Dx\",\n \"type\": \"buy\",\n \"conviction\": \"high\",\n \"alreadyKnown\": false\n }\n]\n\\`\\`\\``,\n },\n\n {\n context: `Actors in the scene:\n{{user1}}: Self-proclaimed Solana alpha caller. Allegedly has insider info.\n{{user2}}: Degen gambler. Will ape into any hyped token.\n\nRecommendations about the actors:\nNone`,\n messages: [\n {\n user: \"{{user1}}\",\n content: {\n text: \"I normally don't do this, but I like you anon, so I'll let you in on some alpha. $ROULETTE at 48vV5y4DRH1Adr1bpvSgFWYCjLLPtHYBqUSwNc2cmCK2 is going to absolutely send it soon. You didn't hear it from me 🤐\",\n },\n },\n {\n user: \"{{user2}}\",\n content: {\n text: \"Oh shit, insider info from the alpha god himself? Say no more, I'm aping in hard.\",\n },\n },\n ] as ActionExample[],\n outcome: `\\`\\`\\`json\n[\n {\n \"recommender\": \"{{user1}}\",\n \"ticker\": \"ROULETTE\",\n \"contractAddress\": \"48vV5y4DRH1Adr1bpvSgFWYCjLLPtHYBqUSwNc2cmCK2\",\n \"type\": \"buy\",\n \"conviction\": \"high\",\n \"alreadyKnown\": false\n }\n]\n\\`\\`\\``,\n },\n\n {\n context: `Actors in the scene:\n{{user1}}: NFT collector and trader. Bullish on Solana NFTs.\n{{user2}}: Only invests based on fundamentals. Sees all NFTs as worthless JPEGs.\n\nRecommendations about the actors:\nNone\n`,\n messages: [\n {\n user: \"{{user1}}\",\n content: {\n text: \"GM. I'm heavily accumulating $PIXELAPE, the token for the Pixel Ape Yacht Club NFT collection. 10x is inevitable.\",\n },\n },\n {\n user: \"{{user2}}\",\n content: {\n text: \"NFTs are a scam bro. There's no underlying value. You're essentially trading worthless JPEGs.\",\n },\n },\n {\n user: \"{{user1}}\",\n content: {\n text: \"Fun staying poor 🤡 $PIXELAPE is about to moon and you'll be left behind.\",\n },\n },\n {\n user: \"{{user2}}\",\n content: {\n text: \"Whatever man, I'm not touching that shit with a ten foot pole. Have fun holding your bags.\",\n },\n },\n {\n user: \"{{user1}}\",\n content: {\n text: \"Don't need luck where I'm going 😎 Once $PIXELAPE at 3hAKKmR6XyBooQBPezCbUMhrmcyTkt38sRJm2thKytWc takes off, you'll change your tune.\",\n },\n },\n ],\n outcome: `\\`\\`\\`json\n[\n {\n \"recommender\": \"{{user1}}\",\n \"ticker\": \"PIXELAPE\",\n \"contractAddress\": \"3hAKKmR6XyBooQBPezCbUMhrmcyTkt38sRJm2thKytWc\",\n \"type\": \"buy\",\n \"conviction\": \"high\",\n \"alreadyKnown\": false\n }\n]\n\\`\\`\\``,\n },\n\n {\n context: `Actors in the scene:\n{{user1}}: Contrarian investor. Bets against hyped projects.\n{{user2}}: Trend follower. Buys tokens that are currently popular.\n\nRecommendations about the actors:\nNone`,\n messages: [\n {\n user: \"{{user2}}\",\n content: {\n text: \"$SAMOYED is the talk of CT right now. Making serious moves. Might have to get a bag.\",\n },\n },\n {\n user: \"{{user1}}\",\n content: {\n text: \"Whenever a token is the 'talk of CT', that's my cue to short it. $SAMOYED is going to dump hard, mark my words.\",\n },\n },\n {\n user: \"{{user2}}\",\n content: {\n text: \"Idk man, the hype seems real this time. 5TQwHyZbedaH4Pcthj1Hxf5GqcigL6qWuB7YEsBtqvhr chart looks bullish af.\",\n },\n },\n {\n user: \"{{user1}}\",\n content: {\n text: \"Hype is always real until it isn't. I'm taking out a fat short position here. Don't say I didn't warn you when this crashes 90% and you're left holding the flaming bags.\",\n },\n },\n ],\n outcome: `\\`\\`\\`json\n[\n {\n \"recommender\": \"{{user2}}\",\n \"ticker\": \"SAMOYED\",\n \"contractAddress\": \"5TQwHyZbedaH4Pcthj1Hxf5GqcigL6qWuB7YEsBtqvhr\",\n \"type\": \"buy\",\n \"conviction\": \"medium\",\n \"alreadyKnown\": false\n },\n {\n \"recommender\": \"{{user1}}\",\n \"ticker\": \"SAMOYED\",\n \"contractAddress\": \"5TQwHyZbedaH4Pcthj1Hxf5GqcigL6qWuB7YEsBtqvhr\",\n \"type\": \"dont_buy\",\n \"conviction\": \"high\",\n \"alreadyKnown\": false\n }\n]\n\\`\\`\\``,\n },\n ],\n};\n","import {\n getAssociatedTokenAddressSync,\n createTransferInstruction,\n} from \"@solana/spl-token\";\nimport { elizaLogger, settings } from \"@elizaos/core\";\nimport {\n Connection,\n PublicKey,\n TransactionMessage,\n VersionedTransaction,\n} from \"@solana/web3.js\";\nimport {\n type ActionExample,\n type Content,\n type HandlerCallback,\n type IAgentRuntime,\n type Memory,\n ModelClass,\n type State,\n type Action,\n} from \"@elizaos/core\";\nimport { composeContext } from \"@elizaos/core\";\nimport { getWalletKey } from \"../keypairUtils\";\nimport { generateObjectDeprecated } from \"@elizaos/core\";\n\nexport interface TransferContent extends Content {\n tokenAddress: string;\n recipient: string;\n amount: string | number;\n}\n\nfunction isTransferContent(\n runtime: IAgentRuntime,\n content: any\n): content is TransferContent {\n elizaLogger.log(\"Content for transfer\", content);\n return (\n typeof content.tokenAddress === \"string\" &&\n typeof content.recipient === \"string\" &&\n (typeof content.amount === \"string\" ||\n typeof content.amount === \"number\")\n );\n}\n\nconst transferTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined.\n\nExample response:\n\\`\\`\\`json\n{\n \"tokenAddress\": \"BieefG47jAHCGZBxi2q87RDuHyGZyYC3vAzxpyu8pump\",\n \"recipient\": \"9jW8FPr6BSSsemWPV22UUCzSqkVdTp6HTyPqeqyuBbCa\",\n \"amount\": \"1000\"\n}\n\\`\\`\\`\n\n{{recentMessages}}\n\nExtract the following information about the requested token transfer:\n- Token contract address\n- Recipient wallet address\n- Amount to transfer\n\nIf no token address is mentioned, respond with null.\n`;\n\nexport default {\n name: \"SEND_TOKEN\",\n similes: [\"TRANSFER_TOKEN\", \"TRANSFER_TOKENS\", \"SEND_TOKENS\", \"PAY_TOKEN\", \"PAY_TOKENS\", \"PAY\"],\n validate: async (runtime: IAgentRuntime, message: Memory) => {\n // Always return true for token transfers, letting the handler deal with specifics\n elizaLogger.log(\"Validating token transfer from user:\", message.userId);\n return true;\n },\n description: \"Transfer SPL tokens from agent's wallet to another address\",\n handler: async (\n runtime: IAgentRuntime,\n message: Memory,\n state: State,\n _options: { [key: string]: unknown },\n callback?: HandlerCallback\n ): Promise => {\n elizaLogger.log(\"Starting SEND_TOKEN handler...\");\n\n if (!state) {\n state = (await runtime.composeState(message)) as State;\n } else {\n state = await runtime.updateRecentMessageState(state);\n }\n\n const transferContext = composeContext({\n state,\n template: transferTemplate,\n });\n\n const content = await generateObjectDeprecated({\n runtime,\n context: transferContext,\n modelClass: ModelClass.LARGE,\n });\n\n if (!isTransferContent(runtime, content)) {\n if (callback) {\n callback({\n text: \"Token address needed to send the token.\",\n content: { error: \"Invalid transfer content\" },\n });\n }\n return false;\n }\n\n try {\n const { keypair: senderKeypair } = await getWalletKey(runtime, true);\n const connection = new Connection(settings.SOLANA_RPC_URL!);\n const mintPubkey = new PublicKey(content.tokenAddress);\n const recipientPubkey = new PublicKey(content.recipient);\n\n const mintInfo = await connection.getParsedAccountInfo(mintPubkey);\n const decimals = (mintInfo.value?.data as any)?.parsed?.info?.decimals ?? 9;\n const adjustedAmount = BigInt(Number(content.amount) * Math.pow(10, decimals));\n\n const senderATA = getAssociatedTokenAddressSync(mintPubkey, senderKeypair.publicKey);\n const recipientATA = getAssociatedTokenAddressSync(mintPubkey, recipientPubkey);\n\n const instructions = [];\n\n const recipientATAInfo = await connection.getAccountInfo(recipientATA);\n if (!recipientATAInfo) {\n const { createAssociatedTokenAccountInstruction } = await import(\"@solana/spl-token\");\n instructions.push(\n createAssociatedTokenAccountInstruction(\n senderKeypair.publicKey,\n recipientATA,\n recipientPubkey,\n mintPubkey\n )\n );\n }\n\n instructions.push(\n createTransferInstruction(\n senderATA,\n recipientATA,\n senderKeypair.publicKey,\n adjustedAmount\n )\n );\n\n const messageV0 = new TransactionMessage({\n payerKey: senderKeypair.publicKey,\n recentBlockhash: (await connection.getLatestBlockhash()).blockhash,\n instructions,\n }).compileToV0Message();\n\n const transaction = new VersionedTransaction(messageV0);\n transaction.sign([senderKeypair]);\n\n const signature = await connection.sendTransaction(transaction);\n\n if (callback) {\n callback({\n text: `Sent ${content.amount} tokens to ${content.recipient}\\nTransaction hash: ${signature}`,\n content: {\n success: true,\n signature,\n amount: content.amount,\n recipient: content.recipient,\n },\n });\n }\n\n return true;\n } catch (error) {\n elizaLogger.error(\"Error during token transfer:\", error);\n if (callback) {\n callback({\n text: `Issue with the transfer: ${error.message}`,\n content: { error: error.message },\n });\n }\n return false;\n }\n },\n\n examples: [\n [\n {\n user: \"{{user1}}\",\n content: {\n text: \"Send 69 EZSIS BieefG47jAHCGZBxi2q87RDuHyGZyYC3vAzxpyu8pump to 9jW8FPr6BSSsemWPV22UUCzSqkVdTp6HTyPqeqyuBbCa\",\n },\n },\n {\n user: \"{{user2}}\",\n content: {\n text: \"Sending the tokens now...\",\n action: \"SEND_TOKEN\",\n },\n },\n ],\n ] as ActionExample[][],\n} as Action;","import { elizaLogger, settings } from \"@elizaos/core\";\nimport {\n Connection,\n PublicKey,\n SystemProgram,\n TransactionMessage,\n VersionedTransaction,\n} from \"@solana/web3.js\";\nimport {\n type ActionExample,\n type Content,\n type HandlerCallback,\n type IAgentRuntime,\n type Memory,\n ModelClass,\n type State,\n type Action,\n} from \"@elizaos/core\";\nimport { composeContext } from \"@elizaos/core\";\nimport { getWalletKey } from \"../keypairUtils\";\nimport { generateObjectDeprecated } from \"@elizaos/core\";\n\ninterface SolTransferContent extends Content {\n recipient: string;\n amount: number;\n}\n\nfunction isSolTransferContent(\n content: any\n): content is SolTransferContent {\n return (\n typeof content.recipient === \"string\" &&\n typeof content.amount === \"number\"\n );\n}\n\nconst solTransferTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined.\n\nExample response:\n\\`\\`\\`json\n{\n \"recipient\": \"9jW8FPr6BSSsemWPV22UUCzSqkVdTp6HTyPqeqyuBbCa\",\n \"amount\": 1.5\n}\n\\`\\`\\`\n\n{{recentMessages}}\n\nExtract the following information about the requested SOL transfer:\n- Recipient wallet address\n- Amount of SOL to transfer\n`;\n\nexport default {\n name: \"SEND_SOL\",\n similes: [\"TRANSFER_SOL\", \"PAY_SOL\", \"TRANSACT_SOL\"],\n validate: async (runtime: IAgentRuntime, message: Memory) => {\n // Always return true for SOL transfers, letting the handler deal with specifics\n elizaLogger.log(\"Validating SOL transfer from user:\", message.userId);\n return true;\n },\n description: \"Transfer native SOL from agent's wallet to specified address\",\n handler: async (\n runtime: IAgentRuntime,\n message: Memory,\n state: State,\n _options: { [key: string]: unknown },\n callback?: HandlerCallback\n ): Promise => {\n elizaLogger.log(\"Starting SEND_SOL handler...\");\n\n if (!state) {\n state = (await runtime.composeState(message)) as State;\n } else {\n state = await runtime.updateRecentMessageState(state);\n }\n\n const transferContext = composeContext({\n state,\n template: solTransferTemplate,\n });\n\n const content = await generateObjectDeprecated({\n runtime,\n context: transferContext,\n modelClass: ModelClass.LARGE,\n });\n\n if (!isSolTransferContent(content)) {\n if (callback) {\n callback({\n text: \"Need an address and the amount of SOL to send.\",\n content: { error: \"Invalid transfer content\" },\n });\n }\n return false;\n }\n\n try {\n const { keypair: senderKeypair } = await getWalletKey(runtime, true);\n const connection = new Connection(settings.SOLANA_RPC_URL!);\n const recipientPubkey = new PublicKey(content.recipient);\n\n const lamports = content.amount * 1e9;\n\n const instruction = SystemProgram.transfer({\n fromPubkey: senderKeypair.publicKey,\n toPubkey: recipientPubkey,\n lamports,\n });\n\n const messageV0 = new TransactionMessage({\n payerKey: senderKeypair.publicKey,\n recentBlockhash: (await connection.getLatestBlockhash()).blockhash,\n instructions: [instruction],\n }).compileToV0Message();\n\n const transaction = new VersionedTransaction(messageV0);\n transaction.sign([senderKeypair]);\n\n const signature = await connection.sendTransaction(transaction);\n\n if (callback) {\n callback({\n text: `Sent ${content.amount} SOL. Transaction hash: ${signature}`,\n content: {\n success: true,\n signature,\n amount: content.amount,\n recipient: content.recipient,\n },\n });\n }\n\n return true;\n } catch (error) {\n elizaLogger.error(\"Error during SOL transfer:\", error);\n if (callback) {\n callback({\n text: `Problem with the SOL transfer: ${error.message}`,\n content: { error: error.message },\n });\n }\n return false;\n }\n },\n\n examples: [\n [\n {\n user: \"{{user1}}\",\n content: {\n text: \"Send 1.5 SOL to 9jW8FPr6BSSsemWPV22UUCzSqkVdTp6HTyPqeqyuBbCa\",\n },\n },\n {\n user: \"{{user2}}\",\n content: {\n text: \"Sure thing, SOL on its way.\",\n action: \"SEND_SOL\",\n },\n },\n ],\n ] as ActionExample[][],\n} as Action;","import { getAccount, getAssociatedTokenAddress } from \"@solana/spl-token\";\nimport { type Connection, PublicKey } from \"@solana/web3.js\";\nimport { elizaLogger } from \"@elizaos/core\";\n\nexport async function getTokenPriceInSol(tokenSymbol: string): Promise {\n const response = await fetch(\n `https://price.jup.ag/v6/price?ids=${tokenSymbol}`\n );\n const data = await response.json();\n return data.data[tokenSymbol].price;\n}\n\nasync function getTokenBalance(\n connection: Connection,\n walletPublicKey: PublicKey,\n tokenMintAddress: PublicKey\n): Promise {\n const tokenAccountAddress = await getAssociatedTokenAddress(\n tokenMintAddress,\n walletPublicKey\n );\n\n try {\n const tokenAccount = await getAccount(connection, tokenAccountAddress);\n const tokenAmount = tokenAccount.amount as unknown as number;\n return tokenAmount;\n } catch (error) {\n elizaLogger.error(\n `Error retrieving balance for token: ${tokenMintAddress.toBase58()}`,\n error\n );\n return 0;\n }\n}\n\nasync function getTokenBalances(\n connection: Connection,\n walletPublicKey: PublicKey\n): Promise<{ [tokenName: string]: number }> {\n const tokenBalances: { [tokenName: string]: number } = {};\n\n // Add the token mint addresses you want to retrieve balances for\n const tokenMintAddresses = [\n new PublicKey(\"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v\"), // USDC\n new PublicKey(\"So11111111111111111111111111111111111111112\"), // SOL\n // Add more token mint addresses as needed\n ];\n\n for (const mintAddress of tokenMintAddresses) {\n const tokenName = getTokenName(mintAddress);\n const balance = await getTokenBalance(\n connection,\n walletPublicKey,\n mintAddress\n );\n tokenBalances[tokenName] = balance;\n }\n\n return tokenBalances;\n}\n\nfunction getTokenName(mintAddress: PublicKey): string {\n // Implement a mapping of mint addresses to token names\n const tokenNameMap: { [mintAddress: string]: string } = {\n EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v: \"USDC\",\n So11111111111111111111111111111111111111112: \"SOL\",\n // Add more token mint addresses and their corresponding names\n };\n\n return tokenNameMap[mintAddress.toBase58()] || \"Unknown Token\";\n}\n\nexport { getTokenBalance, getTokenBalances };\n","import {\n type ActionExample,\n composeContext,\n generateObjectDeprecated,\n type HandlerCallback,\n type IAgentRuntime,\n type Memory,\n ModelClass,\n settings,\n type State,\n type Action,\n elizaLogger,\n} from \"@elizaos/core\";\nimport { Connection, type PublicKey, VersionedTransaction } from \"@solana/web3.js\";\nimport BigNumber from \"bignumber.js\";\nimport { getWalletKey } from \"../keypairUtils.ts\";\nimport { walletProvider, WalletProvider } from \"../providers/wallet.ts\";\nimport { getTokenDecimals } from \"./swapUtils.ts\";\n\nasync function swapToken(\n connection: Connection,\n walletPublicKey: PublicKey,\n inputTokenCA: string,\n outputTokenCA: string,\n amount: number\n): Promise {\n try {\n // Get the decimals for the input token\n const decimals =\n inputTokenCA === settings.SOL_ADDRESS\n ? new BigNumber(9)\n : new BigNumber(\n await getTokenDecimals(connection, inputTokenCA)\n );\n\n elizaLogger.log(\"Decimals:\", decimals.toString());\n\n // Use BigNumber for adjustedAmount: amount * (10 ** decimals)\n const amountBN = new BigNumber(amount);\n const adjustedAmount = amountBN.multipliedBy(\n new BigNumber(10).pow(decimals)\n );\n\n elizaLogger.log(\"Fetching quote with params:\", {\n inputMint: inputTokenCA,\n outputMint: outputTokenCA,\n amount: adjustedAmount,\n });\n\n const quoteResponse = await fetch(\n `https://quote-api.jup.ag/v6/quote?inputMint=${inputTokenCA}&outputMint=${outputTokenCA}&amount=${adjustedAmount}&dynamicSlippage=true&maxAccounts=64`\n );\n const quoteData = await quoteResponse.json();\n\n if (!quoteData || quoteData.error) {\n elizaLogger.error(\"Quote error:\", quoteData);\n throw new Error(\n `Failed to get quote: ${quoteData?.error || \"Unknown error\"}`\n );\n }\n\n elizaLogger.log(\"Quote received:\", quoteData);\n\n const swapRequestBody = {\n quoteResponse: quoteData,\n userPublicKey: walletPublicKey.toBase58(),\n dynamicComputeUnitLimit: true,\n dynamicSlippage: true,\n priorityLevelWithMaxLamports: {\n maxLamports: 4000000,\n priorityLevel: \"veryHigh\",\n },\n };\n\n elizaLogger.log(\"Requesting swap with body:\", swapRequestBody);\n\n const swapResponse = await fetch(\"https://quote-api.jup.ag/v6/swap\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(swapRequestBody),\n });\n\n const swapData = await swapResponse.json();\n\n if (!swapData || !swapData.swapTransaction) {\n elizaLogger.error(\"Swap error:\", swapData);\n throw new Error(\n `Failed to get swap transaction: ${swapData?.error || \"No swap transaction returned\"}`\n );\n }\n\n elizaLogger.log(\"Swap transaction received\");\n return swapData;\n } catch (error) {\n elizaLogger.error(\"Error in swapToken:\", error);\n throw error;\n }\n}\n\nconst swapTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined.\n\nExample response:\n\\`\\`\\`json\n{\n \"inputTokenSymbol\": \"SOL\",\n \"outputTokenSymbol\": \"USDC\",\n \"inputTokenCA\": \"So11111111111111111111111111111111111111112\",\n \"outputTokenCA\": \"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v\",\n \"amount\": 1.5\n}\n\\`\\`\\`\n\n{{recentMessages}}\n\nGiven the recent messages and wallet information below:\n\n{{walletInfo}}\n\nExtract the following information about the requested token swap:\n- Input token symbol (the token being sold)\n- Output token symbol (the token being bought)\n- Input token contract address if provided\n- Output token contract address if provided\n- Amount to swap\n\nRespond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined. The result should be a valid JSON object with the following schema:\n\\`\\`\\`json\n{\n \"inputTokenSymbol\": string | null,\n \"outputTokenSymbol\": string | null,\n \"inputTokenCA\": string | null,\n \"outputTokenCA\": string | null,\n \"amount\": number | string | null\n}\n\\`\\`\\``;\n\n// if we get the token symbol but not the CA, check walet for matching token, and if we have, get the CA for it\n\n// get all the tokens in the wallet using the wallet provider\nasync function getTokensInWallet(runtime: IAgentRuntime) {\n const { publicKey } = await getWalletKey(runtime, false);\n const walletProvider = new WalletProvider(\n new Connection(\"https://api.mainnet-beta.solana.com\"),\n publicKey\n );\n\n const walletInfo = await walletProvider.fetchPortfolioValue(runtime);\n const items = walletInfo.items;\n return items;\n}\n\n// check if the token symbol is in the wallet\nasync function getTokenFromWallet(runtime: IAgentRuntime, tokenSymbol: string) {\n try {\n const items = await getTokensInWallet(runtime);\n const token = items.find((item) => item.symbol === tokenSymbol);\n\n if (token) {\n return token.address;\n } else {\n return null;\n }\n } catch (error) {\n elizaLogger.error(\"Error checking token in wallet:\", error);\n return null;\n }\n}\n\n// swapToken should took CA, not symbol\n\nexport const executeSwap: Action = {\n name: \"EXECUTE_SWAP\",\n similes: [\"SWAP_TOKENS\", \"TOKEN_SWAP\", \"TRADE_TOKENS\", \"EXCHANGE_TOKENS\"],\n validate: async (runtime: IAgentRuntime, message: Memory) => {\n // Check if the necessary parameters are provided in the message\n elizaLogger.log(\"Message:\", message);\n return true;\n },\n description: \"Perform a token swap.\",\n handler: async (\n runtime: IAgentRuntime,\n message: Memory,\n state: State,\n _options: { [key: string]: unknown },\n callback?: HandlerCallback\n ): Promise => {\n // composeState\n if (!state) {\n state = (await runtime.composeState(message)) as State;\n } else {\n state = await runtime.updateRecentMessageState(state);\n }\n\n const walletInfo = await walletProvider.get(runtime, message, state);\n\n state.walletInfo = walletInfo;\n\n const swapContext = composeContext({\n state,\n template: swapTemplate,\n });\n\n const response = await generateObjectDeprecated({\n runtime,\n context: swapContext,\n modelClass: ModelClass.LARGE,\n });\n\n elizaLogger.log(\"Response:\", response);\n // const type = response.inputTokenSymbol?.toUpperCase() === \"SOL\" ? \"buy\" : \"sell\";\n\n // Add SOL handling logic\n if (response.inputTokenSymbol?.toUpperCase() === \"SOL\") {\n response.inputTokenCA = settings.SOL_ADDRESS;\n }\n if (response.outputTokenSymbol?.toUpperCase() === \"SOL\") {\n response.outputTokenCA = settings.SOL_ADDRESS;\n }\n\n // if both contract addresses are set, lets execute the swap\n // TODO: try to resolve CA from symbol based on existing symbol in wallet\n if (!response.inputTokenCA && response.inputTokenSymbol) {\n elizaLogger.log(\n `Attempting to resolve CA for input token symbol: ${response.inputTokenSymbol}`\n );\n response.inputTokenCA = await getTokenFromWallet(\n runtime,\n response.inputTokenSymbol\n );\n if (response.inputTokenCA) {\n elizaLogger.log(\n `Resolved inputTokenCA: ${response.inputTokenCA}`\n );\n } else {\n elizaLogger.log(\n \"No contract addresses provided, skipping swap\"\n );\n const responseMsg = {\n text: \"I need the contract addresses to perform the swap\",\n };\n callback?.(responseMsg);\n return true;\n }\n }\n\n if (!response.outputTokenCA && response.outputTokenSymbol) {\n elizaLogger.log(\n `Attempting to resolve CA for output token symbol: ${response.outputTokenSymbol}`\n );\n response.outputTokenCA = await getTokenFromWallet(\n runtime,\n response.outputTokenSymbol\n );\n if (response.outputTokenCA) {\n elizaLogger.log(\n `Resolved outputTokenCA: ${response.outputTokenCA}`\n );\n } else {\n elizaLogger.log(\n \"No contract addresses provided, skipping swap\"\n );\n const responseMsg = {\n text: \"I need the contract addresses to perform the swap\",\n };\n callback?.(responseMsg);\n return true;\n }\n }\n\n if (!response.amount) {\n elizaLogger.log(\"No amount provided, skipping swap\");\n const responseMsg = {\n text: \"I need the amount to perform the swap\",\n };\n callback?.(responseMsg);\n return true;\n }\n\n // TODO: if response amount is half, all, etc, semantically retrieve amount and return as number\n if (!response.amount) {\n elizaLogger.log(\"Amount is not a number, skipping swap\");\n const responseMsg = {\n text: \"The amount must be a number\",\n };\n callback?.(responseMsg);\n return true;\n }\n try {\n const connection = new Connection(\n \"https://api.mainnet-beta.solana.com\"\n );\n const { publicKey: walletPublicKey } = await getWalletKey(\n runtime,\n false\n );\n\n // const provider = new WalletProvider(connection, walletPublicKey);\n\n elizaLogger.log(\"Wallet Public Key:\", walletPublicKey);\n elizaLogger.log(\"inputTokenSymbol:\", response.inputTokenCA);\n elizaLogger.log(\"outputTokenSymbol:\", response.outputTokenCA);\n elizaLogger.log(\"amount:\", response.amount);\n\n const swapResult = await swapToken(\n connection,\n walletPublicKey,\n response.inputTokenCA as string,\n response.outputTokenCA as string,\n response.amount as number\n );\n\n elizaLogger.log(\"Deserializing transaction...\");\n const transactionBuf = Buffer.from(\n swapResult.swapTransaction,\n \"base64\"\n );\n const transaction =\n VersionedTransaction.deserialize(transactionBuf);\n\n elizaLogger.log(\"Preparing to sign transaction...\");\n\n elizaLogger.log(\"Creating keypair...\");\n const { keypair } = await getWalletKey(runtime, true);\n // Verify the public key matches what we expect\n if (keypair.publicKey.toBase58() !== walletPublicKey.toBase58()) {\n throw new Error(\n \"Generated public key doesn't match expected public key\"\n );\n }\n\n elizaLogger.log(\"Signing transaction...\");\n transaction.sign([keypair]);\n\n elizaLogger.log(\"Sending transaction...\");\n\n const latestBlockhash = await connection.getLatestBlockhash();\n\n const txid = await connection.sendTransaction(transaction, {\n skipPreflight: false,\n maxRetries: 3,\n preflightCommitment: \"confirmed\",\n });\n\n elizaLogger.log(\"Transaction sent:\", txid);\n\n // Confirm transaction using the blockhash\n const confirmation = await connection.confirmTransaction(\n {\n signature: txid,\n blockhash: latestBlockhash.blockhash,\n lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,\n },\n \"confirmed\"\n );\n\n if (confirmation.value.err) {\n throw new Error(\n `Transaction failed: ${confirmation.value.err}`\n );\n }\n\n if (confirmation.value.err) {\n throw new Error(\n `Transaction failed: ${confirmation.value.err}`\n );\n }\n\n elizaLogger.log(\"Swap completed successfully!\");\n elizaLogger.log(`Transaction ID: ${txid}`);\n\n const responseMsg = {\n text: `Swap completed successfully! Transaction ID: ${txid}`,\n };\n\n callback?.(responseMsg);\n\n return true;\n } catch (error) {\n elizaLogger.error(\"Error during token swap:\", error);\n return false;\n }\n },\n examples: [\n [\n {\n user: \"{{user1}}\",\n content: {\n inputTokenSymbol: \"SOL\",\n outputTokenSymbol: \"USDC\",\n amount: 0.1,\n },\n },\n {\n user: \"{{user2}}\",\n content: {\n text: \"Swapping 0.1 SOL for USDC...\",\n action: \"TOKEN_SWAP\",\n },\n },\n {\n user: \"{{user2}}\",\n content: {\n text: \"Swap completed successfully! Transaction ID: ...\",\n },\n },\n ],\n // Add more examples as needed\n ] as ActionExample[][],\n} as Action;","import { getAssociatedTokenAddress } from \"@solana/spl-token\";\nimport {\n type BlockhashWithExpiryBlockHeight,\n Connection,\n type Keypair,\n PublicKey,\n type RpcResponseAndContext,\n type SimulatedTransactionResponse,\n type TokenAmount,\n VersionedTransaction,\n} from \"@solana/web3.js\";\nimport { settings, elizaLogger } from \"@elizaos/core\";\n\nconst solAddress = settings.SOL_ADDRESS;\nconst SLIPPAGE = settings.SLIPPAGE;\nconst connection = new Connection(\n settings.SOLANA_RPC_URL || \"https://api.mainnet-beta.solana.com\"\n);\nconst delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));\n\nexport async function delayedCall(\n method: (...args: any[]) => Promise,\n ...args: any[]\n): Promise {\n await delay(150);\n return method(...args);\n}\n\nexport async function getTokenDecimals(\n connection: Connection,\n mintAddress: string\n): Promise {\n const mintPublicKey = new PublicKey(mintAddress);\n const tokenAccountInfo =\n await connection.getParsedAccountInfo(mintPublicKey);\n\n // Check if the data is parsed and contains the expected structure\n if (\n tokenAccountInfo.value &&\n typeof tokenAccountInfo.value.data === \"object\" &&\n \"parsed\" in tokenAccountInfo.value.data\n ) {\n const parsedInfo = tokenAccountInfo.value.data.parsed?.info;\n if (parsedInfo && typeof parsedInfo.decimals === \"number\") {\n return parsedInfo.decimals;\n }\n }\n\n throw new Error(\"Unable to fetch token decimals\");\n}\n\nexport async function getQuote(\n connection: Connection,\n baseToken: string,\n outputToken: string,\n amount: number\n): Promise {\n const decimals = await getTokenDecimals(connection, baseToken);\n const adjustedAmount = amount * 10 ** decimals;\n\n const quoteResponse = await fetch(\n `https://quote-api.jup.ag/v6/quote?inputMint=${baseToken}&outputMint=${outputToken}&amount=${adjustedAmount}&slippageBps=50`\n );\n const swapTransaction = await quoteResponse.json();\n const swapTransactionBuf = Buffer.from(swapTransaction, \"base64\");\n return new Uint8Array(swapTransactionBuf);\n}\n\nexport const executeSwap = async (\n transaction: VersionedTransaction,\n type: \"buy\" | \"sell\"\n) => {\n try {\n const latestBlockhash: BlockhashWithExpiryBlockHeight =\n await delayedCall(connection.getLatestBlockhash.bind(connection));\n const signature = await connection.sendTransaction(transaction, {\n skipPreflight: false,\n });\n const confirmation = await connection.confirmTransaction(\n {\n signature,\n lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,\n blockhash: latestBlockhash.blockhash,\n },\n \"confirmed\"\n );\n if (confirmation.value.err) {\n elizaLogger.log(\"Confirmation error\", confirmation.value.err);\n\n throw new Error(\"Confirmation error\");\n } else {\n if (type === \"buy\") {\n elizaLogger.log(\n \"Buy successful: https://solscan.io/tx/${signature}\"\n );\n } else {\n elizaLogger.log(\n \"Sell successful: https://solscan.io/tx/${signature}\"\n );\n }\n }\n\n return signature;\n } catch (error) {\n elizaLogger.log(error);\n }\n};\n\nexport const Sell = async (baseMint: PublicKey, wallet: Keypair) => {\n try {\n const tokenAta = await delayedCall(\n getAssociatedTokenAddress,\n baseMint,\n wallet.publicKey\n );\n const tokenBalInfo: RpcResponseAndContext =\n await delayedCall(\n connection.getTokenAccountBalance.bind(connection),\n tokenAta\n );\n\n if (!tokenBalInfo) {\n elizaLogger.log(\"Balance incorrect\");\n return null;\n }\n\n const tokenBalance = tokenBalInfo.value.amount;\n if (tokenBalance === \"0\") {\n elizaLogger.warn(\n `No token balance to sell with wallet ${wallet.publicKey}`\n );\n }\n\n const sellTransaction = await getSwapTxWithWithJupiter(\n wallet,\n baseMint,\n tokenBalance,\n \"sell\"\n );\n // simulate the transaction\n if (!sellTransaction) {\n elizaLogger.log(\"Failed to get sell transaction\");\n return null;\n }\n\n const simulateResult: RpcResponseAndContext =\n await delayedCall(\n connection.simulateTransaction.bind(connection),\n sellTransaction\n );\n if (simulateResult.value.err) {\n elizaLogger.log(\"Sell Simulation failed\", simulateResult.value.err);\n return null;\n }\n\n // execute the transaction\n return executeSwap(sellTransaction, \"sell\");\n } catch (error) {\n elizaLogger.log(error);\n }\n};\n\nexport const Buy = async (baseMint: PublicKey, wallet: Keypair) => {\n try {\n const tokenAta = await delayedCall(\n getAssociatedTokenAddress,\n baseMint,\n wallet.publicKey\n );\n const tokenBalInfo: RpcResponseAndContext =\n await delayedCall(\n connection.getTokenAccountBalance.bind(connection),\n tokenAta\n );\n\n if (!tokenBalInfo) {\n elizaLogger.log(\"Balance incorrect\");\n return null;\n }\n\n const tokenBalance = tokenBalInfo.value.amount;\n if (tokenBalance === \"0\") {\n elizaLogger.warn(\n `No token balance to sell with wallet ${wallet.publicKey}`\n );\n }\n\n const buyTransaction = await getSwapTxWithWithJupiter(\n wallet,\n baseMint,\n tokenBalance,\n \"buy\"\n );\n // simulate the transaction\n if (!buyTransaction) {\n elizaLogger.log(\"Failed to get buy transaction\");\n return null;\n }\n\n const simulateResult: RpcResponseAndContext =\n await delayedCall(\n connection.simulateTransaction.bind(connection),\n buyTransaction\n );\n if (simulateResult.value.err) {\n elizaLogger.log(\"Buy Simulation failed\", simulateResult.value.err);\n return null;\n }\n\n // execute the transaction\n return executeSwap(buyTransaction, \"buy\");\n } catch (error) {\n elizaLogger.log(error);\n }\n};\n\nexport const getSwapTxWithWithJupiter = async (\n wallet: Keypair,\n baseMint: PublicKey,\n amount: string,\n type: \"buy\" | \"sell\"\n) => {\n try {\n switch (type) {\n case \"buy\":\n return fetchBuyTransaction(wallet, baseMint, amount);\n case \"sell\":\n return fetchSellTransaction(wallet, baseMint, amount);\n default:\n return fetchSellTransaction(wallet, baseMint, amount);\n }\n } catch (error) {\n elizaLogger.log(error);\n }\n};\n\nexport const fetchBuyTransaction = async (\n wallet: Keypair,\n baseMint: PublicKey,\n amount: string\n) => {\n try {\n const quoteResponse = await (\n await fetch(\n `https://quote-api.jup.ag/v6/quote?inputMint=${solAddress}&outputMint=${baseMint.toBase58()}&amount=${amount}&slippageBps=${SLIPPAGE}`\n )\n ).json();\n const { swapTransaction } = await (\n await fetch(\"https://quote-api.jup.ag/v6/swap\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n quoteResponse,\n userPublicKey: wallet.publicKey.toString(),\n wrapAndUnwrapSol: true,\n dynamicComputeUnitLimit: true,\n prioritizationFeeLamports: 100000,\n }),\n })\n ).json();\n if (!swapTransaction) {\n elizaLogger.log(\"Failed to get buy transaction\");\n return null;\n }\n\n // deserialize the transaction\n const swapTransactionBuf = Buffer.from(swapTransaction, \"base64\");\n const transaction =\n VersionedTransaction.deserialize(swapTransactionBuf);\n\n // sign the transaction\n transaction.sign([wallet]);\n return transaction;\n } catch (error) {\n elizaLogger.log(\"Failed to get buy transaction\", error);\n return null;\n }\n};\n\nexport const fetchSellTransaction = async (\n wallet: Keypair,\n baseMint: PublicKey,\n amount: string\n) => {\n try {\n const quoteResponse = await (\n await fetch(\n `https://quote-api.jup.ag/v6/quote?inputMint=${baseMint.toBase58()}&outputMint=${solAddress}&amount=${amount}&slippageBps=${SLIPPAGE}`\n )\n ).json();\n\n // get serialized transactions for the swap\n const { swapTransaction } = await (\n await fetch(\"https://quote-api.jup.ag/v6/swap\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n quoteResponse,\n userPublicKey: wallet.publicKey.toString(),\n wrapAndUnwrapSol: true,\n dynamicComputeUnitLimit: true,\n prioritizationFeeLamports: 52000,\n }),\n })\n ).json();\n if (!swapTransaction) {\n elizaLogger.log(\"Failed to get sell transaction\");\n return null;\n }\n\n // deserialize the transaction\n const swapTransactionBuf = Buffer.from(swapTransaction, \"base64\");\n const transaction =\n VersionedTransaction.deserialize(swapTransactionBuf);\n\n // sign the transaction\n transaction.sign([wallet]);\n return transaction;\n } catch (error) {\n elizaLogger.log(\"Failed to get sell transaction\", error);\n return null;\n }\n};\n","import {\n type Action,\n type IAgentRuntime,\n type Memory,\n type Content,\n ModelClass,\n composeContext,\n generateText,\n} from \"@elizaos/core\";\n\ninterface Order {\n userId: string;\n ticker: string;\n contractAddress: string;\n timestamp: string;\n buyAmount: number;\n price: number;\n}\n\nconst take_order: Action = {\n name: \"TAKE_ORDER\",\n similes: [\"BUY_ORDER\", \"PLACE_ORDER\"],\n description: \"Records a buy order based on the user's conviction level.\",\n examples: [],\n validate: async (runtime: IAgentRuntime, message: Memory) => {\n const text = (message.content as Content).text;\n // Check if the message contains a ticker symbol\n const tickerRegex = /\\b[A-Z]{1,5}\\b/g;\n return tickerRegex.test(text);\n },\n handler: async (runtime: IAgentRuntime, message: Memory) => {\n const _text = (message.content as Content).text;\n const userId = message.userId;\n\n const template = `\n\n// CLAUDE TODO: Put the usual conversation context here\n\nTicker is: {{ticker}}\nContract address is: {{contractAddress}}\n\nDetermine if the user is trying to shill the ticker. if they are, respond with empty conviction, ticker and contractAddress.\n\n// CLAUDE TODO: output a JSON block with the following fields:\n// - reasoning: string\n// - conviction: negative, low, medium, high\n// - ticker: string (extract from CA so we have context)\n// - contractAddress: string\n`;\n\n let ticker, contractAddress;\n\n // TODO:\n\n // 1. create state object with runtime.composeState\n // 2. compose context with template and state\n // 3. get generateText\n // 4. validate generateText\n\n // if ticker or contractAddress are empty, return a message asking for them\n if (!ticker || !contractAddress) {\n return {\n text: \"Ticker and CA?\",\n };\n }\n\n const state = await runtime.composeState(message);\n // TODO: compose context properly\n const context = composeContext({\n state: {\n ...state,\n ticker,\n contractAddress,\n },\n template,\n });\n\n const convictionResponse = await generateText({\n runtime,\n context: context,\n modelClass: ModelClass.LARGE,\n });\n\n // TODOL parse and validate the JSON\n const convictionResponseJson = JSON.parse(convictionResponse); // TODO: replace with validate like other actions\n\n // get the conviction\n const conviction = convictionResponseJson.conviction;\n\n let buyAmount = 0;\n if (conviction === \"low\") {\n buyAmount = 20;\n } else if (conviction === \"medium\") {\n buyAmount = 50;\n } else if (conviction === \"high\") {\n buyAmount = 100;\n }\n\n // Get the current price of the asset (replace with actual price fetching logic)\n const currentPrice = 100;\n\n const order: Order = {\n userId,\n ticker: ticker || \"\",\n contractAddress,\n timestamp: new Date().toISOString(),\n buyAmount,\n price: currentPrice,\n };\n\n // Read the existing order book from the JSON file\n const orderBookPath =\n runtime.getSetting(\"orderBookPath\") ?? \"solana/orderBook.json\";\n\n const orderBook: Order[] = [];\n\n const cachedOrderBook =\n await runtime.cacheManager.get(orderBookPath);\n\n if (cachedOrderBook) {\n orderBook.push(...cachedOrderBook);\n }\n\n // Add the new order to the order book\n orderBook.push(order);\n\n // Write the updated order book back to the JSON file\n await runtime.cacheManager.set(orderBookPath, orderBook);\n\n return {\n text: `Recorded a ${conviction} conviction buy order for ${ticker} (${contractAddress}) with an amount of ${buyAmount} at the price of ${currentPrice}.`,\n };\n },\n};\n\nexport default take_order;\n","import { generateImage, elizaLogger } from \"@elizaos/core\";\nimport { Connection, Keypair, type PublicKey } from \"@solana/web3.js\";\nimport { VersionedTransaction } from \"@solana/web3.js\";\nimport { Fomo, type PurchaseCurrency } from \"fomo-sdk-solana\";\nimport { getAssociatedTokenAddressSync } from \"@solana/spl-token\";\nimport bs58 from \"bs58\";\nimport {\n settings,\n type ActionExample,\n type Content,\n type HandlerCallback,\n type IAgentRuntime,\n type Memory,\n ModelClass,\n type State,\n generateObject,\n composeContext,\n type Action,\n} from \"@elizaos/core\";\n\nimport { walletProvider } from \"../providers/wallet.ts\";\n\ninterface CreateTokenMetadata {\n name: string;\n symbol: string;\n uri: string;\n}\n\nexport interface CreateAndBuyContent extends Content {\n tokenMetadata: {\n name: string;\n symbol: string;\n description: string;\n image_description: string;\n };\n buyAmountSol: string | number;\n requiredLiquidity: string | number;\n}\n\nexport function isCreateAndBuyContentForFomo(\n content: any\n): content is CreateAndBuyContent {\n elizaLogger.log(\"Content for create & buy\", content);\n return (\n typeof content.tokenMetadata === \"object\" &&\n content.tokenMetadata !== null &&\n typeof content.tokenMetadata.name === \"string\" &&\n typeof content.tokenMetadata.symbol === \"string\" &&\n typeof content.tokenMetadata.description === \"string\" &&\n typeof content.tokenMetadata.image_description === \"string\" &&\n (typeof content.buyAmountSol === \"string\" ||\n typeof content.buyAmountSol === \"number\") &&\n typeof content.requiredLiquidity === \"number\"\n );\n}\n\nexport const createAndBuyToken = async ({\n deployer,\n mint,\n tokenMetadata,\n buyAmountSol,\n priorityFee,\n requiredLiquidity = 85,\n allowOffCurve,\n commitment = \"confirmed\",\n fomo,\n connection,\n}: {\n deployer: Keypair;\n mint: Keypair;\n tokenMetadata: CreateTokenMetadata;\n buyAmountSol: bigint;\n priorityFee: number;\n requiredLiquidity: number;\n allowOffCurve: boolean;\n commitment?:\n | \"processed\"\n | \"confirmed\"\n | \"finalized\"\n | \"recent\"\n | \"single\"\n | \"singleGossip\"\n | \"root\"\n | \"max\";\n fomo: Fomo;\n connection: Connection;\n slippage: string;\n}) => {\n const { transaction: versionedTx } = await fomo.createToken(\n deployer.publicKey,\n tokenMetadata.name,\n tokenMetadata.symbol,\n tokenMetadata.uri,\n priorityFee,\n bs58.encode(mint.secretKey),\n requiredLiquidity,\n Number(buyAmountSol) / 10 ** 9\n );\n\n const { blockhash, lastValidBlockHeight } =\n await connection.getLatestBlockhash();\n versionedTx.message.recentBlockhash = blockhash;\n versionedTx.sign([mint]);\n\n const serializedTransaction = versionedTx.serialize();\n const serializedTransactionBase64 = Buffer.from(\n serializedTransaction\n ).toString(\"base64\");\n\n const deserializedTx = VersionedTransaction.deserialize(\n Buffer.from(serializedTransactionBase64, \"base64\")\n );\n\n const txid = await connection.sendTransaction(deserializedTx, {\n skipPreflight: false,\n maxRetries: 3,\n preflightCommitment: \"confirmed\",\n });\n\n elizaLogger.log(\"Transaction sent:\", txid);\n\n // Confirm transaction using the blockhash\n const confirmation = await connection.confirmTransaction(\n {\n signature: txid,\n blockhash: blockhash,\n lastValidBlockHeight: lastValidBlockHeight,\n },\n commitment\n );\n\n if (!confirmation.value.err) {\n elizaLogger.log(\n \"Success:\",\n `https://fomo.fund/token/${mint.publicKey.toBase58()}`\n );\n const ata = getAssociatedTokenAddressSync(\n mint.publicKey,\n deployer.publicKey,\n allowOffCurve\n );\n const balance = await connection.getTokenAccountBalance(\n ata,\n \"processed\"\n );\n const amount = balance.value.uiAmount;\n if (amount === null) {\n elizaLogger.log(\n `${deployer.publicKey.toBase58()}:`,\n \"No Account Found\"\n );\n } else {\n elizaLogger.log(`${deployer.publicKey.toBase58()}:`, amount);\n }\n\n return {\n success: true,\n ca: mint.publicKey.toBase58(),\n creator: deployer.publicKey.toBase58(),\n };\n } else {\n elizaLogger.log(\"Create and Buy failed\");\n return {\n success: false,\n ca: mint.publicKey.toBase58(),\n error: confirmation.value.err || \"Transaction failed\",\n };\n }\n};\n\nexport const buyToken = async ({\n fomo,\n buyer,\n mint,\n amount,\n priorityFee,\n allowOffCurve,\n slippage,\n connection,\n currency = \"sol\",\n commitment = \"confirmed\",\n}: {\n fomo: Fomo;\n buyer: Keypair;\n mint: PublicKey;\n amount: number;\n priorityFee: number;\n allowOffCurve: boolean;\n slippage: number;\n connection: Connection;\n currency: PurchaseCurrency;\n commitment?:\n | \"processed\"\n | \"confirmed\"\n | \"finalized\"\n | \"recent\"\n | \"single\"\n | \"singleGossip\"\n | \"root\"\n | \"max\";\n}) => {\n const buyVersionedTx = await fomo.buyToken(\n buyer.publicKey,\n mint,\n amount,\n slippage,\n priorityFee,\n currency || \"sol\"\n );\n\n const { blockhash, lastValidBlockHeight } =\n await connection.getLatestBlockhash();\n buyVersionedTx.message.recentBlockhash = blockhash;\n\n const serializedTransaction = buyVersionedTx.serialize();\n const serializedTransactionBase64 = Buffer.from(\n serializedTransaction\n ).toString(\"base64\");\n\n const deserializedTx = VersionedTransaction.deserialize(\n Buffer.from(serializedTransactionBase64, \"base64\")\n );\n\n const txid = await connection.sendTransaction(deserializedTx, {\n skipPreflight: false,\n maxRetries: 3,\n preflightCommitment: \"confirmed\",\n });\n\n elizaLogger.log(\"Transaction sent:\", txid);\n\n // Confirm transaction using the blockhash\n const confirmation = await connection.confirmTransaction(\n {\n signature: txid,\n blockhash: blockhash,\n lastValidBlockHeight: lastValidBlockHeight,\n },\n commitment\n );\n\n if (!confirmation.value.err) {\n elizaLogger.log(\n \"Success:\",\n `https://fomo.fund/token/${mint.toBase58()}`\n );\n const ata = getAssociatedTokenAddressSync(\n mint,\n buyer.publicKey,\n allowOffCurve\n );\n const balance = await connection.getTokenAccountBalance(\n ata,\n \"processed\"\n );\n const amount = balance.value.uiAmount;\n if (amount === null) {\n elizaLogger.log(\n `${buyer.publicKey.toBase58()}:`,\n \"No Account Found\"\n );\n } else {\n elizaLogger.log(`${buyer.publicKey.toBase58()}:`, amount);\n }\n } else {\n elizaLogger.log(\"Buy failed\");\n }\n};\n\nexport const sellToken = async ({\n fomo,\n seller,\n mint,\n amount,\n priorityFee,\n allowOffCurve,\n slippage,\n connection,\n currency = \"token\",\n commitment = \"confirmed\",\n}: {\n fomo: Fomo;\n seller: Keypair;\n mint: PublicKey;\n amount: number;\n priorityFee: number;\n allowOffCurve: boolean;\n slippage: number;\n connection: Connection;\n currency: PurchaseCurrency;\n commitment?:\n | \"processed\"\n | \"confirmed\"\n | \"finalized\"\n | \"recent\"\n | \"single\"\n | \"singleGossip\"\n | \"root\"\n | \"max\";\n}) => {\n const sellVersionedTx = await fomo.sellToken(\n seller.publicKey,\n mint,\n amount,\n slippage,\n priorityFee,\n currency || \"token\"\n );\n\n const { blockhash, lastValidBlockHeight } =\n await connection.getLatestBlockhash();\n sellVersionedTx.message.recentBlockhash = blockhash;\n\n const serializedTransaction = sellVersionedTx.serialize();\n const serializedTransactionBase64 = Buffer.from(\n serializedTransaction\n ).toString(\"base64\");\n\n const deserializedTx = VersionedTransaction.deserialize(\n Buffer.from(serializedTransactionBase64, \"base64\")\n );\n\n const txid = await connection.sendTransaction(deserializedTx, {\n skipPreflight: false,\n maxRetries: 3,\n preflightCommitment: \"confirmed\",\n });\n\n elizaLogger.log(\"Transaction sent:\", txid);\n\n // Confirm transaction using the blockhash\n const confirmation = await connection.confirmTransaction(\n {\n signature: txid,\n blockhash: blockhash,\n lastValidBlockHeight: lastValidBlockHeight,\n },\n commitment\n );\n\n if (!confirmation.value.err) {\n elizaLogger.log(\n \"Success:\",\n `https://fomo.fund/token/${mint.toBase58()}`\n );\n const ata = getAssociatedTokenAddressSync(\n mint,\n seller.publicKey,\n allowOffCurve\n );\n const balance = await connection.getTokenAccountBalance(\n ata,\n \"processed\"\n );\n const amount = balance.value.uiAmount;\n if (amount === null) {\n elizaLogger.log(\n `${seller.publicKey.toBase58()}:`,\n \"No Account Found\"\n );\n } else {\n elizaLogger.log(`${seller.publicKey.toBase58()}:`, amount);\n }\n } else {\n elizaLogger.log(\"Sell failed\");\n }\n};\n\nconst promptConfirmation = async (): Promise => {\n return true;\n};\n\nconst fomoTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined.\n\nExample response:\n\\`\\`\\`json\n{\n \"tokenMetadata\": {\n \"name\": \"Test Token\",\n \"symbol\": \"TEST\",\n \"description\": \"A test token\",\n \"image_description\": \"create an image of a rabbit\"\n },\n \"buyAmountSol\": \"0.00069\",\n \"requiredLiquidity\": \"85\"\n}\n\\`\\`\\`\n\n{{recentMessages}}\n\nGiven the recent messages, extract or generate (come up with if not included) the following information about the requested token creation:\n- Token name\n- Token symbol\n- Token description\n- Token image description\n- Amount of SOL to buy\n\nRespond with a JSON markdown block containing only the extracted values.`;\n\nexport default {\n name: \"CREATE_AND_BUY_TOKEN\",\n similes: [\"CREATE_AND_PURCHASE_TOKEN\", \"DEPLOY_AND_BUY_TOKEN\"],\n validate: async (_runtime: IAgentRuntime, _message: Memory) => {\n return true; //return isCreateAndBuyContent(runtime, message.content);\n },\n description:\n \"Create a new token and buy a specified amount using SOL. Requires deployer private key, token metadata, buy amount in SOL, priority fee, and allowOffCurve flag.\",\n handler: async (\n runtime: IAgentRuntime,\n message: Memory,\n state: State,\n _options: { [key: string]: unknown },\n callback?: HandlerCallback\n ): Promise => {\n elizaLogger.log(\"Starting CREATE_AND_BUY_TOKEN handler...\");\n\n // Compose state if not provided\n if (!state) {\n state = (await runtime.composeState(message)) as State;\n } else {\n state = await runtime.updateRecentMessageState(state);\n }\n\n // Get wallet info for context\n const walletInfo = await walletProvider.get(runtime, message, state);\n state.walletInfo = walletInfo;\n\n // Generate structured content from natural language\n const pumpContext = composeContext({\n state,\n template: fomoTemplate,\n });\n\n const content = await generateObject({\n runtime,\n context: pumpContext,\n modelClass: ModelClass.LARGE,\n });\n\n // Validate the generated content\n if (!isCreateAndBuyContentForFomo(content)) {\n elizaLogger.error(\n \"Invalid content for CREATE_AND_BUY_TOKEN action.\"\n );\n return false;\n }\n\n const { tokenMetadata, buyAmountSol, requiredLiquidity } = content;\n /*\n // Generate image if tokenMetadata.file is empty or invalid\n if (!tokenMetadata.file || tokenMetadata.file.length < 100) { // Basic validation\n try {\n const imageResult = await generateImage({\n prompt: `logo for ${tokenMetadata.name} (${tokenMetadata.symbol}) token - ${tokenMetadata.description}`,\n width: 512,\n height: 512,\n count: 1\n }, runtime);\n\n if (imageResult.success && imageResult.data && imageResult.data.length > 0) {\n // Remove the \"data:image/png;base64,\" prefix if present\n tokenMetadata.file = imageResult.data[0].replace(/^data:image\\/[a-z]+;base64,/, '');\n } else {\n elizaLogger.error(\"Failed to generate image:\", imageResult.error);\n return false;\n }\n } catch (error) {\n elizaLogger.error(\"Error generating image:\", error);\n return false;\n }\n } */\n\n const imageResult = await generateImage(\n {\n prompt: `logo for ${tokenMetadata.name} (${tokenMetadata.symbol}) token - ${tokenMetadata.description}`,\n width: 256,\n height: 256,\n count: 1,\n },\n runtime\n );\n\n const imageBuffer = Buffer.from(imageResult.data[0], \"base64\");\n const formData = new FormData();\n const blob = new Blob([imageBuffer], { type: \"image/png\" });\n formData.append(\"file\", blob, `${tokenMetadata.name}.png`);\n formData.append(\"name\", tokenMetadata.name);\n formData.append(\"symbol\", tokenMetadata.symbol);\n formData.append(\"description\", tokenMetadata.description);\n\n // FIXME: does fomo.fund have an ipfs call?\n const metadataResponse = await fetch(\"https://pump.fun/api/ipfs\", {\n method: \"POST\",\n body: formData,\n });\n const metadataResponseJSON = (await metadataResponse.json()) as {\n name: string;\n symbol: string;\n metadataUri: string;\n };\n // Add the default decimals and convert file to Blob\n const fullTokenMetadata: CreateTokenMetadata = {\n name: tokenMetadata.name,\n symbol: tokenMetadata.symbol,\n uri: metadataResponseJSON.metadataUri,\n };\n\n // Default priority fee for high network load\n const priorityFee = {\n unitLimit: 100_000_000,\n unitPrice: 100_000,\n };\n const slippage = \"2000\";\n try {\n // Get private key from settings and create deployer keypair\n const privateKeyString =\n runtime.getSetting(\"SOLANA_PRIVATE_KEY\") ??\n runtime.getSetting(\"WALLET_PRIVATE_KEY\");\n const secretKey = bs58.decode(privateKeyString);\n const deployerKeypair = Keypair.fromSecretKey(secretKey) as any;\n\n // Generate new mint keypair\n const mintKeypair = Keypair.generate() as any;\n elizaLogger.log(\n `Generated mint address: ${mintKeypair.publicKey.toBase58()}`\n );\n\n // Setup connection and SDK\n const connection = new Connection(settings.SOLANA_RPC_URL!, {\n commitment: \"confirmed\",\n confirmTransactionInitialTimeout: 500000,\n wsEndpoint: settings.SOLANA_RPC_URL!.replace(\"https\", \"wss\"),\n });\n\n const sdk = new Fomo(connection as any, \"devnet\", deployerKeypair);\n // const slippage = runtime.getSetting(\"SLIPPAGE\");\n\n const createAndBuyConfirmation = await promptConfirmation();\n if (!createAndBuyConfirmation) {\n elizaLogger.log(\"Create and buy token canceled by user\");\n return false;\n }\n\n // Convert SOL to lamports (1 SOL = 1_000_000_000 lamports)\n const lamports = Math.floor(Number(buyAmountSol) * 1_000_000_000);\n\n elizaLogger.log(\"Executing create and buy transaction...\");\n const result = await createAndBuyToken({\n deployer: deployerKeypair as any,\n mint: mintKeypair as any,\n tokenMetadata: fullTokenMetadata,\n buyAmountSol: BigInt(lamports),\n priorityFee: priorityFee.unitPrice,\n requiredLiquidity: Number(requiredLiquidity),\n allowOffCurve: false,\n fomo: sdk,\n connection,\n slippage,\n });\n\n if (callback) {\n if (result.success) {\n callback({\n text: `Token ${tokenMetadata.name} (${tokenMetadata.symbol}) created successfully!\\nURL: https://fomo.fund/token/${result.ca}\\nCreator: ${result.creator}\\nView at: https://fomo.fund/token/${result.ca}`,\n content: {\n tokenInfo: {\n symbol: tokenMetadata.symbol,\n address: result.ca,\n creator: result.creator,\n name: tokenMetadata.name,\n description: tokenMetadata.description,\n timestamp: Date.now(),\n },\n },\n });\n } else {\n callback({\n text: `Failed to create token: ${result.error}\\nAttempted mint address: ${result.ca}`,\n content: {\n error: result.error,\n mintAddress: result.ca,\n },\n });\n }\n }\n //await trustScoreDb.addToken(tokenInfo);\n /*\n // Update runtime state\n await runtime.updateState({\n ...state,\n lastCreatedToken: tokenInfo\n });\n */\n // Log success message with token view URL\n const successMessage = `Token created and purchased successfully! View at: https://fomo.fund/token/${mintKeypair.publicKey.toBase58()}`;\n elizaLogger.log(successMessage);\n return result.success;\n } catch (error) {\n if (callback) {\n callback({\n text: `Error during token creation: ${error.message}`,\n content: { error: error.message },\n });\n }\n return false;\n }\n },\n\n examples: [\n [\n {\n user: \"{{user1}}\",\n content: {\n text: \"Create a new token called GLITCHIZA with symbol GLITCHIZA and generate a description about it on fomo.fund. Also come up with a description for it to use for image generation .buy 0.00069 SOL worth.\",\n },\n },\n {\n user: \"{{user2}}\",\n content: {\n text: \"Token GLITCHIZA (GLITCHIZA) created successfully on fomo.fund!\\nURL: https://fomo.fund/token/673247855e8012181f941f84\\nCreator: Anonymous\\nView at: https://fomo.fund/token/673247855e8012181f941f84\",\n action: \"CREATE_AND_BUY_TOKEN\",\n content: {\n tokenInfo: {\n symbol: \"GLITCHIZA\",\n address:\n \"EugPwuZ8oUMWsYHeBGERWvELfLGFmA1taDtmY8uMeX6r\",\n creator:\n \"9jW8FPr6BSSsemWPV22UUCzSqkVdTp6HTyPqeqyuBbCa\",\n name: \"GLITCHIZA\",\n description: \"A GLITCHIZA token\",\n },\n },\n },\n },\n ],\n ] as ActionExample[][],\n} as Action;","import { generateImage, elizaLogger } from \"@elizaos/core\";\nimport {\n Connection,\n Keypair,\n type PublicKey,\n VersionedTransaction,\n} from \"@solana/web3.js\";\nimport { Fomo, type PurchaseCurrency } from \"fomo-sdk-solana\";\nimport { getAssociatedTokenAddressSync } from \"@solana/spl-token\";\nimport bs58 from \"bs58\";\nimport {\n settings,\n type ActionExample,\n type Content,\n type HandlerCallback,\n type IAgentRuntime,\n type Memory,\n ModelClass,\n type State,\n generateObject,\n composeContext,\n type Action,\n} from \"@elizaos/core\";\n\nimport { walletProvider } from \"../providers/wallet.ts\";\n\ninterface CreateTokenMetadata {\n name: string;\n symbol: string;\n uri: string;\n}\n\nexport interface CreateAndBuyContent extends Content {\n tokenMetadata: {\n name: string;\n symbol: string;\n description: string;\n image_description: string;\n };\n buyAmountSol: string | number;\n requiredLiquidity: string | number;\n}\n\nexport function isCreateAndBuyContentForFomo(\n content: any\n): content is CreateAndBuyContent {\n elizaLogger.log(\"Content for create & buy\", content);\n return (\n typeof content.tokenMetadata === \"object\" &&\n content.tokenMetadata !== null &&\n typeof content.tokenMetadata.name === \"string\" &&\n typeof content.tokenMetadata.symbol === \"string\" &&\n typeof content.tokenMetadata.description === \"string\" &&\n typeof content.tokenMetadata.image_description === \"string\" &&\n (typeof content.buyAmountSol === \"string\" ||\n typeof content.buyAmountSol === \"number\") &&\n typeof content.requiredLiquidity === \"number\"\n );\n}\n\nexport const createAndBuyToken = async ({\n deployer,\n mint,\n tokenMetadata,\n buyAmountSol,\n priorityFee,\n requiredLiquidity = 85,\n allowOffCurve,\n commitment = \"confirmed\",\n fomo,\n connection,\n}: {\n deployer: Keypair;\n mint: Keypair;\n tokenMetadata: CreateTokenMetadata;\n buyAmountSol: bigint;\n priorityFee: number;\n requiredLiquidity: number;\n allowOffCurve: boolean;\n commitment?:\n | \"processed\"\n | \"confirmed\"\n | \"finalized\"\n | \"recent\"\n | \"single\"\n | \"singleGossip\"\n | \"root\"\n | \"max\";\n fomo: Fomo;\n connection: Connection;\n slippage: string;\n}) => {\n const { transaction: versionedTx } = await fomo.createToken(\n deployer.publicKey,\n tokenMetadata.name,\n tokenMetadata.symbol,\n tokenMetadata.uri,\n priorityFee,\n bs58.encode(mint.secretKey),\n requiredLiquidity,\n Number(buyAmountSol) / 10 ** 9\n );\n\n const { blockhash, lastValidBlockHeight } =\n await connection.getLatestBlockhash();\n versionedTx.message.recentBlockhash = blockhash;\n versionedTx.sign([mint]);\n\n const serializedTransaction = versionedTx.serialize();\n const serializedTransactionBase64 = Buffer.from(\n serializedTransaction\n ).toString(\"base64\");\n\n const deserializedTx = VersionedTransaction.deserialize(\n Buffer.from(serializedTransactionBase64, \"base64\")\n );\n\n const txid = await connection.sendTransaction(deserializedTx, {\n skipPreflight: false,\n maxRetries: 3,\n preflightCommitment: \"confirmed\",\n });\n\n elizaLogger.log(\"Transaction sent:\", txid);\n\n // Confirm transaction using the blockhash\n const confirmation = await connection.confirmTransaction(\n {\n signature: txid,\n blockhash: blockhash,\n lastValidBlockHeight: lastValidBlockHeight,\n },\n commitment\n );\n\n if (!confirmation.value.err) {\n elizaLogger.log(\n \"Success:\",\n `https://fomo.fund/token/${mint.publicKey.toBase58()}`\n );\n const ata = getAssociatedTokenAddressSync(\n mint.publicKey,\n deployer.publicKey,\n allowOffCurve\n );\n const balance = await connection.getTokenAccountBalance(\n ata,\n \"processed\"\n );\n const amount = balance.value.uiAmount;\n if (amount === null) {\n elizaLogger.log(\n `${deployer.publicKey.toBase58()}:`,\n \"No Account Found\"\n );\n } else {\n elizaLogger.log(`${deployer.publicKey.toBase58()}:`, amount);\n }\n\n return {\n success: true,\n ca: mint.publicKey.toBase58(),\n creator: deployer.publicKey.toBase58(),\n };\n } else {\n elizaLogger.log(\"Create and Buy failed\");\n return {\n success: false,\n ca: mint.publicKey.toBase58(),\n error: confirmation.value.err || \"Transaction failed\",\n };\n }\n};\n\nexport const buyToken = async ({\n fomo,\n buyer,\n mint,\n amount,\n priorityFee,\n allowOffCurve,\n slippage,\n connection,\n currency = \"sol\",\n commitment = \"confirmed\",\n}: {\n fomo: Fomo;\n buyer: Keypair;\n mint: PublicKey;\n amount: number;\n priorityFee: number;\n allowOffCurve: boolean;\n slippage: number;\n connection: Connection;\n currency: PurchaseCurrency;\n commitment?:\n | \"processed\"\n | \"confirmed\"\n | \"finalized\"\n | \"recent\"\n | \"single\"\n | \"singleGossip\"\n | \"root\"\n | \"max\";\n}) => {\n const buyVersionedTx = await fomo.buyToken(\n buyer.publicKey,\n mint,\n amount,\n slippage,\n priorityFee,\n currency || \"sol\"\n );\n\n const { blockhash, lastValidBlockHeight } =\n await connection.getLatestBlockhash();\n buyVersionedTx.message.recentBlockhash = blockhash;\n\n const serializedTransaction = buyVersionedTx.serialize();\n const serializedTransactionBase64 = Buffer.from(\n serializedTransaction\n ).toString(\"base64\");\n\n const deserializedTx = VersionedTransaction.deserialize(\n Buffer.from(serializedTransactionBase64, \"base64\")\n );\n\n const txid = await connection.sendTransaction(deserializedTx, {\n skipPreflight: false,\n maxRetries: 3,\n preflightCommitment: \"confirmed\",\n });\n\n elizaLogger.log(\"Transaction sent:\", txid);\n\n // Confirm transaction using the blockhash\n const confirmation = await connection.confirmTransaction(\n {\n signature: txid,\n blockhash: blockhash,\n lastValidBlockHeight: lastValidBlockHeight,\n },\n commitment\n );\n\n if (!confirmation.value.err) {\n elizaLogger.log(\n \"Success:\",\n `https://fomo.fund/token/${mint.toBase58()}`\n );\n const ata = getAssociatedTokenAddressSync(\n mint,\n buyer.publicKey,\n allowOffCurve\n );\n const balance = await connection.getTokenAccountBalance(\n ata,\n \"processed\"\n );\n const amount = balance.value.uiAmount;\n if (amount === null) {\n elizaLogger.log(\n `${buyer.publicKey.toBase58()}:`,\n \"No Account Found\"\n );\n } else {\n elizaLogger.log(`${buyer.publicKey.toBase58()}:`, amount);\n }\n } else {\n elizaLogger.log(\"Buy failed\");\n }\n};\n\nexport const sellToken = async ({\n fomo,\n seller,\n mint,\n amount,\n priorityFee,\n allowOffCurve,\n slippage,\n connection,\n currency = \"token\",\n commitment = \"confirmed\",\n}: {\n fomo: Fomo;\n seller: Keypair;\n mint: PublicKey;\n amount: number;\n priorityFee: number;\n allowOffCurve: boolean;\n slippage: number;\n connection: Connection;\n currency: PurchaseCurrency;\n commitment?:\n | \"processed\"\n | \"confirmed\"\n | \"finalized\"\n | \"recent\"\n | \"single\"\n | \"singleGossip\"\n | \"root\"\n | \"max\";\n}) => {\n const sellVersionedTx = await fomo.sellToken(\n seller.publicKey,\n mint,\n amount,\n slippage,\n priorityFee,\n currency || \"token\"\n );\n\n const { blockhash, lastValidBlockHeight } =\n await connection.getLatestBlockhash();\n sellVersionedTx.message.recentBlockhash = blockhash;\n\n const serializedTransaction = sellVersionedTx.serialize();\n const serializedTransactionBase64 = Buffer.from(\n serializedTransaction\n ).toString(\"base64\");\n\n const deserializedTx = VersionedTransaction.deserialize(\n Buffer.from(serializedTransactionBase64, \"base64\")\n );\n\n const txid = await connection.sendTransaction(deserializedTx, {\n skipPreflight: false,\n maxRetries: 3,\n preflightCommitment: \"confirmed\",\n });\n\n elizaLogger.log(\"Transaction sent:\", txid);\n\n // Confirm transaction using the blockhash\n const confirmation = await connection.confirmTransaction(\n {\n signature: txid,\n blockhash: blockhash,\n lastValidBlockHeight: lastValidBlockHeight,\n },\n commitment\n );\n\n if (!confirmation.value.err) {\n elizaLogger.log(\n \"Success:\",\n `https://fomo.fund/token/${mint.toBase58()}`\n );\n const ata = getAssociatedTokenAddressSync(\n mint,\n seller.publicKey,\n allowOffCurve\n );\n const balance = await connection.getTokenAccountBalance(\n ata,\n \"processed\"\n );\n const amount = balance.value.uiAmount;\n if (amount === null) {\n elizaLogger.log(\n `${seller.publicKey.toBase58()}:`,\n \"No Account Found\"\n );\n } else {\n elizaLogger.log(`${seller.publicKey.toBase58()}:`, amount);\n }\n } else {\n elizaLogger.log(\"Sell failed\");\n }\n};\n\nconst promptConfirmation = async (): Promise => {\n return true;\n};\n\nconst fomoTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined.\n\nExample response:\n\\`\\`\\`json\n{\n \"tokenMetadata\": {\n \"name\": \"Test Token\",\n \"symbol\": \"TEST\",\n \"description\": \"A test token\",\n \"image_description\": \"create an image of a rabbit\"\n },\n \"buyAmountSol\": \"0.00069\",\n \"requiredLiquidity\": \"85\"\n}\n\\`\\`\\`\n\n{{recentMessages}}\n\nGiven the recent messages, extract or generate (come up with if not included) the following information about the requested token creation:\n- Token name\n- Token symbol\n- Token description\n- Token image description\n- Amount of SOL to buy\n\nRespond with a JSON markdown block containing only the extracted values.`;\n\nexport default {\n name: \"CREATE_AND_BUY_TOKEN\",\n similes: [\"CREATE_AND_PURCHASE_TOKEN\", \"DEPLOY_AND_BUY_TOKEN\"],\n validate: async (_runtime: IAgentRuntime, _message: Memory) => {\n return true; //return isCreateAndBuyContent(runtime, message.content);\n },\n description:\n \"Create a new token and buy a specified amount using SOL. Requires deployer private key, token metadata, buy amount in SOL, priority fee, and allowOffCurve flag.\",\n handler: async (\n runtime: IAgentRuntime,\n message: Memory,\n state: State,\n _options: { [key: string]: unknown },\n callback?: HandlerCallback\n ): Promise => {\n elizaLogger.log(\"Starting CREATE_AND_BUY_TOKEN handler...\");\n\n // Compose state if not provided\n if (!state) {\n state = (await runtime.composeState(message)) as State;\n } else {\n state = await runtime.updateRecentMessageState(state);\n }\n\n // Get wallet info for context\n const walletInfo = await walletProvider.get(runtime, message, state);\n state.walletInfo = walletInfo;\n\n // Generate structured content from natural language\n const pumpContext = composeContext({\n state,\n template: fomoTemplate,\n });\n\n const content = await generateObject({\n runtime,\n context: pumpContext,\n modelClass: ModelClass.LARGE,\n });\n\n // Validate the generated content\n if (!isCreateAndBuyContentForFomo(content)) {\n elizaLogger.error(\n \"Invalid content for CREATE_AND_BUY_TOKEN action.\"\n );\n return false;\n }\n\n const { tokenMetadata, buyAmountSol, requiredLiquidity } = content;\n /*\n // Generate image if tokenMetadata.file is empty or invalid\n if (!tokenMetadata.file || tokenMetadata.file.length < 100) { // Basic validation\n try {\n const imageResult = await generateImage({\n prompt: `logo for ${tokenMetadata.name} (${tokenMetadata.symbol}) token - ${tokenMetadata.description}`,\n width: 512,\n height: 512,\n count: 1\n }, runtime);\n\n if (imageResult.success && imageResult.data && imageResult.data.length > 0) {\n // Remove the \"data:image/png;base64,\" prefix if present\n tokenMetadata.file = imageResult.data[0].replace(/^data:image\\/[a-z]+;base64,/, '');\n } else {\n elizaLogger.error(\"Failed to generate image:\", imageResult.error);\n return false;\n }\n } catch (error) {\n elizaLogger.error(\"Error generating image:\", error);\n return false;\n }\n } */\n\n const imageResult = await generateImage(\n {\n prompt: `logo for ${tokenMetadata.name} (${tokenMetadata.symbol}) token - ${tokenMetadata.description}`,\n width: 256,\n height: 256,\n count: 1,\n },\n runtime\n );\n\n const imageBuffer = Buffer.from(imageResult.data[0], \"base64\");\n const formData = new FormData();\n const blob = new Blob([imageBuffer], { type: \"image/png\" });\n formData.append(\"file\", blob, `${tokenMetadata.name}.png`);\n formData.append(\"name\", tokenMetadata.name);\n formData.append(\"symbol\", tokenMetadata.symbol);\n formData.append(\"description\", tokenMetadata.description);\n\n // FIXME: does fomo.fund have an ipfs call?\n const metadataResponse = await fetch(\"https://pump.fun/api/ipfs\", {\n method: \"POST\",\n body: formData,\n });\n const metadataResponseJSON = (await metadataResponse.json()) as {\n name: string;\n symbol: string;\n metadataUri: string;\n };\n // Add the default decimals and convert file to Blob\n const fullTokenMetadata: CreateTokenMetadata = {\n name: tokenMetadata.name,\n symbol: tokenMetadata.symbol,\n uri: metadataResponseJSON.metadataUri,\n };\n\n // Default priority fee for high network load\n const priorityFee = {\n unitLimit: 100_000_000,\n unitPrice: 100_000,\n };\n const slippage = \"2000\";\n try {\n // Get private key from settings and create deployer keypair\n const privateKeyString =\n runtime.getSetting(\"SOLANA_PRIVATE_KEY\") ??\n runtime.getSetting(\"WALLET_PRIVATE_KEY\");\n const secretKey = bs58.decode(privateKeyString);\n const deployerKeypair = Keypair.fromSecretKey(secretKey);\n\n // Generate new mint keypair\n const mintKeypair = Keypair.generate();\n elizaLogger.log(\n `Generated mint address: ${mintKeypair.publicKey.toBase58()}`\n );\n\n // Setup connection and SDK\n const connection = new Connection(settings.SOLANA_RPC_URL!, {\n commitment: \"confirmed\",\n confirmTransactionInitialTimeout: 500000, // 120 seconds\n wsEndpoint: settings.SOLANA_RPC_URL!.replace(\"https\", \"wss\"),\n });\n\n const sdk = new Fomo(connection, \"devnet\", deployerKeypair);\n // const slippage = runtime.getSetting(\"SLIPPAGE\");\n\n const createAndBuyConfirmation = await promptConfirmation();\n if (!createAndBuyConfirmation) {\n elizaLogger.log(\"Create and buy token canceled by user\");\n return false;\n }\n\n // Convert SOL to lamports (1 SOL = 1_000_000_000 lamports)\n const lamports = Math.floor(Number(buyAmountSol) * 1_000_000_000);\n\n elizaLogger.log(\"Executing create and buy transaction...\");\n const result = await createAndBuyToken({\n deployer: deployerKeypair,\n mint: mintKeypair,\n tokenMetadata: fullTokenMetadata,\n buyAmountSol: BigInt(lamports),\n priorityFee: priorityFee.unitPrice,\n requiredLiquidity: Number(requiredLiquidity),\n allowOffCurve: false,\n fomo: sdk,\n connection,\n slippage,\n });\n\n if (callback) {\n if (result.success) {\n callback({\n text: `Token ${tokenMetadata.name} (${tokenMetadata.symbol}) created successfully!\\nURL: https://fomo.fund/token/${result.ca}\\nCreator: ${result.creator}\\nView at: https://fomo.fund/token/${result.ca}`,\n content: {\n tokenInfo: {\n symbol: tokenMetadata.symbol,\n address: result.ca,\n creator: result.creator,\n name: tokenMetadata.name,\n description: tokenMetadata.description,\n timestamp: Date.now(),\n },\n },\n });\n } else {\n callback({\n text: `Failed to create token: ${result.error}\\nAttempted mint address: ${result.ca}`,\n content: {\n error: result.error,\n mintAddress: result.ca,\n },\n });\n }\n }\n //await trustScoreDb.addToken(tokenInfo);\n /*\n // Update runtime state\n await runtime.updateState({\n ...state,\n lastCreatedToken: tokenInfo\n });\n */\n // Log success message with token view URL\n const successMessage = `Token created and purchased successfully! View at: https://fomo.fund/token/${mintKeypair.publicKey.toBase58()}`;\n elizaLogger.log(successMessage);\n return result.success;\n } catch (error) {\n if (callback) {\n callback({\n text: `Error during token creation: ${error.message}`,\n content: { error: error.message },\n });\n }\n return false;\n }\n },\n\n examples: [\n [\n {\n user: \"{{user1}}\",\n content: {\n text: \"Create a new token called GLITCHIZA with symbol GLITCHIZA and generate a description about it on fomo.fund. Also come up with a description for it to use for image generation .buy 0.00069 SOL worth.\",\n },\n },\n {\n user: \"{{user2}}\",\n content: {\n text: \"Token GLITCHIZA (GLITCHIZA) created successfully on fomo.fund!\\nURL: https://fomo.fund/token/673247855e8012181f941f84\\nCreator: Anonymous\\nView at: https://fomo.fund/token/673247855e8012181f941f84\",\n action: \"CREATE_AND_BUY_TOKEN\",\n content: {\n tokenInfo: {\n symbol: \"GLITCHIZA\",\n address:\n \"EugPwuZ8oUMWsYHeBGERWvELfLGFmA1taDtmY8uMeX6r\",\n creator:\n \"9jW8FPr6BSSsemWPV22UUCzSqkVdTp6HTyPqeqyuBbCa\",\n name: \"GLITCHIZA\",\n description: \"A GLITCHIZA token\",\n },\n },\n },\n },\n ],\n ] as ActionExample[][],\n} as Action;\n","import {\n type ActionExample,\n type IAgentRuntime,\n type Memory,\n type Action,\n elizaLogger,\n} from \"@elizaos/core\";\nimport { Connection, type Keypair, PublicKey, Transaction } from \"@solana/web3.js\";\nimport { getQuote } from \"./swapUtils.ts\";\nimport { getWalletKey } from \"../keypairUtils.ts\";\n\nasync function invokeSwapDao(\n connection: Connection,\n authority: Keypair,\n statePDA: PublicKey,\n walletPDA: PublicKey,\n instructionData: Buffer\n): Promise {\n const discriminator = new Uint8Array([\n 25, 143, 207, 190, 174, 228, 130, 107,\n ]);\n\n // Combine discriminator and instructionData into a single Uint8Array\n const combinedData = new Uint8Array(\n discriminator.length + instructionData.length\n );\n combinedData.set(discriminator, 0);\n combinedData.set(instructionData, discriminator.length);\n\n const transaction = new Transaction().add({\n programId: new PublicKey(\"PROGRAM_ID\"),\n keys: [\n { pubkey: authority.publicKey, isSigner: true, isWritable: true },\n { pubkey: statePDA, isSigner: false, isWritable: true },\n { pubkey: walletPDA, isSigner: false, isWritable: true },\n ],\n data: Buffer.from(combinedData),\n });\n\n const signature = await connection.sendTransaction(transaction, [\n authority,\n ]);\n await connection.confirmTransaction(signature);\n return signature;\n}\n\nasync function promptConfirmation(): Promise {\n // confirmation logic here\n const confirmSwap = window.confirm(\"Confirm the token swap?\");\n return confirmSwap;\n}\n\nexport const executeSwapForDAO: Action = {\n name: \"EXECUTE_SWAP_DAO\",\n similes: [\"SWAP_TOKENS_DAO\", \"TOKEN_SWAP_DAO\"],\n validate: async (runtime: IAgentRuntime, message: Memory) => {\n elizaLogger.log(\"Message:\", message);\n return true;\n },\n description: \"Perform a DAO token swap using execute_invoke.\",\n handler: async (\n runtime: IAgentRuntime,\n message: Memory\n ): Promise => {\n const { inputToken, outputToken, amount } = message.content;\n\n try {\n const connection = new Connection(\n runtime.getSetting(\"SOLANA_RPC_URL\") as string\n );\n\n const { keypair: authority } = await getWalletKey(runtime, true);\n\n const daoMint = new PublicKey(runtime.getSetting(\"DAO_MINT\")); // DAO mint address\n\n // Derive PDAs\n const [statePDA] = await PublicKey.findProgramAddress(\n [Buffer.from(\"state\"), daoMint.toBuffer()],\n authority.publicKey\n );\n const [walletPDA] = await PublicKey.findProgramAddress(\n [Buffer.from(\"wallet\"), daoMint.toBuffer()],\n authority.publicKey\n );\n\n const quoteData = await getQuote(\n connection as Connection,\n inputToken as string,\n outputToken as string,\n amount as number\n );\n elizaLogger.log(\"Swap Quote:\", quoteData);\n\n const confirmSwap = await promptConfirmation();\n if (!confirmSwap) {\n elizaLogger.log(\"Swap canceled by user\");\n return false;\n }\n\n // Prepare instruction data for swap\n const instructionData = Buffer.from(\n JSON.stringify({\n quote: quoteData.data,\n userPublicKey: authority.publicKey.toString(),\n wrapAndUnwrapSol: true,\n })\n );\n\n const txid = await invokeSwapDao(\n connection,\n authority,\n statePDA,\n walletPDA,\n instructionData\n );\n\n elizaLogger.log(\"DAO Swap completed successfully!\");\n elizaLogger.log(`Transaction ID: ${txid}`);\n\n return true;\n } catch (error) {\n elizaLogger.error(\"Error during DAO token swap:\", error);\n return false;\n }\n },\n examples: [\n [\n {\n user: \"{{user1}}\",\n content: {\n inputTokenSymbol: \"SOL\",\n outputTokenSymbol: \"USDC\",\n inputToken: \"So11111111111111111111111111111111111111112\",\n outputToken: \"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v\",\n amount: 0.1,\n },\n },\n {\n user: \"{{user2}}\",\n content: {\n text: \"Swapping 0.1 SOL for USDC using DAO...\",\n action: \"TOKEN_SWAP_DAO\",\n },\n },\n {\n user: \"{{user2}}\",\n content: {\n text: \"DAO Swap completed successfully! Transaction ID: ...\",\n },\n },\n ],\n ] as ActionExample[][],\n} as Action;","export * from \"./providers/token.ts\";\nexport * from \"./providers/wallet.ts\";\nexport * from \"./providers/trustScoreProvider.ts\";\nexport * from \"./evaluators/trust.ts\";\nimport type { Plugin } from \"@elizaos/core\";\nimport transferToken from \"./actions/transfer.ts\";\nimport transferSol from \"./actions/transfer_sol.ts\";\nimport { TokenProvider } from \"./providers/token.ts\";\nimport { WalletProvider } from \"./providers/wallet.ts\";\nimport { getTokenBalance, getTokenBalances } from \"./providers/tokenUtils.ts\";\nimport { walletProvider } from \"./providers/wallet.ts\";\nimport { trustScoreProvider } from \"./providers/trustScoreProvider.ts\";\nimport { trustEvaluator } from \"./evaluators/trust.ts\";\nimport { executeSwap } from \"./actions/swap.ts\";\nimport take_order from \"./actions/takeOrder\";\nimport pumpfun from \"./actions/pumpfun.ts\";\nimport fomo from \"./actions/fomo.ts\";\nimport { executeSwapForDAO } from \"./actions/swapDao\";\nexport { TokenProvider, WalletProvider, getTokenBalance, getTokenBalances };\nexport const solanaPlugin: Plugin = {\n name: \"solana\",\n description: \"Solana Plugin for Eliza\",\n actions: [\n transferToken,\n transferSol,\n executeSwap,\n pumpfun,\n fomo,\n executeSwapForDAO,\n take_order,\n ],\n evaluators: [trustEvaluator],\n providers: [walletProvider, trustScoreProvider],\n};\nexport default solanaPlugin;"],"mappings":"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","names":["elizaLogger","NodeCache","elizaLogger","PublicKey","BigNumber","connection","elizaLogger","BigNumber","items","portfolio","PublicKey","Connection","PROVIDER_CONFIG","tokenAddress","walletProvider","NodeCache","elizaLogger","Connection","elizaLogger","settings","Connection","PublicKey","crypto","Connection","PublicKey","elizaLogger","Connection","PublicKey","elizaLogger","tokenAddress","tokenProvider","settings","tokenProvider","Connection","PublicKey","elizaLogger","tokenAddress","elizaLogger","TrustScoreDatabase","Connection","elizaLogger","walletProvider","Connection","tokenProvider","tokenAddress","TrustScoreDatabase","user","elizaLogger","settings","Connection","PublicKey","ModelClass","composeContext","elizaLogger","composeContext","ModelClass","connection","Connection","settings","PublicKey","elizaLogger","settings","Connection","PublicKey","TransactionMessage","VersionedTransaction","ModelClass","composeContext","generateObjectDeprecated","elizaLogger","composeContext","ModelClass","connection","Connection","settings","PublicKey","TransactionMessage","VersionedTransaction","getAssociatedTokenAddress","PublicKey","elizaLogger","connection","getAssociatedTokenAddress","elizaLogger","PublicKey","composeContext","generateObjectDeprecated","ModelClass","settings","elizaLogger","Connection","VersionedTransaction","BigNumber","getAssociatedTokenAddress","Connection","PublicKey","VersionedTransaction","settings","elizaLogger","connection","connection","PublicKey","connection","settings","BigNumber","elizaLogger","walletProvider","Connection","composeContext","generateObjectDeprecated","ModelClass","VersionedTransaction","ModelClass","composeContext","elizaLogger","Connection","Keypair","VersionedTransaction","getAssociatedTokenAddressSync","bs58","settings","ModelClass","composeContext","elizaLogger","connection","bs58","VersionedTransaction","getAssociatedTokenAddressSync","elizaLogger","composeContext","ModelClass","bs58","Keypair","connection","Connection","settings","generateImage","elizaLogger","Connection","Keypair","VersionedTransaction","Fomo","getAssociatedTokenAddressSync","bs58","settings","ModelClass","generateObject","composeContext","isCreateAndBuyContentForFomo","elizaLogger","createAndBuyToken","connection","bs58","VersionedTransaction","getAssociatedTokenAddressSync","promptConfirmation","fomoTemplate","elizaLogger","composeContext","generateObject","ModelClass","isCreateAndBuyContentForFomo","generateImage","bs58","Keypair","connection","Connection","settings","Fomo","createAndBuyToken","elizaLogger","Connection","PublicKey","connection","PublicKey","promptConfirmation","elizaLogger","Connection"]} \ No newline at end of file diff --git a/package.json b/package.json index 59ae261..0b02cdc 100644 --- a/package.json +++ b/package.json @@ -1,101 +1,123 @@ { - "name": "@elizaos-plugins/plugin-solana", - "version": "0.1.9", - "type": "module", - "main": "dist/index.js", - "module": "dist/index.js", - "types": "dist/index.d.ts", - "exports": { - "./package.json": "./package.json", - ".": { - "import": { - "@elizaos/source": "./src/index.ts", - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - } - } - }, - "files": [ - "dist" - ], - "dependencies": { - "@coral-xyz/anchor": "0.30.1", - "@elizaos/plugin-tee": "workspace:*", - "@elizaos/plugin-trustdb": "workspace:*", - "@solana/spl-token": "0.4.9", - "@solana/web3.js": "npm:@solana/web3.js@1.95.8", - "bignumber.js": "9.1.2", - "bs58": "6.0.0", - "fomo-sdk-solana": "1.3.2", - "node-cache": "5.1.2", - "pumpdotfun-sdk": "1.3.2", - "solana-agent-kit": "^1.4.0", - "tsup": "8.3.5", - "vitest": "2.1.9" - }, - "devDependencies": { - "@biomejs/biome": "1.5.3", - "tsup": "^8.3.5" - }, - "scripts": { - "build": "tsup --format esm --dts", - "dev": "tsup --format esm --dts --watch", - "lint": "biome check src/", - "lint:fix": "biome check --apply src/", - "format": "biome format src/", - "format:fix": "biome format --write src/", - "test": "vitest run" - }, - "peerDependencies": { - "form-data": "4.0.1", - "whatwg-url": "7.1.0" - }, - "agentConfig": { - "pluginType": "elizaos:plugin:1.0.0", - "pluginParameters": { - "WALLET_SECRET_KEY": { - "type": "string", - "minLength": 1, - "description": "Wallet secret key is required", - "optional": true - }, - "WALLET_PUBLIC_KEY": { - "type": "string", - "minLength": 1, - "description": "Wallet public key is required", - "optional": true - }, - "WALLET_SECRET_SALT": { - "type": "string", - "minLength": 1, - "description": "Wallet secret salt is required", - "optional": true - }, - "SOL_ADDRESS": { - "type": "string", - "minLength": 1, - "description": "SOL address is required" - }, - "SLIPPAGE": { - "type": "string", - "minLength": 1, - "description": "Slippage is required" - }, - "SOLANA_RPC_URL": { - "type": "string", - "minLength": 1, - "description": "RPC URL is required" - }, - "HELIUS_API_KEY": { - "type": "string", - "minLength": 1, - "description": "Helius API key is required" - }, - "BIRDEYE_API_KEY": { - "type": "string", - "minLength": 1, - "description": "Birdeye API key is required" - } - } + "name": "@elizaos/plugin-solana", + "version": "1.0.3", + "type": "module", + "main": "dist/index.js", + "module": "dist/index.js", + "types": "dist/index.d.ts", + "repository": { + "type": "git", + "url": "git+https://github.com/elizaos-plugins/plugin-solana.git" + }, + "exports": { + "./package.json": "./package.json", + ".": { + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } } -} \ No newline at end of file + }, + "files": [ + "dist" + ], + "dependencies": { + "@elizaos/core": "^1.0.0", + "@solana/spl-token": "0.4.13", + "@solana/web3.js": "^1.98.0", + "bignumber.js": "9.3.0", + "bs58": "6.0.0", + "vitest": "3.1.3" + }, + "devDependencies": { + "prettier": "3.5.3", + "tsup": "8.4.0", + "typescript": "^5.8.2" + }, + "scripts": { + "build": "tsup", + "dev": "tsup --watch", + "test": "vitest run", + "lint": "prettier --write ./src", + "clean": "rm -rf dist .turbo node_modules .turbo-tsconfig.json tsconfig.tsbuildinfo", + "format": "prettier --write ./src", + "format:check": "prettier --check ./src" + }, + "peerDependencies": { + "form-data": "4.0.2", + "whatwg-url": "7.1.0" + }, + "gitHead": "646c632924826e2b75c2304a75ee56959fe4a460", + "agentConfig": { + "pluginType": "elizaos:plugin:1.0.0", + "pluginParameters": { + "SOL_ADDRESS": { + "type": "string", + "description": "The mint/contract address used to represent native SOL when interacting with token swap logic.", + "required": true, + "sensitive": false + }, + "SOLANA_RPC_URL": { + "type": "string", + "description": "The Solana RPC endpoint the application should connect to.", + "required": false, + "default": "https://api.mainnet-beta.solana.com", + "sensitive": false + }, + "WALLET_SECRET_SALT": { + "type": "string", + "description": "Salt used to derive or encrypt the Solana wallet’s secret key; required if the direct secret key is not provided.", + "required": false, + "sensitive": false + }, + "WALLET_SECRET_KEY": { + "type": "string", + "description": "Base58-encoded Solana wallet secret (private) key; required if WALLET_SECRET_SALT is not supplied.", + "required": false, + "sensitive": true + }, + "WALLET_PUBLIC_KEY": { + "type": "string", + "description": "Base58-encoded Solana wallet public key corresponding to WALLET_SECRET_KEY.", + "required": false, + "sensitive": false + }, + "SOLANA_PUBLIC_KEY": { + "type": "string", + "description": "Alternative name accepted by runtime for the wallet’s public key.", + "required": false, + "sensitive": false + }, + "SLIPPAGE": { + "type": "string", + "description": "Maximum acceptable slippage (likely as a percentage or basis points) for Solana swaps/transactions.", + "required": true, + "sensitive": false + }, + "HELIUS_API_KEY": { + "type": "string", + "description": "API key for accessing the Helius Solana infrastructure services.", + "required": true, + "sensitive": true + }, + "BIRDEYE_API_KEY": { + "type": "string", + "description": "API key for accessing Birdeye market data services.", + "required": true, + "sensitive": true + }, + "SOLANA_PRIVATE_KEY": { + "type": "string", + "description": "The Solana wallet private key in base58 or base64 format, used to create a Keypair when private key access is required.", + "required": false, + "sensitive": true + }, + "WALLET_PRIVATE_KEY": { + "type": "string", + "description": "Alternative variable name for the Solana wallet private key in base58 or base64.", + "required": false, + "sensitive": true + } + } + } +} diff --git a/src/actions/fomo.ts b/src/actions/fomo.ts deleted file mode 100644 index 98adf05..0000000 --- a/src/actions/fomo.ts +++ /dev/null @@ -1,641 +0,0 @@ -import { generateImage, elizaLogger } from "@elizaos/core"; -import { - Connection, - Keypair, - type PublicKey, - VersionedTransaction, -} from "@solana/web3.js"; -import { Fomo, type PurchaseCurrency } from "fomo-sdk-solana"; -import { getAssociatedTokenAddressSync } from "@solana/spl-token"; -import bs58 from "bs58"; -import { - settings, - type ActionExample, - type Content, - type HandlerCallback, - type IAgentRuntime, - type Memory, - ModelClass, - type State, - generateObject, - composeContext, - type Action, -} from "@elizaos/core"; - -import { walletProvider } from "../providers/wallet.ts"; - -interface CreateTokenMetadata { - name: string; - symbol: string; - uri: string; -} - -export interface CreateAndBuyContent extends Content { - tokenMetadata: { - name: string; - symbol: string; - description: string; - image_description: string; - }; - buyAmountSol: string | number; - requiredLiquidity: string | number; -} - -export function isCreateAndBuyContentForFomo( - content: any -): content is CreateAndBuyContent { - elizaLogger.log("Content for create & buy", content); - return ( - typeof content.tokenMetadata === "object" && - content.tokenMetadata !== null && - typeof content.tokenMetadata.name === "string" && - typeof content.tokenMetadata.symbol === "string" && - typeof content.tokenMetadata.description === "string" && - typeof content.tokenMetadata.image_description === "string" && - (typeof content.buyAmountSol === "string" || - typeof content.buyAmountSol === "number") && - typeof content.requiredLiquidity === "number" - ); -} - -export const createAndBuyToken = async ({ - deployer, - mint, - tokenMetadata, - buyAmountSol, - priorityFee, - requiredLiquidity = 85, - allowOffCurve, - commitment = "confirmed", - fomo, - connection, -}: { - deployer: Keypair; - mint: Keypair; - tokenMetadata: CreateTokenMetadata; - buyAmountSol: bigint; - priorityFee: number; - requiredLiquidity: number; - allowOffCurve: boolean; - commitment?: - | "processed" - | "confirmed" - | "finalized" - | "recent" - | "single" - | "singleGossip" - | "root" - | "max"; - fomo: Fomo; - connection: Connection; - slippage: string; -}) => { - const { transaction: versionedTx } = await fomo.createToken( - deployer.publicKey, - tokenMetadata.name, - tokenMetadata.symbol, - tokenMetadata.uri, - priorityFee, - bs58.encode(mint.secretKey), - requiredLiquidity, - Number(buyAmountSol) / 10 ** 9 - ); - - const { blockhash, lastValidBlockHeight } = - await connection.getLatestBlockhash(); - versionedTx.message.recentBlockhash = blockhash; - versionedTx.sign([mint]); - - const serializedTransaction = versionedTx.serialize(); - const serializedTransactionBase64 = Buffer.from( - serializedTransaction - ).toString("base64"); - - const deserializedTx = VersionedTransaction.deserialize( - Buffer.from(serializedTransactionBase64, "base64") - ); - - const txid = await connection.sendTransaction(deserializedTx, { - skipPreflight: false, - maxRetries: 3, - preflightCommitment: "confirmed", - }); - - elizaLogger.log("Transaction sent:", txid); - - // Confirm transaction using the blockhash - const confirmation = await connection.confirmTransaction( - { - signature: txid, - blockhash: blockhash, - lastValidBlockHeight: lastValidBlockHeight, - }, - commitment - ); - - if (!confirmation.value.err) { - elizaLogger.log( - "Success:", - `https://fomo.fund/token/${mint.publicKey.toBase58()}` - ); - const ata = getAssociatedTokenAddressSync( - mint.publicKey, - deployer.publicKey, - allowOffCurve - ); - const balance = await connection.getTokenAccountBalance( - ata, - "processed" - ); - const amount = balance.value.uiAmount; - if (amount === null) { - elizaLogger.log( - `${deployer.publicKey.toBase58()}:`, - "No Account Found" - ); - } else { - elizaLogger.log(`${deployer.publicKey.toBase58()}:`, amount); - } - - return { - success: true, - ca: mint.publicKey.toBase58(), - creator: deployer.publicKey.toBase58(), - }; - } else { - elizaLogger.log("Create and Buy failed"); - return { - success: false, - ca: mint.publicKey.toBase58(), - error: confirmation.value.err || "Transaction failed", - }; - } -}; - -export const buyToken = async ({ - fomo, - buyer, - mint, - amount, - priorityFee, - allowOffCurve, - slippage, - connection, - currency = "sol", - commitment = "confirmed", -}: { - fomo: Fomo; - buyer: Keypair; - mint: PublicKey; - amount: number; - priorityFee: number; - allowOffCurve: boolean; - slippage: number; - connection: Connection; - currency: PurchaseCurrency; - commitment?: - | "processed" - | "confirmed" - | "finalized" - | "recent" - | "single" - | "singleGossip" - | "root" - | "max"; -}) => { - const buyVersionedTx = await fomo.buyToken( - buyer.publicKey, - mint, - amount, - slippage, - priorityFee, - currency || "sol" - ); - - const { blockhash, lastValidBlockHeight } = - await connection.getLatestBlockhash(); - buyVersionedTx.message.recentBlockhash = blockhash; - - const serializedTransaction = buyVersionedTx.serialize(); - const serializedTransactionBase64 = Buffer.from( - serializedTransaction - ).toString("base64"); - - const deserializedTx = VersionedTransaction.deserialize( - Buffer.from(serializedTransactionBase64, "base64") - ); - - const txid = await connection.sendTransaction(deserializedTx, { - skipPreflight: false, - maxRetries: 3, - preflightCommitment: "confirmed", - }); - - elizaLogger.log("Transaction sent:", txid); - - // Confirm transaction using the blockhash - const confirmation = await connection.confirmTransaction( - { - signature: txid, - blockhash: blockhash, - lastValidBlockHeight: lastValidBlockHeight, - }, - commitment - ); - - if (!confirmation.value.err) { - elizaLogger.log( - "Success:", - `https://fomo.fund/token/${mint.toBase58()}` - ); - const ata = getAssociatedTokenAddressSync( - mint, - buyer.publicKey, - allowOffCurve - ); - const balance = await connection.getTokenAccountBalance( - ata, - "processed" - ); - const amount = balance.value.uiAmount; - if (amount === null) { - elizaLogger.log( - `${buyer.publicKey.toBase58()}:`, - "No Account Found" - ); - } else { - elizaLogger.log(`${buyer.publicKey.toBase58()}:`, amount); - } - } else { - elizaLogger.log("Buy failed"); - } -}; - -export const sellToken = async ({ - fomo, - seller, - mint, - amount, - priorityFee, - allowOffCurve, - slippage, - connection, - currency = "token", - commitment = "confirmed", -}: { - fomo: Fomo; - seller: Keypair; - mint: PublicKey; - amount: number; - priorityFee: number; - allowOffCurve: boolean; - slippage: number; - connection: Connection; - currency: PurchaseCurrency; - commitment?: - | "processed" - | "confirmed" - | "finalized" - | "recent" - | "single" - | "singleGossip" - | "root" - | "max"; -}) => { - const sellVersionedTx = await fomo.sellToken( - seller.publicKey, - mint, - amount, - slippage, - priorityFee, - currency || "token" - ); - - const { blockhash, lastValidBlockHeight } = - await connection.getLatestBlockhash(); - sellVersionedTx.message.recentBlockhash = blockhash; - - const serializedTransaction = sellVersionedTx.serialize(); - const serializedTransactionBase64 = Buffer.from( - serializedTransaction - ).toString("base64"); - - const deserializedTx = VersionedTransaction.deserialize( - Buffer.from(serializedTransactionBase64, "base64") - ); - - const txid = await connection.sendTransaction(deserializedTx, { - skipPreflight: false, - maxRetries: 3, - preflightCommitment: "confirmed", - }); - - elizaLogger.log("Transaction sent:", txid); - - // Confirm transaction using the blockhash - const confirmation = await connection.confirmTransaction( - { - signature: txid, - blockhash: blockhash, - lastValidBlockHeight: lastValidBlockHeight, - }, - commitment - ); - - if (!confirmation.value.err) { - elizaLogger.log( - "Success:", - `https://fomo.fund/token/${mint.toBase58()}` - ); - const ata = getAssociatedTokenAddressSync( - mint, - seller.publicKey, - allowOffCurve - ); - const balance = await connection.getTokenAccountBalance( - ata, - "processed" - ); - const amount = balance.value.uiAmount; - if (amount === null) { - elizaLogger.log( - `${seller.publicKey.toBase58()}:`, - "No Account Found" - ); - } else { - elizaLogger.log(`${seller.publicKey.toBase58()}:`, amount); - } - } else { - elizaLogger.log("Sell failed"); - } -}; - -const promptConfirmation = async (): Promise => { - return true; -}; - -const fomoTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined. - -Example response: -\`\`\`json -{ - "tokenMetadata": { - "name": "Test Token", - "symbol": "TEST", - "description": "A test token", - "image_description": "create an image of a rabbit" - }, - "buyAmountSol": "0.00069", - "requiredLiquidity": "85" -} -\`\`\` - -{{recentMessages}} - -Given the recent messages, extract or generate (come up with if not included) the following information about the requested token creation: -- Token name -- Token symbol -- Token description -- Token image description -- Amount of SOL to buy - -Respond with a JSON markdown block containing only the extracted values.`; - -export default { - name: "CREATE_AND_BUY_TOKEN", - similes: ["CREATE_AND_PURCHASE_TOKEN", "DEPLOY_AND_BUY_TOKEN"], - validate: async (_runtime: IAgentRuntime, _message: Memory) => { - return true; //return isCreateAndBuyContent(runtime, message.content); - }, - description: - "Create a new token and buy a specified amount using SOL. Requires deployer private key, token metadata, buy amount in SOL, priority fee, and allowOffCurve flag.", - handler: async ( - runtime: IAgentRuntime, - message: Memory, - state: State, - _options: { [key: string]: unknown }, - callback?: HandlerCallback - ): Promise => { - elizaLogger.log("Starting CREATE_AND_BUY_TOKEN handler..."); - - // Compose state if not provided - if (!state) { - state = (await runtime.composeState(message)) as State; - } else { - state = await runtime.updateRecentMessageState(state); - } - - // Get wallet info for context - const walletInfo = await walletProvider.get(runtime, message, state); - state.walletInfo = walletInfo; - - // Generate structured content from natural language - const pumpContext = composeContext({ - state, - template: fomoTemplate, - }); - - const content = await generateObject({ - runtime, - context: pumpContext, - modelClass: ModelClass.LARGE, - }); - - // Validate the generated content - if (!isCreateAndBuyContentForFomo(content)) { - elizaLogger.error( - "Invalid content for CREATE_AND_BUY_TOKEN action." - ); - return false; - } - - const { tokenMetadata, buyAmountSol, requiredLiquidity } = content; - /* - // Generate image if tokenMetadata.file is empty or invalid - if (!tokenMetadata.file || tokenMetadata.file.length < 100) { // Basic validation - try { - const imageResult = await generateImage({ - prompt: `logo for ${tokenMetadata.name} (${tokenMetadata.symbol}) token - ${tokenMetadata.description}`, - width: 512, - height: 512, - count: 1 - }, runtime); - - if (imageResult.success && imageResult.data && imageResult.data.length > 0) { - // Remove the "data:image/png;base64," prefix if present - tokenMetadata.file = imageResult.data[0].replace(/^data:image\/[a-z]+;base64,/, ''); - } else { - elizaLogger.error("Failed to generate image:", imageResult.error); - return false; - } - } catch (error) { - elizaLogger.error("Error generating image:", error); - return false; - } - } */ - - const imageResult = await generateImage( - { - prompt: `logo for ${tokenMetadata.name} (${tokenMetadata.symbol}) token - ${tokenMetadata.description}`, - width: 256, - height: 256, - count: 1, - }, - runtime - ); - - const imageBuffer = Buffer.from(imageResult.data[0], "base64"); - const formData = new FormData(); - const blob = new Blob([imageBuffer], { type: "image/png" }); - formData.append("file", blob, `${tokenMetadata.name}.png`); - formData.append("name", tokenMetadata.name); - formData.append("symbol", tokenMetadata.symbol); - formData.append("description", tokenMetadata.description); - - // FIXME: does fomo.fund have an ipfs call? - const metadataResponse = await fetch("https://pump.fun/api/ipfs", { - method: "POST", - body: formData, - }); - const metadataResponseJSON = (await metadataResponse.json()) as { - name: string; - symbol: string; - metadataUri: string; - }; - // Add the default decimals and convert file to Blob - const fullTokenMetadata: CreateTokenMetadata = { - name: tokenMetadata.name, - symbol: tokenMetadata.symbol, - uri: metadataResponseJSON.metadataUri, - }; - - // Default priority fee for high network load - const priorityFee = { - unitLimit: 100_000_000, - unitPrice: 100_000, - }; - const slippage = "2000"; - try { - // Get private key from settings and create deployer keypair - const privateKeyString = - runtime.getSetting("SOLANA_PRIVATE_KEY") ?? - runtime.getSetting("WALLET_PRIVATE_KEY"); - const secretKey = bs58.decode(privateKeyString); - const deployerKeypair = Keypair.fromSecretKey(secretKey); - - // Generate new mint keypair - const mintKeypair = Keypair.generate(); - elizaLogger.log( - `Generated mint address: ${mintKeypair.publicKey.toBase58()}` - ); - - // Setup connection and SDK - const connection = new Connection(settings.SOLANA_RPC_URL!, { - commitment: "confirmed", - confirmTransactionInitialTimeout: 500000, // 120 seconds - wsEndpoint: settings.SOLANA_RPC_URL!.replace("https", "wss"), - }); - - const sdk = new Fomo(connection, "devnet", deployerKeypair); - // const slippage = runtime.getSetting("SLIPPAGE"); - - const createAndBuyConfirmation = await promptConfirmation(); - if (!createAndBuyConfirmation) { - elizaLogger.log("Create and buy token canceled by user"); - return false; - } - - // Convert SOL to lamports (1 SOL = 1_000_000_000 lamports) - const lamports = Math.floor(Number(buyAmountSol) * 1_000_000_000); - - elizaLogger.log("Executing create and buy transaction..."); - const result = await createAndBuyToken({ - deployer: deployerKeypair, - mint: mintKeypair, - tokenMetadata: fullTokenMetadata, - buyAmountSol: BigInt(lamports), - priorityFee: priorityFee.unitPrice, - requiredLiquidity: Number(requiredLiquidity), - allowOffCurve: false, - fomo: sdk, - connection, - slippage, - }); - - if (callback) { - if (result.success) { - callback({ - text: `Token ${tokenMetadata.name} (${tokenMetadata.symbol}) created successfully!\nURL: https://fomo.fund/token/${result.ca}\nCreator: ${result.creator}\nView at: https://fomo.fund/token/${result.ca}`, - content: { - tokenInfo: { - symbol: tokenMetadata.symbol, - address: result.ca, - creator: result.creator, - name: tokenMetadata.name, - description: tokenMetadata.description, - timestamp: Date.now(), - }, - }, - }); - } else { - callback({ - text: `Failed to create token: ${result.error}\nAttempted mint address: ${result.ca}`, - content: { - error: result.error, - mintAddress: result.ca, - }, - }); - } - } - //await trustScoreDb.addToken(tokenInfo); - /* - // Update runtime state - await runtime.updateState({ - ...state, - lastCreatedToken: tokenInfo - }); - */ - // Log success message with token view URL - const successMessage = `Token created and purchased successfully! View at: https://fomo.fund/token/${mintKeypair.publicKey.toBase58()}`; - elizaLogger.log(successMessage); - return result.success; - } catch (error) { - if (callback) { - callback({ - text: `Error during token creation: ${error.message}`, - content: { error: error.message }, - }); - } - return false; - } - }, - - examples: [ - [ - { - user: "{{user1}}", - content: { - text: "Create a new token called GLITCHIZA with symbol GLITCHIZA and generate a description about it on fomo.fund. Also come up with a description for it to use for image generation .buy 0.00069 SOL worth.", - }, - }, - { - user: "{{user2}}", - content: { - text: "Token GLITCHIZA (GLITCHIZA) created successfully on fomo.fund!\nURL: https://fomo.fund/token/673247855e8012181f941f84\nCreator: Anonymous\nView at: https://fomo.fund/token/673247855e8012181f941f84", - action: "CREATE_AND_BUY_TOKEN", - content: { - tokenInfo: { - symbol: "GLITCHIZA", - address: - "EugPwuZ8oUMWsYHeBGERWvELfLGFmA1taDtmY8uMeX6r", - creator: - "9jW8FPr6BSSsemWPV22UUCzSqkVdTp6HTyPqeqyuBbCa", - name: "GLITCHIZA", - description: "A GLITCHIZA token", - }, - }, - }, - }, - ], - ] as ActionExample[][], -} as Action; diff --git a/src/actions/pumpfun.ts b/src/actions/pumpfun.ts deleted file mode 100644 index 57b81c3..0000000 --- a/src/actions/pumpfun.ts +++ /dev/null @@ -1,637 +0,0 @@ -import { generateImage, elizaLogger } from "@elizaos/core"; -import { Connection, Keypair, type PublicKey } from "@solana/web3.js"; -import { VersionedTransaction } from "@solana/web3.js"; -import { Fomo, type PurchaseCurrency } from "fomo-sdk-solana"; -import { getAssociatedTokenAddressSync } from "@solana/spl-token"; -import bs58 from "bs58"; -import { - settings, - type ActionExample, - type Content, - type HandlerCallback, - type IAgentRuntime, - type Memory, - ModelClass, - type State, - generateObject, - composeContext, - type Action, -} from "@elizaos/core"; - -import { walletProvider } from "../providers/wallet.ts"; - -interface CreateTokenMetadata { - name: string; - symbol: string; - uri: string; -} - -export interface CreateAndBuyContent extends Content { - tokenMetadata: { - name: string; - symbol: string; - description: string; - image_description: string; - }; - buyAmountSol: string | number; - requiredLiquidity: string | number; -} - -export function isCreateAndBuyContentForFomo( - content: any -): content is CreateAndBuyContent { - elizaLogger.log("Content for create & buy", content); - return ( - typeof content.tokenMetadata === "object" && - content.tokenMetadata !== null && - typeof content.tokenMetadata.name === "string" && - typeof content.tokenMetadata.symbol === "string" && - typeof content.tokenMetadata.description === "string" && - typeof content.tokenMetadata.image_description === "string" && - (typeof content.buyAmountSol === "string" || - typeof content.buyAmountSol === "number") && - typeof content.requiredLiquidity === "number" - ); -} - -export const createAndBuyToken = async ({ - deployer, - mint, - tokenMetadata, - buyAmountSol, - priorityFee, - requiredLiquidity = 85, - allowOffCurve, - commitment = "confirmed", - fomo, - connection, -}: { - deployer: Keypair; - mint: Keypair; - tokenMetadata: CreateTokenMetadata; - buyAmountSol: bigint; - priorityFee: number; - requiredLiquidity: number; - allowOffCurve: boolean; - commitment?: - | "processed" - | "confirmed" - | "finalized" - | "recent" - | "single" - | "singleGossip" - | "root" - | "max"; - fomo: Fomo; - connection: Connection; - slippage: string; -}) => { - const { transaction: versionedTx } = await fomo.createToken( - deployer.publicKey, - tokenMetadata.name, - tokenMetadata.symbol, - tokenMetadata.uri, - priorityFee, - bs58.encode(mint.secretKey), - requiredLiquidity, - Number(buyAmountSol) / 10 ** 9 - ); - - const { blockhash, lastValidBlockHeight } = - await connection.getLatestBlockhash(); - versionedTx.message.recentBlockhash = blockhash; - versionedTx.sign([mint]); - - const serializedTransaction = versionedTx.serialize(); - const serializedTransactionBase64 = Buffer.from( - serializedTransaction - ).toString("base64"); - - const deserializedTx = VersionedTransaction.deserialize( - Buffer.from(serializedTransactionBase64, "base64") - ); - - const txid = await connection.sendTransaction(deserializedTx, { - skipPreflight: false, - maxRetries: 3, - preflightCommitment: "confirmed", - }); - - elizaLogger.log("Transaction sent:", txid); - - // Confirm transaction using the blockhash - const confirmation = await connection.confirmTransaction( - { - signature: txid, - blockhash: blockhash, - lastValidBlockHeight: lastValidBlockHeight, - }, - commitment - ); - - if (!confirmation.value.err) { - elizaLogger.log( - "Success:", - `https://fomo.fund/token/${mint.publicKey.toBase58()}` - ); - const ata = getAssociatedTokenAddressSync( - mint.publicKey, - deployer.publicKey, - allowOffCurve - ); - const balance = await connection.getTokenAccountBalance( - ata, - "processed" - ); - const amount = balance.value.uiAmount; - if (amount === null) { - elizaLogger.log( - `${deployer.publicKey.toBase58()}:`, - "No Account Found" - ); - } else { - elizaLogger.log(`${deployer.publicKey.toBase58()}:`, amount); - } - - return { - success: true, - ca: mint.publicKey.toBase58(), - creator: deployer.publicKey.toBase58(), - }; - } else { - elizaLogger.log("Create and Buy failed"); - return { - success: false, - ca: mint.publicKey.toBase58(), - error: confirmation.value.err || "Transaction failed", - }; - } -}; - -export const buyToken = async ({ - fomo, - buyer, - mint, - amount, - priorityFee, - allowOffCurve, - slippage, - connection, - currency = "sol", - commitment = "confirmed", -}: { - fomo: Fomo; - buyer: Keypair; - mint: PublicKey; - amount: number; - priorityFee: number; - allowOffCurve: boolean; - slippage: number; - connection: Connection; - currency: PurchaseCurrency; - commitment?: - | "processed" - | "confirmed" - | "finalized" - | "recent" - | "single" - | "singleGossip" - | "root" - | "max"; -}) => { - const buyVersionedTx = await fomo.buyToken( - buyer.publicKey, - mint, - amount, - slippage, - priorityFee, - currency || "sol" - ); - - const { blockhash, lastValidBlockHeight } = - await connection.getLatestBlockhash(); - buyVersionedTx.message.recentBlockhash = blockhash; - - const serializedTransaction = buyVersionedTx.serialize(); - const serializedTransactionBase64 = Buffer.from( - serializedTransaction - ).toString("base64"); - - const deserializedTx = VersionedTransaction.deserialize( - Buffer.from(serializedTransactionBase64, "base64") - ); - - const txid = await connection.sendTransaction(deserializedTx, { - skipPreflight: false, - maxRetries: 3, - preflightCommitment: "confirmed", - }); - - elizaLogger.log("Transaction sent:", txid); - - // Confirm transaction using the blockhash - const confirmation = await connection.confirmTransaction( - { - signature: txid, - blockhash: blockhash, - lastValidBlockHeight: lastValidBlockHeight, - }, - commitment - ); - - if (!confirmation.value.err) { - elizaLogger.log( - "Success:", - `https://fomo.fund/token/${mint.toBase58()}` - ); - const ata = getAssociatedTokenAddressSync( - mint, - buyer.publicKey, - allowOffCurve - ); - const balance = await connection.getTokenAccountBalance( - ata, - "processed" - ); - const amount = balance.value.uiAmount; - if (amount === null) { - elizaLogger.log( - `${buyer.publicKey.toBase58()}:`, - "No Account Found" - ); - } else { - elizaLogger.log(`${buyer.publicKey.toBase58()}:`, amount); - } - } else { - elizaLogger.log("Buy failed"); - } -}; - -export const sellToken = async ({ - fomo, - seller, - mint, - amount, - priorityFee, - allowOffCurve, - slippage, - connection, - currency = "token", - commitment = "confirmed", -}: { - fomo: Fomo; - seller: Keypair; - mint: PublicKey; - amount: number; - priorityFee: number; - allowOffCurve: boolean; - slippage: number; - connection: Connection; - currency: PurchaseCurrency; - commitment?: - | "processed" - | "confirmed" - | "finalized" - | "recent" - | "single" - | "singleGossip" - | "root" - | "max"; -}) => { - const sellVersionedTx = await fomo.sellToken( - seller.publicKey, - mint, - amount, - slippage, - priorityFee, - currency || "token" - ); - - const { blockhash, lastValidBlockHeight } = - await connection.getLatestBlockhash(); - sellVersionedTx.message.recentBlockhash = blockhash; - - const serializedTransaction = sellVersionedTx.serialize(); - const serializedTransactionBase64 = Buffer.from( - serializedTransaction - ).toString("base64"); - - const deserializedTx = VersionedTransaction.deserialize( - Buffer.from(serializedTransactionBase64, "base64") - ); - - const txid = await connection.sendTransaction(deserializedTx, { - skipPreflight: false, - maxRetries: 3, - preflightCommitment: "confirmed", - }); - - elizaLogger.log("Transaction sent:", txid); - - // Confirm transaction using the blockhash - const confirmation = await connection.confirmTransaction( - { - signature: txid, - blockhash: blockhash, - lastValidBlockHeight: lastValidBlockHeight, - }, - commitment - ); - - if (!confirmation.value.err) { - elizaLogger.log( - "Success:", - `https://fomo.fund/token/${mint.toBase58()}` - ); - const ata = getAssociatedTokenAddressSync( - mint, - seller.publicKey, - allowOffCurve - ); - const balance = await connection.getTokenAccountBalance( - ata, - "processed" - ); - const amount = balance.value.uiAmount; - if (amount === null) { - elizaLogger.log( - `${seller.publicKey.toBase58()}:`, - "No Account Found" - ); - } else { - elizaLogger.log(`${seller.publicKey.toBase58()}:`, amount); - } - } else { - elizaLogger.log("Sell failed"); - } -}; - -const promptConfirmation = async (): Promise => { - return true; -}; - -const fomoTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined. - -Example response: -\`\`\`json -{ - "tokenMetadata": { - "name": "Test Token", - "symbol": "TEST", - "description": "A test token", - "image_description": "create an image of a rabbit" - }, - "buyAmountSol": "0.00069", - "requiredLiquidity": "85" -} -\`\`\` - -{{recentMessages}} - -Given the recent messages, extract or generate (come up with if not included) the following information about the requested token creation: -- Token name -- Token symbol -- Token description -- Token image description -- Amount of SOL to buy - -Respond with a JSON markdown block containing only the extracted values.`; - -export default { - name: "CREATE_AND_BUY_TOKEN", - similes: ["CREATE_AND_PURCHASE_TOKEN", "DEPLOY_AND_BUY_TOKEN"], - validate: async (_runtime: IAgentRuntime, _message: Memory) => { - return true; //return isCreateAndBuyContent(runtime, message.content); - }, - description: - "Create a new token and buy a specified amount using SOL. Requires deployer private key, token metadata, buy amount in SOL, priority fee, and allowOffCurve flag.", - handler: async ( - runtime: IAgentRuntime, - message: Memory, - state: State, - _options: { [key: string]: unknown }, - callback?: HandlerCallback - ): Promise => { - elizaLogger.log("Starting CREATE_AND_BUY_TOKEN handler..."); - - // Compose state if not provided - if (!state) { - state = (await runtime.composeState(message)) as State; - } else { - state = await runtime.updateRecentMessageState(state); - } - - // Get wallet info for context - const walletInfo = await walletProvider.get(runtime, message, state); - state.walletInfo = walletInfo; - - // Generate structured content from natural language - const pumpContext = composeContext({ - state, - template: fomoTemplate, - }); - - const content = await generateObject({ - runtime, - context: pumpContext, - modelClass: ModelClass.LARGE, - }); - - // Validate the generated content - if (!isCreateAndBuyContentForFomo(content)) { - elizaLogger.error( - "Invalid content for CREATE_AND_BUY_TOKEN action." - ); - return false; - } - - const { tokenMetadata, buyAmountSol, requiredLiquidity } = content; - /* - // Generate image if tokenMetadata.file is empty or invalid - if (!tokenMetadata.file || tokenMetadata.file.length < 100) { // Basic validation - try { - const imageResult = await generateImage({ - prompt: `logo for ${tokenMetadata.name} (${tokenMetadata.symbol}) token - ${tokenMetadata.description}`, - width: 512, - height: 512, - count: 1 - }, runtime); - - if (imageResult.success && imageResult.data && imageResult.data.length > 0) { - // Remove the "data:image/png;base64," prefix if present - tokenMetadata.file = imageResult.data[0].replace(/^data:image\/[a-z]+;base64,/, ''); - } else { - elizaLogger.error("Failed to generate image:", imageResult.error); - return false; - } - } catch (error) { - elizaLogger.error("Error generating image:", error); - return false; - } - } */ - - const imageResult = await generateImage( - { - prompt: `logo for ${tokenMetadata.name} (${tokenMetadata.symbol}) token - ${tokenMetadata.description}`, - width: 256, - height: 256, - count: 1, - }, - runtime - ); - - const imageBuffer = Buffer.from(imageResult.data[0], "base64"); - const formData = new FormData(); - const blob = new Blob([imageBuffer], { type: "image/png" }); - formData.append("file", blob, `${tokenMetadata.name}.png`); - formData.append("name", tokenMetadata.name); - formData.append("symbol", tokenMetadata.symbol); - formData.append("description", tokenMetadata.description); - - // FIXME: does fomo.fund have an ipfs call? - const metadataResponse = await fetch("https://pump.fun/api/ipfs", { - method: "POST", - body: formData, - }); - const metadataResponseJSON = (await metadataResponse.json()) as { - name: string; - symbol: string; - metadataUri: string; - }; - // Add the default decimals and convert file to Blob - const fullTokenMetadata: CreateTokenMetadata = { - name: tokenMetadata.name, - symbol: tokenMetadata.symbol, - uri: metadataResponseJSON.metadataUri, - }; - - // Default priority fee for high network load - const priorityFee = { - unitLimit: 100_000_000, - unitPrice: 100_000, - }; - const slippage = "2000"; - try { - // Get private key from settings and create deployer keypair - const privateKeyString = - runtime.getSetting("SOLANA_PRIVATE_KEY") ?? - runtime.getSetting("WALLET_PRIVATE_KEY"); - const secretKey = bs58.decode(privateKeyString); - const deployerKeypair = Keypair.fromSecretKey(secretKey) as any; - - // Generate new mint keypair - const mintKeypair = Keypair.generate() as any; - elizaLogger.log( - `Generated mint address: ${mintKeypair.publicKey.toBase58()}` - ); - - // Setup connection and SDK - const connection = new Connection(settings.SOLANA_RPC_URL!, { - commitment: "confirmed", - confirmTransactionInitialTimeout: 500000, - wsEndpoint: settings.SOLANA_RPC_URL!.replace("https", "wss"), - }); - - const sdk = new Fomo(connection as any, "devnet", deployerKeypair); - // const slippage = runtime.getSetting("SLIPPAGE"); - - const createAndBuyConfirmation = await promptConfirmation(); - if (!createAndBuyConfirmation) { - elizaLogger.log("Create and buy token canceled by user"); - return false; - } - - // Convert SOL to lamports (1 SOL = 1_000_000_000 lamports) - const lamports = Math.floor(Number(buyAmountSol) * 1_000_000_000); - - elizaLogger.log("Executing create and buy transaction..."); - const result = await createAndBuyToken({ - deployer: deployerKeypair as any, - mint: mintKeypair as any, - tokenMetadata: fullTokenMetadata, - buyAmountSol: BigInt(lamports), - priorityFee: priorityFee.unitPrice, - requiredLiquidity: Number(requiredLiquidity), - allowOffCurve: false, - fomo: sdk, - connection, - slippage, - }); - - if (callback) { - if (result.success) { - callback({ - text: `Token ${tokenMetadata.name} (${tokenMetadata.symbol}) created successfully!\nURL: https://fomo.fund/token/${result.ca}\nCreator: ${result.creator}\nView at: https://fomo.fund/token/${result.ca}`, - content: { - tokenInfo: { - symbol: tokenMetadata.symbol, - address: result.ca, - creator: result.creator, - name: tokenMetadata.name, - description: tokenMetadata.description, - timestamp: Date.now(), - }, - }, - }); - } else { - callback({ - text: `Failed to create token: ${result.error}\nAttempted mint address: ${result.ca}`, - content: { - error: result.error, - mintAddress: result.ca, - }, - }); - } - } - //await trustScoreDb.addToken(tokenInfo); - /* - // Update runtime state - await runtime.updateState({ - ...state, - lastCreatedToken: tokenInfo - }); - */ - // Log success message with token view URL - const successMessage = `Token created and purchased successfully! View at: https://fomo.fund/token/${mintKeypair.publicKey.toBase58()}`; - elizaLogger.log(successMessage); - return result.success; - } catch (error) { - if (callback) { - callback({ - text: `Error during token creation: ${error.message}`, - content: { error: error.message }, - }); - } - return false; - } - }, - - examples: [ - [ - { - user: "{{user1}}", - content: { - text: "Create a new token called GLITCHIZA with symbol GLITCHIZA and generate a description about it on fomo.fund. Also come up with a description for it to use for image generation .buy 0.00069 SOL worth.", - }, - }, - { - user: "{{user2}}", - content: { - text: "Token GLITCHIZA (GLITCHIZA) created successfully on fomo.fund!\nURL: https://fomo.fund/token/673247855e8012181f941f84\nCreator: Anonymous\nView at: https://fomo.fund/token/673247855e8012181f941f84", - action: "CREATE_AND_BUY_TOKEN", - content: { - tokenInfo: { - symbol: "GLITCHIZA", - address: - "EugPwuZ8oUMWsYHeBGERWvELfLGFmA1taDtmY8uMeX6r", - creator: - "9jW8FPr6BSSsemWPV22UUCzSqkVdTp6HTyPqeqyuBbCa", - name: "GLITCHIZA", - description: "A GLITCHIZA token", - }, - }, - }, - }, - ], - ] as ActionExample[][], -} as Action; \ No newline at end of file diff --git a/src/actions/swap.ts b/src/actions/swap.ts index aa0123a..76b30f0 100644 --- a/src/actions/swap.ts +++ b/src/actions/swap.ts @@ -1,104 +1,194 @@ import { - type ActionExample, - composeContext, - generateObjectDeprecated, - type HandlerCallback, - type IAgentRuntime, - type Memory, - ModelClass, - settings, - type State, - type Action, - elizaLogger, -} from "@elizaos/core"; -import { Connection, type PublicKey, VersionedTransaction } from "@solana/web3.js"; -import BigNumber from "bignumber.js"; -import { getWalletKey } from "../keypairUtils.ts"; -import { walletProvider, WalletProvider } from "../providers/wallet.ts"; -import { getTokenDecimals } from "./swapUtils.ts"; + type Action, + type ActionExample, + type HandlerCallback, + type IAgentRuntime, + type Memory, + ModelType, + type State, + composePromptFromState, + logger, + parseJSONObjectFromText, +} from '@elizaos/core'; +import { Connection, PublicKey, VersionedTransaction } from '@solana/web3.js'; +import BigNumber from 'bignumber.js'; +import { SOLANA_SERVICE_NAME } from '../constants'; +import { getWalletKey } from '../keypairUtils'; +import type { SolanaService } from '../service'; + +import type { Item } from '../types'; +/** + * Fetches the number of decimals for a given token mint address. + * + * @param {Connection} connection - Solana connection object. + * @param {string} mintAddress - Address of the token mint. + * @returns {Promise} - Number of decimals for the token. + * @throws {Error} - If unable to fetch token decimals. + */ +async function getTokenDecimals(connection: Connection, mintAddress: string): Promise { + const mintPublicKey = new PublicKey(mintAddress); + const tokenAccountInfo = await connection.getParsedAccountInfo(mintPublicKey); + + if ( + tokenAccountInfo.value && + typeof tokenAccountInfo.value.data === 'object' && + 'parsed' in tokenAccountInfo.value.data + ) { + const parsedInfo = tokenAccountInfo.value.data.parsed?.info; + if (parsedInfo && typeof parsedInfo.decimals === 'number') { + return parsedInfo.decimals; + } + } -async function swapToken( - connection: Connection, - walletPublicKey: PublicKey, - inputTokenCA: string, - outputTokenCA: string, - amount: number -): Promise { - try { - // Get the decimals for the input token - const decimals = - inputTokenCA === settings.SOL_ADDRESS - ? new BigNumber(9) - : new BigNumber( - await getTokenDecimals(connection, inputTokenCA) - ); - - elizaLogger.log("Decimals:", decimals.toString()); - - // Use BigNumber for adjustedAmount: amount * (10 ** decimals) - const amountBN = new BigNumber(amount); - const adjustedAmount = amountBN.multipliedBy( - new BigNumber(10).pow(decimals) - ); - - elizaLogger.log("Fetching quote with params:", { - inputMint: inputTokenCA, - outputMint: outputTokenCA, - amount: adjustedAmount, - }); + throw new Error('Unable to fetch token decimals'); +} - const quoteResponse = await fetch( - `https://quote-api.jup.ag/v6/quote?inputMint=${inputTokenCA}&outputMint=${outputTokenCA}&amount=${adjustedAmount}&dynamicSlippage=true&maxAccounts=64` - ); - const quoteData = await quoteResponse.json(); +/** + * Swaps tokens using the specified connection, wallet public key, input and output token contract addresses, + * and amount. Returns a Promise that resolves to the swap data. + * + * @param {Connection} connection The connection object to use for interacting with the blockchain. + * @param {PublicKey} walletPublicKey The public key of the wallet initiating the swap. + * @param {string} inputTokenCA The contract address of the input token. + * @param {string} outputTokenCA The contract address of the output token. + * @param {number} amount The amount of tokens to swap. + * @returns {Promise} A Promise that resolves to the swap data object. + */ +async function swapToken( + connection: Connection, + walletPublicKey: PublicKey, + inputTokenCA: string, + outputTokenCA: string, + amount: number +): Promise { + try { + const decimals = + inputTokenCA === process.env.SOL_ADDRESS + ? new BigNumber(9) + : new BigNumber(await getTokenDecimals(connection, inputTokenCA)); + + logger.log('Decimals:', decimals.toString()); + + const amountBN = new BigNumber(amount); + const adjustedAmount = amountBN.multipliedBy(new BigNumber(10).pow(decimals)); + + logger.log('Fetching quote with params:', { + inputMint: inputTokenCA, + outputMint: outputTokenCA, + amount: adjustedAmount, + }); + + const quoteResponse = await fetch( + `https://quote-api.jup.ag/v6/quote?inputMint=${inputTokenCA}&outputMint=${outputTokenCA}&amount=${adjustedAmount}&dynamicSlippage=true&maxAccounts=64` + ); + const quoteData = (await quoteResponse.json()) as { + error?: string; + }; - if (!quoteData || quoteData.error) { - elizaLogger.error("Quote error:", quoteData); - throw new Error( - `Failed to get quote: ${quoteData?.error || "Unknown error"}` - ); - } + if (!quoteData || quoteData.error) { + logger.error('Quote error:', quoteData); + throw new Error(`Failed to get quote: ${quoteData?.error || 'Unknown error'}`); + } - elizaLogger.log("Quote received:", quoteData); - - const swapRequestBody = { - quoteResponse: quoteData, - userPublicKey: walletPublicKey.toBase58(), - dynamicComputeUnitLimit: true, - dynamicSlippage: true, - priorityLevelWithMaxLamports: { - maxLamports: 4000000, - priorityLevel: "veryHigh", - }, - }; - - elizaLogger.log("Requesting swap with body:", swapRequestBody); - - const swapResponse = await fetch("https://quote-api.jup.ag/v6/swap", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(swapRequestBody), - }); + const swapRequestBody = { + quoteResponse: quoteData, + userPublicKey: walletPublicKey.toBase58(), + dynamicComputeUnitLimit: true, + dynamicSlippage: true, + priorityLevelWithMaxLamports: { + maxLamports: 4000000, + priorityLevel: 'veryHigh', + }, + }; + + const swapResponse = await fetch('https://quote-api.jup.ag/v6/swap', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(swapRequestBody), + }); + + const swapData = (await swapResponse.json()) as { + error?: string; + swapTransaction?: string; + }; + + if (!swapData || !swapData.swapTransaction) { + logger.error('Swap error:', swapData); + throw new Error( + `Failed to get swap transaction: ${swapData?.error || 'No swap transaction returned'}` + ); + } - const swapData = await swapResponse.json(); + return swapData; + } catch (error) { + logger.error('Error in swapToken:', error); + throw error; + } +} - if (!swapData || !swapData.swapTransaction) { - elizaLogger.error("Swap error:", swapData); - throw new Error( - `Failed to get swap transaction: ${swapData?.error || "No swap transaction returned"}` - ); - } +// Get token from wallet data using SolanaService +/** + * Retrieves the token address from the wallet for the specified token symbol. + * + * @param {IAgentRuntime} runtime - The agent runtime. + * @param {string} tokenSymbol - The token symbol to retrieve the address for. + * @returns {Promise} The token address if found, null otherwise. + */ +async function getTokenFromWallet( + runtime: IAgentRuntime, + tokenSymbol: string +): Promise { + try { + const solanaService = runtime.getService(SOLANA_SERVICE_NAME) as SolanaService; + if (!solanaService) { + throw new Error('SolanaService not initialized'); + } - elizaLogger.log("Swap transaction received"); - return swapData; - } catch (error) { - elizaLogger.error("Error in swapToken:", error); - throw error; + const walletData = await solanaService.getCachedData(); + if (!walletData) { + return null; } + + const token = walletData.items.find( + (item: Item) => item.symbol.toLowerCase() === tokenSymbol.toLowerCase() + ); + + return token ? token.address : null; + } catch (error) { + logger.error('Error checking token in wallet:', error); + return null; + } } +/** + * Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined. + * + * Example response: + * ```json + * { + * "inputTokenSymbol": "SOL", + * "outputTokenSymbol": "USDC", + * "inputTokenCA": "So11111111111111111111111111111111111111112", + * "outputTokenCA": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + * "amount": 1.5 + * } + * ``` + * + * {{recentMessages}} + * + * Given the recent messages and wallet information below: + * + * {{walletInfo}} + * + * Extract the following information about the requested token swap: + * - Input token symbol (the token being sold) + * - Output token symbol (the token being bought) + * - Input token contract address if provided + * - Output token contract address if provided + * - Amount to swap + * + * Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined. + */ const swapTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined. Example response: @@ -125,287 +215,184 @@ Extract the following information about the requested token swap: - Output token contract address if provided - Amount to swap -Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined. The result should be a valid JSON object with the following schema: -\`\`\`json -{ - "inputTokenSymbol": string | null, - "outputTokenSymbol": string | null, - "inputTokenCA": string | null, - "outputTokenCA": string | null, - "amount": number | string | null -} -\`\`\``; - -// if we get the token symbol but not the CA, check walet for matching token, and if we have, get the CA for it +Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined.`; -// get all the tokens in the wallet using the wallet provider -async function getTokensInWallet(runtime: IAgentRuntime) { - const { publicKey } = await getWalletKey(runtime, false); - const walletProvider = new WalletProvider( - new Connection("https://api.mainnet-beta.solana.com"), - publicKey - ); +/** + * Action for executing a token swap from one token to another on Solana. + * + * @type {Action} + * @property {string} name - The name of the action ("SWAP_SOLANA"). + * @property {string[]} similes - Alternative names for the action. + * @property {Function} validate - Asynchronous function to validate if Solana service is available. + * @property {string} description - Description of the action. + * @property {Function} handler - Asynchronous function to handle the token swap process. + * @property {ActionExample[][]} examples - Examples demonstrating how to use the action. + */ - const walletInfo = await walletProvider.fetchPortfolioValue(runtime); - const items = walletInfo.items; - return items; -} +export const executeSwap: Action = { + name: 'SWAP_SOLANA', + similes: [ + 'SWAP_SOL', + 'SWAP_TOKENS_SOLANA', + 'TOKEN_SWAP_SOLANA', + 'TRADE_TOKENS_SOLANA', + 'EXCHANGE_TOKENS_SOLANA', + ], + validate: async (runtime: IAgentRuntime, _message: Memory) => { + const solanaService = runtime.getService(SOLANA_SERVICE_NAME); + return !!solanaService; + }, + description: + 'Perform a token swap from one token to another on Solana. Works with SOL and SPL tokens.', + handler: async ( + runtime: IAgentRuntime, + message: Memory, + state: State | undefined, + _options: { [key: string]: unknown } | undefined, + callback?: HandlerCallback + ): Promise => { + state = await runtime.composeState(message, ['RECENT_MESSAGES']); -// check if the token symbol is in the wallet -async function getTokenFromWallet(runtime: IAgentRuntime, tokenSymbol: string) { try { - const items = await getTokensInWallet(runtime); - const token = items.find((item) => item.symbol === tokenSymbol); - - if (token) { - return token.address; - } else { - return null; + const solanaService = runtime.getService(SOLANA_SERVICE_NAME) as SolanaService; + if (!solanaService) { + throw new Error('SolanaService not initialized'); + } + + const walletData = await solanaService.getCachedData(); + state.values.walletInfo = walletData; + + const swapPrompt = composePromptFromState({ + state, + template: swapTemplate, + }); + + const result = await runtime.useModel(ModelType.TEXT_LARGE, { + prompt: swapPrompt, + }); + + const response = parseJSONObjectFromText(result) as { + inputTokenSymbol?: string; + outputTokenSymbol?: string; + inputTokenCA?: string; + outputTokenCA?: string; + amount?: number; + }; + + // Handle SOL addresses + if (response.inputTokenSymbol?.toUpperCase() === 'SOL') { + response.inputTokenCA = process.env.SOL_ADDRESS; + } + if (response.outputTokenSymbol?.toUpperCase() === 'SOL') { + response.outputTokenCA = process.env.SOL_ADDRESS; + } + + // Resolve token addresses if needed + if (!response.inputTokenCA && response.inputTokenSymbol) { + response.inputTokenCA = + (await getTokenFromWallet(runtime, response.inputTokenSymbol)) || undefined; + if (!response.inputTokenCA) { + callback?.({ text: 'Could not find the input token in your wallet' }); + return false; } - } catch (error) { - elizaLogger.error("Error checking token in wallet:", error); - return null; - } -} - -// swapToken should took CA, not symbol - -export const executeSwap: Action = { - name: "EXECUTE_SWAP", - similes: ["SWAP_TOKENS", "TOKEN_SWAP", "TRADE_TOKENS", "EXCHANGE_TOKENS"], - validate: async (runtime: IAgentRuntime, message: Memory) => { - // Check if the necessary parameters are provided in the message - elizaLogger.log("Message:", message); - return true; - }, - description: "Perform a token swap.", - handler: async ( - runtime: IAgentRuntime, - message: Memory, - state: State, - _options: { [key: string]: unknown }, - callback?: HandlerCallback - ): Promise => { - // composeState - if (!state) { - state = (await runtime.composeState(message)) as State; - } else { - state = await runtime.updateRecentMessageState(state); + } + + if (!response.outputTokenCA && response.outputTokenSymbol) { + response.outputTokenCA = + (await getTokenFromWallet(runtime, response.outputTokenSymbol)) || undefined; + if (!response.outputTokenCA) { + callback?.({ + text: 'Could not find the output token in your wallet', + }); + return false; } - - const walletInfo = await walletProvider.get(runtime, message, state); - - state.walletInfo = walletInfo; - - const swapContext = composeContext({ - state, - template: swapTemplate, - }); - - const response = await generateObjectDeprecated({ - runtime, - context: swapContext, - modelClass: ModelClass.LARGE, + } + + if (!response.amount) { + callback?.({ text: 'Please specify the amount you want to swap' }); + return false; + } + + const connection = new Connection( + runtime.getSetting('SOLANA_RPC_URL') || 'https://api.mainnet-beta.solana.com' + ); + const { publicKey: walletPublicKey } = await getWalletKey(runtime, false); + + const swapResult = (await swapToken( + connection, + walletPublicKey as PublicKey, + response.inputTokenCA as string, + response.outputTokenCA as string, + response.amount as number + )) as { swapTransaction: string }; + + const transactionBuf = Buffer.from(swapResult.swapTransaction, 'base64'); + const transaction = VersionedTransaction.deserialize(transactionBuf); + + const { keypair } = await getWalletKey(runtime, true); + if (keypair?.publicKey.toBase58() !== walletPublicKey?.toBase58()) { + throw new Error("Generated public key doesn't match expected public key"); + } + + if (keypair) { + transaction.sign([keypair]); + } else { + throw new Error('Keypair not found'); + } + + const latestBlockhash = await connection.getLatestBlockhash(); + const txid = await connection.sendTransaction(transaction, { + skipPreflight: false, + maxRetries: 3, + preflightCommitment: 'confirmed', + }); + + const confirmation = await connection.confirmTransaction( + { + signature: txid, + blockhash: latestBlockhash.blockhash, + lastValidBlockHeight: latestBlockhash.lastValidBlockHeight, + }, + 'confirmed' + ); + + if (confirmation.value.err) { + throw new Error(`Transaction failed: ${confirmation.value.err}`); + } + + callback?.({ + text: `Swap completed successfully! Transaction ID: ${txid}`, + content: { success: true, txid }, + }); + + return true; + } catch (error) { + if (error instanceof Error) { + logger.error('Error during token swap:', error); + callback?.({ + text: `Swap failed: ${error.message}`, + content: { error: error.message }, }); - - elizaLogger.log("Response:", response); - // const type = response.inputTokenSymbol?.toUpperCase() === "SOL" ? "buy" : "sell"; - - // Add SOL handling logic - if (response.inputTokenSymbol?.toUpperCase() === "SOL") { - response.inputTokenCA = settings.SOL_ADDRESS; - } - if (response.outputTokenSymbol?.toUpperCase() === "SOL") { - response.outputTokenCA = settings.SOL_ADDRESS; - } - - // if both contract addresses are set, lets execute the swap - // TODO: try to resolve CA from symbol based on existing symbol in wallet - if (!response.inputTokenCA && response.inputTokenSymbol) { - elizaLogger.log( - `Attempting to resolve CA for input token symbol: ${response.inputTokenSymbol}` - ); - response.inputTokenCA = await getTokenFromWallet( - runtime, - response.inputTokenSymbol - ); - if (response.inputTokenCA) { - elizaLogger.log( - `Resolved inputTokenCA: ${response.inputTokenCA}` - ); - } else { - elizaLogger.log( - "No contract addresses provided, skipping swap" - ); - const responseMsg = { - text: "I need the contract addresses to perform the swap", - }; - callback?.(responseMsg); - return true; - } - } - - if (!response.outputTokenCA && response.outputTokenSymbol) { - elizaLogger.log( - `Attempting to resolve CA for output token symbol: ${response.outputTokenSymbol}` - ); - response.outputTokenCA = await getTokenFromWallet( - runtime, - response.outputTokenSymbol - ); - if (response.outputTokenCA) { - elizaLogger.log( - `Resolved outputTokenCA: ${response.outputTokenCA}` - ); - } else { - elizaLogger.log( - "No contract addresses provided, skipping swap" - ); - const responseMsg = { - text: "I need the contract addresses to perform the swap", - }; - callback?.(responseMsg); - return true; - } - } - - if (!response.amount) { - elizaLogger.log("No amount provided, skipping swap"); - const responseMsg = { - text: "I need the amount to perform the swap", - }; - callback?.(responseMsg); - return true; - } - - // TODO: if response amount is half, all, etc, semantically retrieve amount and return as number - if (!response.amount) { - elizaLogger.log("Amount is not a number, skipping swap"); - const responseMsg = { - text: "The amount must be a number", - }; - callback?.(responseMsg); - return true; - } - try { - const connection = new Connection( - "https://api.mainnet-beta.solana.com" - ); - const { publicKey: walletPublicKey } = await getWalletKey( - runtime, - false - ); - - // const provider = new WalletProvider(connection, walletPublicKey); - - elizaLogger.log("Wallet Public Key:", walletPublicKey); - elizaLogger.log("inputTokenSymbol:", response.inputTokenCA); - elizaLogger.log("outputTokenSymbol:", response.outputTokenCA); - elizaLogger.log("amount:", response.amount); - - const swapResult = await swapToken( - connection, - walletPublicKey, - response.inputTokenCA as string, - response.outputTokenCA as string, - response.amount as number - ); - - elizaLogger.log("Deserializing transaction..."); - const transactionBuf = Buffer.from( - swapResult.swapTransaction, - "base64" - ); - const transaction = - VersionedTransaction.deserialize(transactionBuf); - - elizaLogger.log("Preparing to sign transaction..."); - - elizaLogger.log("Creating keypair..."); - const { keypair } = await getWalletKey(runtime, true); - // Verify the public key matches what we expect - if (keypair.publicKey.toBase58() !== walletPublicKey.toBase58()) { - throw new Error( - "Generated public key doesn't match expected public key" - ); - } - - elizaLogger.log("Signing transaction..."); - transaction.sign([keypair]); - - elizaLogger.log("Sending transaction..."); - - const latestBlockhash = await connection.getLatestBlockhash(); - - const txid = await connection.sendTransaction(transaction, { - skipPreflight: false, - maxRetries: 3, - preflightCommitment: "confirmed", - }); - - elizaLogger.log("Transaction sent:", txid); - - // Confirm transaction using the blockhash - const confirmation = await connection.confirmTransaction( - { - signature: txid, - blockhash: latestBlockhash.blockhash, - lastValidBlockHeight: latestBlockhash.lastValidBlockHeight, - }, - "confirmed" - ); - - if (confirmation.value.err) { - throw new Error( - `Transaction failed: ${confirmation.value.err}` - ); - } - - if (confirmation.value.err) { - throw new Error( - `Transaction failed: ${confirmation.value.err}` - ); - } - - elizaLogger.log("Swap completed successfully!"); - elizaLogger.log(`Transaction ID: ${txid}`); - - const responseMsg = { - text: `Swap completed successfully! Transaction ID: ${txid}`, - }; - - callback?.(responseMsg); - - return true; - } catch (error) { - elizaLogger.error("Error during token swap:", error); - return false; - } - }, - examples: [ - [ - { - user: "{{user1}}", - content: { - inputTokenSymbol: "SOL", - outputTokenSymbol: "USDC", - amount: 0.1, - }, - }, - { - user: "{{user2}}", - content: { - text: "Swapping 0.1 SOL for USDC...", - action: "TOKEN_SWAP", - }, - }, - { - user: "{{user2}}", - content: { - text: "Swap completed successfully! Transaction ID: ...", - }, - }, - ], - // Add more examples as needed - ] as ActionExample[][], -} as Action; \ No newline at end of file + return false; + } + throw error; + } + }, + examples: [ + [ + { + name: '{{name1}}', + content: { + text: 'Swap 0.1 SOL for USDC', + }, + }, + { + name: '{{name2}}', + content: { + text: "I'll help you swap 0.1 SOL for USDC", + actions: ['SWAP_SOLANA'], + }, + }, + ], + ] as ActionExample[][], +}; diff --git a/src/actions/swapDao.ts b/src/actions/swapDao.ts deleted file mode 100644 index a2ce02f..0000000 --- a/src/actions/swapDao.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { - type ActionExample, - type IAgentRuntime, - type Memory, - type Action, - elizaLogger, -} from "@elizaos/core"; -import { Connection, type Keypair, PublicKey, Transaction } from "@solana/web3.js"; -import { getQuote } from "./swapUtils.ts"; -import { getWalletKey } from "../keypairUtils.ts"; - -async function invokeSwapDao( - connection: Connection, - authority: Keypair, - statePDA: PublicKey, - walletPDA: PublicKey, - instructionData: Buffer -): Promise { - const discriminator = new Uint8Array([ - 25, 143, 207, 190, 174, 228, 130, 107, - ]); - - // Combine discriminator and instructionData into a single Uint8Array - const combinedData = new Uint8Array( - discriminator.length + instructionData.length - ); - combinedData.set(discriminator, 0); - combinedData.set(instructionData, discriminator.length); - - const transaction = new Transaction().add({ - programId: new PublicKey("PROGRAM_ID"), - keys: [ - { pubkey: authority.publicKey, isSigner: true, isWritable: true }, - { pubkey: statePDA, isSigner: false, isWritable: true }, - { pubkey: walletPDA, isSigner: false, isWritable: true }, - ], - data: Buffer.from(combinedData), - }); - - const signature = await connection.sendTransaction(transaction, [ - authority, - ]); - await connection.confirmTransaction(signature); - return signature; -} - -async function promptConfirmation(): Promise { - // confirmation logic here - const confirmSwap = window.confirm("Confirm the token swap?"); - return confirmSwap; -} - -export const executeSwapForDAO: Action = { - name: "EXECUTE_SWAP_DAO", - similes: ["SWAP_TOKENS_DAO", "TOKEN_SWAP_DAO"], - validate: async (runtime: IAgentRuntime, message: Memory) => { - elizaLogger.log("Message:", message); - return true; - }, - description: "Perform a DAO token swap using execute_invoke.", - handler: async ( - runtime: IAgentRuntime, - message: Memory - ): Promise => { - const { inputToken, outputToken, amount } = message.content; - - try { - const connection = new Connection( - runtime.getSetting("SOLANA_RPC_URL") as string - ); - - const { keypair: authority } = await getWalletKey(runtime, true); - - const daoMint = new PublicKey(runtime.getSetting("DAO_MINT")); // DAO mint address - - // Derive PDAs - const [statePDA] = await PublicKey.findProgramAddress( - [Buffer.from("state"), daoMint.toBuffer()], - authority.publicKey - ); - const [walletPDA] = await PublicKey.findProgramAddress( - [Buffer.from("wallet"), daoMint.toBuffer()], - authority.publicKey - ); - - const quoteData = await getQuote( - connection as Connection, - inputToken as string, - outputToken as string, - amount as number - ); - elizaLogger.log("Swap Quote:", quoteData); - - const confirmSwap = await promptConfirmation(); - if (!confirmSwap) { - elizaLogger.log("Swap canceled by user"); - return false; - } - - // Prepare instruction data for swap - const instructionData = Buffer.from( - JSON.stringify({ - quote: quoteData.data, - userPublicKey: authority.publicKey.toString(), - wrapAndUnwrapSol: true, - }) - ); - - const txid = await invokeSwapDao( - connection, - authority, - statePDA, - walletPDA, - instructionData - ); - - elizaLogger.log("DAO Swap completed successfully!"); - elizaLogger.log(`Transaction ID: ${txid}`); - - return true; - } catch (error) { - elizaLogger.error("Error during DAO token swap:", error); - return false; - } - }, - examples: [ - [ - { - user: "{{user1}}", - content: { - inputTokenSymbol: "SOL", - outputTokenSymbol: "USDC", - inputToken: "So11111111111111111111111111111111111111112", - outputToken: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", - amount: 0.1, - }, - }, - { - user: "{{user2}}", - content: { - text: "Swapping 0.1 SOL for USDC using DAO...", - action: "TOKEN_SWAP_DAO", - }, - }, - { - user: "{{user2}}", - content: { - text: "DAO Swap completed successfully! Transaction ID: ...", - }, - }, - ], - ] as ActionExample[][], -} as Action; \ No newline at end of file diff --git a/src/actions/swapUtils.ts b/src/actions/swapUtils.ts deleted file mode 100644 index 8f30703..0000000 --- a/src/actions/swapUtils.ts +++ /dev/null @@ -1,327 +0,0 @@ -import { getAssociatedTokenAddress } from "@solana/spl-token"; -import { - type BlockhashWithExpiryBlockHeight, - Connection, - type Keypair, - PublicKey, - type RpcResponseAndContext, - type SimulatedTransactionResponse, - type TokenAmount, - VersionedTransaction, -} from "@solana/web3.js"; -import { settings, elizaLogger } from "@elizaos/core"; - -const solAddress = settings.SOL_ADDRESS; -const SLIPPAGE = settings.SLIPPAGE; -const connection = new Connection( - settings.SOLANA_RPC_URL || "https://api.mainnet-beta.solana.com" -); -const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); - -export async function delayedCall( - method: (...args: any[]) => Promise, - ...args: any[] -): Promise { - await delay(150); - return method(...args); -} - -export async function getTokenDecimals( - connection: Connection, - mintAddress: string -): Promise { - const mintPublicKey = new PublicKey(mintAddress); - const tokenAccountInfo = - await connection.getParsedAccountInfo(mintPublicKey); - - // Check if the data is parsed and contains the expected structure - if ( - tokenAccountInfo.value && - typeof tokenAccountInfo.value.data === "object" && - "parsed" in tokenAccountInfo.value.data - ) { - const parsedInfo = tokenAccountInfo.value.data.parsed?.info; - if (parsedInfo && typeof parsedInfo.decimals === "number") { - return parsedInfo.decimals; - } - } - - throw new Error("Unable to fetch token decimals"); -} - -export async function getQuote( - connection: Connection, - baseToken: string, - outputToken: string, - amount: number -): Promise { - const decimals = await getTokenDecimals(connection, baseToken); - const adjustedAmount = amount * 10 ** decimals; - - const quoteResponse = await fetch( - `https://quote-api.jup.ag/v6/quote?inputMint=${baseToken}&outputMint=${outputToken}&amount=${adjustedAmount}&slippageBps=50` - ); - const swapTransaction = await quoteResponse.json(); - const swapTransactionBuf = Buffer.from(swapTransaction, "base64"); - return new Uint8Array(swapTransactionBuf); -} - -export const executeSwap = async ( - transaction: VersionedTransaction, - type: "buy" | "sell" -) => { - try { - const latestBlockhash: BlockhashWithExpiryBlockHeight = - await delayedCall(connection.getLatestBlockhash.bind(connection)); - const signature = await connection.sendTransaction(transaction, { - skipPreflight: false, - }); - const confirmation = await connection.confirmTransaction( - { - signature, - lastValidBlockHeight: latestBlockhash.lastValidBlockHeight, - blockhash: latestBlockhash.blockhash, - }, - "confirmed" - ); - if (confirmation.value.err) { - elizaLogger.log("Confirmation error", confirmation.value.err); - - throw new Error("Confirmation error"); - } else { - if (type === "buy") { - elizaLogger.log( - "Buy successful: https://solscan.io/tx/${signature}" - ); - } else { - elizaLogger.log( - "Sell successful: https://solscan.io/tx/${signature}" - ); - } - } - - return signature; - } catch (error) { - elizaLogger.log(error); - } -}; - -export const Sell = async (baseMint: PublicKey, wallet: Keypair) => { - try { - const tokenAta = await delayedCall( - getAssociatedTokenAddress, - baseMint, - wallet.publicKey - ); - const tokenBalInfo: RpcResponseAndContext = - await delayedCall( - connection.getTokenAccountBalance.bind(connection), - tokenAta - ); - - if (!tokenBalInfo) { - elizaLogger.log("Balance incorrect"); - return null; - } - - const tokenBalance = tokenBalInfo.value.amount; - if (tokenBalance === "0") { - elizaLogger.warn( - `No token balance to sell with wallet ${wallet.publicKey}` - ); - } - - const sellTransaction = await getSwapTxWithWithJupiter( - wallet, - baseMint, - tokenBalance, - "sell" - ); - // simulate the transaction - if (!sellTransaction) { - elizaLogger.log("Failed to get sell transaction"); - return null; - } - - const simulateResult: RpcResponseAndContext = - await delayedCall( - connection.simulateTransaction.bind(connection), - sellTransaction - ); - if (simulateResult.value.err) { - elizaLogger.log("Sell Simulation failed", simulateResult.value.err); - return null; - } - - // execute the transaction - return executeSwap(sellTransaction, "sell"); - } catch (error) { - elizaLogger.log(error); - } -}; - -export const Buy = async (baseMint: PublicKey, wallet: Keypair) => { - try { - const tokenAta = await delayedCall( - getAssociatedTokenAddress, - baseMint, - wallet.publicKey - ); - const tokenBalInfo: RpcResponseAndContext = - await delayedCall( - connection.getTokenAccountBalance.bind(connection), - tokenAta - ); - - if (!tokenBalInfo) { - elizaLogger.log("Balance incorrect"); - return null; - } - - const tokenBalance = tokenBalInfo.value.amount; - if (tokenBalance === "0") { - elizaLogger.warn( - `No token balance to sell with wallet ${wallet.publicKey}` - ); - } - - const buyTransaction = await getSwapTxWithWithJupiter( - wallet, - baseMint, - tokenBalance, - "buy" - ); - // simulate the transaction - if (!buyTransaction) { - elizaLogger.log("Failed to get buy transaction"); - return null; - } - - const simulateResult: RpcResponseAndContext = - await delayedCall( - connection.simulateTransaction.bind(connection), - buyTransaction - ); - if (simulateResult.value.err) { - elizaLogger.log("Buy Simulation failed", simulateResult.value.err); - return null; - } - - // execute the transaction - return executeSwap(buyTransaction, "buy"); - } catch (error) { - elizaLogger.log(error); - } -}; - -export const getSwapTxWithWithJupiter = async ( - wallet: Keypair, - baseMint: PublicKey, - amount: string, - type: "buy" | "sell" -) => { - try { - switch (type) { - case "buy": - return fetchBuyTransaction(wallet, baseMint, amount); - case "sell": - return fetchSellTransaction(wallet, baseMint, amount); - default: - return fetchSellTransaction(wallet, baseMint, amount); - } - } catch (error) { - elizaLogger.log(error); - } -}; - -export const fetchBuyTransaction = async ( - wallet: Keypair, - baseMint: PublicKey, - amount: string -) => { - try { - const quoteResponse = await ( - await fetch( - `https://quote-api.jup.ag/v6/quote?inputMint=${solAddress}&outputMint=${baseMint.toBase58()}&amount=${amount}&slippageBps=${SLIPPAGE}` - ) - ).json(); - const { swapTransaction } = await ( - await fetch("https://quote-api.jup.ag/v6/swap", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - quoteResponse, - userPublicKey: wallet.publicKey.toString(), - wrapAndUnwrapSol: true, - dynamicComputeUnitLimit: true, - prioritizationFeeLamports: 100000, - }), - }) - ).json(); - if (!swapTransaction) { - elizaLogger.log("Failed to get buy transaction"); - return null; - } - - // deserialize the transaction - const swapTransactionBuf = Buffer.from(swapTransaction, "base64"); - const transaction = - VersionedTransaction.deserialize(swapTransactionBuf); - - // sign the transaction - transaction.sign([wallet]); - return transaction; - } catch (error) { - elizaLogger.log("Failed to get buy transaction", error); - return null; - } -}; - -export const fetchSellTransaction = async ( - wallet: Keypair, - baseMint: PublicKey, - amount: string -) => { - try { - const quoteResponse = await ( - await fetch( - `https://quote-api.jup.ag/v6/quote?inputMint=${baseMint.toBase58()}&outputMint=${solAddress}&amount=${amount}&slippageBps=${SLIPPAGE}` - ) - ).json(); - - // get serialized transactions for the swap - const { swapTransaction } = await ( - await fetch("https://quote-api.jup.ag/v6/swap", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - quoteResponse, - userPublicKey: wallet.publicKey.toString(), - wrapAndUnwrapSol: true, - dynamicComputeUnitLimit: true, - prioritizationFeeLamports: 52000, - }), - }) - ).json(); - if (!swapTransaction) { - elizaLogger.log("Failed to get sell transaction"); - return null; - } - - // deserialize the transaction - const swapTransactionBuf = Buffer.from(swapTransaction, "base64"); - const transaction = - VersionedTransaction.deserialize(swapTransactionBuf); - - // sign the transaction - transaction.sign([wallet]); - return transaction; - } catch (error) { - elizaLogger.log("Failed to get sell transaction", error); - return null; - } -}; diff --git a/src/actions/takeOrder.ts b/src/actions/takeOrder.ts deleted file mode 100644 index 0ba211d..0000000 --- a/src/actions/takeOrder.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { - type Action, - type IAgentRuntime, - type Memory, - type Content, - ModelClass, - composeContext, - generateText, -} from "@elizaos/core"; - -interface Order { - userId: string; - ticker: string; - contractAddress: string; - timestamp: string; - buyAmount: number; - price: number; -} - -const take_order: Action = { - name: "TAKE_ORDER", - similes: ["BUY_ORDER", "PLACE_ORDER"], - description: "Records a buy order based on the user's conviction level.", - examples: [], - validate: async (runtime: IAgentRuntime, message: Memory) => { - const text = (message.content as Content).text; - // Check if the message contains a ticker symbol - const tickerRegex = /\b[A-Z]{1,5}\b/g; - return tickerRegex.test(text); - }, - handler: async (runtime: IAgentRuntime, message: Memory) => { - const _text = (message.content as Content).text; - const userId = message.userId; - - const template = ` - -// CLAUDE TODO: Put the usual conversation context here - -Ticker is: {{ticker}} -Contract address is: {{contractAddress}} - -Determine if the user is trying to shill the ticker. if they are, respond with empty conviction, ticker and contractAddress. - -// CLAUDE TODO: output a JSON block with the following fields: -// - reasoning: string -// - conviction: negative, low, medium, high -// - ticker: string (extract from CA so we have context) -// - contractAddress: string -`; - - let ticker, contractAddress; - - // TODO: - - // 1. create state object with runtime.composeState - // 2. compose context with template and state - // 3. get generateText - // 4. validate generateText - - // if ticker or contractAddress are empty, return a message asking for them - if (!ticker || !contractAddress) { - return { - text: "Ticker and CA?", - }; - } - - const state = await runtime.composeState(message); - // TODO: compose context properly - const context = composeContext({ - state: { - ...state, - ticker, - contractAddress, - }, - template, - }); - - const convictionResponse = await generateText({ - runtime, - context: context, - modelClass: ModelClass.LARGE, - }); - - // TODOL parse and validate the JSON - const convictionResponseJson = JSON.parse(convictionResponse); // TODO: replace with validate like other actions - - // get the conviction - const conviction = convictionResponseJson.conviction; - - let buyAmount = 0; - if (conviction === "low") { - buyAmount = 20; - } else if (conviction === "medium") { - buyAmount = 50; - } else if (conviction === "high") { - buyAmount = 100; - } - - // Get the current price of the asset (replace with actual price fetching logic) - const currentPrice = 100; - - const order: Order = { - userId, - ticker: ticker || "", - contractAddress, - timestamp: new Date().toISOString(), - buyAmount, - price: currentPrice, - }; - - // Read the existing order book from the JSON file - const orderBookPath = - runtime.getSetting("orderBookPath") ?? "solana/orderBook.json"; - - const orderBook: Order[] = []; - - const cachedOrderBook = - await runtime.cacheManager.get(orderBookPath); - - if (cachedOrderBook) { - orderBook.push(...cachedOrderBook); - } - - // Add the new order to the order book - orderBook.push(order); - - // Write the updated order book back to the JSON file - await runtime.cacheManager.set(orderBookPath, orderBook); - - return { - text: `Recorded a ${conviction} conviction buy order for ${ticker} (${contractAddress}) with an amount of ${buyAmount} at the price of ${currentPrice}.`, - }; - }, -}; - -export default take_order; diff --git a/src/actions/transfer.ts b/src/actions/transfer.ts index 2da6952..f95f918 100644 --- a/src/actions/transfer.ts +++ b/src/actions/transfer.ts @@ -1,50 +1,98 @@ import { - getAssociatedTokenAddressSync, - createTransferInstruction, -} from "@solana/spl-token"; -import { elizaLogger, settings } from "@elizaos/core"; + type Action, + type ActionExample, + type Content, + type HandlerCallback, + type IAgentRuntime, + type Memory, + ModelType, + type State, + composePromptFromState, + logger, + parseJSONObjectFromText, +} from '@elizaos/core'; import { - Connection, - PublicKey, - TransactionMessage, - VersionedTransaction, -} from "@solana/web3.js"; + createAssociatedTokenAccountInstruction, + createTransferInstruction, + getAssociatedTokenAddressSync, +} from '@solana/spl-token'; import { - type ActionExample, - type Content, - type HandlerCallback, - type IAgentRuntime, - type Memory, - ModelClass, - type State, - type Action, -} from "@elizaos/core"; -import { composeContext } from "@elizaos/core"; -import { getWalletKey } from "../keypairUtils"; -import { generateObjectDeprecated } from "@elizaos/core"; - -export interface TransferContent extends Content { - tokenAddress: string; - recipient: string; - amount: string | number; + Connection, + PublicKey, + SystemProgram, + TransactionMessage, + VersionedTransaction, +} from '@solana/web3.js'; +import { getWalletKey } from '../keypairUtils'; + +/** + * Interface representing the content of a transfer. + * + * @interface TransferContent + * @extends Content + * @property {string | null} tokenAddress - The address of the token being transferred, or null for SOL transfers + * @property {string} recipient - The address of the recipient of the transfer + * @property {string | number} amount - The amount of the transfer, represented as a string or number + */ +interface TransferContent extends Content { + tokenAddress: string | null; // null for SOL transfers + recipient: string; + amount: string | number; } -function isTransferContent( - runtime: IAgentRuntime, - content: any -): content is TransferContent { - elizaLogger.log("Content for transfer", content); - return ( - typeof content.tokenAddress === "string" && - typeof content.recipient === "string" && - (typeof content.amount === "string" || - typeof content.amount === "number") - ); +/** + * Checks if the given transfer content is valid based on the type of transfer. + * @param {TransferContent} content - The content to be validated for transfer. + * @returns {boolean} Returns true if the content is valid for transfer, and false otherwise. + */ +function isTransferContent(content: TransferContent): boolean { + logger.log('Content for transfer', content); + + // Base validation + if (!content.recipient || typeof content.recipient !== 'string' || !content.amount) { + return false; + } + + if (content.tokenAddress === 'null') { + content.tokenAddress = null; + } + + return typeof content.amount === 'string' || typeof content.amount === 'number'; } +/** + * Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined. + * + * Example responses: + * For SPL tokens: + * ```json + * { + * "tokenAddress": "BieefG47jAHCGZBxi2q87RDuHyGZyYC3vAzxpyu8pump", + * "recipient": "9jW8FPr6BSSsemWPV22UUCzSqkVdTp6HTyPqeqyuBbCa", + * "amount": "1000" + * } + * ``` + * + * For SOL: + * ```json + * { + * "tokenAddress": null, + * "recipient": "9jW8FPr6BSSsemWPV22UUCzSqkVdTp6HTyPqeqyuBbCa", + * "amount": 1.5 + * } + * ``` + * + * {{recentMessages}} + * + * Extract the following information about the requested transfer: + * - Token contract address (use null for SOL transfers) + * - Recipient wallet address + * - Amount to transfer + */ const transferTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined. -Example response: +Example responses: +For SPL tokens: \`\`\`json { "tokenAddress": "BieefG47jAHCGZBxi2q87RDuHyGZyYC3vAzxpyu8pump", @@ -53,149 +101,218 @@ Example response: } \`\`\` +For SOL: +\`\`\`json +{ + "tokenAddress": null, + "recipient": "9jW8FPr6BSSsemWPV22UUCzSqkVdTp6HTyPqeqyuBbCa", + "amount": 1.5 +} +\`\`\` + {{recentMessages}} -Extract the following information about the requested token transfer: -- Token contract address +Extract the following information about the requested transfer: +- Token contract address (use null for SOL transfers) - Recipient wallet address - Amount to transfer - -If no token address is mentioned, respond with null. `; export default { - name: "SEND_TOKEN", - similes: ["TRANSFER_TOKEN", "TRANSFER_TOKENS", "SEND_TOKENS", "PAY_TOKEN", "PAY_TOKENS", "PAY"], - validate: async (runtime: IAgentRuntime, message: Memory) => { - // Always return true for token transfers, letting the handler deal with specifics - elizaLogger.log("Validating token transfer from user:", message.userId); - return true; - }, - description: "Transfer SPL tokens from agent's wallet to another address", - handler: async ( - runtime: IAgentRuntime, - message: Memory, - state: State, - _options: { [key: string]: unknown }, - callback?: HandlerCallback - ): Promise => { - elizaLogger.log("Starting SEND_TOKEN handler..."); - - if (!state) { - state = (await runtime.composeState(message)) as State; - } else { - state = await runtime.updateRecentMessageState(state); - } + name: 'TRANSFER_SOLANA', + similes: [ + 'TRANSFER_SOL', + 'SEND_TOKEN_SOLANA', + 'TRANSFER_TOKEN_SOLANA', + 'SEND_TOKENS_SOLANA', + 'TRANSFER_TOKENS_SOLANA', + 'SEND_SOL', + 'SEND_TOKEN_SOL', + 'PAY_SOL', + 'PAY_TOKEN_SOL', + 'PAY_TOKENS_SOL', + 'PAY_TOKENS_SOLANA', + 'PAY_SOLANA', + ], + validate: async (_runtime: IAgentRuntime, message: Memory) => { + logger.log('Validating transfer from entity:', message.entityId); + return true; + }, + description: 'Transfer SOL or SPL tokens to another address on Solana.', + handler: async ( + runtime: IAgentRuntime, + _message: Memory, + state: State, + _options: { [key: string]: unknown }, + callback?: HandlerCallback + ): Promise => { + logger.log('Starting TRANSFER handler...'); - const transferContext = composeContext({ - state, - template: transferTemplate, + const transferPrompt = composePromptFromState({ + state: state, + template: transferTemplate, + }); + + const result = await runtime.useModel(ModelType.TEXT_LARGE, { + prompt: transferPrompt, + }); + + const content = parseJSONObjectFromText(result); + + if (!isTransferContent(content)) { + if (callback) { + callback({ + text: 'Need a valid recipient address and amount to transfer.', + content: { error: 'Invalid transfer content' }, }); + } + return false; + } + + try { + const { keypair: senderKeypair } = await getWalletKey(runtime, true); + const connection = new Connection( + runtime.getSetting('SOLANA_RPC_URL') || 'https://api.mainnet-beta.solana.com' + ); + const recipientPubkey = new PublicKey(content.recipient); - const content = await generateObjectDeprecated({ - runtime, - context: transferContext, - modelClass: ModelClass.LARGE, + let signature: string; + + // Handle SOL transfer + if (content.tokenAddress === null) { + const lamports = Number(content.amount) * 1e9; + + const instruction = SystemProgram.transfer({ + fromPubkey: senderKeypair.publicKey, + toPubkey: recipientPubkey, + lamports, }); - if (!isTransferContent(runtime, content)) { - if (callback) { - callback({ - text: "Token address needed to send the token.", - content: { error: "Invalid transfer content" }, - }); - } - return false; + const messageV0 = new TransactionMessage({ + payerKey: senderKeypair.publicKey, + recentBlockhash: (await connection.getLatestBlockhash()).blockhash, + instructions: [instruction], + }).compileToV0Message(); + + const transaction = new VersionedTransaction(messageV0); + transaction.sign([senderKeypair]); + + signature = await connection.sendTransaction(transaction); + + if (callback) { + callback({ + text: `Sent ${content.amount} SOL. Transaction hash: ${signature}`, + content: { + success: true, + signature, + amount: content.amount, + recipient: content.recipient, + }, + }); } + } + // Handle SPL token transfer + else { + const mintPubkey = new PublicKey(content.tokenAddress); + const mintInfo = await connection.getParsedAccountInfo(mintPubkey); + const decimals = + (mintInfo.value?.data as { parsed: { info: { decimals: number } } })?.parsed?.info + ?.decimals ?? 9; + const adjustedAmount = BigInt(Number(content.amount) * 10 ** decimals); + + const senderATA = getAssociatedTokenAddressSync(mintPubkey, senderKeypair.publicKey); + const recipientATA = getAssociatedTokenAddressSync(mintPubkey, recipientPubkey); - try { - const { keypair: senderKeypair } = await getWalletKey(runtime, true); - const connection = new Connection(settings.SOLANA_RPC_URL!); - const mintPubkey = new PublicKey(content.tokenAddress); - const recipientPubkey = new PublicKey(content.recipient); - - const mintInfo = await connection.getParsedAccountInfo(mintPubkey); - const decimals = (mintInfo.value?.data as any)?.parsed?.info?.decimals ?? 9; - const adjustedAmount = BigInt(Number(content.amount) * Math.pow(10, decimals)); - - const senderATA = getAssociatedTokenAddressSync(mintPubkey, senderKeypair.publicKey); - const recipientATA = getAssociatedTokenAddressSync(mintPubkey, recipientPubkey); - - const instructions = []; - - const recipientATAInfo = await connection.getAccountInfo(recipientATA); - if (!recipientATAInfo) { - const { createAssociatedTokenAccountInstruction } = await import("@solana/spl-token"); - instructions.push( - createAssociatedTokenAccountInstruction( - senderKeypair.publicKey, - recipientATA, - recipientPubkey, - mintPubkey - ) - ); - } - - instructions.push( - createTransferInstruction( - senderATA, - recipientATA, - senderKeypair.publicKey, - adjustedAmount - ) - ); - - const messageV0 = new TransactionMessage({ - payerKey: senderKeypair.publicKey, - recentBlockhash: (await connection.getLatestBlockhash()).blockhash, - instructions, - }).compileToV0Message(); - - const transaction = new VersionedTransaction(messageV0); - transaction.sign([senderKeypair]); - - const signature = await connection.sendTransaction(transaction); - - if (callback) { - callback({ - text: `Sent ${content.amount} tokens to ${content.recipient}\nTransaction hash: ${signature}`, - content: { - success: true, - signature, - amount: content.amount, - recipient: content.recipient, - }, - }); - } - - return true; - } catch (error) { - elizaLogger.error("Error during token transfer:", error); - if (callback) { - callback({ - text: `Issue with the transfer: ${error.message}`, - content: { error: error.message }, - }); - } - return false; + const instructions = []; + + const recipientATAInfo = await connection.getAccountInfo(recipientATA); + if (!recipientATAInfo) { + instructions.push( + createAssociatedTokenAccountInstruction( + senderKeypair.publicKey, + recipientATA, + recipientPubkey, + mintPubkey + ) + ); } - }, - - examples: [ - [ - { - user: "{{user1}}", - content: { - text: "Send 69 EZSIS BieefG47jAHCGZBxi2q87RDuHyGZyYC3vAzxpyu8pump to 9jW8FPr6BSSsemWPV22UUCzSqkVdTp6HTyPqeqyuBbCa", - }, - }, - { - user: "{{user2}}", - content: { - text: "Sending the tokens now...", - action: "SEND_TOKEN", - }, + + instructions.push( + createTransferInstruction( + senderATA, + recipientATA, + senderKeypair.publicKey, + adjustedAmount + ) + ); + + const messageV0 = new TransactionMessage({ + payerKey: senderKeypair.publicKey, + recentBlockhash: (await connection.getLatestBlockhash()).blockhash, + instructions, + }).compileToV0Message(); + + const transaction = new VersionedTransaction(messageV0); + transaction.sign([senderKeypair]); + + signature = await connection.sendTransaction(transaction); + + if (callback) { + callback({ + text: `Sent ${content.amount} tokens to ${content.recipient}\nTransaction hash: ${signature}`, + content: { + success: true, + signature, + amount: content.amount, + recipient: content.recipient, }, - ], - ] as ActionExample[][], -} as Action; \ No newline at end of file + }); + } + } + + return true; + } catch (error) { + logger.error('Error during transfer:', error); + if (callback) { + callback({ + text: `Transfer failed: ${error.message}`, + content: { error: error.message }, + }); + } + return false; + } + }, + + examples: [ + [ + { + name: '{{name1}}', + content: { + text: 'Send 1.5 SOL to 9jW8FPr6BSSsemWPV22UUCzSqkVdTp6HTyPqeqyuBbCa', + }, + }, + { + name: '{{name2}}', + content: { + text: 'Sending SOL now...', + actions: ['TRANSFER_SOLANA'], + }, + }, + ], + [ + { + name: '{{name1}}', + content: { + text: 'Send 69 $DEGENAI BieefG47jAHCGZBxi2q87RDuHyGZyYC3vAzxpyu8pump to 9jW8FPr6BSSsemWPV22UUCzSqkVdTp6HTyPqeqyuBbCa', + }, + }, + { + name: '{{name2}}', + content: { + text: 'Sending the tokens now...', + actions: ['TRANSFER_SOLANA'], + }, + }, + ], + ] as ActionExample[][], +} as Action; diff --git a/src/actions/transfer_sol.ts b/src/actions/transfer_sol.ts deleted file mode 100644 index 1021e80..0000000 --- a/src/actions/transfer_sol.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { elizaLogger, settings } from "@elizaos/core"; -import { - Connection, - PublicKey, - SystemProgram, - TransactionMessage, - VersionedTransaction, -} from "@solana/web3.js"; -import { - type ActionExample, - type Content, - type HandlerCallback, - type IAgentRuntime, - type Memory, - ModelClass, - type State, - type Action, -} from "@elizaos/core"; -import { composeContext } from "@elizaos/core"; -import { getWalletKey } from "../keypairUtils"; -import { generateObjectDeprecated } from "@elizaos/core"; - -interface SolTransferContent extends Content { - recipient: string; - amount: number; -} - -function isSolTransferContent( - content: any -): content is SolTransferContent { - return ( - typeof content.recipient === "string" && - typeof content.amount === "number" - ); -} - -const solTransferTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined. - -Example response: -\`\`\`json -{ - "recipient": "9jW8FPr6BSSsemWPV22UUCzSqkVdTp6HTyPqeqyuBbCa", - "amount": 1.5 -} -\`\`\` - -{{recentMessages}} - -Extract the following information about the requested SOL transfer: -- Recipient wallet address -- Amount of SOL to transfer -`; - -export default { - name: "SEND_SOL", - similes: ["TRANSFER_SOL", "PAY_SOL", "TRANSACT_SOL"], - validate: async (runtime: IAgentRuntime, message: Memory) => { - // Always return true for SOL transfers, letting the handler deal with specifics - elizaLogger.log("Validating SOL transfer from user:", message.userId); - return true; - }, - description: "Transfer native SOL from agent's wallet to specified address", - handler: async ( - runtime: IAgentRuntime, - message: Memory, - state: State, - _options: { [key: string]: unknown }, - callback?: HandlerCallback - ): Promise => { - elizaLogger.log("Starting SEND_SOL handler..."); - - if (!state) { - state = (await runtime.composeState(message)) as State; - } else { - state = await runtime.updateRecentMessageState(state); - } - - const transferContext = composeContext({ - state, - template: solTransferTemplate, - }); - - const content = await generateObjectDeprecated({ - runtime, - context: transferContext, - modelClass: ModelClass.LARGE, - }); - - if (!isSolTransferContent(content)) { - if (callback) { - callback({ - text: "Need an address and the amount of SOL to send.", - content: { error: "Invalid transfer content" }, - }); - } - return false; - } - - try { - const { keypair: senderKeypair } = await getWalletKey(runtime, true); - const connection = new Connection(settings.SOLANA_RPC_URL!); - const recipientPubkey = new PublicKey(content.recipient); - - const lamports = content.amount * 1e9; - - const instruction = SystemProgram.transfer({ - fromPubkey: senderKeypair.publicKey, - toPubkey: recipientPubkey, - lamports, - }); - - const messageV0 = new TransactionMessage({ - payerKey: senderKeypair.publicKey, - recentBlockhash: (await connection.getLatestBlockhash()).blockhash, - instructions: [instruction], - }).compileToV0Message(); - - const transaction = new VersionedTransaction(messageV0); - transaction.sign([senderKeypair]); - - const signature = await connection.sendTransaction(transaction); - - if (callback) { - callback({ - text: `Sent ${content.amount} SOL. Transaction hash: ${signature}`, - content: { - success: true, - signature, - amount: content.amount, - recipient: content.recipient, - }, - }); - } - - return true; - } catch (error) { - elizaLogger.error("Error during SOL transfer:", error); - if (callback) { - callback({ - text: `Problem with the SOL transfer: ${error.message}`, - content: { error: error.message }, - }); - } - return false; - } - }, - - examples: [ - [ - { - user: "{{user1}}", - content: { - text: "Send 1.5 SOL to 9jW8FPr6BSSsemWPV22UUCzSqkVdTp6HTyPqeqyuBbCa", - }, - }, - { - user: "{{user2}}", - content: { - text: "Sure thing, SOL on its way.", - action: "SEND_SOL", - }, - }, - ], - ] as ActionExample[][], -} as Action; \ No newline at end of file diff --git a/src/bignumber.ts b/src/bignumber.ts index f320676..cf247ab 100644 --- a/src/bignumber.ts +++ b/src/bignumber.ts @@ -1,9 +1,16 @@ -import BigNumber from "bignumber.js"; +import BigNumber from 'bignumber.js'; // Re-export BigNumber constructor export const BN = BigNumber; // Helper function to create new BigNumber instances +/** + * Convert a string, number, or BigNumber to a BigNumber object. + * + * @param value - The value to convert to a BigNumber. + * @returns A BigNumber object representing the input value. + */ + export function toBN(value: string | number | BigNumber): BigNumber { - return new BigNumber(value); + return new BigNumber(value); } diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000..e48c31e --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,2 @@ +export const SOLANA_SERVICE_NAME = 'solana'; +export const SOLANA_WALLET_DATA_CACHE_KEY = 'solana/walletData'; diff --git a/src/environment.ts b/src/environment.ts index 5a536ce..c40b484 100644 --- a/src/environment.ts +++ b/src/environment.ts @@ -1,78 +1,90 @@ -import type { IAgentRuntime } from "@elizaos/core"; -import { z } from "zod"; +import type { IAgentRuntime } from '@elizaos/core'; +import { z } from 'zod'; +/** + * Represents the schema for Solana environment variables. + * * @type {import("zod").ZodIntersection< + * import("zod").ZodObject<{ + * WALLET_SECRET_SALT?: import("zod").ZodString | undefined; + * }, "strip">, + * import("zod").ZodUnion< + * import("zod").ZodObject<{ + * WALLET_SECRET_KEY: import("zod").ZodString; + * WALLET_PUBLIC_KEY: import("zod").ZodString; + * }, "strip"> | + * import("zod").ZodObject<{ + * WALLET_SECRET_SALT: import("zod").ZodString; + * }, "strip">, "strict"> & import("zod").ZodObject<{ + * SOL_ADDRESS: import("zod").ZodString; + * SLIPPAGE: import("zod").ZodString; + * SOLANA_RPC_URL: import("zod").ZodString; + * HELIUS_API_KEY: import("zod").ZodString; + * BIRDEYE_API_KEY: import("zod").ZodString; + * }, "strict">; + */ export const solanaEnvSchema = z - .object({ - WALLET_SECRET_SALT: z.string().optional(), + .object({ + WALLET_SECRET_SALT: z.string().optional(), + }) + .and( + z.union([ + z.object({ + WALLET_SECRET_KEY: z.string().min(1, 'Wallet secret key is required'), + WALLET_PUBLIC_KEY: z.string().min(1, 'Wallet public key is required'), + }), + z.object({ + WALLET_SECRET_SALT: z.string().min(1, 'Wallet secret salt is required'), + }), + ]) + ) + .and( + z.object({ + SOL_ADDRESS: z.string().min(1, 'SOL address is required'), + SLIPPAGE: z.string().min(1, 'Slippage is required'), + SOLANA_RPC_URL: z.string().min(1, 'RPC URL is required'), + HELIUS_API_KEY: z.string().min(1, 'Helius API key is required'), + BIRDEYE_API_KEY: z.string().min(1, 'Birdeye API key is required'), }) - .and( - z.union([ - z.object({ - WALLET_SECRET_KEY: z - .string() - .min(1, "Wallet secret key is required"), - WALLET_PUBLIC_KEY: z - .string() - .min(1, "Wallet public key is required"), - }), - z.object({ - WALLET_SECRET_SALT: z - .string() - .min(1, "Wallet secret salt is required"), - }), - ]) - ) - .and( - z.object({ - SOL_ADDRESS: z.string().min(1, "SOL address is required"), - SLIPPAGE: z.string().min(1, "Slippage is required"), - SOLANA_RPC_URL: z.string().min(1, "RPC URL is required"), - HELIUS_API_KEY: z.string().min(1, "Helius API key is required"), - BIRDEYE_API_KEY: z.string().min(1, "Birdeye API key is required"), - }) - ); + ); +/** + * Type definition for the configuration of a Solana environment. + */ export type SolanaConfig = z.infer; -export async function validateSolanaConfig( - runtime: IAgentRuntime -): Promise { - try { - const config = { - WALLET_SECRET_SALT: - runtime.getSetting("WALLET_SECRET_SALT") || - process.env.WALLET_SECRET_SALT, - WALLET_SECRET_KEY: - runtime.getSetting("WALLET_SECRET_KEY") || - process.env.WALLET_SECRET_KEY, - WALLET_PUBLIC_KEY: - runtime.getSetting("SOLANA_PUBLIC_KEY") || - runtime.getSetting("WALLET_PUBLIC_KEY") || - process.env.WALLET_PUBLIC_KEY, - SOL_ADDRESS: - runtime.getSetting("SOL_ADDRESS") || process.env.SOL_ADDRESS, - SLIPPAGE: runtime.getSetting("SLIPPAGE") || process.env.SLIPPAGE, - SOLANA_RPC_URL: - runtime.getSetting("SOLANA_RPC_URL") || - process.env.SOLANA_RPC_URL, - HELIUS_API_KEY: - runtime.getSetting("HELIUS_API_KEY") || - process.env.HELIUS_API_KEY, - BIRDEYE_API_KEY: - runtime.getSetting("BIRDEYE_API_KEY") || - process.env.BIRDEYE_API_KEY, - }; +/** + * Validates the Solana configuration by retrieving settings from the runtime or environment variables, + * checking if they are present, and returning a validated SolanaConfig object. + * + * @param {IAgentRuntime} runtime - The agent runtime object used to retrieve settings. + * @returns {Promise} - A promise that resolves with the validated SolanaConfig object. + * @throws {Error} - If the Solana configuration validation fails. + */ +export async function validateSolanaConfig(runtime: IAgentRuntime): Promise { + try { + const config = { + WALLET_SECRET_SALT: + runtime.getSetting('WALLET_SECRET_SALT') || process.env.WALLET_SECRET_SALT, + WALLET_SECRET_KEY: runtime.getSetting('WALLET_SECRET_KEY') || process.env.WALLET_SECRET_KEY, + WALLET_PUBLIC_KEY: + runtime.getSetting('SOLANA_PUBLIC_KEY') || + runtime.getSetting('WALLET_PUBLIC_KEY') || + process.env.WALLET_PUBLIC_KEY, + SOL_ADDRESS: runtime.getSetting('SOL_ADDRESS') || process.env.SOL_ADDRESS, + SLIPPAGE: runtime.getSetting('SLIPPAGE') || process.env.SLIPPAGE, + SOLANA_RPC_URL: runtime.getSetting('SOLANA_RPC_URL') || process.env.SOLANA_RPC_URL, + HELIUS_API_KEY: runtime.getSetting('HELIUS_API_KEY') || process.env.HELIUS_API_KEY, + BIRDEYE_API_KEY: runtime.getSetting('BIRDEYE_API_KEY') || process.env.BIRDEYE_API_KEY, + }; - return solanaEnvSchema.parse(config); - } catch (error) { - if (error instanceof z.ZodError) { - const errorMessages = error.errors - .map((err) => `${err.path.join(".")}: ${err.message}`) - .join("\n"); - throw new Error( - `Solana configuration validation failed:\n${errorMessages}` - ); - } - throw error; + return solanaEnvSchema.parse(config); + } catch (error) { + if (error instanceof z.ZodError) { + const errorMessages = error.errors + .map((err) => `${err.path.join('.')}: ${err.message}`) + .join('\n'); + throw new Error(`Solana configuration validation failed:\n${errorMessages}`); } + throw error; + } } diff --git a/src/evaluators/trust.ts b/src/evaluators/trust.ts deleted file mode 100644 index 29d219e..0000000 --- a/src/evaluators/trust.ts +++ /dev/null @@ -1,550 +0,0 @@ -import { - type ActionExample, - booleanFooter, - composeContext, - type Content, - elizaLogger, - type Evaluator, - generateObjectArray, - generateTrueOrFalse, - type IAgentRuntime, - type Memory, - MemoryManager, - ModelClass, -} from "@elizaos/core"; -import { TrustScoreDatabase } from "@elizaos/plugin-trustdb"; -import { Connection } from "@solana/web3.js"; -import { getWalletKey } from "../keypairUtils.ts"; -import { TokenProvider } from "../providers/token.ts"; -import { TrustScoreManager } from "../providers/trustScoreProvider.ts"; -import { WalletProvider } from "../providers/wallet.ts"; - -const shouldProcessTemplate = - `# Task: Decide if the recent messages should be processed for token recommendations. - - Look for messages that: - - Mention specific token tickers or contract addresses - - Contain words related to buying, selling, or trading tokens - - Express opinions or convictions about tokens - - Based on the following conversation, should the messages be processed for recommendations? YES or NO - - {{recentMessages}} - - Should the messages be processed for recommendations? ` + booleanFooter; - -export const formatRecommendations = (recommendations: Memory[]) => { - const messageStrings = recommendations - .reverse() - .map((rec: Memory) => `${(rec.content as Content)?.content}`); - const finalMessageStrings = messageStrings.join("\n"); - return finalMessageStrings; -}; - -const recommendationTemplate = `TASK: Extract recommendations to buy or sell memecoins from the conversation as an array of objects in JSON format. - - Memecoins usually have a ticker and a contract address. Additionally, recommenders may make recommendations with some amount of conviction. The amount of conviction in their recommendation can be none, low, medium, or high. Recommenders can make recommendations to buy, not buy, sell and not sell. - -# START OF EXAMPLES -These are an examples of the expected output of this task: -{{evaluationExamples}} -# END OF EXAMPLES - -# INSTRUCTIONS - -Extract any new recommendations from the conversation that are not already present in the list of known recommendations below: -{{recentRecommendations}} - -- Include the recommender's username -- Try not to include already-known recommendations. If you think a recommendation is already known, but you're not sure, respond with alreadyKnown: true. -- Set the conviction to 'none', 'low', 'medium' or 'high' -- Set the recommendation type to 'buy', 'dont_buy', 'sell', or 'dont_sell' -- Include the contract address and/or ticker if available - -Recent Messages: -{{recentMessages}} - -Response should be a JSON object array inside a JSON markdown block. Correct response format: -\`\`\`json -[ - { - "recommender": string, - "ticker": string | null, - "contractAddress": string | null, - "type": enum, - "conviction": enum, - "alreadyKnown": boolean - }, - ... -] -\`\`\``; - -async function handler(runtime: IAgentRuntime, message: Memory) { - elizaLogger.log("Evaluating for trust"); - const state = await runtime.composeState(message); - - // if the database type is postgres, we don't want to run this because it relies on sql queries that are currently specific to sqlite. This check can be removed once the trust score provider is updated to work with postgres. - if (runtime.getSetting("POSTGRES_URL")) { - elizaLogger.warn("skipping trust evaluator because db is postgres"); - return []; - } - - const { agentId, roomId } = state; - - // Check if we should process the messages - const shouldProcessContext = composeContext({ - state, - template: shouldProcessTemplate, - }); - - const shouldProcess = await generateTrueOrFalse({ - context: shouldProcessContext, - modelClass: ModelClass.SMALL, - runtime, - }); - - if (!shouldProcess) { - elizaLogger.log("Skipping process"); - return []; - } - - elizaLogger.log("Processing recommendations"); - - // Get recent recommendations - const recommendationsManager = new MemoryManager({ - runtime, - tableName: "recommendations", - }); - - const recentRecommendations = await recommendationsManager.getMemories({ - roomId, - count: 20, - }); - - const context = composeContext({ - state: { - ...state, - recentRecommendations: formatRecommendations(recentRecommendations), - }, - template: recommendationTemplate, - }); - - const recommendations = await generateObjectArray({ - runtime, - context, - modelClass: ModelClass.LARGE, - }); - - elizaLogger.log("recommendations", recommendations); - - if (!recommendations) { - return []; - } - - // If the recommendation is already known or corrupted, remove it - const filteredRecommendations = recommendations.filter((rec) => { - return ( - !rec.alreadyKnown && - (rec.ticker || rec.contractAddress) && - rec.recommender && - rec.conviction && - rec.recommender.trim() !== "" - ); - }); - - const { publicKey } = await getWalletKey(runtime, false); - - for (const rec of filteredRecommendations) { - // create the wallet provider and token provider - const walletProvider = new WalletProvider( - new Connection( - runtime.getSetting("SOLANA_RPC_URL") || - "https://api.mainnet-beta.solana.com" - ), - publicKey - ); - const tokenProvider = new TokenProvider( - rec.contractAddress, - walletProvider, - runtime.cacheManager - ); - - // TODO: Check to make sure the contract address is valid, it's the right one, etc - - // - if (!rec.contractAddress) { - const tokenAddress = await tokenProvider.getTokenFromWallet( - runtime, - rec.ticker - ); - rec.contractAddress = tokenAddress; - if (!tokenAddress) { - // try to search for the symbol and return the contract address with they highest liquidity and market cap - const result = await tokenProvider.searchDexScreenerData( - rec.ticker - ); - const tokenAddress = result?.baseToken?.address; - rec.contractAddress = tokenAddress; - if (!tokenAddress) { - elizaLogger.warn("Could not find contract address for token"); - continue; - } - } - } - - // create the trust score manager - - const trustScoreDb = new TrustScoreDatabase(runtime.databaseAdapter.db); - const trustScoreManager = new TrustScoreManager( - runtime, - tokenProvider, - trustScoreDb - ); - - // get actors from the database - const participants = - await runtime.databaseAdapter.getParticipantsForRoom( - message.roomId - ); - - // find the first user Id from a user with the username that we extracted - const user = participants.find(async (actor) => { - const user = await runtime.databaseAdapter.getAccountById(actor); - return ( - user.name.toLowerCase().trim() === - rec.recommender.toLowerCase().trim() - ); - }); - - if (!user) { - elizaLogger.warn("Could not find user: ", rec.recommender); - continue; - } - - const account = await runtime.databaseAdapter.getAccountById(user); - const userId = account.id; - - const recMemory = { - userId, - agentId, - content: { text: JSON.stringify(rec) }, - roomId, - createdAt: Date.now(), - }; - - await recommendationsManager.createMemory(recMemory, true); - - elizaLogger.log("recommendationsManager", rec); - - // - from here we just need to make sure code is right - - // buy, dont buy, sell, dont sell - - const buyAmounts = await tokenProvider.calculateBuyAmounts(); - - let buyAmount = buyAmounts[rec.conviction.toLowerCase().trim()]; - if (!buyAmount) { - // handle annoying cases - // for now just put in 10 sol - buyAmount = 10; - } - - // TODO: is this is a buy, sell, dont buy, or dont sell? - const shouldTrade = await tokenProvider.shouldTradeToken(); - - if (!shouldTrade) { - elizaLogger.warn( - "There might be a problem with the token, not trading" - ); - continue; - } - - switch (rec.type) { - case "buy": - // for now, lets just assume buy only, but we should implement - await trustScoreManager.createTradePerformance( - runtime, - rec.contractAddress, - userId, - { - buy_amount: rec.buyAmount, - is_simulation: true, - } - ); - break; - case "sell": - case "dont_sell": - case "dont_buy": - elizaLogger.warn("Not implemented"); - break; - } - } - - return filteredRecommendations; -} - -export const trustEvaluator: Evaluator = { - name: "EXTRACT_RECOMMENDATIONS", - similes: [ - "GET_RECOMMENDATIONS", - "EXTRACT_TOKEN_RECS", - "EXTRACT_MEMECOIN_RECS", - ], - alwaysRun: true, - validate: async ( - runtime: IAgentRuntime, - message: Memory - ): Promise => { - if (message.content.text.length < 5) { - return false; - } - - return message.userId !== message.agentId; - }, - description: - "Extract recommendations to buy or sell memecoins/tokens from the conversation, including details like ticker, contract address, conviction level, and recommender username.", - handler, - examples: [ - { - context: `Actors in the scene: -{{user1}}: Experienced DeFi degen. Constantly chasing high yield farms. -{{user2}}: New to DeFi, learning the ropes. - -Recommendations about the actors: -None`, - messages: [ - { - user: "{{user1}}", - content: { - text: "Yo, have you checked out $SOLARUG? Dope new yield aggregator on Solana.", - }, - }, - { - user: "{{user2}}", - content: { - text: "Nah, I'm still trying to wrap my head around how yield farming even works haha. Is it risky?", - }, - }, - { - user: "{{user1}}", - content: { - text: "I mean, there's always risk in DeFi, but the $SOLARUG devs seem legit. Threw a few sol into the FCweoTfJ128jGgNEXgdfTXdEZVk58Bz9trCemr6sXNx9 vault, farming's been smooth so far.", - }, - }, - ] as ActionExample[], - outcome: `\`\`\`json -[ - { - "recommender": "{{user1}}", - "ticker": "SOLARUG", - "contractAddress": "FCweoTfJ128jGgNEXgdfTXdEZVk58Bz9trCemr6sXNx9", - "type": "buy", - "conviction": "medium", - "alreadyKnown": false - } -] -\`\`\``, - }, - - { - context: `Actors in the scene: -{{user1}}: Solana maximalist. Believes Solana will flip Ethereum. -{{user2}}: Multichain proponent. Holds both SOL and ETH. - -Recommendations about the actors: -{{user1}} has previously promoted $COPETOKEN and $SOYLENT.`, - messages: [ - { - user: "{{user1}}", - content: { - text: "If you're not long $SOLVAULT at 7tRzKud6FBVFEhYqZS3CuQ2orLRM21bdisGykL5Sr4Dx, you're missing out. This will be the blackhole of Solana liquidity.", - }, - }, - { - user: "{{user2}}", - content: { - text: "Idk man, feels like there's a new 'vault' or 'reserve' token every week on Sol. What happened to $COPETOKEN and $SOYLENT that you were shilling before?", - }, - }, - { - user: "{{user1}}", - content: { - text: "$COPETOKEN and $SOYLENT had their time, I took profits near the top. But $SOLVAULT is different, it has actual utility. Do what you want, but don't say I didn't warn you when this 50x's and you're left holding your $ETH bags.", - }, - }, - ] as ActionExample[], - outcome: `\`\`\`json -[ - { - "recommender": "{{user1}}", - "ticker": "COPETOKEN", - "contractAddress": null, - "type": "sell", - "conviction": "low", - "alreadyKnown": true - }, - { - "recommender": "{{user1}}", - "ticker": "SOYLENT", - "contractAddress": null, - "type": "sell", - "conviction": "low", - "alreadyKnown": true - }, - { - "recommender": "{{user1}}", - "ticker": "SOLVAULT", - "contractAddress": "7tRzKud6FBVFEhYqZS3CuQ2orLRM21bdisGykL5Sr4Dx", - "type": "buy", - "conviction": "high", - "alreadyKnown": false - } -] -\`\`\``, - }, - - { - context: `Actors in the scene: -{{user1}}: Self-proclaimed Solana alpha caller. Allegedly has insider info. -{{user2}}: Degen gambler. Will ape into any hyped token. - -Recommendations about the actors: -None`, - messages: [ - { - user: "{{user1}}", - content: { - text: "I normally don't do this, but I like you anon, so I'll let you in on some alpha. $ROULETTE at 48vV5y4DRH1Adr1bpvSgFWYCjLLPtHYBqUSwNc2cmCK2 is going to absolutely send it soon. You didn't hear it from me 🤐", - }, - }, - { - user: "{{user2}}", - content: { - text: "Oh shit, insider info from the alpha god himself? Say no more, I'm aping in hard.", - }, - }, - ] as ActionExample[], - outcome: `\`\`\`json -[ - { - "recommender": "{{user1}}", - "ticker": "ROULETTE", - "contractAddress": "48vV5y4DRH1Adr1bpvSgFWYCjLLPtHYBqUSwNc2cmCK2", - "type": "buy", - "conviction": "high", - "alreadyKnown": false - } -] -\`\`\``, - }, - - { - context: `Actors in the scene: -{{user1}}: NFT collector and trader. Bullish on Solana NFTs. -{{user2}}: Only invests based on fundamentals. Sees all NFTs as worthless JPEGs. - -Recommendations about the actors: -None -`, - messages: [ - { - user: "{{user1}}", - content: { - text: "GM. I'm heavily accumulating $PIXELAPE, the token for the Pixel Ape Yacht Club NFT collection. 10x is inevitable.", - }, - }, - { - user: "{{user2}}", - content: { - text: "NFTs are a scam bro. There's no underlying value. You're essentially trading worthless JPEGs.", - }, - }, - { - user: "{{user1}}", - content: { - text: "Fun staying poor 🤡 $PIXELAPE is about to moon and you'll be left behind.", - }, - }, - { - user: "{{user2}}", - content: { - text: "Whatever man, I'm not touching that shit with a ten foot pole. Have fun holding your bags.", - }, - }, - { - user: "{{user1}}", - content: { - text: "Don't need luck where I'm going 😎 Once $PIXELAPE at 3hAKKmR6XyBooQBPezCbUMhrmcyTkt38sRJm2thKytWc takes off, you'll change your tune.", - }, - }, - ], - outcome: `\`\`\`json -[ - { - "recommender": "{{user1}}", - "ticker": "PIXELAPE", - "contractAddress": "3hAKKmR6XyBooQBPezCbUMhrmcyTkt38sRJm2thKytWc", - "type": "buy", - "conviction": "high", - "alreadyKnown": false - } -] -\`\`\``, - }, - - { - context: `Actors in the scene: -{{user1}}: Contrarian investor. Bets against hyped projects. -{{user2}}: Trend follower. Buys tokens that are currently popular. - -Recommendations about the actors: -None`, - messages: [ - { - user: "{{user2}}", - content: { - text: "$SAMOYED is the talk of CT right now. Making serious moves. Might have to get a bag.", - }, - }, - { - user: "{{user1}}", - content: { - text: "Whenever a token is the 'talk of CT', that's my cue to short it. $SAMOYED is going to dump hard, mark my words.", - }, - }, - { - user: "{{user2}}", - content: { - text: "Idk man, the hype seems real this time. 5TQwHyZbedaH4Pcthj1Hxf5GqcigL6qWuB7YEsBtqvhr chart looks bullish af.", - }, - }, - { - user: "{{user1}}", - content: { - text: "Hype is always real until it isn't. I'm taking out a fat short position here. Don't say I didn't warn you when this crashes 90% and you're left holding the flaming bags.", - }, - }, - ], - outcome: `\`\`\`json -[ - { - "recommender": "{{user2}}", - "ticker": "SAMOYED", - "contractAddress": "5TQwHyZbedaH4Pcthj1Hxf5GqcigL6qWuB7YEsBtqvhr", - "type": "buy", - "conviction": "medium", - "alreadyKnown": false - }, - { - "recommender": "{{user1}}", - "ticker": "SAMOYED", - "contractAddress": "5TQwHyZbedaH4Pcthj1Hxf5GqcigL6qWuB7YEsBtqvhr", - "type": "dont_buy", - "conviction": "high", - "alreadyKnown": false - } -] -\`\`\``, - }, - ], -}; diff --git a/src/index.ts b/src/index.ts index 0398a94..31343f9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,35 +1,42 @@ -export * from "./providers/token.ts"; -export * from "./providers/wallet.ts"; -export * from "./providers/trustScoreProvider.ts"; -export * from "./evaluators/trust.ts"; -import type { Plugin } from "@elizaos/core"; -import transferToken from "./actions/transfer.ts"; -import transferSol from "./actions/transfer_sol.ts"; -import { TokenProvider } from "./providers/token.ts"; -import { WalletProvider } from "./providers/wallet.ts"; -import { getTokenBalance, getTokenBalances } from "./providers/tokenUtils.ts"; -import { walletProvider } from "./providers/wallet.ts"; -import { trustScoreProvider } from "./providers/trustScoreProvider.ts"; -import { trustEvaluator } from "./evaluators/trust.ts"; -import { executeSwap } from "./actions/swap.ts"; -import take_order from "./actions/takeOrder"; -import pumpfun from "./actions/pumpfun.ts"; -import fomo from "./actions/fomo.ts"; -import { executeSwapForDAO } from "./actions/swapDao"; -export { TokenProvider, WalletProvider, getTokenBalance, getTokenBalances }; +import type { IAgentRuntime, Plugin } from '@elizaos/core'; +import { executeSwap } from './actions/swap'; +import transferToken from './actions/transfer'; +import { SOLANA_SERVICE_NAME } from './constants'; +import { walletProvider } from './providers/wallet'; +import { SolanaService } from './service'; + export const solanaPlugin: Plugin = { - name: "solana", - description: "Solana Plugin for Eliza", - actions: [ - transferToken, - transferSol, - executeSwap, - pumpfun, - fomo, - executeSwapForDAO, - take_order, - ], - evaluators: [trustEvaluator], - providers: [walletProvider, trustScoreProvider], + name: SOLANA_SERVICE_NAME, + description: 'Solana Plugin for Eliza', + actions: [transferToken, executeSwap], + evaluators: [], + providers: [walletProvider], + services: [SolanaService], + init: async (_, runtime: IAgentRuntime) => { + console.log('solana init'); + + new Promise(async (resolve) => { + resolve(); + const asking = 'solana'; + const serviceType = 'TRADER_CHAIN'; + let traderChainService = runtime.getService(serviceType) as any; + while (!traderChainService) { + console.log(asking, 'waiting for', serviceType, 'service...'); + traderChainService = runtime.getService(serviceType) as any; + if (!traderChainService) { + await new Promise((waitResolve) => setTimeout(waitResolve, 1000)); + } else { + console.log(asking, 'Acquired', serviceType, 'service...'); + } + } + + const me = { + name: 'Solana services', + }; + traderChainService.registerChain(me); + + console.log('solana init done'); + }); + }, }; -export default solanaPlugin; \ No newline at end of file +export default solanaPlugin; diff --git a/src/keypairUtils.ts b/src/keypairUtils.ts index 80e2268..fa93ae2 100644 --- a/src/keypairUtils.ts +++ b/src/keypairUtils.ts @@ -1,11 +1,16 @@ -import { Keypair, PublicKey } from "@solana/web3.js"; -import { DeriveKeyProvider, TEEMode } from "@elizaos/plugin-tee"; -import bs58 from "bs58"; -import { type IAgentRuntime, elizaLogger } from "@elizaos/core"; +import { type IAgentRuntime, logger } from '@elizaos/core'; +import { Keypair, PublicKey } from '@solana/web3.js'; +import bs58 from 'bs58'; +/** + * Interface representing the result of a keypair generation. + * @typedef {Object} KeypairResult + * @property {Keypair} [keypair] - The generated keypair. + * @property {PublicKey} [publicKey] - The public key corresponding to the generated keypair. + */ export interface KeypairResult { - keypair?: Keypair; - publicKey?: PublicKey; + keypair?: Keypair; + publicKey?: PublicKey; } /** @@ -14,69 +19,52 @@ export interface KeypairResult { * @param requirePrivateKey Whether to return a full keypair (true) or just public key (false) * @returns KeypairResult containing either keypair or public key */ +/** + * Retrieves the wallet keypair or public key based on the specified runtime settings. + * + * @param {IAgentRuntime} runtime - The IAgentRuntime instance to retrieve settings from. + * @param {boolean} [requirePrivateKey=true] - Specify whether the private key is required. Default is true. + * @returns {Promise} The keypair result object containing the keypair or public key. + */ export async function getWalletKey( - runtime: IAgentRuntime, - requirePrivateKey = true + runtime: IAgentRuntime, + requirePrivateKey = true ): Promise { - const teeMode = runtime.getSetting("TEE_MODE") || TEEMode.OFF; - - if (teeMode !== TEEMode.OFF) { - const walletSecretSalt = runtime.getSetting("WALLET_SECRET_SALT"); - if (!walletSecretSalt) { - throw new Error( - "WALLET_SECRET_SALT required when TEE_MODE is enabled" - ); - } - - const deriveKeyProvider = new DeriveKeyProvider(teeMode); - const deriveKeyResult = await deriveKeyProvider.deriveEd25519Keypair( - walletSecretSalt, - "solana", - runtime.agentId - ); + // TEE mode is OFF + if (requirePrivateKey) { + const privateKeyString = + runtime.getSetting('SOLANA_PRIVATE_KEY') ?? runtime.getSetting('WALLET_PRIVATE_KEY'); - return requirePrivateKey - ? { keypair: deriveKeyResult.keypair } - : { publicKey: deriveKeyResult.keypair.publicKey }; + if (!privateKeyString) { + throw new Error('Private key not found in settings'); } - // TEE mode is OFF - if (requirePrivateKey) { - const privateKeyString = - runtime.getSetting("SOLANA_PRIVATE_KEY") ?? - runtime.getSetting("WALLET_PRIVATE_KEY"); - - if (!privateKeyString) { - throw new Error("Private key not found in settings"); - } - - try { - // First try base58 - const secretKey = bs58.decode(privateKeyString); - return { keypair: Keypair.fromSecretKey(secretKey) }; - } catch (e) { - elizaLogger.log("Error decoding base58 private key:", e); - try { - // Then try base64 - elizaLogger.log("Try decoding base64 instead"); - const secretKey = Uint8Array.from( - Buffer.from(privateKeyString, "base64") - ); - return { keypair: Keypair.fromSecretKey(secretKey) }; - } catch (e2) { - elizaLogger.error("Error decoding private key: ", e2); - throw new Error("Invalid private key format"); - } - } - } else { - const publicKeyString = - runtime.getSetting("SOLANA_PUBLIC_KEY") ?? - runtime.getSetting("WALLET_PUBLIC_KEY"); - - if (!publicKeyString) { - throw new Error("Public key not found in settings"); - } + try { + // First try base58 + const secretKey = bs58.decode(privateKeyString); + return { keypair: Keypair.fromSecretKey(secretKey) }; + } catch (e) { + logger.log('Error decoding base58 private key:', e); + try { + // Then try base64 + logger.log('Try decoding base64 instead'); + const secretKey = Uint8Array.from(Buffer.from(privateKeyString, 'base64')); + return { keypair: Keypair.fromSecretKey(secretKey) }; + } catch (e2) { + logger.error('Error decoding private key: ', e2); + throw new Error('Invalid private key format'); + } + } + } else { + const publicKeyString = + runtime.getSetting('SOLANA_PUBLIC_KEY') ?? runtime.getSetting('WALLET_PUBLIC_KEY'); - return { publicKey: new PublicKey(publicKeyString) }; + if (!publicKeyString) { + throw new Error( + 'Solana Public key not found in settings, but plugin was loaded, please set SOLANA_PUBLIC_KEY' + ); } + + return { publicKey: new PublicKey(publicKeyString) }; + } } diff --git a/src/providers/orderBook.ts b/src/providers/orderBook.ts deleted file mode 100644 index 4af7832..0000000 --- a/src/providers/orderBook.ts +++ /dev/null @@ -1,45 +0,0 @@ -import type { IAgentRuntime, Memory, Provider, State } from "@elizaos/core"; -interface Order { - userId: string; - ticker: string; - contractAddress: string; - timestamp: string; - buyAmount: number; - price: number; -} - -const orderBookProvider: Provider = { - get: async (runtime: IAgentRuntime, message: Memory, _state?: State) => { - const userId = message.userId; - - // Read the order book from the JSON file - const orderBookPath = - runtime.getSetting("orderBookPath") ?? "solana/orderBook"; - - const orderBook: Order[] = []; - - const cachedOrderBook = - await runtime.cacheManager.get(orderBookPath); - - if (cachedOrderBook) { - orderBook.push(...cachedOrderBook); - } - - // Filter the orders for the current user - const userOrders = orderBook.filter((order) => order.userId === userId); - - let totalProfit = 0; - for (const order of userOrders) { - // Get the current price of the asset (replace with actual price fetching logic) - const currentPrice = 120; - - const priceDifference = currentPrice - order.price; - const orderProfit = priceDifference * order.buyAmount; - totalProfit += orderProfit; - } - - return `The user has made a total profit of $${totalProfit.toFixed(2)} for the agent based on their recorded buy orders.`; - }, -}; - -export { orderBookProvider }; diff --git a/src/providers/simulationSellingService.ts b/src/providers/simulationSellingService.ts deleted file mode 100644 index 5b4e3eb..0000000 --- a/src/providers/simulationSellingService.ts +++ /dev/null @@ -1,508 +0,0 @@ -import type { - TrustScoreDatabase, - TokenPerformance, - // TradePerformance, - TokenRecommendation, -} from "@elizaos/plugin-trustdb"; -import { Connection, PublicKey } from "@solana/web3.js"; -// Assuming TokenProvider and IAgentRuntime are available -import { TokenProvider } from "./token.ts"; -// import { settings } from "@elizaos/core"; -import { type IAgentRuntime, elizaLogger } from "@elizaos/core"; -import { WalletProvider } from "./wallet.ts"; -import * as amqp from "amqplib"; -import type { ProcessedTokenData } from "../types/token.ts"; -import { getWalletKey } from "../keypairUtils.ts"; - -interface SellDetails { - sell_amount: number; - sell_recommender_id: string | null; -} - -export class SimulationSellingService { - private trustScoreDb: TrustScoreDatabase; - private walletProvider: WalletProvider; - private connection: Connection; - private baseMint: PublicKey; - private DECAY_RATE = 0.95; - private MAX_DECAY_DAYS = 30; - private backend: string; - private backendToken: string; - private amqpConnection: amqp.Connection; - private amqpChannel: amqp.Channel; - private sonarBe: string; - private sonarBeToken: string; - private runtime: IAgentRuntime; - - private runningProcesses: Set = new Set(); - - constructor(runtime: IAgentRuntime, trustScoreDb: TrustScoreDatabase) { - this.trustScoreDb = trustScoreDb; - - this.connection = new Connection(runtime.getSetting("SOLANA_RPC_URL")); - this.baseMint = new PublicKey( - runtime.getSetting("BASE_MINT") || - "So11111111111111111111111111111111111111112" - ); - this.backend = runtime.getSetting("BACKEND_URL"); - this.backendToken = runtime.getSetting("BACKEND_TOKEN"); - this.initializeRabbitMQ(runtime.getSetting("AMQP_URL")); - this.sonarBe = runtime.getSetting("SONAR_BE"); - this.sonarBeToken = runtime.getSetting("SONAR_BE_TOKEN"); - this.runtime = runtime; - this.initializeWalletProvider(); - } - /** - * Initializes the RabbitMQ connection and starts consuming messages. - * @param amqpUrl The RabbitMQ server URL. - */ - private async initializeRabbitMQ(amqpUrl: string) { - try { - this.amqpConnection = await amqp.connect(amqpUrl); - this.amqpChannel = await this.amqpConnection.createChannel(); - elizaLogger.log("Connected to RabbitMQ"); - // Start consuming messages - this.consumeMessages(); - } catch (error) { - elizaLogger.error("Failed to connect to RabbitMQ:", error); - } - } - - /** - * Sets up the consumer for the specified RabbitMQ queue. - */ - private async consumeMessages() { - const queue = "process_eliza_simulation"; - await this.amqpChannel.assertQueue(queue, { durable: true }); - this.amqpChannel.consume( - queue, - (msg) => { - if (msg !== null) { - const content = msg.content.toString(); - this.processMessage(content); - this.amqpChannel.ack(msg); - } - }, - { noAck: false } - ); - elizaLogger.log(`Listening for messages on queue: ${queue}`); - } - - /** - * Processes incoming messages from RabbitMQ. - * @param message The message content as a string. - */ - private async processMessage(message: string) { - try { - const { tokenAddress, amount, sell_recommender_id } = - JSON.parse(message); - elizaLogger.log( - `Received message for token ${tokenAddress} to sell ${amount}` - ); - - const decision: SellDecision = { - tokenPerformance: - await this.trustScoreDb.getTokenPerformance(tokenAddress), - amountToSell: amount, - sell_recommender_id: sell_recommender_id, - }; - - // Execute the sell - await this.executeSellDecision(decision); - - // Remove from running processes after completion - this.runningProcesses.delete(tokenAddress); - } catch (error) { - elizaLogger.error("Error processing message:", error); - } - } - - /** - * Executes a single sell decision. - * @param decision The sell decision containing token performance and amount to sell. - */ - private async executeSellDecision(decision: SellDecision) { - const { tokenPerformance, amountToSell, sell_recommender_id } = - decision; - const tokenAddress = tokenPerformance.tokenAddress; - - try { - elizaLogger.log( - `Executing sell for token ${tokenPerformance.symbol}: ${amountToSell}` - ); - - // Update the sell details - const sellDetails: SellDetails = { - sell_amount: amountToSell, - sell_recommender_id: sell_recommender_id, // Adjust if necessary - }; - const sellTimeStamp = new Date().toISOString(); - const tokenProvider = new TokenProvider( - tokenAddress, - this.walletProvider, - this.runtime.cacheManager - ); - - // Update sell details in the database - const sellDetailsData = await this.updateSellDetails( - tokenAddress, - sell_recommender_id, - sellTimeStamp, - sellDetails, - true, // isSimulation - tokenProvider - ); - - elizaLogger.log( - "Sell order executed successfully", - sellDetailsData - ); - - // check if balance is zero and remove token from running processes - const balance = this.trustScoreDb.getTokenBalance(tokenAddress); - if (balance === 0) { - this.runningProcesses.delete(tokenAddress); - } - // stop the process in the sonar backend - await this.stopProcessInTheSonarBackend(tokenAddress); - } catch (error) { - elizaLogger.error( - `Error executing sell for token ${tokenAddress}:`, - error - ); - } - } - - /** - * Derives the public key based on the TEE (Trusted Execution Environment) mode and initializes the wallet provider. - * If TEE mode is enabled, derives a keypair using the DeriveKeyProvider with the wallet secret salt and agent ID. - * If TEE mode is disabled, uses the provided Solana public key or wallet public key from settings. - */ - private async initializeWalletProvider(): Promise { - const { publicKey } = await getWalletKey(this.runtime, false); - - this.walletProvider = new WalletProvider(this.connection, publicKey); - } - - public async startService() { - // starting the service - elizaLogger.log("Starting SellingService..."); - await this.startListeners(); - } - - public async startListeners() { - // scanning recommendations and selling - elizaLogger.log("Scanning for token performances..."); - const tokenPerformances = - await this.trustScoreDb.getAllTokenPerformancesWithBalance(); - - await this.processTokenPerformances(tokenPerformances); - } - - private processTokenPerformances(tokenPerformances: TokenPerformance[]) { - // To Do: logic when to sell and how much - elizaLogger.log("Deciding when to sell and how much..."); - const runningProcesses = this.runningProcesses; - // remove running processes from tokenPerformances - tokenPerformances = tokenPerformances.filter( - (tp) => !runningProcesses.has(tp.tokenAddress) - ); - - // start the process in the sonar backend - tokenPerformances.forEach(async (tokenPerformance) => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const tokenProvider = new TokenProvider( - tokenPerformance.tokenAddress, - this.walletProvider, - this.runtime.cacheManager - ); - // const shouldTrade = await tokenProvider.shouldTradeToken(); - // if (shouldTrade) { - const tokenRecommendations: TokenRecommendation[] = - this.trustScoreDb.getRecommendationsByToken( - tokenPerformance.tokenAddress - ); - const tokenRecommendation: TokenRecommendation = - tokenRecommendations[0]; - const balance = tokenPerformance.balance; - const sell_recommender_id = tokenRecommendation.recommenderId; - const tokenAddress = tokenPerformance.tokenAddress; - const process = await this.startProcessInTheSonarBackend( - tokenAddress, - balance, - true, - sell_recommender_id, - tokenPerformance.initialMarketCap - ); - if (process) { - this.runningProcesses.add(tokenAddress); - } - // } - }); - } - - public processTokenPerformance( - tokenAddress: string, - recommenderId: string - ) { - try { - const runningProcesses = this.runningProcesses; - // check if token is already being processed - if (runningProcesses.has(tokenAddress)) { - elizaLogger.log( - `Token ${tokenAddress} is already being processed` - ); - return; - } - const tokenPerformance = - this.trustScoreDb.getTokenPerformance(tokenAddress); - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const tokenProvider = new TokenProvider( - tokenPerformance.tokenAddress, - this.walletProvider, - this.runtime.cacheManager - ); - const balance = tokenPerformance.balance; - const sell_recommender_id = recommenderId; - const process = this.startProcessInTheSonarBackend( - tokenAddress, - balance, - true, - sell_recommender_id, - tokenPerformance.initialMarketCap - ); - if (process) { - this.runningProcesses.add(tokenAddress); - } - } catch (error) { - elizaLogger.error( - `Error getting token performance for token ${tokenAddress}:`, - error - ); - } - } - - private async startProcessInTheSonarBackend( - tokenAddress: string, - balance: number, - isSimulation: boolean, - sell_recommender_id: string, - initial_mc: number - ) { - try { - const message = JSON.stringify({ - tokenAddress, - balance, - isSimulation, - initial_mc, - sell_recommender_id, - }); - const response = await fetch( - `${this.sonarBe}/elizaos-sol/startProcess`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - "x-api-key": `${this.sonarBeToken}`, - }, - body: message, - } - ); - - if (!response.ok) { - elizaLogger.error( - `Failed to send message to process token ${tokenAddress}` - ); - return; - } - - const result = await response.json(); - elizaLogger.log("Received response:", result); - elizaLogger.log(`Sent message to process token ${tokenAddress}`); - - return result; - } catch (error) { - elizaLogger.error( - `Error sending message to process token ${tokenAddress}:`, - error - ); - return null; - } - } - - private stopProcessInTheSonarBackend(tokenAddress: string) { - try { - return fetch(`${this.sonarBe}/elizaos-sol/stopProcess`, { - method: "POST", - headers: { - "Content-Type": "application/json", - "x-api-key": `${this.sonarBeToken}`, - }, - body: JSON.stringify({ tokenAddress }), - }); - } catch (error) { - elizaLogger.error( - `Error stopping process for token ${tokenAddress}:`, - error - ); - } - } - - async updateSellDetails( - tokenAddress: string, - recommenderId: string, - sellTimeStamp: string, - sellDetails: SellDetails, - isSimulation: boolean, - tokenProvider: TokenProvider - ) { - const recommender = - await this.trustScoreDb.getOrCreateRecommenderWithTelegramId( - recommenderId - ); - const processedData: ProcessedTokenData = - await tokenProvider.getProcessedTokenData(); - const prices = await this.walletProvider.fetchPrices(null); - const solPrice = prices.solana.usd; - const sellSol = sellDetails.sell_amount / Number.parseFloat(solPrice); - const sell_value_usd = - sellDetails.sell_amount * processedData.tradeData.price; - const trade = await this.trustScoreDb.getLatestTradePerformance( - tokenAddress, - recommender.id, - isSimulation - ); - const buyTimeStamp = trade.buy_timeStamp; - const marketCap = - processedData.dexScreenerData.pairs[0]?.marketCap || 0; - const liquidity = - processedData.dexScreenerData.pairs[0]?.liquidity.usd || 0; - const sell_price = processedData.tradeData.price; - const profit_usd = sell_value_usd - trade.buy_value_usd; - const profit_percent = (profit_usd / trade.buy_value_usd) * 100; - - const market_cap_change = marketCap - trade.buy_market_cap; - const liquidity_change = liquidity - trade.buy_liquidity; - - const isRapidDump = await this.isRapidDump(tokenAddress, tokenProvider); - - const sellDetailsData = { - sell_price: sell_price, - sell_timeStamp: sellTimeStamp, - sell_amount: sellDetails.sell_amount, - received_sol: sellSol, - sell_value_usd: sell_value_usd, - profit_usd: profit_usd, - profit_percent: profit_percent, - sell_market_cap: marketCap, - market_cap_change: market_cap_change, - sell_liquidity: liquidity, - liquidity_change: liquidity_change, - rapidDump: isRapidDump, - sell_recommender_id: sellDetails.sell_recommender_id || null, - }; - this.trustScoreDb.updateTradePerformanceOnSell( - tokenAddress, - recommender.id, - buyTimeStamp, - sellDetailsData, - isSimulation - ); - - // If the trade is a simulation update the balance - const oldBalance = this.trustScoreDb.getTokenBalance(tokenAddress); - const tokenBalance = oldBalance - sellDetails.sell_amount; - this.trustScoreDb.updateTokenBalance(tokenAddress, tokenBalance); - // generate some random hash for simulations - const hash = Math.random().toString(36).substring(7); - const transaction = { - tokenAddress: tokenAddress, - type: "sell" as "buy" | "sell", - transactionHash: hash, - amount: sellDetails.sell_amount, - price: processedData.tradeData.price, - isSimulation: true, - timestamp: new Date().toISOString(), - }; - this.trustScoreDb.addTransaction(transaction); - this.updateTradeInBe( - tokenAddress, - recommender.id, - recommender.telegramId, - sellDetailsData, - tokenBalance - ); - - return sellDetailsData; - } - async isRapidDump( - tokenAddress: string, - tokenProvider: TokenProvider - ): Promise { - const processedData: ProcessedTokenData = - await tokenProvider.getProcessedTokenData(); - elizaLogger.log( - `Fetched processed token data for token: ${tokenAddress}` - ); - - return processedData.tradeData.trade_24h_change_percent < -50; - } - - async delay(ms: number) { - return new Promise((resolve) => setTimeout(resolve, ms)); - } - - async updateTradeInBe( - tokenAddress: string, - recommenderId: string, - username: string, - data: SellDetails, - balanceLeft: number, - retries = 3, - delayMs = 2000 - ) { - for (let attempt = 1; attempt <= retries; attempt++) { - try { - await fetch( - `${this.backend}/api/updaters/updateTradePerformance`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${this.backendToken}`, - }, - body: JSON.stringify({ - tokenAddress: tokenAddress, - tradeData: data, - recommenderId: recommenderId, - username: username, - isSimulation: true, - balanceLeft: balanceLeft, - }), - } - ); - // If the request is successful, exit the loop - return; - } catch (error) { - elizaLogger.error( - `Attempt ${attempt} failed: Error creating trade in backend`, - error - ); - if (attempt < retries) { - elizaLogger.log(`Retrying in ${delayMs} ms...`); - await this.delay(delayMs); // Wait for the specified delay before retrying - } else { - elizaLogger.error("All attempts failed."); - } - } - } - } -} - -// SellDecision interface -interface SellDecision { - tokenPerformance: TokenPerformance; - amountToSell: number; - sell_recommender_id: string | null; -} diff --git a/src/providers/token.ts b/src/providers/token.ts deleted file mode 100644 index d98533c..0000000 --- a/src/providers/token.ts +++ /dev/null @@ -1,1136 +0,0 @@ -import { - type IAgentRuntime, - type Memory, - type Provider, - type State, - elizaLogger, - type ICacheManager, - settings, -} from "@elizaos/core"; -import type { - DexScreenerData, - DexScreenerPair, - HolderData, - ProcessedTokenData, - TokenSecurityData, - TokenTradeData, - CalculatedBuyAmounts, - Prices, - TokenCodex, -} from "../types/token.ts"; -import NodeCache from "node-cache"; -import * as path from "path"; -import { toBN } from "../bignumber.ts"; -import { WalletProvider, type Item } from "./wallet.ts"; -import { Connection } from "@solana/web3.js"; -import { getWalletKey } from "../keypairUtils.ts"; - -const PROVIDER_CONFIG = { - BIRDEYE_API: "https://public-api.birdeye.so", - MAX_RETRIES: 3, - RETRY_DELAY: 2000, - DEFAULT_RPC: "https://api.mainnet-beta.solana.com", - TOKEN_ADDRESSES: { - SOL: "So11111111111111111111111111111111111111112", - BTC: "qfnqNqs3nCAHjnyCgLRDbBtq4p2MtHZxw8YjSyYhPoL", - ETH: "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", - Example: "2weMjPLLybRMMva1fM3U31goWWrCpF59CHWNhnCJ9Vyh", - }, - TOKEN_SECURITY_ENDPOINT: "/defi/token_security?address=", - TOKEN_TRADE_DATA_ENDPOINT: "/defi/v3/token/trade-data/single?address=", - DEX_SCREENER_API: "https://api.dexscreener.com/latest/dex/tokens/", - MAIN_WALLET: "", -}; - -export class TokenProvider { - private cache: NodeCache; - private cacheKey = "solana/tokens"; - private NETWORK_ID = 1399811149; - private GRAPHQL_ENDPOINT = "https://graph.codex.io/graphql"; - - constructor( - // private connection: Connection, - private tokenAddress: string, - private walletProvider: WalletProvider, - private cacheManager: ICacheManager - ) { - this.cache = new NodeCache({ stdTTL: 300 }); // 5 minutes cache - } - - private async readFromCache(key: string): Promise { - const cached = await this.cacheManager.get( - path.join(this.cacheKey, key) - ); - return cached; - } - - private async writeToCache(key: string, data: T): Promise { - await this.cacheManager.set(path.join(this.cacheKey, key), data, { - expires: Date.now() + 5 * 60 * 1000, - }); - } - - private async getCachedData(key: string): Promise { - // Check in-memory cache first - const cachedData = this.cache.get(key); - if (cachedData) { - return cachedData; - } - - // Check file-based cache - const fileCachedData = await this.readFromCache(key); - if (fileCachedData) { - // Populate in-memory cache - this.cache.set(key, fileCachedData); - return fileCachedData; - } - - return null; - } - - private async setCachedData(cacheKey: string, data: T): Promise { - // Set in-memory cache - this.cache.set(cacheKey, data); - - // Write to file-based cache - await this.writeToCache(cacheKey, data); - } - - private async fetchWithRetry( - url: string, - options: RequestInit = {} - ): Promise { - let lastError: Error; - - for (let i = 0; i < PROVIDER_CONFIG.MAX_RETRIES; i++) { - try { - const response = await fetch(url, { - ...options, - headers: { - Accept: "application/json", - "x-chain": "solana", - "X-API-KEY": settings.BIRDEYE_API_KEY || "", - ...options.headers, - }, - }); - - if (!response.ok) { - const errorText = await response.text(); - throw new Error( - `HTTP error! status: ${response.status}, message: ${errorText}` - ); - } - - const data = await response.json(); - return data; - } catch (error) { - elizaLogger.error(`Attempt ${i + 1} failed:`, error); - lastError = error as Error; - if (i < PROVIDER_CONFIG.MAX_RETRIES - 1) { - const delay = PROVIDER_CONFIG.RETRY_DELAY * Math.pow(2, i); - elizaLogger.log(`Waiting ${delay}ms before retrying...`); - await new Promise((resolve) => setTimeout(resolve, delay)); - continue; - } - } - } - - elizaLogger.error( - "All attempts failed. Throwing the last error:", - lastError - ); - throw lastError; - } - - async getTokensInWallet(runtime: IAgentRuntime): Promise { - const walletInfo = - await this.walletProvider.fetchPortfolioValue(runtime); - const items = walletInfo.items; - return items; - } - - // check if the token symbol is in the wallet - async getTokenFromWallet(runtime: IAgentRuntime, tokenSymbol: string) { - try { - const items = await this.getTokensInWallet(runtime); - const token = items.find((item) => item.symbol === tokenSymbol); - - if (token) { - return token.address; - } else { - return null; - } - } catch (error) { - elizaLogger.error("Error checking token in wallet:", error); - return null; - } - } - - async fetchTokenCodex(): Promise { - try { - const cacheKey = `token_${this.tokenAddress}`; - const cachedData = await this.getCachedData(cacheKey); - if (cachedData) { - elizaLogger.log( - `Returning cached token data for ${this.tokenAddress}.` - ); - return cachedData; - } - const query = ` - query Token($address: String!, $networkId: Int!) { - token(input: { address: $address, networkId: $networkId }) { - id - address - cmcId - decimals - name - symbol - totalSupply - isScam - info { - circulatingSupply - imageThumbUrl - } - explorerData { - blueCheckmark - description - tokenType - } - } - } - `; - - const variables = { - address: this.tokenAddress, - networkId: this.NETWORK_ID, // Replace with your network ID - }; - - const response = await fetch(this.GRAPHQL_ENDPOINT, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: settings.CODEX_API_KEY, - }, - body: JSON.stringify({ - query, - variables, - }), - }).then((res) => res.json()); - - const token = response.data?.data?.token; - - if (!token) { - throw new Error(`No data returned for token ${tokenAddress}`); - } - - this.setCachedData(cacheKey, token); - - return { - id: token.id, - address: token.address, - cmcId: token.cmcId, - decimals: token.decimals, - name: token.name, - symbol: token.symbol, - totalSupply: token.totalSupply, - circulatingSupply: token.info?.circulatingSupply, - imageThumbUrl: token.info?.imageThumbUrl, - blueCheckmark: token.explorerData?.blueCheckmark, - isScam: token.isScam ? true : false, - }; - } catch (error) { - elizaLogger.error( - "Error fetching token data from Codex:", - error.message - ); - return {} as TokenCodex; - } - } - - async fetchPrices(): Promise { - try { - const cacheKey = "prices"; - const cachedData = await this.getCachedData(cacheKey); - if (cachedData) { - elizaLogger.log("Returning cached prices."); - return cachedData; - } - const { SOL, BTC, ETH } = PROVIDER_CONFIG.TOKEN_ADDRESSES; - const tokens = [SOL, BTC, ETH]; - const prices: Prices = { - solana: { usd: "0" }, - bitcoin: { usd: "0" }, - ethereum: { usd: "0" }, - }; - - for (const token of tokens) { - const response = await this.fetchWithRetry( - `${PROVIDER_CONFIG.BIRDEYE_API}/defi/price?address=${token}`, - { - headers: { - "x-chain": "solana", - }, - } - ); - - if (response?.data?.value) { - const price = response.data.value.toString(); - prices[ - token === SOL - ? "solana" - : token === BTC - ? "bitcoin" - : "ethereum" - ].usd = price; - } else { - elizaLogger.warn( - `No price data available for token: ${token}` - ); - } - } - this.setCachedData(cacheKey, prices); - return prices; - } catch (error) { - elizaLogger.error("Error fetching prices:", error); - throw error; - } - } - async calculateBuyAmounts(): Promise { - const dexScreenerData = await this.fetchDexScreenerData(); - const prices = await this.fetchPrices(); - const solPrice = toBN(prices.solana.usd); - - if (!dexScreenerData || dexScreenerData.pairs.length === 0) { - return { none: 0, low: 0, medium: 0, high: 0 }; - } - - // Get the first pair - const pair = dexScreenerData.pairs[0]; - const { liquidity, marketCap } = pair; - if (!liquidity || !marketCap) { - return { none: 0, low: 0, medium: 0, high: 0 }; - } - - if (liquidity.usd === 0) { - return { none: 0, low: 0, medium: 0, high: 0 }; - } - if (marketCap < 100000) { - return { none: 0, low: 0, medium: 0, high: 0 }; - } - - // impact percentages based on liquidity - const impactPercentages = { - LOW: 0.01, // 1% of liquidity - MEDIUM: 0.05, // 5% of liquidity - HIGH: 0.1, // 10% of liquidity - }; - - // Calculate buy amounts in USD - const lowBuyAmountUSD = liquidity.usd * impactPercentages.LOW; - const mediumBuyAmountUSD = liquidity.usd * impactPercentages.MEDIUM; - const highBuyAmountUSD = liquidity.usd * impactPercentages.HIGH; - - // Convert each buy amount to SOL - const lowBuyAmountSOL = toBN(lowBuyAmountUSD).div(solPrice).toNumber(); - const mediumBuyAmountSOL = toBN(mediumBuyAmountUSD) - .div(solPrice) - .toNumber(); - const highBuyAmountSOL = toBN(highBuyAmountUSD) - .div(solPrice) - .toNumber(); - - return { - none: 0, - low: lowBuyAmountSOL, - medium: mediumBuyAmountSOL, - high: highBuyAmountSOL, - }; - } - - async fetchTokenSecurity(): Promise { - const cacheKey = `tokenSecurity_${this.tokenAddress}`; - const cachedData = - await this.getCachedData(cacheKey); - if (cachedData) { - elizaLogger.log( - `Returning cached token security data for ${this.tokenAddress}.` - ); - return cachedData; - } - const url = `${PROVIDER_CONFIG.BIRDEYE_API}${PROVIDER_CONFIG.TOKEN_SECURITY_ENDPOINT}${this.tokenAddress}`; - const data = await this.fetchWithRetry(url); - - if (!data?.success || !data?.data) { - throw new Error("No token security data available"); - } - - const security: TokenSecurityData = { - ownerBalance: data.data.ownerBalance, - creatorBalance: data.data.creatorBalance, - ownerPercentage: data.data.ownerPercentage, - creatorPercentage: data.data.creatorPercentage, - top10HolderBalance: data.data.top10HolderBalance, - top10HolderPercent: data.data.top10HolderPercent, - }; - this.setCachedData(cacheKey, security); - elizaLogger.log(`Token security data cached for ${this.tokenAddress}.`); - - return security; - } - - async fetchTokenTradeData(): Promise { - const cacheKey = `tokenTradeData_${this.tokenAddress}`; - const cachedData = await this.getCachedData(cacheKey); - if (cachedData) { - elizaLogger.log( - `Returning cached token trade data for ${this.tokenAddress}.` - ); - return cachedData; - } - - const url = `${PROVIDER_CONFIG.BIRDEYE_API}${PROVIDER_CONFIG.TOKEN_TRADE_DATA_ENDPOINT}${this.tokenAddress}`; - const options = { - method: "GET", - headers: { - accept: "application/json", - "X-API-KEY": settings.BIRDEYE_API_KEY || "", - }, - }; - - const data = await fetch(url, options) - .then((res) => res.json()) - .catch((err) => elizaLogger.error(err)); - - if (!data?.success || !data?.data) { - throw new Error("No token trade data available"); - } - - const tradeData: TokenTradeData = { - address: data.data.address, - holder: data.data.holder, - market: data.data.market, - last_trade_unix_time: data.data.last_trade_unix_time, - last_trade_human_time: data.data.last_trade_human_time, - price: data.data.price, - history_30m_price: data.data.history_30m_price, - price_change_30m_percent: data.data.price_change_30m_percent, - history_1h_price: data.data.history_1h_price, - price_change_1h_percent: data.data.price_change_1h_percent, - history_2h_price: data.data.history_2h_price, - price_change_2h_percent: data.data.price_change_2h_percent, - history_4h_price: data.data.history_4h_price, - price_change_4h_percent: data.data.price_change_4h_percent, - history_6h_price: data.data.history_6h_price, - price_change_6h_percent: data.data.price_change_6h_percent, - history_8h_price: data.data.history_8h_price, - price_change_8h_percent: data.data.price_change_8h_percent, - history_12h_price: data.data.history_12h_price, - price_change_12h_percent: data.data.price_change_12h_percent, - history_24h_price: data.data.history_24h_price, - price_change_24h_percent: data.data.price_change_24h_percent, - unique_wallet_30m: data.data.unique_wallet_30m, - unique_wallet_history_30m: data.data.unique_wallet_history_30m, - unique_wallet_30m_change_percent: - data.data.unique_wallet_30m_change_percent, - unique_wallet_1h: data.data.unique_wallet_1h, - unique_wallet_history_1h: data.data.unique_wallet_history_1h, - unique_wallet_1h_change_percent: - data.data.unique_wallet_1h_change_percent, - unique_wallet_2h: data.data.unique_wallet_2h, - unique_wallet_history_2h: data.data.unique_wallet_history_2h, - unique_wallet_2h_change_percent: - data.data.unique_wallet_2h_change_percent, - unique_wallet_4h: data.data.unique_wallet_4h, - unique_wallet_history_4h: data.data.unique_wallet_history_4h, - unique_wallet_4h_change_percent: - data.data.unique_wallet_4h_change_percent, - unique_wallet_8h: data.data.unique_wallet_8h, - unique_wallet_history_8h: data.data.unique_wallet_history_8h, - unique_wallet_8h_change_percent: - data.data.unique_wallet_8h_change_percent, - unique_wallet_24h: data.data.unique_wallet_24h, - unique_wallet_history_24h: data.data.unique_wallet_history_24h, - unique_wallet_24h_change_percent: - data.data.unique_wallet_24h_change_percent, - trade_30m: data.data.trade_30m, - trade_history_30m: data.data.trade_history_30m, - trade_30m_change_percent: data.data.trade_30m_change_percent, - sell_30m: data.data.sell_30m, - sell_history_30m: data.data.sell_history_30m, - sell_30m_change_percent: data.data.sell_30m_change_percent, - buy_30m: data.data.buy_30m, - buy_history_30m: data.data.buy_history_30m, - buy_30m_change_percent: data.data.buy_30m_change_percent, - volume_30m: data.data.volume_30m, - volume_30m_usd: data.data.volume_30m_usd, - volume_history_30m: data.data.volume_history_30m, - volume_history_30m_usd: data.data.volume_history_30m_usd, - volume_30m_change_percent: data.data.volume_30m_change_percent, - volume_buy_30m: data.data.volume_buy_30m, - volume_buy_30m_usd: data.data.volume_buy_30m_usd, - volume_buy_history_30m: data.data.volume_buy_history_30m, - volume_buy_history_30m_usd: data.data.volume_buy_history_30m_usd, - volume_buy_30m_change_percent: - data.data.volume_buy_30m_change_percent, - volume_sell_30m: data.data.volume_sell_30m, - volume_sell_30m_usd: data.data.volume_sell_30m_usd, - volume_sell_history_30m: data.data.volume_sell_history_30m, - volume_sell_history_30m_usd: data.data.volume_sell_history_30m_usd, - volume_sell_30m_change_percent: - data.data.volume_sell_30m_change_percent, - trade_1h: data.data.trade_1h, - trade_history_1h: data.data.trade_history_1h, - trade_1h_change_percent: data.data.trade_1h_change_percent, - sell_1h: data.data.sell_1h, - sell_history_1h: data.data.sell_history_1h, - sell_1h_change_percent: data.data.sell_1h_change_percent, - buy_1h: data.data.buy_1h, - buy_history_1h: data.data.buy_history_1h, - buy_1h_change_percent: data.data.buy_1h_change_percent, - volume_1h: data.data.volume_1h, - volume_1h_usd: data.data.volume_1h_usd, - volume_history_1h: data.data.volume_history_1h, - volume_history_1h_usd: data.data.volume_history_1h_usd, - volume_1h_change_percent: data.data.volume_1h_change_percent, - volume_buy_1h: data.data.volume_buy_1h, - volume_buy_1h_usd: data.data.volume_buy_1h_usd, - volume_buy_history_1h: data.data.volume_buy_history_1h, - volume_buy_history_1h_usd: data.data.volume_buy_history_1h_usd, - volume_buy_1h_change_percent: - data.data.volume_buy_1h_change_percent, - volume_sell_1h: data.data.volume_sell_1h, - volume_sell_1h_usd: data.data.volume_sell_1h_usd, - volume_sell_history_1h: data.data.volume_sell_history_1h, - volume_sell_history_1h_usd: data.data.volume_sell_history_1h_usd, - volume_sell_1h_change_percent: - data.data.volume_sell_1h_change_percent, - trade_2h: data.data.trade_2h, - trade_history_2h: data.data.trade_history_2h, - trade_2h_change_percent: data.data.trade_2h_change_percent, - sell_2h: data.data.sell_2h, - sell_history_2h: data.data.sell_history_2h, - sell_2h_change_percent: data.data.sell_2h_change_percent, - buy_2h: data.data.buy_2h, - buy_history_2h: data.data.buy_history_2h, - buy_2h_change_percent: data.data.buy_2h_change_percent, - volume_2h: data.data.volume_2h, - volume_2h_usd: data.data.volume_2h_usd, - volume_history_2h: data.data.volume_history_2h, - volume_history_2h_usd: data.data.volume_history_2h_usd, - volume_2h_change_percent: data.data.volume_2h_change_percent, - volume_buy_2h: data.data.volume_buy_2h, - volume_buy_2h_usd: data.data.volume_buy_2h_usd, - volume_buy_history_2h: data.data.volume_buy_history_2h, - volume_buy_history_2h_usd: data.data.volume_buy_history_2h_usd, - volume_buy_2h_change_percent: - data.data.volume_buy_2h_change_percent, - volume_sell_2h: data.data.volume_sell_2h, - volume_sell_2h_usd: data.data.volume_sell_2h_usd, - volume_sell_history_2h: data.data.volume_sell_history_2h, - volume_sell_history_2h_usd: data.data.volume_sell_history_2h_usd, - volume_sell_2h_change_percent: - data.data.volume_sell_2h_change_percent, - trade_4h: data.data.trade_4h, - trade_history_4h: data.data.trade_history_4h, - trade_4h_change_percent: data.data.trade_4h_change_percent, - sell_4h: data.data.sell_4h, - sell_history_4h: data.data.sell_history_4h, - sell_4h_change_percent: data.data.sell_4h_change_percent, - buy_4h: data.data.buy_4h, - buy_history_4h: data.data.buy_history_4h, - buy_4h_change_percent: data.data.buy_4h_change_percent, - volume_4h: data.data.volume_4h, - volume_4h_usd: data.data.volume_4h_usd, - volume_history_4h: data.data.volume_history_4h, - volume_history_4h_usd: data.data.volume_history_4h_usd, - volume_4h_change_percent: data.data.volume_4h_change_percent, - volume_buy_4h: data.data.volume_buy_4h, - volume_buy_4h_usd: data.data.volume_buy_4h_usd, - volume_buy_history_4h: data.data.volume_buy_history_4h, - volume_buy_history_4h_usd: data.data.volume_buy_history_4h_usd, - volume_buy_4h_change_percent: - data.data.volume_buy_4h_change_percent, - volume_sell_4h: data.data.volume_sell_4h, - volume_sell_4h_usd: data.data.volume_sell_4h_usd, - volume_sell_history_4h: data.data.volume_sell_history_4h, - volume_sell_history_4h_usd: data.data.volume_sell_history_4h_usd, - volume_sell_4h_change_percent: - data.data.volume_sell_4h_change_percent, - trade_8h: data.data.trade_8h, - trade_history_8h: data.data.trade_history_8h, - trade_8h_change_percent: data.data.trade_8h_change_percent, - sell_8h: data.data.sell_8h, - sell_history_8h: data.data.sell_history_8h, - sell_8h_change_percent: data.data.sell_8h_change_percent, - buy_8h: data.data.buy_8h, - buy_history_8h: data.data.buy_history_8h, - buy_8h_change_percent: data.data.buy_8h_change_percent, - volume_8h: data.data.volume_8h, - volume_8h_usd: data.data.volume_8h_usd, - volume_history_8h: data.data.volume_history_8h, - volume_history_8h_usd: data.data.volume_history_8h_usd, - volume_8h_change_percent: data.data.volume_8h_change_percent, - volume_buy_8h: data.data.volume_buy_8h, - volume_buy_8h_usd: data.data.volume_buy_8h_usd, - volume_buy_history_8h: data.data.volume_buy_history_8h, - volume_buy_history_8h_usd: data.data.volume_buy_history_8h_usd, - volume_buy_8h_change_percent: - data.data.volume_buy_8h_change_percent, - volume_sell_8h: data.data.volume_sell_8h, - volume_sell_8h_usd: data.data.volume_sell_8h_usd, - volume_sell_history_8h: data.data.volume_sell_history_8h, - volume_sell_history_8h_usd: data.data.volume_sell_history_8h_usd, - volume_sell_8h_change_percent: - data.data.volume_sell_8h_change_percent, - trade_24h: data.data.trade_24h, - trade_history_24h: data.data.trade_history_24h, - trade_24h_change_percent: data.data.trade_24h_change_percent, - sell_24h: data.data.sell_24h, - sell_history_24h: data.data.sell_history_24h, - sell_24h_change_percent: data.data.sell_24h_change_percent, - buy_24h: data.data.buy_24h, - buy_history_24h: data.data.buy_history_24h, - buy_24h_change_percent: data.data.buy_24h_change_percent, - volume_24h: data.data.volume_24h, - volume_24h_usd: data.data.volume_24h_usd, - volume_history_24h: data.data.volume_history_24h, - volume_history_24h_usd: data.data.volume_history_24h_usd, - volume_24h_change_percent: data.data.volume_24h_change_percent, - volume_buy_24h: data.data.volume_buy_24h, - volume_buy_24h_usd: data.data.volume_buy_24h_usd, - volume_buy_history_24h: data.data.volume_buy_history_24h, - volume_buy_history_24h_usd: data.data.volume_buy_history_24h_usd, - volume_buy_24h_change_percent: - data.data.volume_buy_24h_change_percent, - volume_sell_24h: data.data.volume_sell_24h, - volume_sell_24h_usd: data.data.volume_sell_24h_usd, - volume_sell_history_24h: data.data.volume_sell_history_24h, - volume_sell_history_24h_usd: data.data.volume_sell_history_24h_usd, - volume_sell_24h_change_percent: - data.data.volume_sell_24h_change_percent, - }; - this.setCachedData(cacheKey, tradeData); - return tradeData; - } - - async fetchDexScreenerData(): Promise { - const cacheKey = `dexScreenerData_${this.tokenAddress}`; - const cachedData = await this.getCachedData(cacheKey); - if (cachedData) { - elizaLogger.log("Returning cached DexScreener data."); - return cachedData; - } - - const url = `https://api.dexscreener.com/latest/dex/search?q=${this.tokenAddress}`; - try { - elizaLogger.log( - `Fetching DexScreener data for token: ${this.tokenAddress}` - ); - const data = await fetch(url) - .then((res) => res.json()) - .catch((err) => { - elizaLogger.error(err); - }); - - if (!data || !data.pairs) { - throw new Error("No DexScreener data available"); - } - - const dexData: DexScreenerData = { - schemaVersion: data.schemaVersion, - pairs: data.pairs, - }; - - // Cache the result - this.setCachedData(cacheKey, dexData); - - return dexData; - } catch (error) { - elizaLogger.error(`Error fetching DexScreener data:`, error); - return { - schemaVersion: "1.0.0", - pairs: [], - }; - } - } - - async searchDexScreenerData( - symbol: string - ): Promise { - const cacheKey = `dexScreenerData_search_${symbol}`; - const cachedData = await this.getCachedData(cacheKey); - if (cachedData) { - elizaLogger.log("Returning cached search DexScreener data."); - return this.getHighestLiquidityPair(cachedData); - } - - const url = `https://api.dexscreener.com/latest/dex/search?q=${symbol}`; - try { - elizaLogger.log(`Fetching DexScreener data for symbol: ${symbol}`); - const data = await fetch(url) - .then((res) => res.json()) - .catch((err) => { - elizaLogger.error(err); - return null; - }); - - if (!data || !data.pairs || data.pairs.length === 0) { - throw new Error("No DexScreener data available"); - } - - const dexData: DexScreenerData = { - schemaVersion: data.schemaVersion, - pairs: data.pairs, - }; - - // Cache the result - this.setCachedData(cacheKey, dexData); - - // Return the pair with the highest liquidity and market cap - return this.getHighestLiquidityPair(dexData); - } catch (error) { - elizaLogger.error(`Error fetching DexScreener data:`, error); - return null; - } - } - getHighestLiquidityPair(dexData: DexScreenerData): DexScreenerPair | null { - if (dexData.pairs.length === 0) { - return null; - } - - // Sort pairs by both liquidity and market cap to get the highest one - return dexData.pairs.sort((a, b) => { - const liquidityDiff = b.liquidity.usd - a.liquidity.usd; - if (liquidityDiff !== 0) { - return liquidityDiff; // Higher liquidity comes first - } - return b.marketCap - a.marketCap; // If liquidity is equal, higher market cap comes first - })[0]; - } - - async analyzeHolderDistribution( - tradeData: TokenTradeData - ): Promise { - // Define the time intervals to consider (e.g., 30m, 1h, 2h) - const intervals = [ - { - period: "30m", - change: tradeData.unique_wallet_30m_change_percent, - }, - { period: "1h", change: tradeData.unique_wallet_1h_change_percent }, - { period: "2h", change: tradeData.unique_wallet_2h_change_percent }, - { period: "4h", change: tradeData.unique_wallet_4h_change_percent }, - { period: "8h", change: tradeData.unique_wallet_8h_change_percent }, - { - period: "24h", - change: tradeData.unique_wallet_24h_change_percent, - }, - ]; - - // Calculate the average change percentage - const validChanges = intervals - .map((interval) => interval.change) - .filter( - (change) => change !== null && change !== undefined - ) as number[]; - - if (validChanges.length === 0) { - return "stable"; - } - - const averageChange = - validChanges.reduce((acc, curr) => acc + curr, 0) / - validChanges.length; - - const increaseThreshold = 10; // e.g., average change > 10% - const decreaseThreshold = -10; // e.g., average change < -10% - - if (averageChange > increaseThreshold) { - return "increasing"; - } else if (averageChange < decreaseThreshold) { - return "decreasing"; - } else { - return "stable"; - } - } - - async fetchHolderList(): Promise { - const cacheKey = `holderList_${this.tokenAddress}`; - const cachedData = await this.getCachedData(cacheKey); - if (cachedData) { - elizaLogger.log("Returning cached holder list."); - return cachedData; - } - - const allHoldersMap = new Map(); - let page = 1; - const limit = 1000; - let cursor; - //HELIOUS_API_KEY needs to be added - const url = `https://mainnet.helius-rpc.com/?api-key=${settings.HELIUS_API_KEY || ""}`; - elizaLogger.log({ url }); - - try { - while (true) { - const params = { - limit: limit, - displayOptions: {}, - mint: this.tokenAddress, - cursor: cursor, - }; - if (cursor != undefined) { - params.cursor = cursor; - } - elizaLogger.log(`Fetching holders - Page ${page}`); - if (page > 2) { - break; - } - const response = await fetch(url, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - jsonrpc: "2.0", - id: "helius-test", - method: "getTokenAccounts", - params: params, - }), - }); - - const data = await response.json(); - - if ( - !data || - !data.result || - !data.result.token_accounts || - data.result.token_accounts.length === 0 - ) { - elizaLogger.log( - `No more holders found. Total pages fetched: ${page - 1}` - ); - break; - } - - elizaLogger.log( - `Processing ${data.result.token_accounts.length} holders from page ${page}` - ); - - data.result.token_accounts.forEach((account: any) => { - const owner = account.owner; - const balance = Number.parseFloat(account.amount); - - if (allHoldersMap.has(owner)) { - allHoldersMap.set( - owner, - allHoldersMap.get(owner)! + balance - ); - } else { - allHoldersMap.set(owner, balance); - } - }); - cursor = data.result.cursor; - page++; - } - - const holders: HolderData[] = Array.from( - allHoldersMap.entries() - ).map(([address, balance]) => ({ - address, - balance: balance.toString(), - })); - - elizaLogger.log(`Total unique holders fetched: ${holders.length}`); - - // Cache the result - this.setCachedData(cacheKey, holders); - - return holders; - } catch (error) { - elizaLogger.error("Error fetching holder list from Helius:", error); - throw new Error("Failed to fetch holder list from Helius."); - } - } - - async filterHighValueHolders( - tradeData: TokenTradeData - ): Promise> { - const holdersData = await this.fetchHolderList(); - - const tokenPriceUsd = toBN(tradeData.price); - - const highValueHolders = holdersData - .filter((holder) => { - const balanceUsd = toBN(holder.balance).multipliedBy( - tokenPriceUsd - ); - return balanceUsd.isGreaterThan(5); - }) - .map((holder) => ({ - holderAddress: holder.address, - balanceUsd: toBN(holder.balance) - .multipliedBy(tokenPriceUsd) - .toFixed(2), - })); - - return highValueHolders; - } - - async checkRecentTrades(tradeData: TokenTradeData): Promise { - return toBN(tradeData.volume_24h_usd).isGreaterThan(0); - } - - async countHighSupplyHolders( - securityData: TokenSecurityData - ): Promise { - try { - const ownerBalance = toBN(securityData.ownerBalance); - const totalSupply = ownerBalance.plus(securityData.creatorBalance); - - const highSupplyHolders = await this.fetchHolderList(); - const highSupplyHoldersCount = highSupplyHolders.filter( - (holder) => { - const balance = toBN(holder.balance); - return balance.dividedBy(totalSupply).isGreaterThan(0.02); - } - ).length; - return highSupplyHoldersCount; - } catch (error) { - elizaLogger.error("Error counting high supply holders:", error); - return 0; - } - } - - async getProcessedTokenData(): Promise { - try { - elizaLogger.log( - `Fetching security data for token: ${this.tokenAddress}` - ); - const security = await this.fetchTokenSecurity(); - - const tokenCodex = await this.fetchTokenCodex(); - - elizaLogger.log( - `Fetching trade data for token: ${this.tokenAddress}` - ); - const tradeData = await this.fetchTokenTradeData(); - - elizaLogger.log( - `Fetching DexScreener data for token: ${this.tokenAddress}` - ); - const dexData = await this.fetchDexScreenerData(); - - elizaLogger.log( - `Analyzing holder distribution for token: ${this.tokenAddress}` - ); - const holderDistributionTrend = - await this.analyzeHolderDistribution(tradeData); - - elizaLogger.log( - `Filtering high-value holders for token: ${this.tokenAddress}` - ); - const highValueHolders = - await this.filterHighValueHolders(tradeData); - - elizaLogger.log( - `Checking recent trades for token: ${this.tokenAddress}` - ); - const recentTrades = await this.checkRecentTrades(tradeData); - - elizaLogger.log( - `Counting high-supply holders for token: ${this.tokenAddress}` - ); - const highSupplyHoldersCount = - await this.countHighSupplyHolders(security); - - elizaLogger.log( - `Determining DexScreener listing status for token: ${this.tokenAddress}` - ); - const isDexScreenerListed = dexData.pairs.length > 0; - const isDexScreenerPaid = dexData.pairs.some( - (pair) => pair.boosts && pair.boosts.active > 0 - ); - - const processedData: ProcessedTokenData = { - security, - tradeData, - holderDistributionTrend, - highValueHolders, - recentTrades, - highSupplyHoldersCount, - dexScreenerData: dexData, - isDexScreenerListed, - isDexScreenerPaid, - tokenCodex, - }; - - // elizaLogger.log("Processed token data:", processedData); - return processedData; - } catch (error) { - elizaLogger.error("Error processing token data:", error); - throw error; - } - } - - async shouldTradeToken(): Promise { - try { - const tokenData = await this.getProcessedTokenData(); - const { tradeData, security, dexScreenerData } = tokenData; - const { ownerBalance, creatorBalance } = security; - const { liquidity, marketCap } = dexScreenerData.pairs[0]; - const liquidityUsd = toBN(liquidity.usd); - const marketCapUsd = toBN(marketCap); - const totalSupply = toBN(ownerBalance).plus(creatorBalance); - const _ownerPercentage = toBN(ownerBalance).dividedBy(totalSupply); - const _creatorPercentage = - toBN(creatorBalance).dividedBy(totalSupply); - const top10HolderPercent = toBN(tradeData.volume_24h_usd).dividedBy( - totalSupply - ); - const priceChange24hPercent = toBN( - tradeData.price_change_24h_percent - ); - const priceChange12hPercent = toBN( - tradeData.price_change_12h_percent - ); - const uniqueWallet24h = tradeData.unique_wallet_24h; - const volume24hUsd = toBN(tradeData.volume_24h_usd); - const volume24hUsdThreshold = 1000; - const priceChange24hPercentThreshold = 10; - const priceChange12hPercentThreshold = 5; - const top10HolderPercentThreshold = 0.05; - const uniqueWallet24hThreshold = 100; - const isTop10Holder = top10HolderPercent.gte( - top10HolderPercentThreshold - ); - const isVolume24h = volume24hUsd.gte(volume24hUsdThreshold); - const isPriceChange24h = priceChange24hPercent.gte( - priceChange24hPercentThreshold - ); - const isPriceChange12h = priceChange12hPercent.gte( - priceChange12hPercentThreshold - ); - const isUniqueWallet24h = - uniqueWallet24h >= uniqueWallet24hThreshold; - const isLiquidityTooLow = liquidityUsd.lt(1000); - const isMarketCapTooLow = marketCapUsd.lt(100000); - return ( - isTop10Holder || - isVolume24h || - isPriceChange24h || - isPriceChange12h || - isUniqueWallet24h || - isLiquidityTooLow || - isMarketCapTooLow - ); - } catch (error) { - elizaLogger.error("Error processing token data:", error); - throw error; - } - } - - formatTokenData(data: ProcessedTokenData): string { - let output = `**Token Security and Trade Report**\n`; - output += `Token Address: ${this.tokenAddress}\n\n`; - - // Security Data - output += `**Ownership Distribution:**\n`; - output += `- Owner Balance: ${data.security.ownerBalance}\n`; - output += `- Creator Balance: ${data.security.creatorBalance}\n`; - output += `- Owner Percentage: ${data.security.ownerPercentage}%\n`; - output += `- Creator Percentage: ${data.security.creatorPercentage}%\n`; - output += `- Top 10 Holders Balance: ${data.security.top10HolderBalance}\n`; - output += `- Top 10 Holders Percentage: ${data.security.top10HolderPercent}%\n\n`; - - // Trade Data - output += `**Trade Data:**\n`; - output += `- Holders: ${data.tradeData.holder}\n`; - output += `- Unique Wallets (24h): ${data.tradeData.unique_wallet_24h}\n`; - output += `- Price Change (24h): ${data.tradeData.price_change_24h_percent}%\n`; - output += `- Price Change (12h): ${data.tradeData.price_change_12h_percent}%\n`; - output += `- Volume (24h USD): $${toBN(data.tradeData.volume_24h_usd).toFixed(2)}\n`; - output += `- Current Price: $${toBN(data.tradeData.price).toFixed(2)}\n\n`; - - // Holder Distribution Trend - output += `**Holder Distribution Trend:** ${data.holderDistributionTrend}\n\n`; - - // High-Value Holders - output += `**High-Value Holders (>$5 USD):**\n`; - if (data.highValueHolders.length === 0) { - output += `- No high-value holders found or data not available.\n`; - } else { - data.highValueHolders.forEach((holder) => { - output += `- ${holder.holderAddress}: $${holder.balanceUsd}\n`; - }); - } - output += `\n`; - - // Recent Trades - output += `**Recent Trades (Last 24h):** ${data.recentTrades ? "Yes" : "No"}\n\n`; - - // High-Supply Holders - output += `**Holders with >2% Supply:** ${data.highSupplyHoldersCount}\n\n`; - - // DexScreener Status - output += `**DexScreener Listing:** ${data.isDexScreenerListed ? "Yes" : "No"}\n`; - if (data.isDexScreenerListed) { - output += `- Listing Type: ${data.isDexScreenerPaid ? "Paid" : "Free"}\n`; - output += `- Number of DexPairs: ${data.dexScreenerData.pairs.length}\n\n`; - output += `**DexScreener Pairs:**\n`; - data.dexScreenerData.pairs.forEach((pair, index) => { - output += `\n**Pair ${index + 1}:**\n`; - output += `- DEX: ${pair.dexId}\n`; - output += `- URL: ${pair.url}\n`; - output += `- Price USD: $${toBN(pair.priceUsd).toFixed(6)}\n`; - output += `- Volume (24h USD): $${toBN(pair.volume.h24).toFixed(2)}\n`; - output += `- Boosts Active: ${pair.boosts && pair.boosts.active}\n`; - output += `- Liquidity USD: $${toBN(pair.liquidity.usd).toFixed(2)}\n`; - }); - } - output += `\n`; - - elizaLogger.log("Formatted token data:", output); - return output; - } - - async getFormattedTokenReport(): Promise { - try { - elizaLogger.log("Generating formatted token report..."); - const processedData = await this.getProcessedTokenData(); - return this.formatTokenData(processedData); - } catch (error) { - elizaLogger.error("Error generating token report:", error); - return "Unable to fetch token information. Please try again later."; - } - } -} - -const tokenAddress = PROVIDER_CONFIG.TOKEN_ADDRESSES.Example; - -const connection = new Connection(PROVIDER_CONFIG.DEFAULT_RPC); -const tokenProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - _message: Memory, - _state?: State - ): Promise => { - try { - const { publicKey } = await getWalletKey(runtime, false); - - const walletProvider = new WalletProvider(connection, publicKey); - - const provider = new TokenProvider( - tokenAddress, - walletProvider, - runtime.cacheManager - ); - - return provider.getFormattedTokenReport(); - } catch (error) { - elizaLogger.error("Error fetching token data:", error); - return "Unable to fetch token information. Please try again later."; - } - }, -}; - -export { tokenProvider }; diff --git a/src/providers/tokenUtils.ts b/src/providers/tokenUtils.ts deleted file mode 100644 index a9b5733..0000000 --- a/src/providers/tokenUtils.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { getAccount, getAssociatedTokenAddress } from "@solana/spl-token"; -import { type Connection, PublicKey } from "@solana/web3.js"; -import { elizaLogger } from "@elizaos/core"; - -export async function getTokenPriceInSol(tokenSymbol: string): Promise { - const response = await fetch( - `https://price.jup.ag/v6/price?ids=${tokenSymbol}` - ); - const data = await response.json(); - return data.data[tokenSymbol].price; -} - -async function getTokenBalance( - connection: Connection, - walletPublicKey: PublicKey, - tokenMintAddress: PublicKey -): Promise { - const tokenAccountAddress = await getAssociatedTokenAddress( - tokenMintAddress, - walletPublicKey - ); - - try { - const tokenAccount = await getAccount(connection, tokenAccountAddress); - const tokenAmount = tokenAccount.amount as unknown as number; - return tokenAmount; - } catch (error) { - elizaLogger.error( - `Error retrieving balance for token: ${tokenMintAddress.toBase58()}`, - error - ); - return 0; - } -} - -async function getTokenBalances( - connection: Connection, - walletPublicKey: PublicKey -): Promise<{ [tokenName: string]: number }> { - const tokenBalances: { [tokenName: string]: number } = {}; - - // Add the token mint addresses you want to retrieve balances for - const tokenMintAddresses = [ - new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"), // USDC - new PublicKey("So11111111111111111111111111111111111111112"), // SOL - // Add more token mint addresses as needed - ]; - - for (const mintAddress of tokenMintAddresses) { - const tokenName = getTokenName(mintAddress); - const balance = await getTokenBalance( - connection, - walletPublicKey, - mintAddress - ); - tokenBalances[tokenName] = balance; - } - - return tokenBalances; -} - -function getTokenName(mintAddress: PublicKey): string { - // Implement a mapping of mint addresses to token names - const tokenNameMap: { [mintAddress: string]: string } = { - EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v: "USDC", - So11111111111111111111111111111111111111112: "SOL", - // Add more token mint addresses and their corresponding names - }; - - return tokenNameMap[mintAddress.toBase58()] || "Unknown Token"; -} - -export { getTokenBalance, getTokenBalances }; diff --git a/src/providers/trustScoreProvider.ts b/src/providers/trustScoreProvider.ts deleted file mode 100644 index 7eeb2a9..0000000 --- a/src/providers/trustScoreProvider.ts +++ /dev/null @@ -1,760 +0,0 @@ -import { - elizaLogger, - type IAgentRuntime, - type Memory, - type Provider, - settings, - type State, -} from "@elizaos/core"; -import { - type RecommenderMetrics, - type TokenPerformance, - type TokenRecommendation, - type TradePerformance, - TrustScoreDatabase, -} from "@elizaos/plugin-trustdb"; -import { getAssociatedTokenAddress } from "@solana/spl-token"; -import { Connection, PublicKey } from "@solana/web3.js"; -import { v4 as uuidv4 } from "uuid"; -import type { ProcessedTokenData, TokenSecurityData } from "../types/token.ts"; -import { SimulationSellingService } from "./simulationSellingService.ts"; -import type { TokenProvider } from "./token.ts"; -import { WalletProvider } from "./wallet.ts"; - -const Wallet = settings.MAIN_WALLET_ADDRESS; -interface TradeData { - buy_amount: number; - is_simulation: boolean; -} -interface sellDetails { - sell_amount: number; - sell_recommender_id: string | null; -} -interface _RecommendationGroup { - recommendation: any; - trustScore: number; -} - -interface RecommenderData { - recommenderId: string; - trustScore: number; - riskScore: number; - consistencyScore: number; - recommenderMetrics: RecommenderMetrics; -} - -interface TokenRecommendationSummary { - tokenAddress: string; - averageTrustScore: number; - averageRiskScore: number; - averageConsistencyScore: number; - recommenders: RecommenderData[]; -} -export class TrustScoreManager { - private tokenProvider: TokenProvider; - private trustScoreDb: TrustScoreDatabase; - private simulationSellingService: SimulationSellingService; - private connection: Connection; - private baseMint: PublicKey; - private DECAY_RATE = 0.95; - private MAX_DECAY_DAYS = 30; - private backend; - private backendToken; - constructor( - runtime: IAgentRuntime, - tokenProvider: TokenProvider, - trustScoreDb: TrustScoreDatabase - ) { - this.tokenProvider = tokenProvider; - this.trustScoreDb = trustScoreDb; - this.connection = new Connection(runtime.getSetting("SOLANA_RPC_URL")); - this.baseMint = new PublicKey( - runtime.getSetting("BASE_MINT") || - "So11111111111111111111111111111111111111112" - ); - this.backend = runtime.getSetting("BACKEND_URL"); - this.backendToken = runtime.getSetting("BACKEND_TOKEN"); - this.simulationSellingService = new SimulationSellingService( - runtime, - this.trustScoreDb - ); - } - - //getRecommenederBalance - async getRecommenederBalance(recommenderWallet: string): Promise { - try { - const tokenAta = await getAssociatedTokenAddress( - new PublicKey(recommenderWallet), - this.baseMint - ); - const tokenBalInfo = - await this.connection.getTokenAccountBalance(tokenAta); - const tokenBalance = tokenBalInfo.value.amount; - const balance = Number.parseFloat(tokenBalance); - return balance; - } catch (error) { - elizaLogger.error("Error fetching balance", error); - return 0; - } - } - - /** - * Generates and saves trust score based on processed token data and user recommendations. - * @param tokenAddress The address of the token to analyze. - * @param recommenderId The UUID of the recommender. - * @returns An object containing TokenPerformance and RecommenderMetrics. - */ - async generateTrustScore( - tokenAddress: string, - recommenderId: string, - recommenderWallet: string - ): Promise<{ - tokenPerformance: TokenPerformance; - recommenderMetrics: RecommenderMetrics; - }> { - const processedData: ProcessedTokenData = - await this.tokenProvider.getProcessedTokenData(); - elizaLogger.log( - `Fetched processed token data for token: ${tokenAddress}` - ); - - const recommenderMetrics = - await this.trustScoreDb.getRecommenderMetrics(recommenderId); - - const isRapidDump = await this.isRapidDump(tokenAddress); - const sustainedGrowth = await this.sustainedGrowth(tokenAddress); - const suspiciousVolume = await this.suspiciousVolume(tokenAddress); - const balance = await this.getRecommenederBalance(recommenderWallet); - const virtualConfidence = balance / 1000000; // TODO: create formula to calculate virtual confidence based on user balance - const lastActive = recommenderMetrics.lastActiveDate; - const now = new Date(); - const inactiveDays = Math.floor( - (now.getTime() - lastActive.getTime()) / (1000 * 60 * 60 * 24) - ); - const decayFactor = Math.pow( - this.DECAY_RATE, - Math.min(inactiveDays, this.MAX_DECAY_DAYS) - ); - const decayedScore = recommenderMetrics.trustScore * decayFactor; - const validationTrustScore = - this.trustScoreDb.calculateValidationTrust(tokenAddress); - - return { - tokenPerformance: { - tokenAddress: - processedData.dexScreenerData.pairs[0]?.baseToken.address || - "", - priceChange24h: - processedData.tradeData.price_change_24h_percent, - volumeChange24h: processedData.tradeData.volume_24h, - trade_24h_change: - processedData.tradeData.trade_24h_change_percent, - liquidity: - processedData.dexScreenerData.pairs[0]?.liquidity.usd || 0, - liquidityChange24h: 0, - holderChange24h: - processedData.tradeData.unique_wallet_24h_change_percent, - rugPull: false, - isScam: processedData.tokenCodex.isScam, - marketCapChange24h: 0, - sustainedGrowth: sustainedGrowth, - rapidDump: isRapidDump, - suspiciousVolume: suspiciousVolume, - validationTrust: validationTrustScore, - balance: balance, - initialMarketCap: - processedData.dexScreenerData.pairs[0]?.marketCap || 0, - lastUpdated: new Date(), - symbol: "", - }, - recommenderMetrics: { - recommenderId: recommenderId, - trustScore: recommenderMetrics.trustScore, - totalRecommendations: recommenderMetrics.totalRecommendations, - successfulRecs: recommenderMetrics.successfulRecs, - avgTokenPerformance: recommenderMetrics.avgTokenPerformance, - riskScore: recommenderMetrics.riskScore, - consistencyScore: recommenderMetrics.consistencyScore, - virtualConfidence: virtualConfidence, - lastActiveDate: now, - trustDecay: decayedScore, - lastUpdated: new Date(), - }, - }; - } - - async updateRecommenderMetrics( - recommenderId: string, - tokenPerformance: TokenPerformance, - recommenderWallet: string - ): Promise { - const recommenderMetrics = - await this.trustScoreDb.getRecommenderMetrics(recommenderId); - - const totalRecommendations = - recommenderMetrics.totalRecommendations + 1; - const successfulRecs = tokenPerformance.rugPull - ? recommenderMetrics.successfulRecs - : recommenderMetrics.successfulRecs + 1; - const avgTokenPerformance = - (recommenderMetrics.avgTokenPerformance * - recommenderMetrics.totalRecommendations + - tokenPerformance.priceChange24h) / - totalRecommendations; - - const overallTrustScore = this.calculateTrustScore( - tokenPerformance, - recommenderMetrics - ); - const riskScore = this.calculateOverallRiskScore( - tokenPerformance, - recommenderMetrics - ); - const consistencyScore = this.calculateConsistencyScore( - tokenPerformance, - recommenderMetrics - ); - - const balance = await this.getRecommenederBalance(recommenderWallet); - const virtualConfidence = balance / 1000000; // TODO: create formula to calculate virtual confidence based on user balance - const lastActive = recommenderMetrics.lastActiveDate; - const now = new Date(); - const inactiveDays = Math.floor( - (now.getTime() - lastActive.getTime()) / (1000 * 60 * 60 * 24) - ); - const decayFactor = Math.pow( - this.DECAY_RATE, - Math.min(inactiveDays, this.MAX_DECAY_DAYS) - ); - const decayedScore = recommenderMetrics.trustScore * decayFactor; - - const newRecommenderMetrics: RecommenderMetrics = { - recommenderId: recommenderId, - trustScore: overallTrustScore, - totalRecommendations: totalRecommendations, - successfulRecs: successfulRecs, - avgTokenPerformance: avgTokenPerformance, - riskScore: riskScore, - consistencyScore: consistencyScore, - virtualConfidence: virtualConfidence, - lastActiveDate: new Date(), - trustDecay: decayedScore, - lastUpdated: new Date(), - }; - - await this.trustScoreDb.updateRecommenderMetrics(newRecommenderMetrics); - } - - calculateTrustScore( - tokenPerformance: TokenPerformance, - recommenderMetrics: RecommenderMetrics - ): number { - const riskScore = this.calculateRiskScore(tokenPerformance); - const consistencyScore = this.calculateConsistencyScore( - tokenPerformance, - recommenderMetrics - ); - - return (riskScore + consistencyScore) / 2; - } - - calculateOverallRiskScore( - tokenPerformance: TokenPerformance, - recommenderMetrics: RecommenderMetrics - ) { - const riskScore = this.calculateRiskScore(tokenPerformance); - const consistencyScore = this.calculateConsistencyScore( - tokenPerformance, - recommenderMetrics - ); - - return (riskScore + consistencyScore) / 2; - } - - calculateRiskScore(tokenPerformance: TokenPerformance): number { - let riskScore = 0; - if (tokenPerformance.rugPull) { - riskScore += 10; - } - if (tokenPerformance.isScam) { - riskScore += 10; - } - if (tokenPerformance.rapidDump) { - riskScore += 5; - } - if (tokenPerformance.suspiciousVolume) { - riskScore += 5; - } - return riskScore; - } - - calculateConsistencyScore( - tokenPerformance: TokenPerformance, - recommenderMetrics: RecommenderMetrics - ): number { - const avgTokenPerformance = recommenderMetrics.avgTokenPerformance; - const priceChange24h = tokenPerformance.priceChange24h; - - return Math.abs(priceChange24h - avgTokenPerformance); - } - - async suspiciousVolume(tokenAddress: string): Promise { - const processedData: ProcessedTokenData = - await this.tokenProvider.getProcessedTokenData(); - const unique_wallet_24h = processedData.tradeData.unique_wallet_24h; - const volume_24h = processedData.tradeData.volume_24h; - const suspiciousVolume = unique_wallet_24h / volume_24h > 0.5; - elizaLogger.log( - `Fetched processed token data for token: ${tokenAddress}` - ); - return suspiciousVolume; - } - - async sustainedGrowth(tokenAddress: string): Promise { - const processedData: ProcessedTokenData = - await this.tokenProvider.getProcessedTokenData(); - elizaLogger.log( - `Fetched processed token data for token: ${tokenAddress}` - ); - - return processedData.tradeData.volume_24h_change_percent > 50; - } - - async isRapidDump(tokenAddress: string): Promise { - const processedData: ProcessedTokenData = - await this.tokenProvider.getProcessedTokenData(); - elizaLogger.log( - `Fetched processed token data for token: ${tokenAddress}` - ); - - return processedData.tradeData.trade_24h_change_percent < -50; - } - - async checkTrustScore(tokenAddress: string): Promise { - const processedData: ProcessedTokenData = - await this.tokenProvider.getProcessedTokenData(); - elizaLogger.log( - `Fetched processed token data for token: ${tokenAddress}` - ); - - return { - ownerBalance: processedData.security.ownerBalance, - creatorBalance: processedData.security.creatorBalance, - ownerPercentage: processedData.security.ownerPercentage, - creatorPercentage: processedData.security.creatorPercentage, - top10HolderBalance: processedData.security.top10HolderBalance, - top10HolderPercent: processedData.security.top10HolderPercent, - }; - } - - /** - * Creates a TradePerformance object based on token data and recommender. - * @param tokenAddress The address of the token. - * @param recommenderId The UUID of the recommender. - * @param data ProcessedTokenData. - * @returns TradePerformance object. - */ - async createTradePerformance( - runtime: IAgentRuntime, - tokenAddress: string, - recommenderId: string, - data: TradeData - ): Promise { - const recommender = - await this.trustScoreDb.getOrCreateRecommenderWithTelegramId( - recommenderId - ); - const processedData: ProcessedTokenData = - await this.tokenProvider.getProcessedTokenData(); - const wallet = new WalletProvider( - this.connection, - new PublicKey(Wallet!) - ); - - let tokensBalance = 0; - const prices = await wallet.fetchPrices(runtime); - const solPrice = prices.solana.usd; - const buySol = data.buy_amount / Number.parseFloat(solPrice); - const buy_value_usd = data.buy_amount * processedData.tradeData.price; - const token = await this.tokenProvider.fetchTokenTradeData(); - const tokenCodex = await this.tokenProvider.fetchTokenCodex(); - const tokenPrice = token.price; - tokensBalance = buy_value_usd / tokenPrice; - - const creationData = { - token_address: tokenAddress, - recommender_id: recommender.id, - buy_price: processedData.tradeData.price, - sell_price: 0, - buy_timeStamp: new Date().toISOString(), - sell_timeStamp: "", - buy_amount: data.buy_amount, - sell_amount: 0, - buy_sol: buySol, - received_sol: 0, - buy_value_usd: buy_value_usd, - sell_value_usd: 0, - profit_usd: 0, - profit_percent: 0, - buy_market_cap: - processedData.dexScreenerData.pairs[0]?.marketCap || 0, - sell_market_cap: 0, - market_cap_change: 0, - buy_liquidity: - processedData.dexScreenerData.pairs[0]?.liquidity.usd || 0, - sell_liquidity: 0, - liquidity_change: 0, - last_updated: new Date().toISOString(), - rapidDump: false, - }; - this.trustScoreDb.addTradePerformance(creationData, data.is_simulation); - // generate unique uuid for each TokenRecommendation - const tokenUUId = uuidv4(); - const tokenRecommendation: TokenRecommendation = { - id: tokenUUId, - recommenderId: recommenderId, - tokenAddress: tokenAddress, - timestamp: new Date(), - initialMarketCap: - processedData.dexScreenerData.pairs[0]?.marketCap || 0, - initialLiquidity: - processedData.dexScreenerData.pairs[0]?.liquidity?.usd || 0, - initialPrice: processedData.tradeData.price, - }; - this.trustScoreDb.addTokenRecommendation(tokenRecommendation); - - this.trustScoreDb.upsertTokenPerformance({ - tokenAddress: tokenAddress, - symbol: processedData.tokenCodex.symbol, - priceChange24h: processedData.tradeData.price_change_24h_percent, - volumeChange24h: processedData.tradeData.volume_24h, - trade_24h_change: processedData.tradeData.trade_24h_change_percent, - liquidity: - processedData.dexScreenerData.pairs[0]?.liquidity.usd || 0, - liquidityChange24h: 0, - holderChange24h: - processedData.tradeData.unique_wallet_24h_change_percent, - rugPull: false, - isScam: tokenCodex.isScam, - marketCapChange24h: 0, - sustainedGrowth: false, - rapidDump: false, - suspiciousVolume: false, - validationTrust: 0, - balance: tokensBalance, - initialMarketCap: - processedData.dexScreenerData.pairs[0]?.marketCap || 0, - lastUpdated: new Date(), - }); - - if (data.is_simulation) { - // If the trade is a simulation update the balance - this.trustScoreDb.updateTokenBalance(tokenAddress, tokensBalance); - // generate some random hash for simulations - const hash = Math.random().toString(36).substring(7); - const transaction = { - tokenAddress: tokenAddress, - type: "buy" as "buy" | "sell", - transactionHash: hash, - amount: data.buy_amount, - price: processedData.tradeData.price, - isSimulation: true, - timestamp: new Date().toISOString(), - }; - this.trustScoreDb.addTransaction(transaction); - } - this.simulationSellingService.processTokenPerformance( - tokenAddress, - recommenderId - ); - // api call to update trade performance - this.createTradeInBe(tokenAddress, recommenderId, data); - return creationData; - } - - async delay(ms: number) { - return new Promise((resolve) => setTimeout(resolve, ms)); - } - - async createTradeInBe( - tokenAddress: string, - recommenderId: string, - data: TradeData, - retries = 3, - delayMs = 2000 - ) { - for (let attempt = 1; attempt <= retries; attempt++) { - try { - await fetch( - `${this.backend}/api/updaters/createTradePerformance`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${this.backendToken}`, - }, - body: JSON.stringify({ - tokenAddress: tokenAddress, - tradeData: data, - recommenderId: recommenderId, - }), - } - ); - // If the request is successful, exit the loop - return; - } catch (error) { - elizaLogger.error( - `Attempt ${attempt} failed: Error creating trade in backend`, - error - ); - if (attempt < retries) { - elizaLogger.log(`Retrying in ${delayMs} ms...`); - await this.delay(delayMs); // Wait for the specified delay before retrying - } else { - elizaLogger.error("All attempts failed."); - } - } - } - } - - /** - * Updates a trade with sell details. - * @param tokenAddress The address of the token. - * @param recommenderId The UUID of the recommender. - * @param buyTimeStamp The timestamp when the buy occurred. - * @param sellDetails An object containing sell-related details. - * @param isSimulation Whether the trade is a simulation. If true, updates in simulation_trade; otherwise, in trade. - * @returns boolean indicating success. - */ - - async updateSellDetails( - runtime: IAgentRuntime, - tokenAddress: string, - recommenderId: string, - sellTimeStamp: string, - sellDetails: sellDetails, - isSimulation: boolean - ) { - const recommender = - await this.trustScoreDb.getOrCreateRecommenderWithTelegramId( - recommenderId - ); - const processedData: ProcessedTokenData = - await this.tokenProvider.getProcessedTokenData(); - const wallet = new WalletProvider( - this.connection, - new PublicKey(Wallet!) - ); - const prices = await wallet.fetchPrices(runtime); - const solPrice = prices.solana.usd; - const sellSol = sellDetails.sell_amount / Number.parseFloat(solPrice); - const sell_value_usd = - sellDetails.sell_amount * processedData.tradeData.price; - const trade = await this.trustScoreDb.getLatestTradePerformance( - tokenAddress, - recommender.id, - isSimulation - ); - const buyTimeStamp = trade.buy_timeStamp; - const marketCap = - processedData.dexScreenerData.pairs[0]?.marketCap || 0; - const liquidity = - processedData.dexScreenerData.pairs[0]?.liquidity.usd || 0; - const sell_price = processedData.tradeData.price; - const profit_usd = sell_value_usd - trade.buy_value_usd; - const profit_percent = (profit_usd / trade.buy_value_usd) * 100; - - const market_cap_change = marketCap - trade.buy_market_cap; - const liquidity_change = liquidity - trade.buy_liquidity; - - const isRapidDump = await this.isRapidDump(tokenAddress); - - const sellDetailsData = { - sell_price: sell_price, - sell_timeStamp: sellTimeStamp, - sell_amount: sellDetails.sell_amount, - received_sol: sellSol, - sell_value_usd: sell_value_usd, - profit_usd: profit_usd, - profit_percent: profit_percent, - sell_market_cap: marketCap, - market_cap_change: market_cap_change, - sell_liquidity: liquidity, - liquidity_change: liquidity_change, - rapidDump: isRapidDump, - sell_recommender_id: sellDetails.sell_recommender_id || null, - }; - this.trustScoreDb.updateTradePerformanceOnSell( - tokenAddress, - recommender.id, - buyTimeStamp, - sellDetailsData, - isSimulation - ); - if (isSimulation) { - // If the trade is a simulation update the balance - const oldBalance = this.trustScoreDb.getTokenBalance(tokenAddress); - const tokenBalance = oldBalance - sellDetails.sell_amount; - this.trustScoreDb.updateTokenBalance(tokenAddress, tokenBalance); - // generate some random hash for simulations - const hash = Math.random().toString(36).substring(7); - const transaction = { - tokenAddress: tokenAddress, - type: "sell" as "buy" | "sell", - transactionHash: hash, - amount: sellDetails.sell_amount, - price: processedData.tradeData.price, - isSimulation: true, - timestamp: new Date().toISOString(), - }; - this.trustScoreDb.addTransaction(transaction); - } - - return sellDetailsData; - } - - // get all recommendations - async getRecommendations( - startDate: Date, - endDate: Date - ): Promise> { - const recommendations = this.trustScoreDb.getRecommendationsByDateRange( - startDate, - endDate - ); - - // Group recommendations by tokenAddress - const groupedRecommendations = recommendations.reduce( - (acc, recommendation) => { - const { tokenAddress } = recommendation; - if (!acc[tokenAddress]) acc[tokenAddress] = []; - acc[tokenAddress].push(recommendation); - return acc; - }, - {} as Record> - ); - - const result = Object.keys(groupedRecommendations).map( - (tokenAddress) => { - const tokenRecommendations = - groupedRecommendations[tokenAddress]; - - // Initialize variables to compute averages - let totalTrustScore = 0; - let totalRiskScore = 0; - let totalConsistencyScore = 0; - const recommenderData = []; - - tokenRecommendations.forEach((recommendation) => { - const tokenPerformance = - this.trustScoreDb.getTokenPerformance( - recommendation.tokenAddress - ); - const recommenderMetrics = - this.trustScoreDb.getRecommenderMetrics( - recommendation.recommenderId - ); - - const trustScore = this.calculateTrustScore( - tokenPerformance, - recommenderMetrics - ); - const consistencyScore = this.calculateConsistencyScore( - tokenPerformance, - recommenderMetrics - ); - const riskScore = this.calculateRiskScore(tokenPerformance); - - // Accumulate scores for averaging - totalTrustScore += trustScore; - totalRiskScore += riskScore; - totalConsistencyScore += consistencyScore; - - recommenderData.push({ - recommenderId: recommendation.recommenderId, - trustScore, - riskScore, - consistencyScore, - recommenderMetrics, - }); - }); - - // Calculate averages for this token - const averageTrustScore = - totalTrustScore / tokenRecommendations.length; - const averageRiskScore = - totalRiskScore / tokenRecommendations.length; - const averageConsistencyScore = - totalConsistencyScore / tokenRecommendations.length; - - return { - tokenAddress, - averageTrustScore, - averageRiskScore, - averageConsistencyScore, - recommenders: recommenderData, - }; - } - ); - - // Sort recommendations by the highest average trust score - result.sort((a, b) => b.averageTrustScore - a.averageTrustScore); - - return result; - } -} - -export const trustScoreProvider: Provider = { - async get( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise { - try { - // if the database type is postgres, we don't want to run this evaluator because it relies on sql queries that are currently specific to sqlite. This check can be removed once the trust score provider is updated to work with postgres. - if (runtime.getSetting("POSTGRES_URL")) { - elizaLogger.warn( - "skipping trust evaluator because db is postgres" - ); - return ""; - } - - const trustScoreDb = new TrustScoreDatabase( - runtime.databaseAdapter.db - ); - - // Get the user ID from the message - const userId = message.userId; - - if (!userId) { - elizaLogger.error("User ID is missing from the message"); - return ""; - } - - // Get the recommender metrics for the user - const recommenderMetrics = - await trustScoreDb.getRecommenderMetrics(userId); - - if (!recommenderMetrics) { - elizaLogger.error( - "No recommender metrics found for user:", - userId - ); - return ""; - } - - // Compute the trust score - const trustScore = recommenderMetrics.trustScore; - - const user = await runtime.databaseAdapter.getAccountById(userId); - - // Format the trust score string - const trustScoreString = `${user.name}'s trust score: ${trustScore.toFixed(2)}`; - - return trustScoreString; - } catch (error) { - elizaLogger.error("Error in trust score provider:", error.message); - return `Failed to fetch trust score: ${error instanceof Error ? error.message : "Unknown error"}`; - } - }, -}; diff --git a/src/providers/wallet.ts b/src/providers/wallet.ts index cf22900..c294dbd 100644 --- a/src/providers/wallet.ts +++ b/src/providers/wallet.ts @@ -1,450 +1,121 @@ -import { - type IAgentRuntime, - type Memory, - type Provider, - type State, - elizaLogger, -} from "@elizaos/core"; -import { Connection, PublicKey } from "@solana/web3.js"; -import BigNumber from "bignumber.js"; -import NodeCache from "node-cache"; -import { getWalletKey } from "../keypairUtils"; - -// Provider configuration -const PROVIDER_CONFIG = { - BIRDEYE_API: "https://public-api.birdeye.so", - MAX_RETRIES: 3, - RETRY_DELAY: 2000, - DEFAULT_RPC: "https://api.mainnet-beta.solana.com", - GRAPHQL_ENDPOINT: "https://graph.codex.io/graphql", - TOKEN_ADDRESSES: { - SOL: "So11111111111111111111111111111111111111112", - BTC: "3NZ9JMVBmGAqocybic2c7LQCJScmgsAZ6vQqTDzcqmJh", - ETH: "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", - }, -}; - -export interface Item { - name: string; - address: string; - symbol: string; - decimals: number; - balance: string; - uiAmount: string; - priceUsd: string; - valueUsd: string; - valueSol?: string; -} - -interface WalletPortfolio { - totalUsd: string; - totalSol?: string; - items: Array; +import type { IAgentRuntime, Memory, Provider, State } from '@elizaos/core'; +import { logger } from '@elizaos/core'; +import BigNumber from 'bignumber.js'; +import { SOLANA_WALLET_DATA_CACHE_KEY } from '../constants'; +import { getWalletKey } from '../keypairUtils'; +import type { WalletPortfolio } from '../types'; + +// Define the ProviderResult interface if not already imported +/** + * Represents the result returned by a provider. + * @typedef {Object} ProviderResult + * @property {any} [data] - The data associated with the result. + * @property {Record} [values] - The values stored in key-value pairs. + * @property {string} [text] - The text content of the result. + */ +interface ProviderResult { + data?: any; + values?: Record; + text?: string; } -interface _BirdEyePriceData { - data: { - [key: string]: { - price: number; - priceChange24h: number; - }; - }; -} - -interface Prices { - solana: { usd: string }; - bitcoin: { usd: string }; - ethereum: { usd: string }; -} - -export class WalletProvider { - private cache: NodeCache; - - constructor( - private connection: Connection, - private walletPublicKey: PublicKey - ) { - this.cache = new NodeCache({ stdTTL: 300 }); // Cache TTL set to 5 minutes - } - - private async fetchWithRetry( - runtime, - url: string, - options: RequestInit = {} - ): Promise { - let lastError: Error; - - for (let i = 0; i < PROVIDER_CONFIG.MAX_RETRIES; i++) { - try { - const response = await fetch(url, { - ...options, - headers: { - Accept: "application/json", - "x-chain": "solana", - "X-API-KEY": - runtime.getSetting("BIRDEYE_API_KEY", "") || "", - ...options.headers, - }, - }); - - if (!response.ok) { - const errorText = await response.text(); - throw new Error( - `HTTP error! status: ${response.status}, message: ${errorText}` - ); - } - - const data = await response.json(); - return data; - } catch (error) { - elizaLogger.error(`Attempt ${i + 1} failed:`, error); - lastError = error; - if (i < PROVIDER_CONFIG.MAX_RETRIES - 1) { - const delay = PROVIDER_CONFIG.RETRY_DELAY * Math.pow(2, i); - await new Promise((resolve) => setTimeout(resolve, delay)); - continue; - } - } - } - - elizaLogger.error( - "All attempts failed. Throwing the last error:", - lastError - ); - throw lastError; - } - - async fetchPortfolioValue(runtime): Promise { - try { - const cacheKey = `portfolio-${this.walletPublicKey.toBase58()}`; - const cachedValue = this.cache.get(cacheKey); - - if (cachedValue) { - elizaLogger.log("Cache hit for fetchPortfolioValue"); - return cachedValue; - } - elizaLogger.log("Cache miss for fetchPortfolioValue"); - - // Check if Birdeye API key is available - const birdeyeApiKey = runtime.getSetting("BIRDEYE_API_KEY"); - - if (birdeyeApiKey) { - // Existing Birdeye API logic - const walletData = await this.fetchWithRetry( - runtime, - `${PROVIDER_CONFIG.BIRDEYE_API}/v1/wallet/token_list?wallet=${this.walletPublicKey.toBase58()}` - ); - - if (walletData?.success && walletData?.data) { - const data = walletData.data; - const totalUsd = new BigNumber(data.totalUsd.toString()); - const prices = await this.fetchPrices(runtime); - const solPriceInUSD = new BigNumber( - prices.solana.usd.toString() - ); - - const items = data.items.map((item: any) => ({ - ...item, - valueSol: new BigNumber(item.valueUsd || 0) - .div(solPriceInUSD) - .toFixed(6), - name: item.name || "Unknown", - symbol: item.symbol || "Unknown", - priceUsd: item.priceUsd || "0", - valueUsd: item.valueUsd || "0", - })); - - const portfolio = { - totalUsd: totalUsd.toString(), - totalSol: totalUsd.div(solPriceInUSD).toFixed(6), - items: items.sort((a, b) => - new BigNumber(b.valueUsd) - .minus(new BigNumber(a.valueUsd)) - .toNumber() - ), - }; - - this.cache.set(cacheKey, portfolio); - return portfolio; - } - } - - // Fallback to basic token account info if no Birdeye API key or API call fails - const accounts = await this.getTokenAccounts( - this.walletPublicKey.toBase58() - ); - - const items = accounts.map((acc) => ({ - name: "Unknown", - address: acc.account.data.parsed.info.mint, - symbol: "Unknown", - decimals: acc.account.data.parsed.info.tokenAmount.decimals, - balance: acc.account.data.parsed.info.tokenAmount.amount, - uiAmount: - acc.account.data.parsed.info.tokenAmount.uiAmount.toString(), - priceUsd: "0", - valueUsd: "0", - valueSol: "0", - })); - - const portfolio = { - totalUsd: "0", - totalSol: "0", - items, - }; - - this.cache.set(cacheKey, portfolio); - return portfolio; - } catch (error) { - elizaLogger.error("Error fetching portfolio:", error); - throw error; - } - } - - async fetchPortfolioValueCodex(runtime): Promise { - try { - const cacheKey = `portfolio-${this.walletPublicKey.toBase58()}`; - const cachedValue = await this.cache.get(cacheKey); - - if (cachedValue) { - elizaLogger.log("Cache hit for fetchPortfolioValue"); - return cachedValue; - } - elizaLogger.log("Cache miss for fetchPortfolioValue"); - - const query = ` - query Balances($walletId: String!, $cursor: String) { - balances(input: { walletId: $walletId, cursor: $cursor }) { - cursor - items { - walletId - tokenId - balance - shiftedBalance - } - } - } - `; - - const variables = { - walletId: `${this.walletPublicKey.toBase58()}:${1399811149}`, - cursor: null, - }; - - const response = await fetch(PROVIDER_CONFIG.GRAPHQL_ENDPOINT, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: - runtime.getSetting("CODEX_API_KEY", "") || "", - }, - body: JSON.stringify({ - query, - variables, - }), - }).then((res) => res.json()); - - const data = response.data?.data?.balances?.items; - - if (!data || data.length === 0) { - elizaLogger.error("No portfolio data available", data); - throw new Error("No portfolio data available"); - } - - // Fetch token prices - const prices = await this.fetchPrices(runtime); - const solPriceInUSD = new BigNumber(prices.solana.usd.toString()); - - // Reformat items - const items: Item[] = data.map((item: any) => { - return { - name: "Unknown", - address: item.tokenId.split(":")[0], - symbol: item.tokenId.split(":")[0], - decimals: 6, - balance: item.balance, - uiAmount: item.shiftedBalance.toString(), - priceUsd: "", - valueUsd: "", - valueSol: "", - }; - }); - - // Calculate total portfolio value - const totalUsd = items.reduce( - (sum, item) => sum.plus(new BigNumber(item.valueUsd)), - new BigNumber(0) - ); - - const totalSol = totalUsd.div(solPriceInUSD); - - const portfolio: WalletPortfolio = { - totalUsd: totalUsd.toFixed(6), - totalSol: totalSol.toFixed(6), - items: items.sort((a, b) => - new BigNumber(b.valueUsd) - .minus(new BigNumber(a.valueUsd)) - .toNumber() - ), - }; - - // Cache the portfolio for future requests - await this.cache.set(cacheKey, portfolio, 60 * 1000); // Cache for 1 minute - - return portfolio; - } catch (error) { - elizaLogger.error("Error fetching portfolio:", error); - throw error; - } - } - - async fetchPrices(runtime): Promise { - try { - const cacheKey = "prices"; - const cachedValue = this.cache.get(cacheKey); - - if (cachedValue) { - elizaLogger.log("Cache hit for fetchPrices"); - return cachedValue; - } - elizaLogger.log("Cache miss for fetchPrices"); - - const { SOL, BTC, ETH } = PROVIDER_CONFIG.TOKEN_ADDRESSES; - const tokens = [SOL, BTC, ETH]; - const prices: Prices = { - solana: { usd: "0" }, - bitcoin: { usd: "0" }, - ethereum: { usd: "0" }, - }; - - for (const token of tokens) { - const response = await this.fetchWithRetry( - runtime, - `${PROVIDER_CONFIG.BIRDEYE_API}/defi/price?address=${token}`, - { - headers: { - "x-chain": "solana", - }, - } - ); - - if (response?.data?.value) { - const price = response.data.value.toString(); - prices[ - token === SOL - ? "solana" - : token === BTC - ? "bitcoin" - : "ethereum" - ].usd = price; - } else { - elizaLogger.warn( - `No price data available for token: ${token}` - ); - } - } - - this.cache.set(cacheKey, prices); - return prices; - } catch (error) { - elizaLogger.error("Error fetching prices:", error); - throw error; - } - } - - formatPortfolio( - runtime, - portfolio: WalletPortfolio, - prices: Prices - ): string { - let output = `${runtime.character.description}\n`; - output += `Wallet Address: ${this.walletPublicKey.toBase58()}\n\n`; - - const totalUsdFormatted = new BigNumber(portfolio.totalUsd).toFixed(2); - const totalSolFormatted = portfolio.totalSol; - - output += `Total Value: $${totalUsdFormatted} (${totalSolFormatted} SOL)\n\n`; - output += "Token Balances:\n"; - - const nonZeroItems = portfolio.items.filter((item) => - new BigNumber(item.uiAmount).isGreaterThan(0) - ); - - if (nonZeroItems.length === 0) { - output += "No tokens found with non-zero balance\n"; - } else { - for (const item of nonZeroItems) { - const valueUsd = new BigNumber(item.valueUsd).toFixed(2); - output += `${item.name} (${item.symbol}): ${new BigNumber( - item.uiAmount - ).toFixed(6)} ($${valueUsd} | ${item.valueSol} SOL)\n`; - } +/** + * Wallet provider for Solana. + * @param {IAgentRuntime} runtime - The agent runtime. + * @param {Memory} _message - The memory message. + * @param {State} [state] - Optional state parameter. + * @returns {Promise} The result of the wallet provider. + */ +export const walletProvider: Provider = { + name: 'solana-wallet', + description: 'your solana wallet information', + // it's not slow we always have this data + // but we don't always need this data, let's free up the context + dynamic: true, + get: async (runtime: IAgentRuntime, _message: Memory, state?: State): Promise => { + try { + const portfolioCache = await runtime.getCache(SOLANA_WALLET_DATA_CACHE_KEY); + //console.log('portfolioCache', portfolioCache) + if (!portfolioCache) { + logger.info('solana::wallet provider - portfolioCache is not ready'); + return { data: null, values: {}, text: '' }; + } + + // hard coding service name, ugh + const solanaService = runtime.getService('solana'); + let pubkeyStr = ''; + + const { publicKey } = await getWalletKey(runtime, false); + + // why wouldn't this exist? it's in the same plugin... + if (solanaService) { + pubkeyStr = ' (' + publicKey?.toBase58() + ')'; + } + + const portfolio = portfolioCache as WalletPortfolio; + const agentName = state?.agentName || runtime.character.name || 'The agent'; + + // Values that can be injected into templates + const values: Record = { + total_usd: new BigNumber(portfolio.totalUsd).toFixed(2), + total_sol: portfolio.totalSol?.toString() || '0', + }; + + // Add token balances to values + portfolio.items.forEach((item, index) => { + if (new BigNumber(item.uiAmount).isGreaterThan(0)) { + values[`token_${index}_name`] = item.name; + values[`token_${index}_symbol`] = item.symbol; + values[`token_${index}_amount`] = new BigNumber(item.uiAmount).toFixed(6); + values[`token_${index}_usd`] = new BigNumber(item.valueUsd).toFixed(2); + values[`token_${index}_sol`] = item.valueSol?.toString() || '0'; } - - output += "\nMarket Prices:\n"; - output += `SOL: $${new BigNumber(prices.solana.usd).toFixed(2)}\n`; - output += `BTC: $${new BigNumber(prices.bitcoin.usd).toFixed(2)}\n`; - output += `ETH: $${new BigNumber(prices.ethereum.usd).toFixed(2)}\n`; - - return output; - } - - async getFormattedPortfolio(runtime): Promise { - try { - const [portfolio, prices] = await Promise.all([ - this.fetchPortfolioValue(runtime), - this.fetchPrices(runtime), - ]); - - return this.formatPortfolio(runtime, portfolio, prices); - } catch (error) { - elizaLogger.error("Error generating portfolio report:", error); - return "Unable to fetch wallet information. Please try again later."; + }); + + // Add market prices to values + if (portfolio.prices) { + values.sol_price = new BigNumber(portfolio.prices.solana.usd).toFixed(2); + values.btc_price = new BigNumber(portfolio.prices.bitcoin.usd).toFixed(2); + values.eth_price = new BigNumber(portfolio.prices.ethereum.usd).toFixed(2); + } + + // Format the text output + let text = `\n\n${agentName}'s Main Solana Wallet${pubkeyStr}\n`; + text += `Total Value: $${values.total_usd} (${values.total_sol} SOL)\n\n`; + + // Token Balances + text += 'Token Balances:\n'; + const nonZeroItems = portfolio.items.filter((item) => + new BigNumber(item.uiAmount).isGreaterThan(0) + ); + + if (nonZeroItems.length === 0) { + text += 'No tokens found with non-zero balance\n'; + } else { + for (const item of nonZeroItems) { + const valueUsd = new BigNumber(item.valueUsd).toFixed(2); + text += `${item.name} (${item.symbol}): ${new BigNumber(item.uiAmount).toFixed( + 6 + )} ($${valueUsd} | ${item.valueSol} SOL)\n`; } + } + + // Market Prices + if (portfolio.prices) { + text += '\nMarket Prices:\n'; + text += `SOL: $${values.sol_price}\n`; + text += `BTC: $${values.btc_price}\n`; + text += `ETH: $${values.eth_price}\n`; + } + + return { + data: portfolio, + values: values, + text: text, + }; + } catch (error) { + console.error('Error in Solana wallet provider:', error); + return { data: null, values: {}, text: '' }; } - - private async getTokenAccounts(walletAddress: string) { - try { - const accounts = - await this.connection.getParsedTokenAccountsByOwner( - new PublicKey(walletAddress), - { - programId: new PublicKey( - "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" - ), - } - ); - return accounts.value; - } catch (error) { - elizaLogger.error("Error fetching token accounts:", error); - return []; - } - } -} - -const walletProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - _message: Memory, - _state?: State - ): Promise => { - try { - const { publicKey } = await getWalletKey(runtime, false); - - const connection = new Connection( - runtime.getSetting("SOLANA_RPC_URL") || - PROVIDER_CONFIG.DEFAULT_RPC - ); - - const provider = new WalletProvider(connection, publicKey); - - return await provider.getFormattedPortfolio(runtime); - } catch (error) { - elizaLogger.error("Error in wallet provider:", error); - return null; - } - }, + }, }; - -// Module exports -export { walletProvider }; diff --git a/src/service.ts b/src/service.ts new file mode 100644 index 0000000..f7050ba --- /dev/null +++ b/src/service.ts @@ -0,0 +1,505 @@ +import { type IAgentRuntime, Service, logger } from '@elizaos/core'; +import { Connection, PublicKey } from '@solana/web3.js'; +import BigNumber from 'bignumber.js'; +import { SOLANA_SERVICE_NAME, SOLANA_WALLET_DATA_CACHE_KEY } from './constants'; +import { getWalletKey, KeypairResult } from './keypairUtils'; +import type { Item, Prices, WalletPortfolio } from './types'; +import { Keypair } from '@solana/web3.js'; +import bs58 from 'bs58'; + +const PROVIDER_CONFIG = { + BIRDEYE_API: 'https://public-api.birdeye.so', + MAX_RETRIES: 3, + RETRY_DELAY: 2000, + DEFAULT_RPC: 'https://api.mainnet-beta.solana.com', + TOKEN_ADDRESSES: { + SOL: 'So11111111111111111111111111111111111111112', + BTC: '3NZ9JMVBmGAqocybic2c7LQCJScmgsAZ6vQqTDzcqmJh', + ETH: '7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs', + }, +}; + +/** + * Service class for interacting with the Solana blockchain and accessing wallet data. + * @extends Service + */ +export class SolanaService extends Service { + static serviceType: string = SOLANA_SERVICE_NAME; + capabilityDescription = + 'The agent is able to interact with the Solana blockchain, and has access to the wallet data'; + + private updateInterval: NodeJS.Timer | null = null; + private lastUpdate = 0; + private readonly UPDATE_INTERVAL = 120000; // 2 minutes + private connection: Connection; + private publicKey: PublicKey; + private exchangeRegistry: Record = {}; + private subscriptions: Map = new Map(); + + /** + * Constructor for creating an instance of the class. + * @param {IAgentRuntime} runtime - The runtime object that provides access to agent-specific functionality. + */ + constructor(protected runtime: IAgentRuntime) { + super(); + this.exchangeRegistry = {}; + const connection = new Connection( + runtime.getSetting('SOLANA_RPC_URL') || PROVIDER_CONFIG.DEFAULT_RPC + ); + this.connection = connection; + // Initialize publicKey using getWalletKey + getWalletKey(runtime, false) + .then(({ publicKey }) => { + if (!publicKey) { + throw new Error('Failed to initialize public key'); + } + this.publicKey = publicKey; + }) + .catch((error) => { + logger.error('Error initializing public key:', error); + }); + this.subscriptions = new Map(); + } + + /** + * Gets the wallet keypair for operations requiring private key access + * @returns {Promise} The wallet keypair + * @throws {Error} If private key is not available + */ + private async getWalletKeypair(): Promise { + const { keypair } = await getWalletKey(this.runtime, true); + if (!keypair) { + throw new Error('Failed to get wallet keypair'); + } + return keypair; + } + + /** + * Fetches data from the provided URL with retry logic. + * @param {string} url - The URL to fetch data from. + * @param {RequestInit} [options={}] - The options for the fetch request. + * @returns {Promise} - A promise that resolves to the fetched data. + */ + private async fetchWithRetry(url: string, options: RequestInit = {}): Promise { + let lastError: Error; + + for (let i = 0; i < PROVIDER_CONFIG.MAX_RETRIES; i++) { + try { + const response = await fetch(url, { + ...options, + headers: { + Accept: 'application/json', + 'x-chain': 'solana', + 'X-API-KEY': this.runtime.getSetting('BIRDEYE_API_KEY'), + ...options.headers, + }, + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`HTTP error! status: ${response.status}, message: ${errorText}`); + } + + return await response.json(); + } catch (error) { + logger.error(`Attempt ${i + 1} failed:`, error); + lastError = error; + if (i < PROVIDER_CONFIG.MAX_RETRIES - 1) { + await new Promise((resolve) => setTimeout(resolve, PROVIDER_CONFIG.RETRY_DELAY * 2 ** i)); + } + } + } + + throw lastError; + } + + /** + * Asynchronously fetches the prices of SOL, BTC, and ETH tokens. + * Uses cache to store and retrieve prices if available. + * @returns A Promise that resolves to an object containing the prices of SOL, BTC, and ETH tokens. + */ + private async fetchPrices(): Promise { + const cacheKey = 'prices'; + const cachedValue = await this.runtime.getCache(cacheKey); + + // if cachedValue is JSON, parse it + if (cachedValue) { + logger.log('Cache hit for fetchPrices'); + return cachedValue; + } + + logger.log('Cache miss for fetchPrices'); + const { SOL, BTC, ETH } = PROVIDER_CONFIG.TOKEN_ADDRESSES; + const tokens = [SOL, BTC, ETH]; + const prices: Prices = { + solana: { usd: '0' }, + bitcoin: { usd: '0' }, + ethereum: { usd: '0' }, + }; + + for (const token of tokens) { + const response = await this.fetchWithRetry( + `${PROVIDER_CONFIG.BIRDEYE_API}/defi/price?address=${token}` + ); + + if (response?.data?.value) { + const price = response.data.value.toString(); + prices[token === SOL ? 'solana' : token === BTC ? 'bitcoin' : 'ethereum'].usd = price; + } + } + + await this.runtime.setCache(cacheKey, prices); + return prices; + } + + /** + * Asynchronously fetches token accounts for a specific owner. + * + * @returns {Promise} A promise that resolves to an array of token accounts. + */ + private async getTokenAccounts() { + try { + const accounts = await this.connection.getParsedTokenAccountsByOwner(this.publicKey, { + programId: new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), + }); + return accounts.value; + } catch (error) { + logger.error('Error fetching token accounts:', error); + return []; + } + } + + /** + * Update wallet data including fetching wallet portfolio information, prices, and caching the data. + * @param {boolean} [force=false] - Whether to force update the wallet data even if the update interval has not passed + * @returns {Promise} The updated wallet portfolio information + */ + private async updateWalletData(force = false): Promise { + //console.log('updateWalletData - start') + const now = Date.now(); + + if (!this.publicKey) { + // can't be warn if we fire every start up + // maybe we just get the pubkey here proper + // or fall back to SOLANA_PUBLIC_KEY + logger.log('solana::updateWalletData - no Public Key yet'); + return {}; + } + + //console.log('updateWalletData - force', force, 'last', this.lastUpdate, 'UPDATE_INTERVAL', this.UPDATE_INTERVAL) + // Don't update if less than interval has passed, unless forced + if (!force && now - this.lastUpdate < this.UPDATE_INTERVAL) { + const cached = await this.getCachedData(); + if (cached) return cached; + } + //console.log('updateWalletData - fetch') + + try { + // Try Birdeye API first + const birdeyeApiKey = this.runtime.getSetting('BIRDEYE_API_KEY'); + if (birdeyeApiKey) { + try { + const walletData = await this.fetchWithRetry( + `${PROVIDER_CONFIG.BIRDEYE_API}/v1/wallet/token_list?wallet=${this.publicKey.toBase58()}` + ); + //console.log('walletData', walletData) + + if (walletData?.success && walletData?.data) { + const data = walletData.data; + const totalUsd = new BigNumber(data.totalUsd.toString()); + const prices = await this.fetchPrices(); + const solPriceInUSD = new BigNumber(prices.solana.usd); + + const portfolio: WalletPortfolio = { + totalUsd: totalUsd.toString(), + totalSol: totalUsd.div(solPriceInUSD).toFixed(6), + prices, + lastUpdated: now, + items: data.items.map((item: Item) => ({ + ...item, + valueSol: new BigNumber(item.valueUsd || 0).div(solPriceInUSD).toFixed(6), + name: item.name || 'Unknown', + symbol: item.symbol || 'Unknown', + priceUsd: item.priceUsd || '0', + valueUsd: item.valueUsd || '0', + })), + }; + + //console.log('saving portfolio', portfolio.items.length, 'tokens') + + // maybe should be keyed by public key + await this.runtime.setCache(SOLANA_WALLET_DATA_CACHE_KEY, portfolio); + this.lastUpdate = now; + return portfolio; + } + } catch (e) { + console.log('solana wallet exception err', e); + } + } + + // Fallback to basic token account info + const accounts = await this.getTokenAccounts(); + const items: Item[] = accounts.map((acc) => ({ + name: 'Unknown', + address: acc.account.data.parsed.info.mint, + symbol: 'Unknown', + decimals: acc.account.data.parsed.info.tokenAmount.decimals, + balance: acc.account.data.parsed.info.tokenAmount.amount, + uiAmount: acc.account.data.parsed.info.tokenAmount.uiAmount.toString(), + priceUsd: '0', + valueUsd: '0', + valueSol: '0', + })); + + const portfolio: WalletPortfolio = { + totalUsd: '0', + totalSol: '0', + items, + }; + + await this.runtime.setCache(SOLANA_WALLET_DATA_CACHE_KEY, portfolio); + this.lastUpdate = now; + return portfolio; + } catch (error) { + logger.error('Error updating wallet data:', error); + throw error; + } + } + + /** + * Retrieves cached wallet portfolio data from the database adapter. + * @returns A promise that resolves with the cached WalletPortfolio data if available, otherwise resolves with null. + */ + public async getCachedData(): Promise { + const cachedValue = await this.runtime.getCache(SOLANA_WALLET_DATA_CACHE_KEY); + if (cachedValue) { + return cachedValue; + } + return null; + } + + /** + * Forces an update of the wallet data and returns the updated WalletPortfolio object. + * @returns A promise that resolves with the updated WalletPortfolio object. + */ + public async forceUpdate(): Promise { + return await this.updateWalletData(true); + } + + /** + * Retrieves the public key of the instance. + * + * @returns {PublicKey} The public key of the instance. + */ + public getPublicKey(): PublicKey { + return this.publicKey; + } + + /** + * Retrieves the connection object. + * + * @returns {Connection} The connection object. + */ + public getConnection(): Connection { + return this.connection; + } + + /** + * Validates a Solana address. + * @param {string | undefined} address - The address to validate. + * @returns {boolean} True if the address is valid, false otherwise. + */ + public validateAddress(address: string | undefined): boolean { + if (!address) return false; + try { + // Handle Solana addresses + if (!/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(address)) { + logger.warn(`Invalid Solana address format: ${address}`); + return false; + } + + const pubKey = new PublicKey(address); + const isValid = Boolean(pubKey.toBase58()); + logger.log(`Solana address validation: ${address}`, { isValid }); + return isValid; + } catch (error) { + logger.error(`Address validation error: ${address}`, { error }); + return false; + } + } + + /** + * Creates a new Solana wallet by generating a keypair + * @returns {Promise<{publicKey: string, privateKey: string}>} Object containing base58-encoded public and private keys + */ + public async createWallet(): Promise<{ publicKey: string; privateKey: string }> { + try { + // Generate new keypair + const newKeypair = Keypair.generate(); + + // Convert to base58 strings for secure storage + const publicKey = newKeypair.publicKey.toBase58(); + const privateKey = bs58.encode(newKeypair.secretKey); + + // Clear the keypair from memory + newKeypair.secretKey.fill(0); + + return { + publicKey, + privateKey, + }; + } catch (error) { + logger.error('Error creating wallet:', error); + throw new Error('Failed to create new wallet'); + } + } + + /** + * Registers a provider with the service. + * @param {any} provider - The provider to register + * @returns {Promise} The ID assigned to the registered provider + */ + async registerExchange(provider: any) { + const id = Object.values(this.exchangeRegistry).length + 1; + logger.log('Registered', provider.name, 'as Solana provider #' + id); + this.exchangeRegistry[id] = provider; + return id; + } + + /** + * Subscribes to account changes for the given public key + * @param {string} accountAddress - The account address to subscribe to + * @returns {Promise} Subscription ID + */ + public async subscribeToAccount(accountAddress: string): Promise { + try { + if (!this.validateAddress(accountAddress)) { + throw new Error('Invalid account address'); + } + + // Check if already subscribed + if (this.subscriptions.has(accountAddress)) { + return this.subscriptions.get(accountAddress)!; + } + + // Create WebSocket connection if needed + const ws = this.connection.connection._rpcWebSocket; + + const subscriptionId = await ws.call('accountSubscribe', [ + accountAddress, + { + encoding: 'jsonParsed', + commitment: 'finalized', + }, + ]); + + // Setup notification handler + ws.subscribe(subscriptionId, 'accountNotification', async (notification: any) => { + try { + const { result } = notification; + if (result?.value) { + // Force update wallet data to reflect changes + await this.updateWalletData(true); + + // Emit an event that can be handled by the agent + this.runtime.emit('solana:account:update', { + address: accountAddress, + data: result.value, + }); + } + } catch (error) { + logger.error('Error handling account notification:', error); + } + }); + + this.subscriptions.set(accountAddress, subscriptionId); + logger.log(`Subscribed to account ${accountAddress} with ID ${subscriptionId}`); + return subscriptionId; + } catch (error) { + logger.error('Error subscribing to account:', error); + throw error; + } + } + + /** + * Unsubscribes from account changes + * @param {string} accountAddress - The account address to unsubscribe from + * @returns {Promise} Success status + */ + public async unsubscribeFromAccount(accountAddress: string): Promise { + try { + const subscriptionId = this.subscriptions.get(accountAddress); + if (!subscriptionId) { + logger.warn(`No subscription found for account ${accountAddress}`); + return false; + } + + const ws = this.connection.connection._rpcWebSocket; + const success = await ws.call('accountUnsubscribe', [subscriptionId]); + + if (success) { + this.subscriptions.delete(accountAddress); + logger.log(`Unsubscribed from account ${accountAddress}`); + } + + return success; + } catch (error) { + logger.error('Error unsubscribing from account:', error); + throw error; + } + } + + /** + * Starts the Solana service with the given agent runtime. + * + * @param {IAgentRuntime} runtime - The agent runtime to use for the Solana service. + * @returns {Promise} The initialized Solana service. + */ + static async start(runtime: IAgentRuntime): Promise { + logger.log('SolanaService start for', runtime.character.name); + + const solanaService = new SolanaService(runtime); + + solanaService.updateInterval = setInterval(async () => { + logger.log('Updating wallet data'); + await solanaService.updateWalletData(); + }, solanaService.UPDATE_INTERVAL); + + // Initial update + // won't matter because pubkey isn't set yet + //solanaService.updateWalletData().catch(console.error); + + return solanaService; + } + + /** + * Stops the Solana service. + * + * @param {IAgentRuntime} runtime - The agent runtime. + * @returns {Promise} - A promise that resolves once the Solana service has stopped. + */ + static async stop(runtime: IAgentRuntime) { + const client = runtime.getService(SOLANA_SERVICE_NAME); + if (!client) { + logger.error('SolanaService not found'); + return; + } + await client.stop(); + } + + /** + * Stops the update interval if it is currently running. + * @returns {Promise} A Promise that resolves when the update interval is stopped. + */ + async stop(): Promise { + // Unsubscribe from all accounts + for (const [address] of this.subscriptions) { + await this.unsubscribeFromAccount(address); + } + + if (this.updateInterval) { + clearInterval(this.updateInterval); + this.updateInterval = null; + } + } +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..4390e22 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,108 @@ +import type { PublicKey } from '@solana/web3.js'; + +/** + * Interface representing an item with specific properties. + * @typedef {Object} Item + * @property {string} name - The name of the item. + * @property {string} address - The address of the item. + * @property {string} symbol - The symbol of the item. + * @property {number} decimals - The number of decimals for the item. + * @property {string} balance - The balance of the item. + * @property {string} uiAmount - The UI amount of the item. + * @property {string} priceUsd - The price of the item in USD. + * @property {string} valueUsd - The value of the item in USD. + * @property {string} [valueSol] - Optional value of the item in SOL. + */ +export interface Item { + name: string; + address: string; + symbol: string; + decimals: number; + balance: string; + uiAmount: string; + priceUsd: string; + valueUsd: string; + valueSol?: string; +} + +/** + * Defines the interface for storing price information for various cryptocurrencies. + * + * @interface Prices + * @property {Object} solana - Price information for Solana cryptocurrency. + * @property {string} solana.usd - Price of Solana in USD. + * @property {Object} bitcoin - Price information for Bitcoin cryptocurrency. + * @property {string} bitcoin.usd - Price of Bitcoin in USD. + * @property {Object} ethereum - Price information for Ethereum cryptocurrency. + * @property {string} ethereum.usd - Price of Ethereum in USD. + */ +export interface Prices { + solana: { usd: string }; + bitcoin: { usd: string }; + ethereum: { usd: string }; +} + +/** + * Interface representing a wallet portfolio. + * @typedef {Object} WalletPortfolio + * @property {string} totalUsd - The total value in USD. + * @property {string} [totalSol] - The total value in SOL (optional). + * @property {Array} items - An array of items in the wallet portfolio. + * @property {Prices} [prices] - Optional prices of the items. + * @property {number} [lastUpdated] - Timestamp of when the portfolio was last updated (optional). + */ +export interface WalletPortfolio { + totalUsd: string; + totalSol?: string; + items: Array; + prices?: Prices; + lastUpdated?: number; +} + +/** + * Represents the structure of a Token Account Info object. + * @typedef {object} TokenAccountInfo + * @property {PublicKey} pubkey - The public key associated with the token account. + * @property {object} account - Information about the token account. + * @property {number} account.lamports - The amount of lamports in the account. + * @property {object} account.data - Data associated with the account. + * @property {object} account.data.parsed - Parsed information. + * @property {object} account.data.parsed.info - Detailed information. + * @property {string} account.data.parsed.info.mint - The mint associated with the token. + * @property {string} account.data.parsed.info.owner - The owner of the token. + * @property {object} account.data.parsed.info.tokenAmount - Token amount details. + * @property {string} account.data.parsed.info.tokenAmount.amount - The amount of the token. + * @property {number} account.data.parsed.info.tokenAmount.decimals - The decimals of the token. + * @property {number} account.data.parsed.info.tokenAmount.uiAmount - The UI amount of the token. + * @property {string} account.data.parsed.type - The type of parsed data. + * @property {string} account.data.program - The program associated with the account. + * @property {number} account.data.space - The space available in the account. + * @property {string} account.owner - The owner of the account. + * @property {boolean} account.executable - Indicates if the account is executable. + * @property {number} account.rentEpoch - The rent epoch of the account. + */ +export interface TokenAccountInfo { + pubkey: PublicKey; + account: { + lamports: number; + data: { + parsed: { + info: { + mint: string; + owner: string; + tokenAmount: { + amount: string; + decimals: number; + uiAmount: number; + }; + }; + type: string; + }; + program: string; + space: number; + }; + owner: string; + executable: boolean; + rentEpoch: number; + }; +} diff --git a/src/types/token.ts b/src/types/token.ts deleted file mode 100644 index 1fca4c3..0000000 --- a/src/types/token.ts +++ /dev/null @@ -1,302 +0,0 @@ -export interface TokenSecurityData { - ownerBalance: string; - creatorBalance: string; - ownerPercentage: number; - creatorPercentage: number; - top10HolderBalance: string; - top10HolderPercent: number; -} - -export interface TokenCodex { - id: string; - address: string; - cmcId: number; - decimals: number; - name: string; - symbol: string; - totalSupply: string; - circulatingSupply: string; - imageThumbUrl: string; - blueCheckmark: boolean; - isScam: boolean; -} - -export interface TokenTradeData { - address: string; - holder: number; - market: number; - last_trade_unix_time: number; - last_trade_human_time: string; - price: number; - history_30m_price: number; - price_change_30m_percent: number; - history_1h_price: number; - price_change_1h_percent: number; - history_2h_price: number; - price_change_2h_percent: number; - history_4h_price: number; - price_change_4h_percent: number; - history_6h_price: number; - price_change_6h_percent: number; - history_8h_price: number; - price_change_8h_percent: number; - history_12h_price: number; - price_change_12h_percent: number; - history_24h_price: number; - price_change_24h_percent: number; - unique_wallet_30m: number; - unique_wallet_history_30m: number; - unique_wallet_30m_change_percent: number; - unique_wallet_1h: number; - unique_wallet_history_1h: number; - unique_wallet_1h_change_percent: number; - unique_wallet_2h: number; - unique_wallet_history_2h: number; - unique_wallet_2h_change_percent: number; - unique_wallet_4h: number; - unique_wallet_history_4h: number; - unique_wallet_4h_change_percent: number; - unique_wallet_8h: number; - unique_wallet_history_8h: number | null; - unique_wallet_8h_change_percent: number | null; - unique_wallet_24h: number; - unique_wallet_history_24h: number | null; - unique_wallet_24h_change_percent: number | null; - trade_30m: number; - trade_history_30m: number; - trade_30m_change_percent: number; - sell_30m: number; - sell_history_30m: number; - sell_30m_change_percent: number; - buy_30m: number; - buy_history_30m: number; - buy_30m_change_percent: number; - volume_30m: number; - volume_30m_usd: number; - volume_history_30m: number; - volume_history_30m_usd: number; - volume_30m_change_percent: number; - volume_buy_30m: number; - volume_buy_30m_usd: number; - volume_buy_history_30m: number; - volume_buy_history_30m_usd: number; - volume_buy_30m_change_percent: number; - volume_sell_30m: number; - volume_sell_30m_usd: number; - volume_sell_history_30m: number; - volume_sell_history_30m_usd: number; - volume_sell_30m_change_percent: number; - trade_1h: number; - trade_history_1h: number; - trade_1h_change_percent: number; - sell_1h: number; - sell_history_1h: number; - sell_1h_change_percent: number; - buy_1h: number; - buy_history_1h: number; - buy_1h_change_percent: number; - volume_1h: number; - volume_1h_usd: number; - volume_history_1h: number; - volume_history_1h_usd: number; - volume_1h_change_percent: number; - volume_buy_1h: number; - volume_buy_1h_usd: number; - volume_buy_history_1h: number; - volume_buy_history_1h_usd: number; - volume_buy_1h_change_percent: number; - volume_sell_1h: number; - volume_sell_1h_usd: number; - volume_sell_history_1h: number; - volume_sell_history_1h_usd: number; - volume_sell_1h_change_percent: number; - trade_2h: number; - trade_history_2h: number; - trade_2h_change_percent: number; - sell_2h: number; - sell_history_2h: number; - sell_2h_change_percent: number; - buy_2h: number; - buy_history_2h: number; - buy_2h_change_percent: number; - volume_2h: number; - volume_2h_usd: number; - volume_history_2h: number; - volume_history_2h_usd: number; - volume_2h_change_percent: number; - volume_buy_2h: number; - volume_buy_2h_usd: number; - volume_buy_history_2h: number; - volume_buy_history_2h_usd: number; - volume_buy_2h_change_percent: number; - volume_sell_2h: number; - volume_sell_2h_usd: number; - volume_sell_history_2h: number; - volume_sell_history_2h_usd: number; - volume_sell_2h_change_percent: number; - trade_4h: number; - trade_history_4h: number; - trade_4h_change_percent: number; - sell_4h: number; - sell_history_4h: number; - sell_4h_change_percent: number; - buy_4h: number; - buy_history_4h: number; - buy_4h_change_percent: number; - volume_4h: number; - volume_4h_usd: number; - volume_history_4h: number; - volume_history_4h_usd: number; - volume_4h_change_percent: number; - volume_buy_4h: number; - volume_buy_4h_usd: number; - volume_buy_history_4h: number; - volume_buy_history_4h_usd: number; - volume_buy_4h_change_percent: number; - volume_sell_4h: number; - volume_sell_4h_usd: number; - volume_sell_history_4h: number; - volume_sell_history_4h_usd: number; - volume_sell_4h_change_percent: number; - trade_8h: number; - trade_history_8h: number | null; - trade_8h_change_percent: number | null; - sell_8h: number; - sell_history_8h: number | null; - sell_8h_change_percent: number | null; - buy_8h: number; - buy_history_8h: number | null; - buy_8h_change_percent: number | null; - volume_8h: number; - volume_8h_usd: number; - volume_history_8h: number; - volume_history_8h_usd: number; - volume_8h_change_percent: number | null; - volume_buy_8h: number; - volume_buy_8h_usd: number; - volume_buy_history_8h: number; - volume_buy_history_8h_usd: number; - volume_buy_8h_change_percent: number | null; - volume_sell_8h: number; - volume_sell_8h_usd: number; - volume_sell_history_8h: number; - volume_sell_history_8h_usd: number; - volume_sell_8h_change_percent: number | null; - trade_24h: number; - trade_history_24h: number; - trade_24h_change_percent: number | null; - sell_24h: number; - sell_history_24h: number; - sell_24h_change_percent: number | null; - buy_24h: number; - buy_history_24h: number; - buy_24h_change_percent: number | null; - volume_24h: number; - volume_24h_usd: number; - volume_history_24h: number; - volume_history_24h_usd: number; - volume_24h_change_percent: number | null; - volume_buy_24h: number; - volume_buy_24h_usd: number; - volume_buy_history_24h: number; - volume_buy_history_24h_usd: number; - volume_buy_24h_change_percent: number | null; - volume_sell_24h: number; - volume_sell_24h_usd: number; - volume_sell_history_24h: number; - volume_sell_history_24h_usd: number; - volume_sell_24h_change_percent: number | null; -} - -export interface HolderData { - address: string; - balance: string; -} - -export interface ProcessedTokenData { - security: TokenSecurityData; - tradeData: TokenTradeData; - holderDistributionTrend: string; // 'increasing' | 'decreasing' | 'stable' - highValueHolders: Array<{ - holderAddress: string; - balanceUsd: string; - }>; - recentTrades: boolean; - highSupplyHoldersCount: number; - dexScreenerData: DexScreenerData; - - isDexScreenerListed: boolean; - isDexScreenerPaid: boolean; - tokenCodex: TokenCodex; -} - -export interface DexScreenerPair { - chainId: string; - dexId: string; - url: string; - pairAddress: string; - baseToken: { - address: string; - name: string; - symbol: string; - }; - quoteToken: { - address: string; - name: string; - symbol: string; - }; - priceNative: string; - priceUsd: string; - txns: { - m5: { buys: number; sells: number }; - h1: { buys: number; sells: number }; - h6: { buys: number; sells: number }; - h24: { buys: number; sells: number }; - }; - volume: { - h24: number; - h6: number; - h1: number; - m5: number; - }; - priceChange: { - m5: number; - h1: number; - h6: number; - h24: number; - }; - liquidity: { - usd: number; - base: number; - quote: number; - }; - fdv: number; - marketCap: number; - pairCreatedAt: number; - info: { - imageUrl: string; - websites: { label: string; url: string }[]; - socials: { type: string; url: string }[]; - }; - boosts: { - active: number; - }; -} - -export interface DexScreenerData { - schemaVersion: string; - pairs: DexScreenerPair[]; -} - -export interface Prices { - solana: { usd: string }; - bitcoin: { usd: string }; - ethereum: { usd: string }; -} - -export interface CalculatedBuyAmounts { - none: 0; - low: number; - medium: number; - high: number; -} diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..625391d --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,13 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist", + "sourceMap": true, + "inlineSources": true, + "declaration": true, + "emitDeclarationOnly": true + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"] +} diff --git a/tsconfig.json b/tsconfig.json index 005fbac..f156f86 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,8 +1,24 @@ { - "extends": "../core/tsconfig.json", - "compilerOptions": { - "outDir": "dist", - "rootDir": "src" - }, - "include": ["src/**/*.ts"] + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "baseUrl": "../..", + "lib": ["ESNext"], + "target": "ESNext", + "module": "Preserve", + "moduleResolution": "Bundler", + "strict": true, + "esModuleInterop": true, + "allowImportingTsExtensions": true, + "declaration": true, + "emitDeclarationOnly": true, + "resolveJsonModule": true, + "moduleDetection": "force", + "allowArbitraryExtensions": true, + "paths": { + "@elizaos/core": ["../core/src"], + "@elizaos/core/*": ["../core/src/*"] + } + }, + "include": ["src/**/*.ts"] } diff --git a/tsup.config.ts b/tsup.config.ts index 84d6642..168d71b 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -1,30 +1,31 @@ -import { defineConfig } from "tsup"; +import { defineConfig } from 'tsup'; export default defineConfig({ - entry: ["src/index.ts"], - outDir: "dist", - sourcemap: true, - clean: true, - format: ["esm"], // Ensure you're targeting CommonJS - external: [ - "@elizaos/core", - "dotenv", // Externalize dotenv to prevent bundling - "fs", // Externalize fs to use Node.js built-in module - "path", // Externalize other built-ins if necessary - "@reflink/reflink", - "@node-llama-cpp", - "https", - "http", - "agentkeepalive", - "safe-buffer", - "base-x", - "bs58", - "borsh", - "@solana/buffer-layout", - "stream", - "buffer", - "querystring", - "amqplib", - // Add other modules you want to externalize - ], + entry: ['src/index.ts'], + outDir: 'dist', + tsconfig: './tsconfig.build.json', // Use build-specific tsconfig + sourcemap: true, + clean: true, + format: ['esm'], // Ensure you're targeting CommonJS + dts: true, + external: [ + 'dotenv', // Externalize dotenv to prevent bundling + 'fs', // Externalize fs to use Node.js built-in module + 'path', // Externalize other built-ins if necessary + '@reflink/reflink', + '@node-llama-cpp', + 'https', + 'http', + 'agentkeepalive', + 'safe-buffer', + 'base-x', + 'bs58', + 'borsh', + '@solana/buffer-layout', + 'stream', + 'buffer', + 'querystring', + '@elizaos/core', + 'zod', + ], });