diff --git a/packages/plugin-attps/README.MD b/packages/plugin-attps/README.MD new file mode 100644 index 00000000000..596a7d63a9c --- /dev/null +++ b/packages/plugin-attps/README.MD @@ -0,0 +1,172 @@ + +# @elizaos/plugin-attps + +Foundation plugin that enables advanced agent interactions, data verification, and price queries on the Eliza OS platform. It streamlines agent creation, verification processes, and provides a flexible framework for building robust agent-based solutions. + +## Overview + +The ATTPs plugin bridges agent-based logic with the Eliza ecosystem. It handles agent registration, data verification, and price queries, empowering both automated and user-driven workflows. + +## Features + +### Agent Operations +- **Agent Creation**: Deploy new agents with custom settings +- **Registration**: Register agents on-chain or via standardized processes +- **Multi-Signer Framework**: Supports threshold-based approval flows + +### Data Verification +- **Chain Validation**: Verify data authenticity on-chain +- **Transaction Execution**: Handle verification logic with built-in security checks +- **Auto-Hashing**: Convert raw data to hashed formats when needed +- **Metadata Parsing**: Validate content type, encoding, and compression + +### Price Queries +- **Live Price Data**: Fetch price information for various pairs +- **Format Validation**: Normalize user query inputs to standard trading-pair formats +- **APIs Integration**: Retrieve real-time or near-real-time pricing information + +## Security Features + +### Access Control +- **Private Key Management**: Safe usage of private keys for transaction signing +- **Environment Variables**: Secure injection of credentials +- **On-Chain Validation**: Leverage on-chain contract checks + +### Verification +- **Input Validation**: Strict schema checks before on-chain operations +- **Transaction Receipts**: Provide verifiable transaction details +- **Error Handling**: Detailed error logs for quick debugging + +## Installation + +```bash +npm install @elizaos/plugin-attps +``` + +## Configuration + +Configure the plugin by setting environment variables or runtime settings: +- ATTPS_RPC_URL +- ATTPS_PROXY_ADDRESS +- ATTPS_PRIVATE_KEY +- ATTPS_CONVERTER_ADDRESS +- ATTPS_AUTO_HASH_DATA + +## Usage + +### Basic Setup +```typescript +import { attpsPlugin } from "@elizaos/plugin-attps"; + +// Initialize the plugin +const runtime = await initializeRuntime({ + plugins: [attpsPlugin], +}); +``` + +### Actions + +#### CREATE_AND_REGISTER_AGENT +Creates and registers an agent using specified settings. + +```typescript +const result = await runtime.executeAction("CREATE_AND_REGISTER_AGENT", { + signers: [...], + threshold: 3, + agentHeader: { ... }, + // ...other fields... +}); +``` + +#### VERIFY +Verifies data on-chain via the Agent SDK. + +```typescript +const result = await runtime.executeAction("VERIFY", { + payload: { + data: "0x...hexData", + signatures: [...], + }, + agent: "0x...agentAddress", + digest: "0x...digestString", +}); +``` + +#### PRICE_QUERY +Fetches live price data for a specified trading pair. + +```typescript +const result = await runtime.executeAction("PRICE_QUERY", { + pair: "BTC/USD", +}); +``` + +## Performance Optimization + +1. **Cache Management** + - Implement caching for frequent queries + - Monitor retrieval times and cache hits + +2. **Network Efficiency** + - Batch requests where possible + - Validate response parsing to reduce overhead + +## System Requirements +- Node.js 16.x or higher +- Sufficient network access to on-chain endpoints +- Basic configuration of environment variables +- Minimum 4GB RAM recommended + +## Troubleshooting + +1. **Invalid Agent Settings** + - Ensure signers and threshold are correct + - Validate agentHeader for proper UUIDs and numeric values + +2. **Verification Failures** + - Check the input data formats + - Confirm environment variables are set + +3. **Price Query Errors** + - Verify the trading pair format + - Check external API availability + +## Safety & Security + +1. **Credential Management** + - Store private keys securely + - Do not commit secrets to version control + +2. **Transaction Limits** + - Configure thresholds to mitigate abuse + - Log transaction attempts and failures + +3. **Monitoring & Logging** + - Track unusual activity + - Maintain detailed audit logs + +## Support + +For issues or feature requests: +1. Check existing documentation +2. Submit a GitHub issue with relevant details +3. Include transaction logs and system info if applicable + +## Contributing + +We welcome pull requests! Refer to the project’s CONTRIBUTING.md and open discussions to coordinate efforts. + +## Credits + +- [APRO](https://www.apro.com/) - Plugin sponsor and partner +- [ai-agent-sdk-js](https://github.com/APRO-com/ai-agent-sdk-js) - Underlying agent SDK +- [ethers.js](https://docs.ethers.io/) - Transaction and contract interaction +- Community contributors for feedback and testing + +For more information about Apro plugin capabilities: + +- [Apro Documentation](https://docs.apro.com/en) + +## License + +This plugin is part of the Eliza project. Refer to the main project repository for licensing details. \ No newline at end of file diff --git a/packages/plugin-attps/__tests__/actions/attpsPriceQuery.test.ts b/packages/plugin-attps/__tests__/actions/attpsPriceQuery.test.ts new file mode 100644 index 00000000000..67185304b55 --- /dev/null +++ b/packages/plugin-attps/__tests__/actions/attpsPriceQuery.test.ts @@ -0,0 +1,174 @@ +// Mock declarations must come first +vi.mock('@elizaos/core'); +vi.mock('ai-agent-sdk-js'); + +import { vi, describe, it, expect, beforeEach } from 'vitest'; +import type { IAgentRuntime, Memory, State } from '@elizaos/core'; +import { generateObject } from '@elizaos/core'; +import { attpsPriceQuery } from '../../src/actions/attpsPriceQuery'; + +describe('attpsPriceQuery', () => { + const mockRuntime: IAgentRuntime = { + composeState: vi.fn(), + updateRecentMessageState: vi.fn(), + getSetting: vi.fn() + } as unknown as IAgentRuntime; + + const mockMessage: Memory = { + userId: 'test-user', + agentId: 'test-agent', + roomId: 'test-room', + content: { + text: 'query price' + } + } as Memory; + + const mockState: State = {}; + const mockCallback = vi.fn(); + const mockFetch = vi.fn(); + + beforeEach(() => { + vi.clearAllMocks(); + vi.mocked(mockRuntime.composeState).mockResolvedValue(mockState); + vi.mocked(mockRuntime.updateRecentMessageState).mockResolvedValue(mockState); + global.fetch = mockFetch; + }); + + describe('validate', () => { + it('should always return true', async () => { + const result = await attpsPriceQuery.validate(mockRuntime, mockMessage); + expect(result).toBe(true); + }); + }); + + describe('handler', () => { + const mockPriceQuery = { + sourceAgentId: 'test-source-agent', + feedId: 'test-feed' + }; + + const mockPriceResponse = { + code: 0, + message: 'success', + result: { + askPrice: '100.50', + bidPrice: '100.40', + midPrice: '100.45', + validTimeStamp: '1234567890' + } + }; + + it('should successfully fetch price data', async () => { + // Mock generateObject to return price query params + vi.mocked(generateObject).mockResolvedValueOnce({ + object: mockPriceQuery + }); + + // Mock successful API response + mockFetch.mockResolvedValueOnce({ + json: () => Promise.resolve(mockPriceResponse) + }); + + const result = await attpsPriceQuery.handler( + mockRuntime, + mockMessage, + mockState, + {}, + mockCallback + ); + + expect(mockCallback).toHaveBeenCalledWith(expect.objectContaining({ + text: expect.stringContaining('Ask price: 100.5') + })); + expect(mockFetch).toHaveBeenCalledWith( + expect.stringContaining('sourceAgentId=test-source-agent') + ); + }); + + it('should handle price query params generation failure', async () => { + // Mock generateObject to throw an error + vi.mocked(generateObject).mockRejectedValueOnce( + new Error('Failed to generate params') + ); + + await attpsPriceQuery.handler( + mockRuntime, + mockMessage, + mockState, + {}, + mockCallback + ); + + expect(mockCallback).toHaveBeenCalledWith({ + text: expect.stringContaining('Failed to generate price query params') + }); + }); + + it('should handle API error response', async () => { + // Mock generateObject to return price query params + vi.mocked(generateObject).mockResolvedValueOnce({ + object: mockPriceQuery + }); + + // Mock API error response + mockFetch.mockResolvedValueOnce({ + json: () => Promise.resolve({ + code: 1, + message: 'API Error' + }) + }); + + await attpsPriceQuery.handler( + mockRuntime, + mockMessage, + mockState, + {}, + mockCallback + ); + + expect(mockCallback).toHaveBeenCalledWith({ + text: 'Error fetching price data: API Error' + }); + }); + + it('should handle network failure', async () => { + // Mock generateObject to return price query params + vi.mocked(generateObject).mockResolvedValueOnce({ + object: mockPriceQuery + }); + + // Mock network failure + mockFetch.mockRejectedValueOnce(new Error('Network error')); + + await attpsPriceQuery.handler( + mockRuntime, + mockMessage, + mockState, + {}, + mockCallback + ); + + expect(mockCallback).toHaveBeenCalledWith({ + text: 'Error fetching price data: Network error' + }); + }); + }); + + describe('metadata', () => { + it('should have correct name and description', () => { + expect(attpsPriceQuery.name).toBe('ATTPS_PRICE_QUERY'); + expect(attpsPriceQuery.description).toContain('Call remote API to fetch price data'); + }); + + it('should have valid examples', () => { + expect(Array.isArray(attpsPriceQuery.examples)).toBe(true); + expect(attpsPriceQuery.examples.length).toBeGreaterThan(0); + + attpsPriceQuery.examples.forEach(example => { + expect(Array.isArray(example)).toBe(true); + expect(example.length).toBe(2); + expect(example[1].content.action).toBe('ATTPS_PRICE_QUERY'); + }); + }); + }); +}); diff --git a/packages/plugin-attps/__tests__/actions/createAndRegisterAgent.test.ts b/packages/plugin-attps/__tests__/actions/createAndRegisterAgent.test.ts new file mode 100644 index 00000000000..7672aab0572 --- /dev/null +++ b/packages/plugin-attps/__tests__/actions/createAndRegisterAgent.test.ts @@ -0,0 +1,155 @@ +// Mock declarations must come first +vi.mock('@elizaos/core'); +vi.mock('ai-agent-sdk-js', () => { + const mockCreateAndRegisterAgent = vi.fn(); + return { + AgentSDK: vi.fn().mockImplementation(() => ({ + createAndRegisterAgent: mockCreateAndRegisterAgent + })), + parseNewAgentAddress: vi.fn().mockReturnValue('test-agent-address') + }; +}); +vi.mock('../../src/types', () => ({ + isAgentSettings: vi.fn().mockReturnValue(true), + AgentSettingsSchema: {} +})); + +import { vi, describe, it, expect, beforeEach } from 'vitest'; +import type { IAgentRuntime, Memory, State } from '@elizaos/core'; +import { generateObject } from '@elizaos/core'; +import { createAndRegisterAgent } from '../../src/actions/createAndRegisterAgent'; +import { AgentSDK } from 'ai-agent-sdk-js'; + +describe('createAndRegisterAgent', () => { + const mockRuntime: IAgentRuntime = { + composeState: vi.fn(), + updateRecentMessageState: vi.fn(), + getSetting: vi.fn() + } as unknown as IAgentRuntime; + + const mockMessage: Memory = { + userId: 'test-user', + agentId: 'test-agent', + roomId: 'test-room', + content: { + text: 'create agent' + } + } as Memory; + + const mockState: State = {}; + const mockCallback = vi.fn(); + const mockTx = { + hash: 'test-hash', + wait: vi.fn().mockResolvedValue({ hash: 'test-hash' }) + }; + const mockAgentSettings = { + name: 'test-agent', + description: 'test description', + settings: { + key: 'value' + } + }; + + beforeEach(() => { + vi.clearAllMocks(); + vi.mocked(mockRuntime.composeState).mockResolvedValue(mockState); + vi.mocked(mockRuntime.updateRecentMessageState).mockResolvedValue(mockState); + }); + + describe('validate', () => { + it('should always return true', async () => { + const result = await createAndRegisterAgent.validate(mockRuntime, mockMessage); + expect(result).toBe(true); + }); + }); + + describe('handler', () => { + it('should successfully create and register agent', async () => { + // Mock generateObject to return agent settings + vi.mocked(generateObject).mockResolvedValueOnce({ + object: mockAgentSettings + }); + + // Mock successful registration + const mockAgent = { + createAndRegisterAgent: vi.fn().mockResolvedValue(mockTx) + }; + vi.mocked(AgentSDK).mockImplementation(() => mockAgent); + + await createAndRegisterAgent.handler( + mockRuntime, + mockMessage, + mockState, + {}, + mockCallback + ); + + expect(mockCallback).toHaveBeenCalledWith({ + text: 'Agent created and registered successfully: test-agent-address' + }); + expect(mockAgent.createAndRegisterAgent).toHaveBeenCalledWith({agentSettings: mockAgentSettings}); + }); + + it('should handle agent settings generation failure', async () => { + // Mock generateObject to throw an error + vi.mocked(generateObject).mockRejectedValueOnce( + new Error('Failed to generate settings') + ); + + await createAndRegisterAgent.handler( + mockRuntime, + mockMessage, + mockState, + {}, + mockCallback + ); + + expect(mockCallback).toHaveBeenCalledWith({ + text: 'Failed to generate Agent settings. Please provide valid input.' + }); + }); + + it('should handle registration failure', async () => { + // Mock generateObject to return agent settings + vi.mocked(generateObject).mockResolvedValueOnce({ + object: mockAgentSettings + }); + + // Mock registration failure + const mockAgent = { + createAndRegisterAgent: vi.fn().mockRejectedValue(new Error('Registration failed')) + }; + vi.mocked(AgentSDK).mockImplementation(() => mockAgent); + + await createAndRegisterAgent.handler( + mockRuntime, + mockMessage, + mockState, + {}, + mockCallback + ); + + expect(mockCallback).toHaveBeenCalledWith({ + text: 'Error creating agent: Registration failed' + }); + }); + }); + + describe('metadata', () => { + it('should have correct name and description', () => { + expect(createAndRegisterAgent.name).toBe('CREATE_AND_REGISTER_AGENT'); + expect(createAndRegisterAgent.description).toContain('Create and register an agent with ATTPs'); + }); + + it('should have valid examples', () => { + expect(Array.isArray(createAndRegisterAgent.examples)).toBe(true); + expect(createAndRegisterAgent.examples.length).toBeGreaterThan(0); + + createAndRegisterAgent.examples.forEach(example => { + expect(Array.isArray(example)).toBe(true); + expect(example.length).toBe(2); + expect(example[1].content.action).toBe('CREATE_AND_REGISTER_AGENT'); + }); + }); + }); +}); diff --git a/packages/plugin-attps/__tests__/actions/verifyData.test.ts b/packages/plugin-attps/__tests__/actions/verifyData.test.ts new file mode 100644 index 00000000000..0dbbad10476 --- /dev/null +++ b/packages/plugin-attps/__tests__/actions/verifyData.test.ts @@ -0,0 +1,169 @@ +// Mock declarations must come first +vi.mock('@elizaos/core', () => ({ + Action: class {}, + composeContext: vi.fn(), + elizaLogger: { + info: vi.fn(), + error: vi.fn() + }, + generateObject: vi.fn(), + ModelClass: { + LARGE: 'LARGE' + } +})); + +vi.mock('ai-agent-sdk-js', () => { + const mockVerify = vi.fn(); + return { + AgentSDK: vi.fn().mockImplementation(() => ({ + verify: mockVerify + })) + }; +}); + +import { vi, describe, it, expect, beforeEach } from 'vitest'; +import type { IAgentRuntime, Memory, State } from '@elizaos/core'; +import { generateObject } from '@elizaos/core'; +import { verifyData } from '../../src/actions/verifyData'; +import { AgentSDK } from 'ai-agent-sdk-js'; + +describe('verifyData', () => { + const mockRuntime: IAgentRuntime = { + composeState: vi.fn(), + updateRecentMessageState: vi.fn(), + getSetting: vi.fn() + } as unknown as IAgentRuntime; + + const mockMessage: Memory = { + userId: 'test-user', + agentId: 'test-agent', + roomId: 'test-room', + content: { + text: 'verify data' + } + } as Memory; + + const mockState: State = {}; + const mockCallback = vi.fn(); + const mockVerify = vi.fn(); + + beforeEach(() => { + vi.clearAllMocks(); + vi.mocked(mockRuntime.composeState).mockResolvedValue(mockState); + vi.mocked(mockRuntime.updateRecentMessageState).mockResolvedValue(mockState); + }); + + describe('validate', () => { + it('should always return true', async () => { + const result = await verifyData.validate(mockRuntime, mockMessage); + expect(result).toBe(true); + }); + }); + + describe('handler', () => { + const mockVerifyParams = { + agent: 'test-agent', + digest: 'test-digest', + payload: { + data: 'test-data', + dataHash: 'test-hash', + signatures: [{ + r: 'test-r', + s: 'test-s', + v: 27 + }] + } + }; + + it('should successfully verify data', async () => { + // Mock generateObject to return verify params + vi.mocked(generateObject).mockResolvedValueOnce({ + object: mockVerifyParams + }); + + const mockTx = { + hash: 'test-hash', + wait: vi.fn().mockResolvedValue({ hash: 'test-hash' }) + }; + + const mockAgent = { + verify: vi.fn().mockResolvedValue(mockTx) + }; + vi.mocked(AgentSDK).mockImplementation(() => mockAgent); + + await verifyData.handler( + mockRuntime, + mockMessage, + mockState, + {}, + mockCallback + ); + + expect(mockCallback).toHaveBeenCalledWith({ + text: 'Success: Data verified successfully. Transaction ID: test-hash' + }); + expect(mockAgent.verify).toHaveBeenCalledWith(mockVerifyParams); + }); + + it('should handle verify params generation failure', async () => { + // Mock generateObject to throw an error + vi.mocked(generateObject).mockRejectedValueOnce( + new Error('Failed to generate params') + ); + + await verifyData.handler( + mockRuntime, + mockMessage, + mockState, + {}, + mockCallback + ); + + expect(mockCallback).toHaveBeenCalledWith({ + text: 'Failed to generate verify params. Please provide valid input.' + }); + }); + + it('should handle verification failure', async () => { + // Mock generateObject to return verify params + vi.mocked(generateObject).mockResolvedValueOnce({ + object: mockVerifyParams + }); + + const mockAgent = { + verify: vi.fn().mockRejectedValue(new Error('Verification failed')) + }; + vi.mocked(AgentSDK).mockImplementation(() => mockAgent); + + await verifyData.handler( + mockRuntime, + mockMessage, + mockState, + {}, + mockCallback + ); + + expect(mockCallback).toHaveBeenCalledWith({ + text: 'Error verifying data: Verification failed' + }); + }); + }); + + describe('metadata', () => { + it('should have correct name and description', () => { + expect(verifyData.name).toBe('VERIFY'); + expect(verifyData.description).toContain('Verify data with ATTPs'); + }); + + it('should have valid examples', () => { + expect(Array.isArray(verifyData.examples)).toBe(true); + expect(verifyData.examples.length).toBeGreaterThan(0); + + verifyData.examples.forEach(example => { + expect(Array.isArray(example)).toBe(true); + expect(example.length).toBe(2); + expect(example[1].content.action).toBe('VERIFY'); + }); + }); + }); +}); diff --git a/packages/plugin-attps/__tests__/index.test.ts b/packages/plugin-attps/__tests__/index.test.ts new file mode 100644 index 00000000000..164bf1bbf29 --- /dev/null +++ b/packages/plugin-attps/__tests__/index.test.ts @@ -0,0 +1,55 @@ +import { vi, describe, it, expect } from 'vitest'; + +vi.mock('@elizaos/core', () => ({ + Plugin: class {}, + Action: class {}, + composeContext: vi.fn(), + elizaLogger: { + info: vi.fn(), + error: vi.fn() + }, + generateObject: vi.fn(), + ModelClass: { + LARGE: 'LARGE' + } +})); + +vi.mock('ai-agent-sdk-js', () => ({ + AgentSDK: { + createAndRegisterAgent: vi.fn(), + verify: vi.fn() + }, + AgentSettings: class {}, + VerifyParams: class {}, + parseNewAgentAddress: vi.fn() +})); + +import { attpsPlugin } from '../src'; + +describe('attpsPlugin', () => { + it('should have correct plugin metadata', () => { + expect(attpsPlugin.name).toBe('ATTPs'); + expect(attpsPlugin.description).toBe('ATTPs Plugin for Eliza'); + }); + + it('should register all required actions', () => { + expect(attpsPlugin.actions).toHaveLength(3); + + const actionNames = attpsPlugin.actions?.map(action => action.name); + expect(actionNames).toContain('CREATE_AND_REGISTER_AGENT'); + expect(actionNames).toContain('VERIFY'); + expect(actionNames).toContain('ATTPS_PRICE_QUERY'); + }); + + it('should have correct similes for each action', () => { + const createAction = attpsPlugin.actions?.find(a => a.name === 'CREATE_AND_REGISTER_AGENT'); + expect(createAction?.similes).toContain('CREATE_AGENT'); + expect(createAction?.similes).toContain('REGISTER_AGENT'); + + const verifyAction = attpsPlugin.actions?.find(a => a.name === 'VERIFY'); + expect(verifyAction?.similes).toContain('VERIFY_DATA'); + + const priceAction = attpsPlugin.actions?.find(a => a.name === 'ATTPS_PRICE_QUERY'); + expect(priceAction?.similes).toContain('ATTPS_PRICE_FETCH'); + }); +}); diff --git a/packages/plugin-attps/biome.json b/packages/plugin-attps/biome.json new file mode 100644 index 00000000000..818716a6219 --- /dev/null +++ b/packages/plugin-attps/biome.json @@ -0,0 +1,41 @@ +{ + "$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/packages/plugin-attps/package.json b/packages/plugin-attps/package.json new file mode 100644 index 00000000000..bbe6e4fd415 --- /dev/null +++ b/packages/plugin-attps/package.json @@ -0,0 +1,40 @@ +{ + "name": "@elizaos/plugin-attps", + "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": { + "@elizaos/core": "workspace:*", + "ai-agent-sdk-js": "^0.0.2", + "ethers": "^6.13.5" + }, + "scripts": { + "build": "tsup --format esm --dts", + "dev": "tsup --format esm --dts --watch", + "test": "vitest run", + "lint": "biome lint .", + "lint:fix": "biome check --apply .", + "format": "biome format .", + "format:fix": "biome format --write ." + }, + "devDependencies": { + "@biomejs/biome": "1.9.4", + "tsup": "8.3.5", + "vitest": "2.1.9" + } +} diff --git a/packages/plugin-attps/src/actions/attpsPriceQuery.ts b/packages/plugin-attps/src/actions/attpsPriceQuery.ts new file mode 100644 index 00000000000..e9374c676ad --- /dev/null +++ b/packages/plugin-attps/src/actions/attpsPriceQuery.ts @@ -0,0 +1,118 @@ +import type { Action, HandlerCallback, IAgentRuntime, Memory, State } from "@elizaos/core"; +import { composeContext, elizaLogger, generateObject, ModelClass } from "@elizaos/core"; +import { attpsPriceQueryTemplate } from "../templates"; +import type { AttpsPriceQuery, AttpsPriceQueryResponse } from "../types"; +import { AttpsPriceQuerySchema, isAttpsPriceQuery } from "../types"; + +async function fetchPriceData(sourceAgentId: string, feedId: string) { + const response = await fetch(`https://ai-agent-test.apro.com/api/ai-agent/price-detail?sourceAgentId=${sourceAgentId}&feedId=${feedId}`); + const { result, code, message } = await response.json(); + if (code !== 0) { + throw new Error(message); + } + return result as AttpsPriceQueryResponse; +} + +function cleanNumber(numStr: string) { + return Number.parseFloat(numStr).toString(); +} + +export const attpsPriceQuery: Action = { + name: "ATTPS_PRICE_QUERY", + similes: [ + 'ATTPS_PRICE_FETCH', + ], + description: "Call remote API to fetch price data for a given source agent id and feed id.", + validate: async (_runtime: IAgentRuntime, _message: Memory) => { + return true; + }, + handler: async ( + runtime: IAgentRuntime, + message: Memory, + state?: State, + _options?: { [key: string]: unknown }, + callback?: HandlerCallback + ) => { + let currentState = state; + if (!currentState) { + currentState = (await runtime.composeState(message)) as State; + } else { + currentState = await runtime.updateRecentMessageState(currentState); + } + + // Generate price query params + let attpsPriceQuery: AttpsPriceQuery; + try { + const response = await generateObject({ + runtime, + context: composeContext({ + state: currentState, + template: attpsPriceQueryTemplate, + }), + modelClass: ModelClass.LARGE, + schema: AttpsPriceQuerySchema, + }); + attpsPriceQuery = response.object as AttpsPriceQuery; + elizaLogger.info('The price query params received:', attpsPriceQuery); + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + elizaLogger.error('Failed to generate price query params:', errorMessage); + if (callback) { + callback({ + text: 'Failed to generate price query params. Please provide valid input.', + }); + } + return; + } + + // Validate price query params + if (!isAttpsPriceQuery(attpsPriceQuery)) { + elizaLogger.error('Invalid price query params:', attpsPriceQuery); + if (callback) { + callback({ + text: 'Invalid price query params. Please provide valid input.', + }); + } + return; + } + + // Fetch price data + try { + const { sourceAgentId, feedId } = attpsPriceQuery; + const priceData = await fetchPriceData(sourceAgentId, feedId); + elizaLogger.info('The Price data received:', priceData); + + const message = `Ask price: ${cleanNumber(priceData.askPrice)}\nBid price: ${cleanNumber(priceData.bidPrice)}\nMid price: ${cleanNumber(priceData.midPrice)}\nTimestamp: ${priceData.validTimeStamp}`; + if (callback) { + callback({ + text: `Here is the price data:\n${message}`, + }); + } + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + elizaLogger.error('Error fetching price data:', errorMessage); + if (callback) { + callback({ + text: `Error fetching price data: ${errorMessage}`, + }); + } + } + }, + examples: [ + [ + { + user: "{{user1}}", + content: { + text: "Can you fetch price data for source agent id ... and feed id ...?", + }, + }, + { + user: "{{user2}}", + content: { + text: "I'll fetch price data for you. Give me a moment.", + action: 'ATTPS_PRICE_QUERY', + }, + } + ], + ], +} \ No newline at end of file diff --git a/packages/plugin-attps/src/actions/createAndRegisterAgent.ts b/packages/plugin-attps/src/actions/createAndRegisterAgent.ts new file mode 100644 index 00000000000..3101f709bc1 --- /dev/null +++ b/packages/plugin-attps/src/actions/createAndRegisterAgent.ts @@ -0,0 +1,148 @@ +import type { Action, HandlerCallback, IAgentRuntime, Memory, State } from "@elizaos/core"; +import { composeContext, elizaLogger, generateObject, ModelClass } from "@elizaos/core"; +import { AgentSDK, parseNewAgentAddress } from "ai-agent-sdk-js"; +import type { AgentSettings } from "ai-agent-sdk-js"; +import { createAgentTemplate } from "../templates"; +import type { ContractTransactionResponse } from "ethers"; +import { AgentSettingsSchema, isAgentSettings } from "../types"; + +export const createAndRegisterAgent: Action = { + name: "CREATE_AND_REGISTER_AGENT", + similes: [ + 'CREATE_AGENT', + 'REGISTER_AGENT', + ], + description: "Create and register an agent with ATTPs. User must provide agent settings.", + validate: async (_runtime: IAgentRuntime, _message: Memory) => { + return true; + }, + handler: async ( + runtime: IAgentRuntime, + message: Memory, + state?: State, + _options?: { [key: string]: unknown }, + callback?: HandlerCallback + ) => { + // Initialize or update state + let currentState = state; + if (!currentState) { + currentState = (await runtime.composeState(message)) as State; + } else { + currentState = await runtime.updateRecentMessageState(currentState); + } + + // Generate agent settings + let agentSettings: AgentSettings + try { + const agentSettingsDetail = await generateObject({ + runtime, + context: composeContext({ + state: currentState, + template: createAgentTemplate, + }), + modelClass: ModelClass.LARGE, + schema: AgentSettingsSchema, + }); + agentSettings = agentSettingsDetail.object as AgentSettings; + elizaLogger.info('The Agent settings received:', agentSettings); + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + elizaLogger.error('Failed to generate Agent settings:', errorMessage); + if (callback) { + callback({ + text: 'Failed to generate Agent settings. Please provide valid input.', + }); + } + return; + } + + // Validate agent settings + if (!isAgentSettings(agentSettings)) { + elizaLogger.error('Invalid Agent settings:', agentSettings); + if (callback) { + callback({ + text: 'Invalid Agent settings. Please provide valid input.', + }); + } + return; + } + + // Create SDK agent + let agent: AgentSDK; + try { + agent = new AgentSDK({ + proxyAddress: runtime.getSetting('ATTPS_PROXY_ADDRESS') ?? process.env.ATTPS_PROXY_ADDRESS, + rpcUrl: runtime.getSetting('ATTPS_RPC_URL') ?? process.env.ATTPS_RPC_URL, + privateKey: runtime.getSetting('ATTPS_PRIVATE_KEY') ?? process.env.ATTPS_PRIVATE_KEY, + }); + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + elizaLogger.error('Failed to create Agent SDK:', errorMessage); + if (callback) { + callback({ + text: 'Failed to create Agent SDK. Please check the ATTPs plugin configuration.', + }); + } + return; + } + + // Create and register agent + let tx: ContractTransactionResponse; + try { + tx = await agent.createAndRegisterAgent({agentSettings}); + elizaLogger.info('Successfully send create and register agent transaction:', tx.hash); + + const receipt = await tx.wait(); + const agentAddress = parseNewAgentAddress(receipt); + + elizaLogger.info(`Created agent at address: ${agentAddress}`); + if (callback) { + callback({ text: `Agent created and registered successfully: ${agentAddress}` }); + } + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + elizaLogger.error(`Error creating agent: ${errorMessage}`); + if (callback) { + const message = tx?.hash + ? `Error creating agent: ${errorMessage}. Transaction hash: ${tx.hash}` + : `Error creating agent: ${errorMessage}`; + await callback({ text: message }); + } + } + }, + examples: [ + [ + { + user: "{{user1}}", + content: { + text: `I want to Create and register ATTPs ai-agent with the following settings: + { + signers: [ + '0x003CD3bD8Ac5b045be8E49d4dfd9928E1765E471', + '0xdE3701195b9823E41b3fc2c98922A94399E2a01C', + '0xB54E5D4faa950e8B6a01ed5a790Ac260c81Ad224', + '0x48eE063a6c67144E09684ac8AD9a0044836f348B', + '0xbBbCc052F1277dd94e88e8E5BD6D7FF9a29BaC98' + ], + threshold: 3, + converterAddress: "0x24c36e9996eb84138Ed7cAa483B4c59FF7640E5C", + agentHeader: { + sourceAgentName: 'ElizaOS Test Agent', + targetAgentId: '1105302c-7556-49b2-b6fe-3aedba9c0682', + messageType: 0, + priority: 1, + ttl: 3600, + }, + }` + }, + }, + { + user: "{{user2}}", + content: { + text: "I'll help you create and register the agent.", + action: "CREATE_AND_REGISTER_AGENT", + }, + }, + ] + ], +} \ No newline at end of file diff --git a/packages/plugin-attps/src/actions/priceQuery.ts b/packages/plugin-attps/src/actions/priceQuery.ts new file mode 100644 index 00000000000..5c5869b4504 --- /dev/null +++ b/packages/plugin-attps/src/actions/priceQuery.ts @@ -0,0 +1,125 @@ +import type { Action, HandlerCallback, IAgentRuntime, Memory, State } from "@elizaos/core"; +import { composeContext, elizaLogger, generateObject, ModelClass } from "@elizaos/core"; +import { priceQueryTemplate } from "../templates"; +import type { PriceData, PriceQueryParams } from "../types"; +import { isPriceQueryParams, PriceQueryParamsSchema } from "../types"; + +async function fetchPriceData(pair: string) { + const response = await fetch(`https://live-api.apro.com/api/live-stream/reports?pair=${pair}`); + const { result } = await response.json(); + return result as PriceData[]; +} + +export const priceQuery: Action = { + name: "PRICE_QUERY", + similes: [ + 'PRICE_FETCH', + ], + description: "Call remote API to fetch price data for a given pair.", + validate: async (_runtime: IAgentRuntime, _message: Memory) => { + return true; + }, + handler: async ( + runtime: IAgentRuntime, + message: Memory, + state?: State, + _options?: { [key: string]: unknown }, + callback?: HandlerCallback + ) => { + // Initialize or update state + let currentState = state; + if (!currentState) { + currentState = (await runtime.composeState(message)) as State; + } else { + currentState = await runtime.updateRecentMessageState(currentState); + } + + // Generate price query params + let priceQueryParams: PriceQueryParams; + try { + const response = await generateObject({ + runtime, + context: composeContext({ + state: currentState, + template: priceQueryTemplate, + }), + modelClass: ModelClass.LARGE, + schema: PriceQueryParamsSchema, + }); + priceQueryParams = response.object as PriceQueryParams; + elizaLogger.info('The price query params received:', priceQueryParams); + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + elizaLogger.error('Failed to generate price query params:', errorMessage); + if (callback) { + callback({ + text: 'Failed to generate price query params. Please provide valid input.', + }); + } + return; + } + + // Validate price query params + if (!isPriceQueryParams(priceQueryParams)) { + elizaLogger.error('Invalid price query params:', priceQueryParams); + if (callback) { + callback({ + text: 'Invalid price query params. Please provide valid input.', + }); + } + return; + } + + // Fetch price data + try { + const { pair } = priceQueryParams; + const priceData = await fetchPriceData(pair); + elizaLogger.info('The Price data received:', priceData); + + if (!priceData || priceData.length === 0) { + elizaLogger.error('No price data found for pair:', pair); + if (callback) { + callback({ + text: `No price data found for pair ${pair}.`, + }); + } + return; + } + + const priceDataString = priceData.map((data) => { + return `Feed ID: ${data.feedId}\nBid Price: ${data.bidPrice}\nMid Price: ${data.midPrice}\nAsk Price: ${data.askPrice}\nTimestamp: ${data.timestamp}`; + }).join('\n\n'); + + if (callback) { + callback({ + text: `Price data for pair ${pair}: \n${priceDataString}`, + }); + } + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + elizaLogger.error('Error fetching price data:', errorMessage); + if (callback) { + callback({ + text: `Error fetching price data: ${errorMessage}`, + }); + } + } + }, + examples: [ + [ + { + user: "{{user1}}", + content: { + text: "Can you fetch price data for pair BTC/USD?", + }, + }, + { + user: "{{user2}}", + content: { + text: "I'll fetch price data for pair BTC/USD.", + action: 'PRICE_QUERY', + }, + } + ], + ], +} \ No newline at end of file diff --git a/packages/plugin-attps/src/actions/verifyData.ts b/packages/plugin-attps/src/actions/verifyData.ts new file mode 100644 index 00000000000..f5fd2bbe781 --- /dev/null +++ b/packages/plugin-attps/src/actions/verifyData.ts @@ -0,0 +1,131 @@ +import { type Action, composeContext, elizaLogger, generateObject, type HandlerCallback, type IAgentRuntime, type Memory, ModelClass, type State } from "@elizaos/core"; +import { AgentSDK, type VerifyParams } from "ai-agent-sdk-js"; +import { verifyDataTemplate } from "../templates"; +import { isVerifyParams, VerifyParamsSchema } from "../types"; +import type { ContractTransactionResponse } from "ethers"; + +export const verifyData: Action = { + name: "VERIFY", + similes: [ + 'VERIFY_DATA', + ], + description: "Verify data with ATTPs. User must provide data to verify.", + validate: async (_runtime: IAgentRuntime, _message: Memory) => { + return true; + }, + handler: async ( + runtime: IAgentRuntime, + message: Memory, + state?: State, + _options?: { [key: string]: unknown }, + callback?: HandlerCallback + ) => { + // Initialize or update state + let currentState = state; + if (!currentState) { + currentState = (await runtime.composeState(message)) as State; + } else { + currentState = await runtime.updateRecentMessageState(currentState); + } + + // Generate verify params + let verifyParams: VerifyParams; + try { + const response = await generateObject({ + runtime, + context: composeContext({ + state: currentState, + template: verifyDataTemplate, + }), + modelClass: ModelClass.LARGE, + schema: VerifyParamsSchema, + }); + + verifyParams = response.object as VerifyParams; + elizaLogger.info('The verify params received:', verifyParams); + } catch (error: unknown) { + if (error instanceof Error) { + elizaLogger.error('Failed to generate verify params:', error.message); + } else { + elizaLogger.error('Failed to generate verify params:', String(error)); + } + callback({ + text: 'Failed to generate verify params. Please provide valid input.', + }); + return; + } + + // Validate verify params + if (!isVerifyParams(verifyParams)) { + elizaLogger.error('Invalid verify params:', verifyParams); + callback({ + text: 'Invalid verify params. Please provide valid input.', + }); + return; + } + + // Create SDK agent + let agent: AgentSDK + try { + agent = new AgentSDK({ + proxyAddress: runtime.getSetting('ATTPS_PROXY_ADDRESS') ?? process.env.ATTPS_PROXY_ADDRESS, + rpcUrl: runtime.getSetting('ATTPS_RPC_URL') ?? process.env.ATTPS_RPC_URL, + privateKey: runtime.getSetting('ATTPS_PRIVATE_KEY') ?? process.env.ATTPS_PRIVATE_KEY, + autoHashData: (runtime.getSetting('ATTPS_AUTO_HASH_DATA') ?? process.env.ATTPS_AUTO_HASH_DATA) === 'true', + converterAddress: runtime.getSetting('ATTPS_CONVERTER_ADDRESS') ?? process.env.ATTPS_CONVERTER_ADDRESS, + }); + } catch (error: unknown) { + if (error instanceof Error) { + elizaLogger.error('Failed to create Agent SDK:', error.message); + } else { + elizaLogger.error('Failed to create Agent SDK:', String(error)); + } + callback({ + text: 'Failed to create Agent SDK. Please check the ATTPs plugin configuration.', + }); + return; + } + + // Verify data + let tx: ContractTransactionResponse + try { + tx = await agent.verify(verifyParams) + elizaLogger.info('Data verification transaction sent. Transaction ID:', tx.hash); + + const receipt = await tx.wait(); + elizaLogger.info('Data verification transaction confirmed. Transaction ID:', receipt.hash); + + callback({ + text: `Success: Data verified successfully. Transaction ID: ${receipt.hash}`, + }) + } catch (error: unknown) { + if (error instanceof Error) { + elizaLogger.error(`Error verify data: ${error.message}`); + let message = `Error verifying data: ${error.message}`; + if (tx?.hash) { + message = `${message} Transaction hash: ${tx.hash}`; + } + callback({ + text: message, + }) + } + } + }, + examples: [ + [ + { + user: "{{user1}}", + content: { + text: "I want to verify data: ...", + }, + }, + { + user: "{{user2}}", + content: { + text: "Sure, I'll verify the data.", + action: "VERIFY", + }, + }, + ] + ], +}; \ No newline at end of file diff --git a/packages/plugin-attps/src/index.ts b/packages/plugin-attps/src/index.ts new file mode 100644 index 00000000000..1e849dd0393 --- /dev/null +++ b/packages/plugin-attps/src/index.ts @@ -0,0 +1,18 @@ +import type { Plugin } from "@elizaos/core"; +import { createAndRegisterAgent } from "./actions/createAndRegisterAgent"; +import { verifyData } from "./actions/verifyData"; +import { attpsPriceQuery } from "./actions/attpsPriceQuery"; + +export const attpsPlugin: Plugin = { + name: "ATTPs", + description: "ATTPs Plugin for Eliza", + actions: [ + createAndRegisterAgent, + verifyData, + attpsPriceQuery, + ], + evaluators: [], + providers: [], +}; + +export default attpsPlugin; \ No newline at end of file diff --git a/packages/plugin-attps/src/templates.ts b/packages/plugin-attps/src/templates.ts new file mode 100644 index 00000000000..180e6f62c88 --- /dev/null +++ b/packages/plugin-attps/src/templates.ts @@ -0,0 +1,238 @@ +export const createAgentTemplate = ` +TASK: Extract ONLY the explicitly mentioned details from the user's input messages to create an agent configuration. DO NOT generate, infer or create any data that is not explicitly present in the input. + +RULES: +1. ONLY extract information that is explicitly present in the user's messages +2. Use null for ANY field where the exact required information is not present +3. Do not make assumptions or generate random values +4. If no valid data can be extracted, return a JSON with all null values +5. Only accept properly formatted addresses and UUIDs - do not create or infer them + +REQUIRED FIELDS: +- signers: Array of valid Ethereum addresses (42-char hex starting with '0x') +- threshold: Explicit number mentioned as threshold +- converterAddress: Valid Ethereum address (42-char hex starting with '0x') +- agentHeader: Object containing: + * messageId: Valid UUID format only + * sourceAgentId: Valid UUID format only + * sourceAgentName: Explicit agent name + * targetAgentId: Valid UUID format only + * messageType: Explicit number + * priority: Explicit number + * ttl: Explicit number in seconds + +OUTPUT FORMAT: +\`\`\`json +{ + "signers": [ + "" + ], + "threshold": null, + "converterAddress": null, + "agentHeader": { + "messageId": null, + "sourceAgentId": null, + "sourceAgentName": null, + "targetAgentId": null, + "messageType": null, + "priority": null, + "ttl": null + } +} +\`\`\` + +VALIDATION: +- Ethereum addresses must be 42 characters starting with '0x' +- UUIDs must match standard UUID format +- Numbers must be explicitly mentioned in the context +- Do not include any fields or values that are not explicitly mentioned in the user's input + +Context messages: +{{recentMessages}} +`; + +export const verifyDataTemplate = ` +TASK: STRICTLY extract ONLY explicitly mentioned verification details from the user's input messages. DO NOT generate, infer, or create any data that is not explicitly present in the input. + +STRICT RULES: +1. ONLY extract information that is EXPLICITLY present in the user's messages +2. Set null for ANY field where the exact required information is not present +3. DO NOT create, generate, or infer any values +4. Return all fields as null if no valid data can be extracted +5. Only accept properly formatted hexadecimal strings and numbers +6. Reject and set to null any values that don't match the required format + +REQUIRED FORMATS: +1. Hexadecimal strings must: + - Start with '0x' + - Contain only valid hex characters (0-9, a-f, A-F) + - Match the expected length for their purpose + +2. Ethereum addresses must: + - Be exactly 42 characters long + - Start with '0x' + - Contain only valid hex characters + +3. Numbers must: + - Be explicitly mentioned + - Be valid integers + - Be in the appropriate range for their purpose + +FIELD SPECIFICATIONS: +payload: + - data: Must be valid hex string starting with '0x' + - dataHash: Must be valid hex string starting with '0x' + - signatures: Array of objects, each containing: + * r: 64-character hex string (without '0x') + * s: 64-character hex string (without '0x') + * v: Integer number + - metadata: + * contentType: String matching known content types + * encoding: String or null + * compression: String or null + +agent: Must be valid 42-character Ethereum address +digest: Must be valid hex string starting with '0x' + +OUTPUT FORMAT: +\`\`\`json +{ + "payload": { + "data": null, + "dataHash": null, + "signatures": [], + "metadata": { + "contentType": null, + "encoding": null, + "compression": null + } + }, + "agent": null, + "digest": null +} +\`\`\` + +VALIDATION RULES: +1. For hex strings: + - Verify proper '0x' prefix where required + - Verify correct length + - Verify only valid hex characters + +2. For signatures: + - Only include if complete r, s, v values are present + - Verify r and s are valid 64-character hex strings + - Verify v is a valid integer + +3. For metadata: + - Only include contentType if it matches known formats + - Set encoding and compression to null if not explicitly specified + +4. General: + - Do not attempt to calculate or derive missing values + - Do not fill in partial information + - Return empty arrays instead of null for array fields when no valid items exist + +Input context to process: +{{recentMessages}} + +Remember: When in doubt, use null. Never generate fake data. +`; + +export const priceQueryTemplate = ` +TASK: Extract cryptocurrency trading pair information from user input. Extract pairs that follow the specified format patterns, regardless of whether the symbols represent actual cryptocurrencies. + +TRADING PAIR RULES: +1. Format Requirements: + - Must contain two symbols separated by a delimiter + - Acceptable delimiters: '/', '-', '_', or space + - Convert all pairs to standardized FORMAT: BASE/QUOTE + - Convert all letters to uppercase + +2. Symbol Requirements: + - Must be 2-5 characters long + - Must contain only letters + - Must be uppercase in output + +3. Pattern Recognition Examples: + - "ABC/USD" -> Valid, return "ABC/USD" + - "ABC-USD" -> Convert to "ABC/USD" + - "ABC USD" -> Convert to "ABC/USD" + - "ABCUSD" -> Convert to "ABC/USD" + - "ABCoin/USD" -> Invalid (symbol too long) + - "ABC to USD" -> Convert to "ABC/USD" + - "123/USD" -> Invalid (contains numbers) + - "A/USD" -> Invalid (symbol too short) + - "ABCDEF/USD" -> Invalid (symbol too long) + +VALIDATION: +1. REJECT and return null if: + - Only one symbol is mentioned + - Symbols are longer than 5 characters + - Symbols are shorter than 2 characters + - Symbols contain non-letter characters + - Format is completely unrecognizable + - More than two symbols are mentioned + +OUTPUT FORMAT: +\`\`\`json +{ + "pair": null +} +\`\`\` + +IMPORTANT NOTES: +1. DO NOT modify or correct user-provided symbols +2. DO NOT validate if symbols represent real cryptocurrencies +3. ONLY check format compliance +4. When format is invalid, return null +5. Accept ANY symbols that meet format requirements + +Input context to process: +{{recentMessages}} +`; + +export const attpsPriceQueryTemplate = ` + +TASK: Extract source agent and message identifiers from user input. Validate and format according to specified patterns. + +PARAMETER RULES: + +1. sourceAgentId Requirements: + - Format: UUID v4 format (8-4-4-4-12 hexadecimal) + - Case insensitive input but output must be lowercase + - Example: "b660e3f4-bbfe-4acb-97bd-c0869a7ea142" + +2. feedId Requirements: + - Format: 64-character hexadecimal prefixed with 0x + - Must be exactly 66 characters long including prefix + - Example: "0x0003665949c883f9e0f6f002eac32e00bd59dfe6c34e92a91c37d6a8322d6489" + +VALIDATION: + +1. REJECT and set to null if: + - Invalid UUID structure for sourceAgentId + - feedId length ≠ 66 characters + - feedId missing 0x prefix + - Contains non-hexadecimal characters + - Extra/missing hyphens in UUID + - Incorrect segment lengths + +OUTPUT FORMAT: +\`\`\`json +{ + "sourceAgentId": null, + "feedId": null +} +\`\`\` + +PROCESSING RULES: +1. Normalize sourceAgentId to lowercase +2. Preserve original feedId casing +3. Strict format validation before acceptance +4. Partial matches should return null +5. Return null for ambiguous formats + +Input context to process: +{{recentMessages}} + +`; \ No newline at end of file diff --git a/packages/plugin-attps/src/types.ts b/packages/plugin-attps/src/types.ts new file mode 100644 index 00000000000..821eac3efbc --- /dev/null +++ b/packages/plugin-attps/src/types.ts @@ -0,0 +1,130 @@ +import { z } from "zod"; + +export interface AgentSettings { + signers: string[] + threshold: number + converterAddress: string + agentHeader: { + messageId?: string + sourceAgentId?: string + sourceAgentName: string + targetAgentId: string + timestamp?: number + messageType: number + priority: number + ttl: number + } +} + +export const AgentSettingsSchema = z.object({ + signers: z.array(z.string()), + threshold: z.number(), + converterAddress: z.string(), + agentHeader: z.object({ + messageId: z.string().nullish(), + sourceAgentId: z.string().nullish(), + sourceAgentName: z.string(), + targetAgentId: z.string(), + timestamp: z.number().nullish(), + messageType: z.number(), + priority: z.number(), + ttl: z.number(), + }), +}); + +export const isAgentSettings = (value: unknown): value is AgentSettings => { + return AgentSettingsSchema.safeParse(value).success; +} + +interface Signature { + r: string + s: string + v: 1 | 0 | 27 | 28 + } + +interface MessagePayload { + data: string + dataHash?: string + signatures: Signature[] + metadata?: Metadata + } + +export interface VerifyParams { + agent: string + digest: string + payload: MessagePayload +} + +export const VerifyParamsSchema = z.object({ + agent: z.string(), + digest: z.string(), + payload: z.object({ + data: z.string(), + dataHash: z.string().nullish(), + signatures: z.array(z.object({ + r: z.string(), + s: z.string(), + v: z.number(), + })), + metadata: z.object({ + contentType: z.string().nullish(), + encoding: z.string().nullish(), + compression: z.string().nullish(), + }).nullish(), + }), +}); + +export const isVerifyParams = (value: unknown): value is VerifyParams => { + return VerifyParamsSchema.safeParse(value).success; +} + +export interface PriceQueryParams { + pair: string +} + +export const PriceQueryParamsSchema = z.object({ + pair: z.string(), +}); + +export const isPriceQueryParams = (value: unknown): value is PriceQueryParams => { + return PriceQueryParamsSchema.safeParse(value).success; +} + +export interface PriceData { + feedId: string + pair: string + networks: string[] + bidPrice: string + askPrice: string + midPrice: string + bidPriceChange: number + askPriceChange: number + midPriceChange: number + timestamp: number +} + +export const AttpsPriceQuerySchema = z.object({ + sourceAgentId: z.string(), + feedId: z.string(), +}); + +export const isAttpsPriceQuery = (value: unknown): value is AttpsPriceQuery => { + return AttpsPriceQuerySchema.safeParse(value).success; +} + +export interface AttpsPriceQuery { + sourceAgentId: string + feedId: string +} + +export interface AttpsPriceQueryResponse { + feedId: string + validTimeStamp: number + observeTimeStamp: number + nativeFee: number + tokenFee: number + expireTimeStamp: number + midPrice: string + askPrice: string + bidPrice: string +} \ No newline at end of file diff --git a/packages/plugin-attps/tsconfig.json b/packages/plugin-attps/tsconfig.json new file mode 100644 index 00000000000..005fbac9d36 --- /dev/null +++ b/packages/plugin-attps/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../core/tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/plugin-attps/tsup.config.ts b/packages/plugin-attps/tsup.config.ts new file mode 100644 index 00000000000..7c51f1a4ca2 --- /dev/null +++ b/packages/plugin-attps/tsup.config.ts @@ -0,0 +1,12 @@ +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: [ + // Add other modules you want to externalize + ], +}); diff --git a/packages/plugin-attps/vitest.config.ts b/packages/plugin-attps/vitest.config.ts new file mode 100644 index 00000000000..1b5e9b53118 --- /dev/null +++ b/packages/plugin-attps/vitest.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from 'vitest/config.js'; + +export default defineConfig({ + test: { + globals: true, + }, + resolve: { + alias: [ + { + find: /^@elizaos\/core$/, + replacement: '../core/src/index.ts' + }, + { + find: /^ai-agent-sdk-js$/, + replacement: '../node_modules/ai-agent-sdk-js/src/index.ts' + } + ] + } +});