diff --git a/.env.example b/.env.example index 03f3b3bbcfb..a668b9b2d5a 100644 --- a/.env.example +++ b/.env.example @@ -512,6 +512,9 @@ ECHOCHAMBERS_MAX_MESSAGES=10 ALLORA_API_KEY= # Allora API key, format: UP-f8db7d6558ab432ca0d92716 ALLORA_CHAIN_SLUG= # must be one of mainnet, testnet. If not specified, it will use testnet by default +# B2 Network +B2_PRIVATE_KEY= # Private key of the B2 Network account to use for the agent + # Opacity zkTLS OPACITY_TEAM_ID=f309ac8ae8a9a14a7e62cd1a521b1c5f OPACITY_CLOUDFLARE_NAME=eigen-test diff --git a/agent/src/index.ts b/agent/src/index.ts index 2f7ebd4170d..4afabc5d1cc 100644 --- a/agent/src/index.ts +++ b/agent/src/index.ts @@ -46,6 +46,7 @@ import { aptosPlugin } from "@elizaos/plugin-aptos"; import { artheraPlugin } from "@elizaos/plugin-arthera"; import { availPlugin } from "@elizaos/plugin-avail"; import { avalanchePlugin } from "@elizaos/plugin-avalanche"; +import { b2Plugin } from "@elizaos/plugin-b2"; import { binancePlugin } from "@elizaos/plugin-binance"; import { advancedTradePlugin, @@ -822,6 +823,7 @@ export async function createAgent( getSecret(character, "ABSTRACT_PRIVATE_KEY") ? abstractPlugin : null, + getSecret(character, "B2_PRIVATE_KEY") ? b2Plugin: null, getSecret(character, "BINANCE_API_KEY") && getSecret(character, "BINANCE_SECRET_KEY") ? binancePlugin diff --git a/packages/plugin-b2/.npmignore b/packages/plugin-b2/.npmignore new file mode 100644 index 00000000000..078562eceab --- /dev/null +++ b/packages/plugin-b2/.npmignore @@ -0,0 +1,6 @@ +* + +!dist/** +!package.json +!readme.md +!tsup.config.ts \ No newline at end of file diff --git a/packages/plugin-b2/README.md b/packages/plugin-b2/README.md new file mode 100644 index 00000000000..1e5ba623cab --- /dev/null +++ b/packages/plugin-b2/README.md @@ -0,0 +1,169 @@ +# @elizaos/plugin-b2 + +A plugin for interacting with the B2-Network within the ElizaOS ecosystem. + +## Description + +The B2 Network Plugin offers a set of features that can be integrated into the Eliza platform to enhance its capabilities. This plugin enables seamless token transfers on the B2-Network. It provides functionality to transfer both native B2-BTC and ERC20 tokens using secure wallet operations. + +## Installation + +```bash +pnpm install @elizaos/plugin-b2 +``` + +## Configuration + +The plugin requires the following environment variable: + +```typescript +B2_PRIVATE_KEY= +``` + +## Features + +### 1. Token Transfers + +- Send native B2-BTC and ERC20 tokens +- Support for multiple token standards +- Built-in address validation + +## Supported Tokens + +```typescript +const TOKENS = { + "B2-BTC": "0x0000000000000000000000000000000000000000", + uBTC: "0x796e4D53067FF374B89b2Ac101ce0c1f72ccaAc2", + USDC: "0xE544e8a38aDD9B1ABF21922090445Ba93f74B9E5", + USDT: "0x681202351a488040Fa4FdCc24188AfB582c9DD62", + // ... and more +}; +``` + +## Usage Examples + +### Token Transfer + +```typescript +// Send B2-BTC +"Send 1 B2-BTC to 0x4f9e2dc50B4Cd632CC2D24edaBa3Da2a9338832a"; + +// Send ERC20 +"Transfer 100 USDC to [address]"; +``` + +## Providers + +### 1. Wallet Provider + +- Displays wallet balances +- Real-time balance updates + +### 2. Tokens Provider + +- Lists supported tokens +- Shows token addresses + +## Development + +1. Clone the repository +2. Install dependencies: +3. Build the plugin: + +```bash +pnpm run build +``` + +4. Run linting: + +```bash +pnpm run lint +``` + +## Dependencies + +- viem: ^2.21.49 +- @elizaos/core: workspace:\* + +## Future Enhancements + +1. **Advanced DeFi Operations** + + - Multi-hop yield strategies + - Auto-compounding features + - Yield optimization algorithms + - Risk assessment tools + - Portfolio rebalancing automation + - Cross-chain yield farming + +2. **Enhanced Token Management** + + - Batch token operations + - Advanced token creation templates + - Token migration tools + - Automated token listing + - Token analytics dashboard + - Custom tokenomics implementation + +3. **YAK Protocol Integration** + + - Advanced routing algorithms + - MEV protection features + - Gas optimization strategies + - Liquidity analysis tools + - Price impact predictions + - Custom trading strategies + +4. **Benqi Protocol Features** + + - Collateral optimization + - Liquidation protection + - Interest rate monitoring + - Position management tools + - Risk assessment dashboard + - Auto-repayment features + +5. **Token Mill Improvements** + + - Advanced token customization + - Automated market making + - Token distribution tools + - Vesting schedule management + - Governance token features + - Token upgrade mechanisms + +6. **Security Enhancements** + + - Transaction simulation + - Smart contract auditing tools + - Real-time monitoring + - Automated safety checks + - Emergency shutdown features + - Multi-signature support + +7. **Developer Tools** + + - Enhanced debugging capabilities + - Testing framework improvements + - Documentation generator + - CLI tools for common operations + - Integration templates + - Performance monitoring + +8. **Analytics and Reporting** + - Portfolio tracking + - Performance metrics + - Gas usage optimization + - Transaction history analysis + - Yield comparison tools + - Risk assessment reports + +We welcome community feedback and contributions to help prioritize these enhancements. + +## Contributing + +Contributions are welcome! Please see the [CONTRIBUTING.md](CONTRIBUTING.md) file for more information. + +## License + +This plugin is part of the Eliza project. See the main project repository for license information. diff --git a/packages/plugin-b2/eslint.config.mjs b/packages/plugin-b2/eslint.config.mjs new file mode 100644 index 00000000000..92fe5bbebef --- /dev/null +++ b/packages/plugin-b2/eslint.config.mjs @@ -0,0 +1,3 @@ +import eslintGlobalConfig from "../../eslint.config.mjs"; + +export default [...eslintGlobalConfig]; diff --git a/packages/plugin-b2/package.json b/packages/plugin-b2/package.json new file mode 100644 index 00000000000..106f79e45e8 --- /dev/null +++ b/packages/plugin-b2/package.json @@ -0,0 +1,37 @@ +{ + "name": "@elizaos/plugin-b2", + "version": "0.1.7", + "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:*", + "tsup": "8.3.5" + }, + "devDependencies": { + "tsup": "8.3.5" + }, + "scripts": { + "build": "tsup src/index.ts --format esm --no-dts", + "dev": "tsup --format esm --dts --watch", + "lint": "eslint --fix --cache .", + "test": "vitest run" + }, + "peerDependencies": { + "whatwg-url": "7.1.0" + } +} diff --git a/packages/plugin-b2/src/actions/stake.ts b/packages/plugin-b2/src/actions/stake.ts new file mode 100644 index 00000000000..abc47000722 --- /dev/null +++ b/packages/plugin-b2/src/actions/stake.ts @@ -0,0 +1,147 @@ +import { + Action, + ActionExample, + IAgentRuntime, + generateObjectDeprecated, + Memory, + State, + HandlerCallback, + elizaLogger, + composeContext, + ModelClass, +} from "@elizaos/core"; +import { getTxReceipt, sendNativeAsset, sendToken, depositBTC } from "../utils"; +import { Address, Hash } from "viem"; +import { validateB2NetworkConfig } from "../environment"; +import { stakeTemplate } from "../templates"; +import { WalletProvider } from "../providers"; +import { StakeParams } from "../types"; +import { initWalletProvider } from "../providers"; +import { FARM_ADDRESS } from "../utils/constants"; + +// Exported for tests +export class StakeAction { + + constructor(private walletProvider: WalletProvider) {} + + async stake(params: StakeParams): Promise { + try { + const balance = await this.walletProvider.getNativeBalance(this.walletProvider.getAddress()); + if ( balance == BigInt(0) ) { + throw new Error(`The total cost (gas * gas fee + value) of executing this transaction exceeds the balance of the account.`); + } + const txHash = await depositBTC( + this.walletProvider, + FARM_ADDRESS, + params.amount, + ); + return txHash; + } catch(error) { + elizaLogger.error(`Stake failed: ${error.message}`); + throw new Error(`Stake failed: ${error.message}`); + } + } + + async txReceipt(tx: Hash) { + const receipt = await getTxReceipt(this.walletProvider, tx); + if (receipt.status === "success") { + return true; + } else { + return false; + } + } + + async buildStakeDetails( + state: State, + runtime: IAgentRuntime, + ): Promise { + const context = composeContext({ + state, + template: stakeTemplate, + }); + + const stakeDetails = (await generateObjectDeprecated({ + runtime, + context, + modelClass: ModelClass.SMALL, + })) as StakeParams; + + return stakeDetails; + } +} + +export const stakeAction: Action = { + name: "STAKE", + similes: [ + "STAKE_BTC_ON_B2", + "STAKE_NATIVE_BTC_ON_B2", + "DEPOSIT_BTC_ON_B2", + "DEPOSIT_NATIVE_BTC_ON_B2", + ], + validate: async (runtime: IAgentRuntime, _message: Memory) => { + await validateB2NetworkConfig(runtime); + return true; + }, + description: + "stake B2-BTC.", + handler: async ( + runtime: IAgentRuntime, + message: Memory, + state: State, + _options: { [key: string]: unknown }, + callback?: HandlerCallback + ) => { + elizaLogger.debug("Starting STAKE handler..."); + + // Initialize or update state + if (!state) { + state = (await runtime.composeState(message)) as State; + } else { + state = await runtime.updateRecentMessageState(state); + } + + elizaLogger.debug("stake action handler called"); + const walletProvider = await initWalletProvider(runtime); + const action = new StakeAction(walletProvider); + + // Compose stake context + const paramOptions = await action.buildStakeDetails( + state, + runtime, + ); + + elizaLogger.debug("Stake paramOptions:", paramOptions); + + const txHash = await action.stake(paramOptions); + if (txHash) { + const result = await action.txReceipt(txHash); + if (result) { + callback?.({ + text: "stake successful", + content: { success: true, txHash: txHash }, + }); + } else { + callback?.({ + text: "stake failed", + content: { error: "Stake failed" }, + }); + } + } else { + callback?.({ + text: "stake failed", + content: { error: "Stake failed" }, + }); + } + return true; + }, + examples: [ + [ + { + user: "{{user1}}", + content: { + text: "Stake 1 B2-BTC", + }, + }, + ], + ] as ActionExample[][], +}; diff --git a/packages/plugin-b2/src/actions/transfer.ts b/packages/plugin-b2/src/actions/transfer.ts new file mode 100644 index 00000000000..52106bc8cc9 --- /dev/null +++ b/packages/plugin-b2/src/actions/transfer.ts @@ -0,0 +1,159 @@ +import { + Action, + ActionExample, + IAgentRuntime, + generateObjectDeprecated, + Memory, + State, + HandlerCallback, + elizaLogger, + composeContext, + ModelClass, +} from "@elizaos/core"; +import { getTxReceipt, sendNativeAsset, sendToken } from "../utils"; +import { Address, Hash } from "viem"; +import { validateB2NetworkConfig } from "../environment"; +import { transferTemplate } from "../templates"; +import { WalletProvider } from "../providers"; +import { Transaction, TransferParams } from "../types"; +import { initWalletProvider } from "../providers"; +import { TOKEN_ADDRESSES } from "../utils/constants" +// Exported for tests +export class TransferAction { + + constructor(private walletProvider: WalletProvider) {} + + async transfer(params: TransferParams): Promise { + try { + let txHash: Hash; + if (params.tokenAddress === TOKEN_ADDRESSES["B2-BTC"]) { + txHash = await sendNativeAsset( + this.walletProvider, + params.recipient as Address, + params.amount as number + ); + } else { + txHash = await sendToken( + this.walletProvider, + params.tokenAddress as Address, + params.recipient as Address, + params.amount as number + ); + } + return { + hash: txHash, + from: this.walletProvider.getAddress(), + tokenAddress: params.tokenAddress, + recipient: params.recipient, + amount: params.amount, + }; + } catch(error) { + elizaLogger.error(`Transfer failed: ${error.message}`); + throw new Error(`Transfer failed: ${error.message}`); + } + } + + async txReceipt(tx: Hash) { + const receipt = await getTxReceipt(this.walletProvider, tx); + if (receipt.status === "success") { + return true; + } else { + return false; + } + } + + async buildTransferDetails( + state: State, + runtime: IAgentRuntime, + ): Promise { + const context = composeContext({ + state, + template: transferTemplate, + }); + + const transferDetails = (await generateObjectDeprecated({ + runtime, + context, + modelClass: ModelClass.SMALL, + })) as TransferParams; + + return transferDetails; + } +} + +export const transferAction: Action = { + name: "SEND_TOKEN", + similes: [ + "TRANSFER_TOKEN_ON_B2", + "TRANSFER_TOKENS_ON_B2", + "SEND_TOKENS_ON_B2", + "SEND_B2BTC_ON_B2", + "PAY_ON_B2", + ], + validate: async (runtime: IAgentRuntime, _message: Memory) => { + await validateB2NetworkConfig(runtime); + return true; + }, + description: + "MUST use this action if the user requests send a token or transfer a token, the request might be varied, but it will always be a token transfer.", + handler: async ( + runtime: IAgentRuntime, + message: Memory, + state: State, + _options: { [key: string]: unknown }, + callback?: HandlerCallback + ) => { + elizaLogger.debug("Starting SEND_TOKEN handler..."); + + // Initialize or update state + if (!state) { + state = (await runtime.composeState(message)) as State; + } else { + state = await runtime.updateRecentMessageState(state); + } + + elizaLogger.debug("Transfer action handler called"); + const walletProvider = await initWalletProvider(runtime); + const action = new TransferAction(walletProvider); + + // Compose transfer context + const paramOptions = await action.buildTransferDetails( + state, + runtime, + ); + + elizaLogger.debug("Transfer paramOptions:", paramOptions); + + const tx = await action.transfer(paramOptions); + if (tx) { + const result = await action.txReceipt(tx.hash); + if (result) { + callback?.({ + text: "transfer successful", + content: { success: true, txHash: tx.hash }, + }); + } else { + callback?.({ + text: "transfer failed", + content: { error: "Transfer failed" }, + }); + } + } else { + callback?.({ + text: "transfer failed", + content: { error: "Transfer failed" }, + }); + } + return true; + }, + examples: [ + [ + { + user: "{{user1}}", + content: { + text: "Send 1 B2-BTC to 0x4f9e2dc50B4Cd632CC2D24edaBa3Da2a9338832a", + }, + }, + ], + ] as ActionExample[][], +}; diff --git a/packages/plugin-b2/src/actions/unstake.ts b/packages/plugin-b2/src/actions/unstake.ts new file mode 100644 index 00000000000..7e6f6fd58a0 --- /dev/null +++ b/packages/plugin-b2/src/actions/unstake.ts @@ -0,0 +1,147 @@ +import { + Action, + ActionExample, + IAgentRuntime, + generateObjectDeprecated, + Memory, + State, + HandlerCallback, + elizaLogger, + composeContext, + ModelClass, +} from "@elizaos/core"; +import { getTxReceipt, unstake } from "../utils"; +import { Hash } from "viem"; +import { validateB2NetworkConfig } from "../environment"; +import { unstakeTemplate } from "../templates"; +import { WalletProvider } from "../providers"; +import { UnstakeParams } from "../types"; +import { initWalletProvider } from "../providers"; +import { FARM_ADDRESS } from "../utils/constants"; + +// Exported for tests +export class UnstakeAction { + + constructor(private walletProvider: WalletProvider) {} + + async unstake(params: UnstakeParams): Promise { + try { + const balance = await this.walletProvider.getNativeBalance(this.walletProvider.getAddress()); + if ( balance == BigInt(0) ) { + throw new Error(`The total cost (gas * gas fee + value) of executing this transaction exceeds the balance of the account.`); + } + const txHash = await unstake( + this.walletProvider, + FARM_ADDRESS, + params.amount, + ); + return txHash; + } catch(error) { + elizaLogger.error(`Unstake failed: ${error.message}`); + throw new Error(`Unstake failed: ${error.message}`); + } + } + + async txReceipt(tx: Hash) { + const receipt = await getTxReceipt(this.walletProvider, tx); + if (receipt.status === "success") { + return true; + } else { + return false; + } + } + + async buildUnstakeDetails( + state: State, + runtime: IAgentRuntime, + ): Promise { + const context = composeContext({ + state, + template: unstakeTemplate, + }); + + const unstakeDetails = (await generateObjectDeprecated({ + runtime, + context, + modelClass: ModelClass.SMALL, + })) as UnstakeParams; + + return unstakeDetails; + } +} + +export const unstakeAction: Action = { + name: "UNSTAKE", + similes: [ + "UNSTAKE_BTC_ON_B2", + "UNSTAKE_NATIVE_BTC_ON_B2", + "UNSTAKE_BTC_ON_B2", + "UNSTAKE_NATIVE_BTC_ON_B2", + ], + validate: async (runtime: IAgentRuntime, _message: Memory) => { + await validateB2NetworkConfig(runtime); + return true; + }, + description: + "unstake B2-BTC.", + handler: async ( + runtime: IAgentRuntime, + message: Memory, + state: State, + _options: { [key: string]: unknown }, + callback?: HandlerCallback + ) => { + elizaLogger.debug("Starting UNSTAKE handler..."); + + // Initialize or update state + if (!state) { + state = (await runtime.composeState(message)) as State; + } else { + state = await runtime.updateRecentMessageState(state); + } + + elizaLogger.debug("unstake action handler called"); + const walletProvider = await initWalletProvider(runtime); + const action = new UnstakeAction(walletProvider); + + // Compose unstake context + const paramOptions = await action.buildUnstakeDetails( + state, + runtime, + ); + + elizaLogger.debug("Unstake paramOptions:", paramOptions); + + const txHash = await action.unstake(paramOptions); + if (txHash) { + const result = await action.txReceipt(txHash); + if (result) { + callback?.({ + text: "unstake successful", + content: { success: true, txHash: txHash }, + }); + } else { + callback?.({ + text: "unstake failed", + content: { error: "Unstake failed" }, + }); + } + } else { + callback?.({ + text: "unstake failed", + content: { error: "Unstake failed" }, + }); + } + return true; + }, + examples: [ + [ + { + user: "{{user1}}", + content: { + text: "Unstake 1 B2-BTC", + }, + }, + ], + ] as ActionExample[][], +}; diff --git a/packages/plugin-b2/src/actions/withdraw.ts b/packages/plugin-b2/src/actions/withdraw.ts new file mode 100644 index 00000000000..d1e2ecb9193 --- /dev/null +++ b/packages/plugin-b2/src/actions/withdraw.ts @@ -0,0 +1,145 @@ +import { + Action, + ActionExample, + IAgentRuntime, + generateObjectDeprecated, + Memory, + State, + HandlerCallback, + elizaLogger, + composeContext, + ModelClass, +} from "@elizaos/core"; +import { getTxReceipt, withdraw } from "../utils"; +import { Hash } from "viem"; +import { validateB2NetworkConfig } from "../environment"; +import { withdrawTemplate } from "../templates"; +import { WalletProvider } from "../providers"; +import { WithdrawParams } from "../types"; +import { initWalletProvider } from "../providers"; +import { FARM_ADDRESS } from "../utils/constants"; + +// Exported for tests +export class WithdrawAction { + + constructor(private walletProvider: WalletProvider) {} + + async withdraw(_params: WithdrawParams): Promise { + try { + const balance = await this.walletProvider.getNativeBalance(this.walletProvider.getAddress()); + if ( balance == BigInt(0) ) { + throw new Error(`The total cost (gas * gas fee + value) of executing this transaction exceeds the balance of the account.`); + } + const txHash = await withdraw( + this.walletProvider, + FARM_ADDRESS, + ); + return txHash; + } catch(error) { + elizaLogger.log(`Withdraw failed: ${error.message}`); + throw new Error(`Withdraw failed: ${error.message}`); + } + } + + async txReceipt(tx: Hash) { + const receipt = await getTxReceipt(this.walletProvider, tx); + if (receipt.status === "success") { + return true; + } else { + return false; + } + } + + async buildWithdrawDetails( + state: State, + runtime: IAgentRuntime, + ): Promise { + const context = composeContext({ + state, + template: withdrawTemplate, + }); + + const withdrawDetails = (await generateObjectDeprecated({ + runtime, + context, + modelClass: ModelClass.SMALL, + })) as WithdrawParams; + + return withdrawDetails; + } +} + +export const withdrawAction: Action = { + name: "WITHDRAW", + similes: [ + "WITHDRAW_BTC_ON_B2", + "WITHDRAW_NATIVE_BTC_ON_B2", + "WITHDRAW_BTC_ON_B2", + "WITHDRAW_NATIVE_BTC_ON_B2", + ], + validate: async (runtime: IAgentRuntime, _message: Memory) => { + await validateB2NetworkConfig(runtime); + return true; + }, + description: + "withdraw B2-BTC.", + handler: async ( + runtime: IAgentRuntime, + message: Memory, + state: State, + _options: { [key: string]: unknown }, + callback?: HandlerCallback + ) => { + elizaLogger.debug("Starting WITHDRAW handler..."); + + // Initialize or update state + if (!state) { + state = (await runtime.composeState(message)) as State; + } else { + state = await runtime.updateRecentMessageState(state); + } + + elizaLogger.debug("withdraw action handler called"); + const walletProvider = await initWalletProvider(runtime); + const action = new WithdrawAction(walletProvider); + + // Compose unstake context + const paramOptions = await action.buildWithdrawDetails( + state, + runtime, + ); + elizaLogger.debug("Unstake paramOptions:", paramOptions); + + const txHash = await action.withdraw(paramOptions); + if (txHash) { + const result = await action.txReceipt(txHash); + if (result) { + callback?.({ + text: "withdraw successful", + content: { success: true, txHash: txHash }, + }); + } else { + callback?.({ + text: "withdraw failed", + content: { error: "Withdraw failed" }, + }); + } + } else { + callback?.({ + text: "withdraw failed", + content: { error: "Withdraw failed" }, + }); + } + return true; + }, + examples: [ + [ + { + user: "{{user1}}", + content: { + text: "Withdraw B2-BTC", + }, + }, + ], + ] as ActionExample[][], +}; diff --git a/packages/plugin-b2/src/environment.ts b/packages/plugin-b2/src/environment.ts new file mode 100644 index 00000000000..637f5dacde9 --- /dev/null +++ b/packages/plugin-b2/src/environment.ts @@ -0,0 +1,31 @@ +import { IAgentRuntime } from "@elizaos/core"; +import { z } from "zod"; + +export const b2NetworkEnvSchema = z.object({ + B2_PRIVATE_KEY: z + .string() + .min(1, "b2 network private key is required"), +}); + +export type b2NetworkConfig = z.infer; +export async function validateB2NetworkConfig( + runtime: IAgentRuntime +): Promise { + try { + const config = { + B2_PRIVATE_KEY: + runtime.getSetting("B2_PRIVATE_KEY") || + process.env.B2_PRIVATE_KEY, + }; + + return b2NetworkEnvSchema.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(errorMessages); + } + throw error; + } +} diff --git a/packages/plugin-b2/src/index.ts b/packages/plugin-b2/src/index.ts new file mode 100644 index 00000000000..7cc4d86f4d2 --- /dev/null +++ b/packages/plugin-b2/src/index.ts @@ -0,0 +1,18 @@ +import { Plugin } from "@elizaos/core"; +import { transferAction } from "./actions/transfer"; +import { stakeAction } from "./actions/stake"; +import { unstakeAction } from "./actions/unstake"; +import { withdrawAction } from "./actions/withdraw"; +import { walletProvider } from "./providers"; + +const b2Plugin: Plugin = { + name: "b2", + description: "B2 Network Plugin for Eliza", + actions: [transferAction, stakeAction, unstakeAction, withdrawAction], + providers: [walletProvider], + evaluators: [], + services: [], + clients: [], +}; + +export default b2Plugin; diff --git a/packages/plugin-b2/src/providers/index.ts b/packages/plugin-b2/src/providers/index.ts new file mode 100644 index 00000000000..34149ee555c --- /dev/null +++ b/packages/plugin-b2/src/providers/index.ts @@ -0,0 +1,222 @@ +import { + IAgentRuntime, + Memory, + Provider, + State, + elizaLogger, +} from "@elizaos/core"; +import { privateKeyToAccount } from "viem/accounts"; +import { + formatUnits, + Address, + Chain, + Account, + WalletClient, + PrivateKeyAccount, + http, + createPublicClient, + createWalletClient, + PublicClient, + Transport, + RpcSchema, +} from "viem"; +import { TOKEN_ADDRESSES } from "../utils/constants"; +import { b2Network } from "../utils/chains"; + +export class WalletProvider implements Provider { + private account: PrivateKeyAccount; + + constructor(accountOrPrivateKey: PrivateKeyAccount | `0x${string}`) { + this.setAccount(accountOrPrivateKey); + } + + private setAccount = ( + accountOrPrivateKey: PrivateKeyAccount | `0x${string}` + ) => { + if (typeof accountOrPrivateKey === "string") { + this.account = privateKeyToAccount(accountOrPrivateKey); + } else { + this.account = accountOrPrivateKey; + } + }; + + async getNativeBalance ( + owner: Address + ) { + const publicClient = this.getPublicClient(); + const balance = await publicClient.getBalance({ + address: owner, + }); + return balance; + }; + + async getTokenBalance ( + tokenAddress: Address, + owner: Address + ) { + if (tokenAddress === TOKEN_ADDRESSES["B2-BTC"]) { + return this.getNativeBalance(owner); + } + const publicClient = this.getPublicClient(); + const balance = await publicClient.readContract({ + address: tokenAddress, + abi: [ + { + inputs: [ + { + internalType: "address", + name: "account", + type: "address", + }, + ], + name: "balanceOf", + outputs: [ + { internalType: "uint256", name: "", type: "uint256" }, + ], + stateMutability: "view", + type: "function", + }, + ], + functionName: "balanceOf", + args: [owner], + }); + return balance; + }; + + getAccount(): Account { + return this.account; + } + + getAddress(): Address { + return this.account.address; + } + + getPublicClient(): PublicClient { + return createPublicClient({ + chain: b2Network, + transport: http(), + }); + } + + getWalletClient(): WalletClient { + const transport = http(b2Network.rpcUrls.default.http[0]); + const walletClient = createWalletClient({ + chain: b2Network, + transport, + account: this.account, + }); + return walletClient; + } + + async getDecimals(tokenAddress: Address) { + if (tokenAddress === TOKEN_ADDRESSES["B2-BTC"]) { + return b2Network.nativeCurrency.decimals; + } + const publicClient = this.getPublicClient(); + const decimals = await publicClient.readContract({ + address: tokenAddress, + abi: [ + { + inputs: [], + name: "decimals", + outputs: [{ internalType: "uint8", name: "", type: "uint8" }], + stateMutability: "view", + type: "function", + }, + ], + functionName: "decimals", + }); + return decimals; + } + + async get( + runtime: IAgentRuntime, + _message: Memory, + _state?: State + ): Promise { + elizaLogger.debug("walletProvider::get"); + try { + const privateKey = runtime.getSetting("B2_PRIVATE_KEY"); + if (!privateKey) { + throw new Error( + "B2_PRIVATE_KEY not found in environment variables" + ); + } + let accountAddress; + if (this.account) { + accountAddress = this.getAddress(); + } else { + const walletProvider = await initWalletProvider(runtime); + accountAddress = walletProvider.getAddress(); + } + + let output = `# Wallet Balances\n\n`; + output += `## Wallet Address\n\n\`${accountAddress}\`\n\n`; + + output += `## Latest Token Balances\n\n`; + for (const [token, address] of Object.entries(TOKEN_ADDRESSES)) { + const decimals = await this.getDecimals(address); + const balance = await this.getTokenBalance( + address, + accountAddress, + ); + output += `${token}: ${formatUnits(balance, decimals)}\n`; + } + output += `Note: These balances can be used at any time.\n\n`; + elizaLogger.debug("walletProvider::get output:", output); + return output; + } catch (error) { + elizaLogger.error("Error in b2 wallet provider:", error); + return null; + } + } + +}; + +export const initWalletProvider = async (runtime: IAgentRuntime) => { + const privateKey = runtime.getSetting("B2_PRIVATE_KEY"); + if (!privateKey) { + throw new Error( + "B2_PRIVATE_KEY not found in environment variables" + ); + } + return new WalletProvider(privateKey as `0x${string}`); +}; + +export const walletProvider: Provider = { + async get( + runtime: IAgentRuntime, + _message: Memory, + _state?: State + ): Promise { + elizaLogger.debug("walletProvider::get"); + const privateKey = runtime.getSetting("B2_PRIVATE_KEY"); + if (!privateKey) { + throw new Error( + "B2_PRIVATE_KEY not found in environment variables" + ); + } + try { + const walletProvider = await initWalletProvider(runtime); + const account = walletProvider.getAccount(); + let output = `# Wallet Balances\n\n`; + output += `## Wallet Address\n\n\`${account.address}\`\n\n`; + + output += `## Latest Token Balances\n\n`; + for (const [token, address] of Object.entries(TOKEN_ADDRESSES)) { + const decimals = await walletProvider.getDecimals(address); + const balance = await walletProvider.getTokenBalance( + address, + account.address + ); + output += `${token}: ${formatUnits(balance, decimals)}\n`; + } + output += `Note: These balances can be used at any time.\n\n`; + elizaLogger.debug("walletProvider::get output:", output); + return output; + } catch (error) { + elizaLogger.error("Error in b2 wallet provider:", error); + return null; + } + } +}; \ No newline at end of file diff --git a/packages/plugin-b2/src/templates/index.ts b/packages/plugin-b2/src/templates/index.ts new file mode 100644 index 00000000000..238a506a122 --- /dev/null +++ b/packages/plugin-b2/src/templates/index.ts @@ -0,0 +1,11 @@ +import { transferTemplate } from "./transfer"; +import { stakeTemplate } from "./stake"; +import { unstakeTemplate } from "./unstake"; +import {withdrawTemplate} from "./withdraw" + +export { + transferTemplate, + stakeTemplate, + unstakeTemplate, + withdrawTemplate +}; \ No newline at end of file diff --git a/packages/plugin-b2/src/templates/stake.ts b/packages/plugin-b2/src/templates/stake.ts new file mode 100644 index 00000000000..1bb0fb9bbad --- /dev/null +++ b/packages/plugin-b2/src/templates/stake.ts @@ -0,0 +1,17 @@ +export const stakeTemplate = `Respond with a JSON markdown block containing only the extracted values + +Example response for a 10 B2-BTC stake: +\`\`\`json +{ + "amount": "10" +} +\`\`\` + +## Recent Messages + +{{recentMessages}} + +Given the recent messages, extract the following information about the requested stake: +- Amount to stake + +Respond with a JSON markdown block containing only the extracted values.`; \ No newline at end of file diff --git a/packages/plugin-b2/src/templates/transfer.ts b/packages/plugin-b2/src/templates/transfer.ts new file mode 100644 index 00000000000..d9e23698940 --- /dev/null +++ b/packages/plugin-b2/src/templates/transfer.ts @@ -0,0 +1,40 @@ +import { TOKEN_ADDRESSES } from "../utils/constants"; + +export const transferTemplate = `Respond with a JSON markdown block containing only the extracted values +- Use null for any values that cannot be determined. +- Use address zero for native B2-BTC transfers. + +Example response for a 10 uBTC transfer: +\`\`\`json +{ + "tokenAddress": "0x796e4D53067FF374B89b2Ac101ce0c1f72ccaAc2", + "recipient": "0x4f9e2dc50B4Cd632CC2D24edaBa3Da2a9338832a", + "amount": "10" +} +\`\`\` + +Example response for a 0.1 B2-BTC transfer: +\`\`\`json +{ + "tokenAddress": "0x0000000000000000000000000000000000000000", + "recipient": "0x4f9e2dc50B4Cd632CC2D24edaBa3Da2a9338832a", + "amount": "0.1" +} +\`\`\` + +## Token Addresses + +${Object.entries(TOKEN_ADDRESSES) + .map(([key, value]) => `- ${key}: ${value}`) + .join("\n")} + +## Recent Messages + +{{recentMessages}} + +Given the recent messages, extract the following information about the requested token transfer: +- Token contract address +- Recipient wallet address +- Amount to transfer + +Respond with a JSON markdown block containing only the extracted values.`; \ No newline at end of file diff --git a/packages/plugin-b2/src/templates/unstake.ts b/packages/plugin-b2/src/templates/unstake.ts new file mode 100644 index 00000000000..35f2ee0418e --- /dev/null +++ b/packages/plugin-b2/src/templates/unstake.ts @@ -0,0 +1,18 @@ +export const unstakeTemplate = `Respond with a JSON markdown block containing only the extracted values +- Use null for any values that cannot be determined. + +Example response for a 5 B2-BTC unstake: +\`\`\`json +{ + "amount": "5" +} +\`\`\` + +## Recent Messages + +{{recentMessages}} + +Given the recent messages, extract the following information about the requested unstake: +- Amount to unstake + +Respond with a JSON markdown block containing only the extracted values.`; diff --git a/packages/plugin-b2/src/templates/withdraw.ts b/packages/plugin-b2/src/templates/withdraw.ts new file mode 100644 index 00000000000..751df636ef4 --- /dev/null +++ b/packages/plugin-b2/src/templates/withdraw.ts @@ -0,0 +1,15 @@ +export const withdrawTemplate = `Respond with a JSON markdown block containing only the extracted values +- This action does not require any parameters. + +Example response for a withdraw request: +\`\`\`json +{} +\`\`\` + +## Recent Messages + +{{recentMessages}} + +Given the recent messages, confirm the request for withdrawal. + +Respond with a JSON markdown block containing only an empty object.`; diff --git a/packages/plugin-b2/src/tests/stake.test.ts b/packages/plugin-b2/src/tests/stake.test.ts new file mode 100644 index 00000000000..e71eaa01caf --- /dev/null +++ b/packages/plugin-b2/src/tests/stake.test.ts @@ -0,0 +1,45 @@ +import { describe, it, expect, beforeEach } from "vitest"; +import { generatePrivateKey } from "viem/accounts"; +import { getEnvVariable } from "@elizaos/core"; + +import { StakeAction } from "../actions/stake"; +import { WalletProvider } from "../providers"; +import { StakeParams } from "../types"; + +describe("Stake Action", () => { + let wp: WalletProvider; + + beforeEach(async () => { + const pk = generatePrivateKey(); + wp = new WalletProvider(pk); + }); + describe("Constructor", () => { + it("should initialize with stake action", () => { + const sa = new StakeAction(wp); + expect(sa).toBeDefined(); + }); + }); + describe("Stake", () => { + let sa: StakeAction; + beforeEach(() => { + sa = new StakeAction(wp); + expect(sa).toBeDefined(); + }); + it("should initialize with stake action", () => { + const sa = new StakeAction(wp); + expect(sa).toBeDefined(); + }); + + it("throws if not enough gas", async () => { + const params = { + amount: "1", + } as StakeParams; + await expect( + sa.stake(params) + ).rejects.toThrow( + "Stake failed: The total cost (gas * gas fee + value) of executing this transaction exceeds the balance of the account." + ); + }); + + }); +}); diff --git a/packages/plugin-b2/src/tests/transfer.test.ts b/packages/plugin-b2/src/tests/transfer.test.ts new file mode 100644 index 00000000000..a48a59ffeaf --- /dev/null +++ b/packages/plugin-b2/src/tests/transfer.test.ts @@ -0,0 +1,75 @@ +import { describe, it, expect, beforeEach } from "vitest"; +import { generatePrivateKey } from "viem/accounts"; +import { getEnvVariable } from "@elizaos/core"; + +import { TransferAction } from "../actions/transfer"; +import { WalletProvider } from "../providers"; +import { TransferParams } from "../types"; +import { TOKEN_ADDRESSES } from "../utils/constants"; + +describe("Transfer Action", () => { + let wp: WalletProvider; + let wp1: WalletProvider; + + beforeEach(async () => { + const pk = generatePrivateKey(); + const pk1 = getEnvVariable("ARTHERA_PRIVATE_KEY") as `0x${string}`; + wp = new WalletProvider(pk); + console.log(wp.getAddress()); + if (pk1) { + wp1 = new WalletProvider(pk1); + } + }); + describe("Constructor", () => { + it("should initialize with transfer action", () => { + const ta = new TransferAction(wp); + + expect(ta).toBeDefined(); + }); + }); + describe("Transfer", () => { + let ta: TransferAction; + let ta1: TransferAction; + let receiverAddress: `0x${string}`; + + beforeEach(() => { + ta = new TransferAction(wp); + if (wp1) { + ta1 = new TransferAction(wp1); + receiverAddress = wp1.getAddress(); + } + else { + receiverAddress = wp.getAddress(); + } + }); + + it("throws if not enough gas", async () => { + const params = { + tokenAddress: TOKEN_ADDRESSES["B2-BTC"], + recipient: receiverAddress, + amount: "1", + } as TransferParams; + await expect( + ta.transfer(params) + ).rejects.toThrow( + "Transfer failed: The total cost (gas * gas fee + value) of executing this transaction exceeds the balance of the account." + ); + }); + + if (wp1) { + console.log("----------------------------------------------"); + it("transfers tokens", async () => { + const params = { + tokenAddress: TOKEN_ADDRESSES["B2-BTC"], + recipient: receiverAddress, + amount: "0.001", + } as TransferParams; + const tx = await ta1.transfer(params); + expect(tx).toBeDefined(); + expect(tx.from).toEqual(wp1.getAddress()); + expect(tx.recipient).toEqual(receiverAddress); + expect(tx.amount).toEqual(1000000000000000n); + }); + } + }); +}); diff --git a/packages/plugin-b2/src/tests/unstake.test.ts b/packages/plugin-b2/src/tests/unstake.test.ts new file mode 100644 index 00000000000..d1aa5e21364 --- /dev/null +++ b/packages/plugin-b2/src/tests/unstake.test.ts @@ -0,0 +1,45 @@ +import { describe, it, expect, beforeEach } from "vitest"; +import { generatePrivateKey } from "viem/accounts"; +import { getEnvVariable } from "@elizaos/core"; + +import { UnstakeAction } from "../actions/unstake"; +import { WalletProvider } from "../providers"; +import { TransferParams, UnstakeParams } from "../types"; + +describe("Unstake Action", () => { + let wp: WalletProvider; + + beforeEach(async () => { + const pk = generatePrivateKey(); + wp = new WalletProvider(pk); + }); + describe("Constructor", () => { + it("should initialize with unstake action", () => { + const ua = new UnstakeAction(wp); + expect(ua).toBeDefined(); + }); + }); + describe("Unstake", () => { + let ua: UnstakeAction; + + beforeEach(() => { + ua = new UnstakeAction(wp); + expect(ua).toBeDefined(); + }); + it("should initialize with unstake action", () => { + const ua = new UnstakeAction(wp); + expect(ua).toBeDefined(); + }); + + it("throws if not enough gas", async () => { + const params = { + amount: "1", + } as UnstakeParams; + await expect( + ua.unstake(params) + ).rejects.toThrow( + "Unstake failed: The total cost (gas * gas fee + value) of executing this transaction exceeds the balance of the account." + ); + }); + }); +}); diff --git a/packages/plugin-b2/src/tests/wallet.test.ts b/packages/plugin-b2/src/tests/wallet.test.ts new file mode 100644 index 00000000000..c1429545e4f --- /dev/null +++ b/packages/plugin-b2/src/tests/wallet.test.ts @@ -0,0 +1,40 @@ +import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; +import { initWalletProvider, WalletProvider } from "../providers"; +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; +import { Memory, State } from "@elizaos/core"; + +describe("B2 Network Wallet Provider", () => { + let walletProvider: WalletProvider; + let mockRuntime; + + beforeEach(() => { + vi.clearAllMocks(); + const pk = generatePrivateKey(); + walletProvider = new WalletProvider(pk); + mockRuntime = { + getSetting: vi.fn(), + }; + mockRuntime.getSetting.mockImplementation((key: string) => { + const settings = { + B2_PRIVATE_KEY: pk, + }; + return settings[key]; + }); + }); + + afterEach(() => { + vi.clearAllTimers(); + }); + + describe("Constructor", () => { + it("new wallet provider", () => { + const pk = generatePrivateKey(); + const ta = new WalletProvider(pk); + expect(ta).toBeDefined(); + }); + it("init wallet provider",async () => { + const ta = await initWalletProvider(mockRuntime); + expect(ta).toBeDefined(); + }); + }); +}); diff --git a/packages/plugin-b2/src/tests/withdraw.test.ts b/packages/plugin-b2/src/tests/withdraw.test.ts new file mode 100644 index 00000000000..69404237479 --- /dev/null +++ b/packages/plugin-b2/src/tests/withdraw.test.ts @@ -0,0 +1,41 @@ +import { describe, it, expect, beforeEach } from "vitest"; +import { generatePrivateKey } from "viem/accounts"; +import { WithdrawAction } from "../actions/withdraw"; +import { WalletProvider } from "../providers"; +import { WithdrawParams } from "../types"; + +describe("Withdraw Action", () => { + let wp: WalletProvider; + + beforeEach(async () => { + const pk = generatePrivateKey(); + wp = new WalletProvider(pk); + }); + describe("Constructor", () => { + it("should initialize with withdraw action", () => { + const wa = new WithdrawAction(wp); + expect(wa).toBeDefined(); + }); + }); + describe("Withdraw", () => { + let wa: WithdrawAction; + beforeEach(() => { + wa = new WithdrawAction(wp); + expect(wa).toBeDefined(); + }); + it("should initialize with withdraw action", () => { + wa = new WithdrawAction(wp); + expect(wa).toBeDefined(); + }); + it("throws if not enough gas", async () => { + const params = {} as WithdrawParams; + wa = new WithdrawAction(wp); + await expect( + wa.withdraw(params) + ).rejects.toThrow( + "Withdraw failed: The total cost (gas * gas fee + value) of executing this transaction exceeds the balance of the account." + ); + }); + + }); +}); diff --git a/packages/plugin-b2/src/types/index.ts b/packages/plugin-b2/src/types/index.ts new file mode 100644 index 00000000000..97a6ac06590 --- /dev/null +++ b/packages/plugin-b2/src/types/index.ts @@ -0,0 +1,30 @@ +import type { Token } from "@lifi/types"; +import type { + Address, + Hash, +} from "viem"; + +export interface Transaction { + hash: Hash; + from: Address; + tokenAddress: string; + recipient: string; + amount: string | number; +} + +export interface TransferParams { + tokenAddress: string; + recipient: string; + amount: string | number; +} + +export interface StakeParams { + amount: string | number; +} + +export interface UnstakeParams { + amount: string | number; +} + +export interface WithdrawParams { +} diff --git a/packages/plugin-b2/src/utils/chains.ts b/packages/plugin-b2/src/utils/chains.ts new file mode 100644 index 00000000000..0eec893a835 --- /dev/null +++ b/packages/plugin-b2/src/utils/chains.ts @@ -0,0 +1,26 @@ +import { defineChain } from 'viem' + +export const b2Network = defineChain({ + id: 223, + name: 'B2Network', + network: 'B2Network', + nativeCurrency: { + decimals: 18, + name: 'Bitcoin', + symbol: 'BTC', + }, + blockExplorers: { + default: { + name: 'B2Network', + url: 'https://explorer.bsquared.network/' + } + }, + rpcUrls: { + default: { + http: ['https://rpc.bsquared.network/'], + }, + public: { + http: ['https://rpc.bsquared.network/'], + }, + }, +}) \ No newline at end of file diff --git a/packages/plugin-b2/src/utils/constants.ts b/packages/plugin-b2/src/utils/constants.ts new file mode 100644 index 00000000000..23fec41ee6e --- /dev/null +++ b/packages/plugin-b2/src/utils/constants.ts @@ -0,0 +1,15 @@ +import { Address } from "viem"; + +const TOKEN_ADDRESSES: Record = { + "B2-BTC": "0x0000000000000000000000000000000000000000", + uBTC: "0x796e4D53067FF374B89b2Ac101ce0c1f72ccaAc2", + USDC: "0xE544e8a38aDD9B1ABF21922090445Ba93f74B9E5", + USDT: "0x681202351a488040Fa4FdCc24188AfB582c9DD62", +}; + +const FARM_ADDRESS: Address = "0xd5B5f1CA0fa5636ac54b0a0007BA374A1513346e"; + +export { + TOKEN_ADDRESSES, + FARM_ADDRESS, +}; diff --git a/packages/plugin-b2/src/utils/index.ts b/packages/plugin-b2/src/utils/index.ts new file mode 100644 index 00000000000..e4a1666c3cf --- /dev/null +++ b/packages/plugin-b2/src/utils/index.ts @@ -0,0 +1,285 @@ +import { IAgentRuntime, elizaLogger } from "@elizaos/core"; +import { + Hash, + Address, + parseUnits, + encodeFunctionData, + SendTransactionParameters, +} from "viem"; +import { b2Network } from "./chains"; +import { WalletProvider } from "../providers"; +import { TOKEN_ADDRESSES } from "./constants"; + +export const getTxReceipt = async (walletProvider: WalletProvider, tx: Hash) => { + const publicClient = walletProvider.getPublicClient(); + const receipt = await publicClient.waitForTransactionReceipt({ + hash: tx, + }); + return receipt; +}; + +export const sendNativeAsset = async ( + walletProvider: WalletProvider, + recipient: Address, + amount: number +) => { + const decimals = await walletProvider.getDecimals(TOKEN_ADDRESSES["B2-BTC"]); + const walletClient = walletProvider.getWalletClient(); + + const args = { + to: recipient, + value: parseUnits(amount.toString(), decimals), + }; + const tx = await walletClient.sendTransaction(args); + return tx as Hash; +}; + +export const sendToken = async ( + walletProvider: WalletProvider, + tokenAddress: Address, + recipient: Address, + amount: number +) => { + const decimals = await walletProvider.getDecimals(tokenAddress); + const publicClient = walletProvider.getPublicClient(); + try { + const { result, request } = await publicClient.simulateContract({ + account: walletProvider.getAccount(), + address: tokenAddress, + abi: [ + { + inputs: [ + { + internalType: "address", + name: "dst", + type: "address", + }, + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + ], + name: "transfer", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + ], + functionName: "transfer", + args: [recipient, parseUnits(amount.toString(), decimals)], + }); + + if (!result) { + throw new Error("Transfer failed"); + } + + elizaLogger.debug("Request:", request); + const walletClient = walletProvider.getWalletClient(); + const tx = await walletClient.writeContract(request); + elizaLogger.debug("Transaction:", tx); + return tx as Hash; + } catch (error) { + elizaLogger.error("Error simulating contract:", error); + return; + } +}; + +export const approve = async ( + walletProvider: WalletProvider, + tokenAddress: Address, + spender: Address, + amount: number +) => { + try { + const decimals = await walletProvider.getDecimals(tokenAddress); + const publicClient = walletProvider.getPublicClient(); + const { result, request } = await publicClient.simulateContract({ + account: walletProvider.getAccount(), + address: tokenAddress, + abi: [ + { + inputs: [ + { + internalType: "address", + name: "_spender", + type: "address", + }, + { + internalType: "uint256", + name: "_value", + type: "uint256", + }, + ], + name: "approve", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + ], + functionName: "approve", + args: [spender, parseUnits(amount.toString(), decimals)], + }); + + if (!result) { + throw new Error("Approve failed"); + } + elizaLogger.debug("Request:", request); + const walletClient = walletProvider.getWalletClient(); + const tx = await walletClient.writeContract(request); + elizaLogger.debug("Transaction:", tx); + return tx; + } catch (error) { + elizaLogger.error("Error approving:", error); + return; + } +}; + +export const depositBTC = async ( + walletProvider: WalletProvider, + farmAddress: Address, + amount: string | number +) => { + try { + const decimals = b2Network.nativeCurrency.decimals; + // const publicClient = walletProvider.getPublicClient(); + + const walletClient = walletProvider.getWalletClient(); + const data = encodeFunctionData({ + abi: [ + { + "inputs": [ + + ], + "name": "depositBTC", + "outputs": [ + + ], + "stateMutability": "payable", + "type": "function" + }, + ], + functionName: 'depositBTC', + args: [], + }); + + const args = { + account: walletProvider.getAddress(), + to: farmAddress, + data, + value: parseUnits(amount.toString(), decimals), + }; + const txHash = await walletClient.sendTransaction(args); + + elizaLogger.debug("Transaction hash:", txHash); + return txHash; + } catch (error) { + elizaLogger.error("Error depositBTC:", error); + return; + } +}; + +// function unstake(uint256 _pid, uint256 _amount) public {} +export const unstake = async ( + walletProvider: WalletProvider, + farmAddress: Address, + amount: string | number +) => { + try { + const BTC_PID = 0; + const decimals = b2Network.nativeCurrency.decimals; + const publicClient = walletProvider.getPublicClient(); + const { _result, request } = await publicClient.simulateContract({ + account: walletProvider.getAccount(), + address: farmAddress, + abi: [ + { + "inputs": [ + { + "internalType": "uint256", + "name": "_pid", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "unstake", + "outputs": [ + + ], + "stateMutability": "nonpayable", + "type": "function" + }, + ], + functionName: "unstake", + args: [BigInt(BTC_PID), parseUnits(amount.toString(), decimals)], + }); + elizaLogger.debug("Request:", request); + + const walletClient = walletProvider.getWalletClient(); + const tx = await walletClient.writeContract(request); + elizaLogger.debug("Transaction:", tx); + return tx; + } catch (error) { + elizaLogger.error("Error unstake:", error); + return; + } +}; + +// function withdraw(uint256 _pid) public {} +export const withdraw = async ( + walletProvider: WalletProvider, + farmAddress: Address, +) => { + try { + const BTC_PID = 0; + const publicClient = walletProvider.getPublicClient(); + const { _result, request } = await publicClient.simulateContract({ + account: walletProvider.getAccount(), + address: farmAddress, + abi: [ + { + "inputs": [ + { + "internalType": "uint256", + "name": "_pid", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [ + + ], + "stateMutability": "nonpayable", + "type": "function" + }, + ], + functionName: "withdraw", + args: [BigInt(BTC_PID)], + }); + elizaLogger.debug("Request:", request); + + const walletClient = walletProvider.getWalletClient(); + const tx = await walletClient.writeContract(request); + elizaLogger.debug("Transaction:", tx); + return tx; + } catch (error) { + elizaLogger.error("Error withdraw:", error); + return; + } +}; \ No newline at end of file diff --git a/packages/plugin-b2/tsconfig.json b/packages/plugin-b2/tsconfig.json new file mode 100644 index 00000000000..e9c2e9f8527 --- /dev/null +++ b/packages/plugin-b2/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../core/tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "types": ["node"] + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/plugin-b2/tsup.config.ts b/packages/plugin-b2/tsup.config.ts new file mode 100644 index 00000000000..1a96f24afa1 --- /dev/null +++ b/packages/plugin-b2/tsup.config.ts @@ -0,0 +1,21 @@ +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: [ + "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", + // Add other modules you want to externalize + ], +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eeaeb37fbe2..39593d21ab4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1491,6 +1491,18 @@ importers: specifier: 8.3.5 version: 8.3.5(@swc/core@1.10.7(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.5.0)(tsx@4.19.2)(typescript@5.7.3)(yaml@2.7.0) + packages/plugin-b2: + dependencies: + '@elizaos/core': + specifier: workspace:* + version: link:../core + tsup: + specifier: 8.3.5 + version: 8.3.5(@swc/core@1.10.7(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.5.0)(tsx@4.19.2)(typescript@5.7.3)(yaml@2.7.0) + whatwg-url: + specifier: 7.1.0 + version: 7.1.0 + packages/plugin-binance: dependencies: '@binance/connector':