diff --git a/packages/plugin-aptos/package.json b/packages/plugin-aptos/package.json index bc1badd89b..55a11ffc80 100644 --- a/packages/plugin-aptos/package.json +++ b/packages/plugin-aptos/package.json @@ -9,14 +9,17 @@ "@aptos-labs/ts-sdk": "^1.26.0", "bignumber": "1.1.0", "bignumber.js": "9.1.2", - "node-cache": "5.1.2", + "node-cache": "5.1.2" + }, + "devDependencies": { "tsup": "8.3.5", - "vitest": "2.1.4" + "vitest": "2.1.4", + "typescript": "^5.0.0" }, "scripts": { - "build": "tsup --format esm --dts", - "dev": "tsup --format esm --dts --watch", - "lint": "eslint --fix --cache .", + "build": "tsup", + "dev": "tsup --watch", + "lint": "eslint --fix --cache .", "test": "vitest run" }, "peerDependencies": { diff --git a/packages/plugin-aptos/src/constants.ts b/packages/plugin-aptos/src/constants.ts index 93a32ffcc7..92df2614da 100644 --- a/packages/plugin-aptos/src/constants.ts +++ b/packages/plugin-aptos/src/constants.ts @@ -1,9 +1 @@ -export const APT_DECIMALS = 8; -export const MOVEMENT_NETWORK = { - MAINNET: { - fullnode: 'https://mainnet.movementnetwork.xyz/v1', - }, - TESTNET: { - fullnode: 'https://aptos.testnet.bardock.movementlabs.xyz/v1', - }, -}; \ No newline at end of file +export const APT_DECIMALS = 8; \ No newline at end of file diff --git a/packages/plugin-aptos/src/enviroment.ts b/packages/plugin-aptos/src/enviroment.ts index 2d7b2e3c14..59719eaab0 100644 --- a/packages/plugin-aptos/src/enviroment.ts +++ b/packages/plugin-aptos/src/enviroment.ts @@ -3,7 +3,7 @@ import { z } from "zod"; export const aptosEnvSchema = z.object({ APTOS_PRIVATE_KEY: z.string().min(1, "Aptos private key is required"), - APTOS_NETWORK: z.enum(["mainnet", "testnet", "movement_mainnet", "movement_testnet"]), + APTOS_NETWORK: z.enum(["mainnet", "testnet"]), }); export type AptosConfig = z.infer; diff --git a/packages/plugin-aptos/src/providers/wallet.ts b/packages/plugin-aptos/src/providers/wallet.ts index ff948837bb..fbb209c3ac 100644 --- a/packages/plugin-aptos/src/providers/wallet.ts +++ b/packages/plugin-aptos/src/providers/wallet.ts @@ -17,8 +17,7 @@ import { import BigNumber from "bignumber.js"; import NodeCache from "node-cache"; import * as path from "path"; -import { APT_DECIMALS, MOVEMENT_NETWORK } from "../constants"; -import { isMovementNetwork, getMovementNetworkType } from "../utils"; +import { APT_DECIMALS } from "../constants"; // Provider configuration const PROVIDER_CONFIG = { @@ -238,12 +237,7 @@ const walletProvider: Provider = { try { const aptosClient = new Aptos( new AptosConfig({ - network: isMovementNetwork(network) - ? { - network: Network.CUSTOM, - fullnode: MOVEMENT_NETWORK[getMovementNetworkType(network)].fullnode - } - : { network } + network, }) ); const provider = new WalletProvider( diff --git a/packages/plugin-aptos/tsup.config.ts b/packages/plugin-aptos/tsup.config.ts index dd25475bb6..682efa51ea 100644 --- a/packages/plugin-aptos/tsup.config.ts +++ b/packages/plugin-aptos/tsup.config.ts @@ -5,25 +5,27 @@ export default defineConfig({ outDir: "dist", sourcemap: true, clean: true, - format: ["esm"], // Ensure you're targeting CommonJS + format: ["esm"], + dts: true, + minify: false, + splitting: false, 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", + "@elizaos/core", + "@aptos-labs/ts-sdk", + "bignumber", + "bignumber.js", + "node-cache", + "dotenv", + "fs", + "path", "https", "http", - "agentkeepalive", - "safe-buffer", - "base-x", - "bs58", - "borsh", - "@solana/buffer-layout", "stream", "buffer", - "querystring", - "amqplib", - // Add other modules you want to externalize + "querystring" ], + noExternal: [], + esbuildOptions(options) { + options.platform = 'node' + } }); diff --git a/packages/plugin-movement/.npmignore b/packages/plugin-movement/.npmignore new file mode 100644 index 0000000000..078562ecea --- /dev/null +++ b/packages/plugin-movement/.npmignore @@ -0,0 +1,6 @@ +* + +!dist/** +!package.json +!readme.md +!tsup.config.ts \ No newline at end of file diff --git a/packages/plugin-movement/eslint.config.mjs b/packages/plugin-movement/eslint.config.mjs new file mode 100644 index 0000000000..92fe5bbebe --- /dev/null +++ b/packages/plugin-movement/eslint.config.mjs @@ -0,0 +1,3 @@ +import eslintGlobalConfig from "../../eslint.config.mjs"; + +export default [...eslintGlobalConfig]; diff --git a/packages/plugin-movement/package.json b/packages/plugin-movement/package.json new file mode 100644 index 0000000000..2396c2c80b --- /dev/null +++ b/packages/plugin-movement/package.json @@ -0,0 +1,30 @@ +{ + "name": "@elizaos/plugin-movement", + "version": "0.1.0", + "main": "dist/index.js", + "type": "module", + "types": "dist/index.d.ts", + "description": "Movement Network Plugin for Eliza", + "dependencies": { + "@elizaos/core": "workspace:*", + "@aptos-labs/ts-sdk": "^1.26.0", + "bignumber": "1.1.0", + "bignumber.js": "9.1.2", + "node-cache": "5.1.2" + }, + "devDependencies": { + "tsup": "8.3.5", + "vitest": "2.1.4", + "typescript": "^5.0.0" + }, + "scripts": { + "build": "tsup", + "dev": "tsup --watch", + "lint": "eslint --fix --cache .", + "test": "vitest run" + }, + "peerDependencies": { + "form-data": "4.0.1", + "whatwg-url": "7.1.0" + } +} diff --git a/packages/plugin-movement/src/actions/transfer.ts b/packages/plugin-movement/src/actions/transfer.ts new file mode 100644 index 0000000000..626a751a8e --- /dev/null +++ b/packages/plugin-movement/src/actions/transfer.ts @@ -0,0 +1,224 @@ +import { elizaLogger } from "@elizaos/core"; +import { + ActionExample, + Content, + HandlerCallback, + IAgentRuntime, + Memory, + ModelClass, + State, + type Action, +} from "@elizaos/core"; +import { composeContext } from "@elizaos/core"; +import { generateObjectDeprecated } from "@elizaos/core"; +import { + Account, + Aptos, + AptosConfig, + Ed25519PrivateKey, + Network, + PrivateKey, + PrivateKeyVariants, +} from "@aptos-labs/ts-sdk"; +import { walletProvider } from "../providers/wallet"; + +export interface TransferContent extends Content { + recipient: string; + amount: string | number; +} + +function isTransferContent(content: any): content is TransferContent { + console.log("Content for transfer", content); + return ( + typeof content.recipient === "string" && + (typeof content.amount === "string" || + typeof content.amount === "number") + ); +} + +const transferTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined. + +Example response: +\`\`\`json +{ + "recipient": "0x2badda48c062e861ef17a96a806c451fd296a49f45b272dee17f85b0e32663fd", + "amount": "1000" +} +\`\`\` + +{{recentMessages}} + +Given the recent messages, extract the following information about the requested token transfer: +- Recipient wallet address +- Amount to transfer + +Respond with a JSON markdown block containing only the extracted values.`; + +export default { + name: "SEND_TOKEN", + similes: [ + "TRANSFER_TOKEN", + "TRANSFER_TOKENS", + "SEND_TOKENS", + "SEND_APT", + "PAY", + ], + validate: async (runtime: IAgentRuntime, message: Memory) => { + console.log("Validating apt transfer from user:", message.userId); + //add custom validate logic here + /* + const adminIds = runtime.getSetting("ADMIN_USER_IDS")?.split(",") || []; + //console.log("Admin IDs from settings:", adminIds); + + const isAdmin = adminIds.includes(message.userId); + + if (isAdmin) { + //console.log(`Authorized transfer from user: ${message.userId}`); + return true; + } + else + { + //console.log(`Unauthorized transfer attempt from user: ${message.userId}`); + return false; + } + */ + return false; + }, + description: "Transfer tokens from the 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..."); + + const walletInfo = await walletProvider.get(runtime, message, state); + state.walletInfo = walletInfo; + + // Initialize or update state + if (!state) { + state = (await runtime.composeState(message)) as State; + } else { + state = await runtime.updateRecentMessageState(state); + } + + // Compose transfer context + const transferContext = composeContext({ + state, + template: transferTemplate, + }); + + // Generate transfer content + const content = await generateObjectDeprecated({ + runtime, + context: transferContext, + modelClass: ModelClass.SMALL, + }); + + // Validate transfer content + if (!isTransferContent(content)) { + console.error("Invalid content for TRANSFER_TOKEN action."); + if (callback) { + callback({ + text: "Unable to process transfer request. Invalid content provided.", + content: { error: "Invalid transfer content" }, + }); + } + return false; + } + + try { + const privateKey = runtime.getSetting("APTOS_PRIVATE_KEY"); + const aptosAccount = Account.fromPrivateKey({ + privateKey: new Ed25519PrivateKey( + PrivateKey.formatPrivateKey( + privateKey, + PrivateKeyVariants.Ed25519 + ) + ), + }); + const network = runtime.getSetting("APTOS_NETWORK") as Network; + const aptosClient = new Aptos( + new AptosConfig({ + network, + }) + ); + + const APT_DECIMALS = 8; + const adjustedAmount = BigInt( + Number(content.amount) * Math.pow(10, APT_DECIMALS) + ); + console.log( + `Transferring: ${content.amount} tokens (${adjustedAmount} base units)` + ); + + const tx = await aptosClient.transaction.build.simple({ + sender: aptosAccount.accountAddress.toStringLong(), + data: { + function: "0x1::aptos_account::transfer", + typeArguments: [], + functionArguments: [content.recipient, adjustedAmount], + }, + }); + const committedTransaction = + await aptosClient.signAndSubmitTransaction({ + signer: aptosAccount, + transaction: tx, + }); + const executedTransaction = await aptosClient.waitForTransaction({ + transactionHash: committedTransaction.hash, + }); + + console.log("Transfer successful:", executedTransaction.hash); + + if (callback) { + callback({ + text: `Successfully transferred ${content.amount} APT to ${content.recipient}, Transaction: ${executedTransaction.hash}`, + content: { + success: true, + hash: executedTransaction.hash, + amount: content.amount, + recipient: content.recipient, + }, + }); + } + + return true; + } catch (error) { + console.error("Error during token transfer:", error); + if (callback) { + callback({ + text: `Error transferring tokens: ${error.message}`, + content: { error: error.message }, + }); + } + return false; + } + }, + + examples: [ + [ + { + user: "{{user1}}", + content: { + text: "Send 69 APT tokens to 0x4f2e63be8e7fe287836e29cde6f3d5cbc96eefd0c0e3f3747668faa2ae7324b0", + }, + }, + { + user: "{{user2}}", + content: { + text: "I'll send 69 APT tokens now...", + action: "SEND_TOKEN", + }, + }, + { + user: "{{user2}}", + content: { + text: "Successfully sent 69 APT tokens to 0x4f2e63be8e7fe287836e29cde6f3d5cbc96eefd0c0e3f3747668faa2ae7324b0, Transaction: 0x39a8c432d9bdad993a33cc1faf2e9b58fb7dd940c0425f1d6db3997e4b4b05c0", + }, + }, + ], + ] as ActionExample[][], +} as Action; diff --git a/packages/plugin-movement/src/constants.ts b/packages/plugin-movement/src/constants.ts new file mode 100644 index 0000000000..eec5ddec5f --- /dev/null +++ b/packages/plugin-movement/src/constants.ts @@ -0,0 +1,16 @@ +export const MOV_DECIMALS = 8; + +export const MOVEMENT_NETWORKS = { + mainnet: { + fullnode: 'https://fullnode.mainnet.mov.network/v1', + chainId: '1', + name: 'Movement Mainnet' + }, + bardock: { + fullnode: 'https://fullnode.testnet.mov.network/v1', + chainId: '2', + name: 'Movement Bardock Testnet' + } +} as const; + +export const DEFAULT_NETWORK = 'bardock'; \ No newline at end of file diff --git a/packages/plugin-movement/src/enviroment.ts b/packages/plugin-movement/src/enviroment.ts new file mode 100644 index 0000000000..2d7b2e3c14 --- /dev/null +++ b/packages/plugin-movement/src/enviroment.ts @@ -0,0 +1,36 @@ +import { IAgentRuntime } from "@elizaos/core"; +import { z } from "zod"; + +export const aptosEnvSchema = z.object({ + APTOS_PRIVATE_KEY: z.string().min(1, "Aptos private key is required"), + APTOS_NETWORK: z.enum(["mainnet", "testnet", "movement_mainnet", "movement_testnet"]), +}); + +export type AptosConfig = z.infer; + +export async function validateAptosConfig( + runtime: IAgentRuntime +): Promise { + try { + const config = { + APTOS_PRIVATE_KEY: + runtime.getSetting("APTOS_PRIVATE_KEY") || + process.env.APTOS_PRIVATE_KEY, + APTOS_NETWORK: + runtime.getSetting("APTOS_NETWORK") || + process.env.APTOS_NETWORK, + }; + + return aptosEnvSchema.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( + `Aptos configuration validation failed:\n${errorMessages}` + ); + } + throw error; + } +} diff --git a/packages/plugin-movement/src/environment.ts b/packages/plugin-movement/src/environment.ts new file mode 100644 index 0000000000..a990396252 --- /dev/null +++ b/packages/plugin-movement/src/environment.ts @@ -0,0 +1,39 @@ +import { IAgentRuntime } from "@elizaos/core"; +import { z } from "zod"; + +export const movementEnvSchema = z.object({ + MOVEMENT_PRIVATE_KEY: z.string().min(1, "Movement private key is required"), + MOVEMENT_NETWORK: z.enum(["mainnet", "bardock"]).default("bardock"), +}); + +export type MovementConfig = z.infer; + +export async function validateMovementConfig( + runtime: IAgentRuntime +): Promise { + try { + const config = { + MOVEMENT_PRIVATE_KEY: + runtime.getSetting("MOVEMENT_PRIVATE_KEY") || + runtime.getSetting("APTOS_PRIVATE_KEY") || // Fallback for compatibility + process.env.MOVEMENT_PRIVATE_KEY, + MOVEMENT_NETWORK: + runtime.getSetting("MOVEMENT_NETWORK") || + runtime.getSetting("APTOS_NETWORK")?.replace("movement_", "") || // Handle movement_bardock -> bardock + process.env.MOVEMENT_NETWORK || + "bardock", + }; + + return movementEnvSchema.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( + `Movement configuration validation failed:\n${errorMessages}` + ); + } + throw error; + } +} \ No newline at end of file diff --git a/packages/plugin-movement/src/index.ts b/packages/plugin-movement/src/index.ts new file mode 100644 index 0000000000..d8d54ba243 --- /dev/null +++ b/packages/plugin-movement/src/index.ts @@ -0,0 +1,15 @@ +import { Plugin } from "@elizaos/core"; +import transferToken from "./actions/transfer"; +import { WalletProvider, walletProvider } from "./providers/wallet"; + +export { WalletProvider, transferToken as TransferMovementToken }; + +export const movementPlugin: Plugin = { + name: "movement", + description: "Movement Network Plugin for Eliza", + actions: [transferToken], + evaluators: [], + providers: [walletProvider], +}; + +export default movementPlugin; diff --git a/packages/plugin-movement/src/providers/wallet.ts b/packages/plugin-movement/src/providers/wallet.ts new file mode 100644 index 0000000000..3e4f8f6a16 --- /dev/null +++ b/packages/plugin-movement/src/providers/wallet.ts @@ -0,0 +1,266 @@ +import { + IAgentRuntime, + ICacheManager, + Memory, + Provider, + State, +} from "@elizaos/core"; +import { + Account, + Aptos, + AptosConfig, + Ed25519PrivateKey, + Network, + PrivateKey, + PrivateKeyVariants, +} from "@aptos-labs/ts-sdk"; +import BigNumber from "bignumber.js"; +import NodeCache from "node-cache"; +import * as path from "path"; +import { APT_DECIMALS, MOVEMENT_NETWORK } from "../constants"; +import { isMovementNetwork, getMovementNetworkType } from "../utils"; + +// Provider configuration +const PROVIDER_CONFIG = { + MAX_RETRIES: 3, + RETRY_DELAY: 2000, +}; + +interface WalletPortfolio { + totalUsd: string; + totalApt: string; +} + +interface Prices { + apt: { usd: string }; +} + +export class WalletProvider { + private cache: NodeCache; + private cacheKey: string = "aptos/wallet"; + + constructor( + private aptosClient: Aptos, + private address: string, + private cacheManager: ICacheManager + ) { + this.cache = new NodeCache({ stdTTL: 300 }); // Cache TTL set to 5 minutes + } + + 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 fetchPricesWithRetry() { + let lastError: Error; + + for (let i = 0; i < PROVIDER_CONFIG.MAX_RETRIES; i++) { + try { + const cellanaAptUsdcPoolAddr = + "0x234f0be57d6acfb2f0f19c17053617311a8d03c9ce358bdf9cd5c460e4a02b7c"; + const response = await fetch( + `https://api.dexscreener.com/latest/dex/pairs/aptos/${cellanaAptUsdcPoolAddr}` + ); + + 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) { + console.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; + } + } + } + + console.error( + "All attempts failed. Throwing the last error:", + lastError + ); + throw lastError; + } + + async fetchPortfolioValue(): Promise { + try { + const cacheKey = `portfolio-${this.address}`; + const cachedValue = + await this.getCachedData(cacheKey); + + if (cachedValue) { + console.log("Cache hit for fetchPortfolioValue", cachedValue); + return cachedValue; + } + console.log("Cache miss for fetchPortfolioValue"); + + const prices = await this.fetchPrices().catch((error) => { + console.error("Error fetching APT price:", error); + throw error; + }); + const aptAmountOnChain = await this.aptosClient + .getAccountAPTAmount({ + accountAddress: this.address, + }) + .catch((error) => { + console.error("Error fetching APT amount:", error); + throw error; + }); + + const aptAmount = new BigNumber(aptAmountOnChain).div( + new BigNumber(10).pow(APT_DECIMALS) + ); + const totalUsd = new BigNumber(aptAmount).times(prices.apt.usd); + + const portfolio = { + totalUsd: totalUsd.toString(), + totalApt: aptAmount.toString(), + }; + this.setCachedData(cacheKey, portfolio); + console.log("Fetched portfolio:", portfolio); + return portfolio; + } catch (error) { + console.error("Error fetching portfolio:", error); + throw error; + } + } + + async fetchPrices(): Promise { + try { + const cacheKey = "prices"; + const cachedValue = await this.getCachedData(cacheKey); + + if (cachedValue) { + console.log("Cache hit for fetchPrices"); + return cachedValue; + } + console.log("Cache miss for fetchPrices"); + + const aptPriceData = await this.fetchPricesWithRetry().catch( + (error) => { + console.error("Error fetching APT price:", error); + throw error; + } + ); + const prices: Prices = { + apt: { usd: aptPriceData.pair.priceUsd }, + }; + this.setCachedData(cacheKey, prices); + return prices; + } catch (error) { + console.error("Error fetching prices:", error); + throw error; + } + } + + formatPortfolio(runtime, portfolio: WalletPortfolio): string { + let output = `${runtime.character.name}\n`; + output += `Wallet Address: ${this.address}\n`; + + const totalUsdFormatted = new BigNumber(portfolio.totalUsd).toFixed(2); + const totalAptFormatted = new BigNumber(portfolio.totalApt).toFixed(4); + + output += `Total Value: $${totalUsdFormatted} (${totalAptFormatted} APT)\n`; + + return output; + } + + async getFormattedPortfolio(runtime): Promise { + try { + const portfolio = await this.fetchPortfolioValue(); + return this.formatPortfolio(runtime, portfolio); + } catch (error) { + console.error("Error generating portfolio report:", error); + return "Unable to fetch wallet information. Please try again later."; + } + } +} + +const walletProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + _message: Memory, + _state?: State + ): Promise => { + const privateKey = runtime.getSetting("APTOS_PRIVATE_KEY"); + const aptosAccount = Account.fromPrivateKey({ + privateKey: new Ed25519PrivateKey( + PrivateKey.formatPrivateKey( + privateKey, + PrivateKeyVariants.Ed25519 + ) + ), + }); + const network = runtime.getSetting("APTOS_NETWORK") as Network; + + try { + console.log("Network:", network); + const aptosClient = new Aptos( + new AptosConfig( + isMovementNetwork(network) + ? { + network: Network.CUSTOM, + fullnode: MOVEMENT_NETWORK[getMovementNetworkType(network)].fullnode + } + : { + network + } + ) + ); + const provider = new WalletProvider( + aptosClient, + aptosAccount.accountAddress.toStringLong(), + runtime.cacheManager + ); + return await provider.getFormattedPortfolio(runtime); + } catch (error) { + console.error("Error in wallet provider:", error); + return null; + } + }, +}; + +// Module exports +export { walletProvider }; diff --git a/packages/plugin-movement/src/tests/wallet.test.ts b/packages/plugin-movement/src/tests/wallet.test.ts new file mode 100644 index 0000000000..f7d2829413 --- /dev/null +++ b/packages/plugin-movement/src/tests/wallet.test.ts @@ -0,0 +1,104 @@ +import { describe, it, expect, beforeEach, vi, afterEach } from "vitest"; +import { WalletProvider } from "../providers/wallet.ts"; +import { + Account, + Aptos, + AptosConfig, + Ed25519PrivateKey, + Network, + PrivateKey, + PrivateKeyVariants, +} from "@aptos-labs/ts-sdk"; +import { defaultCharacter } from "@elizaos/core"; +import BigNumber from "bignumber.js"; +import { APT_DECIMALS } from "../constants.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, + join: vi.fn().mockImplementation((...args) => args.join("/")), + }; +}); + +// Mock the ICacheManager +const mockCacheManager = { + get: vi.fn().mockResolvedValue(null), + set: vi.fn(), + delete: vi.fn(), +}; + +describe("WalletProvider", () => { + let walletProvider; + let mockedRuntime; + + beforeEach(() => { + vi.clearAllMocks(); + mockCacheManager.get.mockResolvedValue(null); + + const aptosClient = new Aptos( + new AptosConfig({ + network: Network.TESTNET, + }) + ); + const aptosAccount = Account.fromPrivateKey({ + privateKey: new Ed25519PrivateKey( + PrivateKey.formatPrivateKey( + // this is a testnet private key + "0x90e02bf2439492bd9be1ec5f569704accefd65ba88a89c4dcef1977e0203211e", + PrivateKeyVariants.Ed25519 + ) + ), + }); + + // Create new instance of TokenProvider with mocked dependencies + walletProvider = new WalletProvider( + aptosClient, + aptosAccount.accountAddress.toStringLong(), + mockCacheManager + ); + + mockedRuntime = { + character: defaultCharacter, + }; + }); + + afterEach(() => { + vi.clearAllTimers(); + }); + + describe("Wallet Integration", () => { + it("should check wallet address", async () => { + const result = + await walletProvider.getFormattedPortfolio(mockedRuntime); + + const prices = await walletProvider.fetchPrices(); + const aptAmountOnChain = + await walletProvider.aptosClient.getAccountAPTAmount({ + accountAddress: walletProvider.address, + }); + const aptAmount = new BigNumber(aptAmountOnChain) + .div(new BigNumber(10).pow(APT_DECIMALS)) + .toFixed(4); + const totalUsd = new BigNumber(aptAmount) + .times(prices.apt.usd) + .toFixed(2); + + expect(result).toEqual( + `Eliza\nWallet Address: ${walletProvider.address}\n` + + `Total Value: $${totalUsd} (${aptAmount} APT)\n` + ); + }); + }); +}); diff --git a/packages/plugin-movement/src/utils.ts b/packages/plugin-movement/src/utils.ts new file mode 100644 index 0000000000..1ec60a0d8f --- /dev/null +++ b/packages/plugin-movement/src/utils.ts @@ -0,0 +1,11 @@ +export function isMovementNetwork(network: string): boolean { + return network.startsWith('movement_'); +} + +export function getMovementNetworkType(network: string): 'MAINNET' | 'TESTNET' { + return network === 'movement_mainnet' ? 'MAINNET' : 'TESTNET'; +} + +export function getTokenSymbol(network: string): string { + return network.startsWith('movement_') ? 'MOVE' : 'APT'; +} \ No newline at end of file diff --git a/packages/plugin-movement/tsconfig.json b/packages/plugin-movement/tsconfig.json new file mode 100644 index 0000000000..73993deaaf --- /dev/null +++ b/packages/plugin-movement/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../core/tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": [ + "src/**/*.ts" + ] +} \ No newline at end of file diff --git a/packages/plugin-movement/tsup.config.ts b/packages/plugin-movement/tsup.config.ts new file mode 100644 index 0000000000..682efa51ea --- /dev/null +++ b/packages/plugin-movement/tsup.config.ts @@ -0,0 +1,31 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + entry: ["src/index.ts"], + outDir: "dist", + sourcemap: true, + clean: true, + format: ["esm"], + dts: true, + minify: false, + splitting: false, + external: [ + "@elizaos/core", + "@aptos-labs/ts-sdk", + "bignumber", + "bignumber.js", + "node-cache", + "dotenv", + "fs", + "path", + "https", + "http", + "stream", + "buffer", + "querystring" + ], + noExternal: [], + esbuildOptions(options) { + options.platform = 'node' + } +});