diff --git a/.env.example b/.env.example index 26b83206a0f..e227b3768ce 100644 --- a/.env.example +++ b/.env.example @@ -495,6 +495,12 @@ STARGAZE_ENDPOINT= # GenLayer GENLAYER_PRIVATE_KEY= # Private key of the GenLayer account to use for the agent in this format (0x0000000000000000000000000000000000000000000000000000000000000000) +# BNB chain +BNB_PRIVATE_KEY= # BNB chain private key +BNB_PUBLIC_KEY= # BNB-smart-chain public key (address) +BSC_PROVIDER_URL= # BNB-smart-chain rpc url +OPBNB_PROVIDER_URL= # OPBNB rpc url + #################################### #### Misc Plugin Configurations #### #################################### diff --git a/.gitignore b/.gitignore index 7c6c92eb7b9..86be41efaf2 100644 --- a/.gitignore +++ b/.gitignore @@ -60,4 +60,4 @@ agent/content eliza.manifest eliza.manifest.sgx -eliza.sig \ No newline at end of file +eliza.sig diff --git a/agent/package.json b/agent/package.json index 44c42c84a9b..bf0ec127329 100644 --- a/agent/package.json +++ b/agent/package.json @@ -41,6 +41,7 @@ "@elizaos/plugin-coinmarketcap": "workspace:*", "@elizaos/plugin-binance": "workspace:*", "@elizaos/plugin-avail": "workspace:*", + "@elizaos/plugin-bnb": "workspace:*", "@elizaos/plugin-bootstrap": "workspace:*", "@elizaos/plugin-cosmos": "workspace:*", "@elizaos/plugin-intiface": "workspace:*", diff --git a/agent/src/index.ts b/agent/src/index.ts index 5e4c817efec..03fa4e700a4 100644 --- a/agent/src/index.ts +++ b/agent/src/index.ts @@ -53,6 +53,7 @@ import { avalanchePlugin } from "@elizaos/plugin-avalanche"; import { b2Plugin } from "@elizaos/plugin-b2"; import { binancePlugin } from "@elizaos/plugin-binance"; import { birdeyePlugin } from "@elizaos/plugin-birdeye"; +import { bnbPlugin } from "@elizaos/plugin-bnb"; import { advancedTradePlugin, coinbaseCommercePlugin, @@ -940,6 +941,11 @@ export async function createAgent( getSecret(character, "RESERVOIR_API_KEY") ? createNFTCollectionsPlugin() : null, + getSecret(character, "BNB_PRIVATE_KEY") || + (getSecret(character, "BNB_PUBLIC_KEY") && + getSecret(character, "BNB_PUBLIC_KEY")?.startsWith("0x")) + ? bnbPlugin + : null, ].filter(Boolean), providers: [], actions: [], diff --git a/packages/plugin-bnb/README.md b/packages/plugin-bnb/README.md new file mode 100644 index 00000000000..e73e828655f --- /dev/null +++ b/packages/plugin-bnb/README.md @@ -0,0 +1,141 @@ +# `@ai16z/plugin-bnb` + +This plugin enables interaction with the BNB Chain ecosystem, providing support for BNB Smart Chain, opBNB, and BNB Greenfield networks. + +--- + +## Configuration + +### Default Setup + +By default, **plugin-bnb** is not enabled. To use it, simply add your private key and public key to the `.env` file: + +```env +BNB_PRIVATE_KEY=your-private-key-here +BNB_PUBLIC_KEY=your-public-key-here +``` + +### Custom RPC URLs + +By default, the RPC URL is inferred from the `viem/chains` config. To use custom RPC URLs, add the following to your `.env` file: + +```env +BSC_PROVIDER_URL=https://your-custom-bsc-rpc-url +OPBNB_PROVIDER_URL=https://your-custom-opbnb-rpc-url +``` + +## Provider + +The **Wallet Provider** initializes with BSC as the default. It: + +- Provides the **context** of the currently connected address and its balance. +- Creates **Public** and **Wallet clients** to interact with the supported chains. + +--- + +## Actions + +### Get Balance + +Get the balance of an address on BSC. Just specify the: + +- **Chain** +- **Address** +- **Token** + +**Example usage:** + +```bash +Get the USDC balance of 0x1234567890 on BSC. +``` + +### Transfer + +Transfer tokens from one address to another on BSC/opBNB. Just specify the: + +- **Chain** +- **Token** +- **Amount** +- **Recipient Address** +- **Data**(Optional) + +**Example usage:** + +```bash +Transfer 1 BNB to 0xRecipient on BSC. +``` + +### Swap + +Swap tokens from one address to another on BSC. Just specify the: + +- **Input Token** +- **Output Token** +- **Amount** +- **Chain** +- **Slippage**(Optional) + +**Example usage:** + +```bash +Swap 1 BNB to USDC on BSC. +``` + +### Bridge + +Bridge tokens from one chain to another on BSC/opBNB. Just specify the: + +- **From Chain** +- **To Chain** +- **From Token** +- **To Token** +- **Amount** +- **Recipient Address**(Optional) + +**Example usage:** + +```bash +Bridge 1 BNB from BSC to opBNB. +``` + +### Stake + +Perform staking operations on BSC through [Lista Dao](https://lista.org/liquid-staking/BNB). User will receive sliBNB(0xB0b84D294e0C75A6abe60171b70edEb2EFd14A1B) as staking credit. Just specify the: + +- **Action** +- **Amount** + +**Example usage:** + +```bash +Deposit 1 BNB to Lista Dao. +``` + +### Faucet + +Request testnet tokens from the faucet. You could request any of the supported tokens(BNB, BTC, BUSD, DAI, ETH, USDC). Just specify the: + +- **Token**(Optional) +- **Recipient Address** + +The recipient address must maintain a minimum balance of 0.002 BNB on BSC Mainnet to qualify. + +**Example usage:** + +```bash +Get some testnet USDC from the faucet. +``` + +--- + +## Contribution + +The plugin contains tests. Whether you're using **TDD** or not, please make sure to run the tests before submitting a PR. + +### Running Tests + +Navigate to the `plugin-bnb` directory and run: + +```bash +pnpm test +``` diff --git a/packages/plugin-bnb/package.json b/packages/plugin-bnb/package.json new file mode 100644 index 00000000000..9906cc24764 --- /dev/null +++ b/packages/plugin-bnb/package.json @@ -0,0 +1,29 @@ +{ + "name": "@elizaos/plugin-bnb", + "version": "0.1.0-alpha.1", + "main": "dist/index.js", + "type": "module", + "types": "dist/index.d.ts", + "dependencies": { + "@elizaos/core": "workspace:*", + "@lifi/data-types": "5.15.5", + "@lifi/sdk": "3.4.1", + "@lifi/types": "16.3.0", + "@web3-name-sdk/core": "^0.3.2", + "@openzeppelin/contracts": "^5.1.0", + "@types/node": "^22.10.5", + "solc": "^0.8.28", + "tsup": "8.3.5", + "viem": "2.21.53", + "ws": "^8.18.0" + }, + "scripts": { + "build": "tsup --format esm --dts", + "dev": "tsup --format esm --dts --watch", + "test": "vitest run", + "lint": "eslint --fix --cache ." + }, + "peerDependencies": { + "whatwg-url": "7.1.0" + } +} diff --git a/packages/plugin-bnb/src/actions/bridge.ts b/packages/plugin-bnb/src/actions/bridge.ts new file mode 100644 index 00000000000..16e5c940329 --- /dev/null +++ b/packages/plugin-bnb/src/actions/bridge.ts @@ -0,0 +1,378 @@ +import { + composeContext, + elizaLogger, + generateObjectDeprecated, + HandlerCallback, + ModelClass, + type IAgentRuntime, + type Memory, + type State, +} from "@elizaos/core"; +import { Hex, parseEther, getContract, Address, parseUnits } from "viem"; + +import { initWalletProvider, WalletProvider } from "../providers/wallet"; +import { bridgeTemplate } from "../templates"; +import { + ERC20Abi, + L1StandardBridgeAbi, + L2StandardBridgeAbi, + type BridgeParams, + type BridgeResponse, + type SupportedChain, +} from "../types"; + +export { bridgeTemplate }; + +// Exported for tests +export class BridgeAction { + private readonly L1_BRIDGE_ADDRESS = + "0xF05F0e4362859c3331Cb9395CBC201E3Fa6757Ea" as const; + private readonly L2_BRIDGE_ADDRESS = + "0x4000698e3De52120DE28181BaACda82B21568416" as const; + private readonly LEGACY_ERC20_ETH = + "0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000" as const; + + constructor(private walletProvider: WalletProvider) {} + + async bridge(params: BridgeParams): Promise { + this.validateBridgeParams(params); + + const fromAddress = this.walletProvider.getAddress(); + + this.walletProvider.switchChain(params.fromChain); + const walletClient = this.walletProvider.getWalletClient( + params.fromChain + ); + const publicClient = this.walletProvider.getPublicClient( + params.fromChain + ); + + try { + const nativeToken = + this.walletProvider.chains[params.fromChain].nativeCurrency + .symbol; + + let resp: BridgeResponse = { + fromChain: params.fromChain, + toChain: params.toChain, + txHash: "0x", + recipient: params.toAddress ?? fromAddress, + amount: params.amount, + fromToken: params.fromToken ?? nativeToken, + toToken: params.toToken ?? nativeToken, + }; + + const account = walletClient.account!; + const chain = this.walletProvider.getChainConfigs(params.fromChain); + + const selfBridge = + !params.toAddress || params.toAddress == fromAddress; + const nativeTokenBridge = + !params.fromToken || params.fromToken == nativeToken; + + let amount: bigint; + if (nativeTokenBridge) { + amount = parseEther(params.amount); + } else { + const decimals = await publicClient.readContract({ + address: params.fromToken!, + abi: ERC20Abi, + functionName: "decimals", + }); + amount = parseUnits(params.amount, decimals); + } + + let hash: Hex; + if (params.fromChain == "bsc" && params.toChain == "opBNB") { + // from L1 to L2 + const l1BridgeContract = getContract({ + address: this.L1_BRIDGE_ADDRESS, + abi: L1StandardBridgeAbi, + client: { + public: publicClient, + wallet: walletClient, + }, + }); + + // check ERC20 allowance + if (!nativeTokenBridge) { + this.checkTokenAllowance( + params.fromChain, + params.fromToken!, + fromAddress, + this.L1_BRIDGE_ADDRESS, + amount + ); + } + + if (selfBridge && nativeTokenBridge) { + const args = [1, "0x"] as const; + await l1BridgeContract.simulate.depositETH(args); + hash = await l1BridgeContract.write.depositETH(args, { + account, + chain, + amount, + }); + } else if (selfBridge && !nativeTokenBridge) { + const args = [ + params.fromToken!, + params.toToken!, + amount, + 1, + "0x", + ] as const; + await l1BridgeContract.simulate.depositERC20(args); + hash = await l1BridgeContract.write.depositERC20(args, { + account, + chain, + }); + } else if (!selfBridge && nativeTokenBridge) { + const args = [params.toAddress!, 1, "0x"] as const; + await l1BridgeContract.simulate.depositETHTo(args); + hash = await l1BridgeContract.write.depositETHTo(args, { + account, + chain, + amount, + }); + } else { + const args = [ + params.fromToken!, + params.toToken!, + params.toAddress!, + amount, + 1, + "0x", + ] as const; + await l1BridgeContract.simulate.depositERC20To(args); + hash = await l1BridgeContract.write.depositERC20To(args, { + account, + chain, + }); + } + } else if (params.fromChain == "opBNB" && params.toChain == "bsc") { + // from L2 to L1 + const l2BridgeContract = getContract({ + address: this.L2_BRIDGE_ADDRESS, + abi: L2StandardBridgeAbi, + client: { + public: publicClient, + wallet: walletClient, + }, + }); + + const delegationFee = await publicClient.readContract({ + address: this.L2_BRIDGE_ADDRESS, + abi: L2StandardBridgeAbi, + functionName: "delegationFee", + }); + + // check ERC20 allowance + if (!nativeTokenBridge) { + this.checkTokenAllowance( + params.fromChain, + params.fromToken!, + fromAddress, + this.L2_BRIDGE_ADDRESS, + amount + ); + } + + if (selfBridge && nativeTokenBridge) { + const args = [ + this.LEGACY_ERC20_ETH, + amount, + 1, + "0x", + ] as const; + const value = amount + delegationFee; + await l2BridgeContract.simulate.withdraw(args, { value }); + hash = await l2BridgeContract.write.withdraw(args, { + account, + chain, + value, + }); + } else if (selfBridge && !nativeTokenBridge) { + const args = [params.fromToken!, amount, 1, "0x"] as const; + const value = delegationFee; + await l2BridgeContract.simulate.withdraw(args, { value }); + hash = await l2BridgeContract.write.withdraw(args, { + account, + chain, + value, + }); + } else if (!selfBridge && nativeTokenBridge) { + const args = [ + this.LEGACY_ERC20_ETH, + params.toAddress!, + amount, + 1, + "0x", + ] as const; + const value = amount + delegationFee; + await l2BridgeContract.simulate.withdrawTo(args, { value }); + hash = await l2BridgeContract.write.withdrawTo(args, { + account, + chain, + value, + }); + } else { + const args = [ + params.fromToken!, + params.toAddress!, + amount, + 1, + "0x", + ] as const; + const value = delegationFee; + await l2BridgeContract.simulate.withdrawTo(args, { value }); + hash = await l2BridgeContract.write.withdrawTo(args, { + account, + chain, + value, + }); + } + + resp.txHash = hash; + } else { + throw new Error("Unsupported bridge direction"); + } + + return resp; + } catch (error) { + throw new Error(`Bridge failed: ${error.message}`); + } + } + + validateBridgeParams(params: BridgeParams) { + // Both tokens should be either null or both provided + if ((params.fromToken === null) !== (params.toToken === null)) { + throw new Error( + "fromToken and toToken must be either both null or both provided" + ); + } + } + + async checkTokenAllowance( + chain: SupportedChain, + token: Address, + owner: Address, + spender: Address, + amount: bigint + ) { + const publicClient = this.walletProvider.getPublicClient(chain); + const allowance = await publicClient.readContract({ + address: token, + abi: ERC20Abi, + functionName: "allowance", + args: [owner, spender], + }); + + if (allowance < amount) { + elizaLogger.log("Increasing allowance for ERC20 bridge"); + const walletClient = this.walletProvider.getWalletClient(chain); + const { request } = await publicClient.simulateContract({ + account: walletClient.account, + address: token, + abi: ERC20Abi, + functionName: "increaseAllowance", + args: [spender, amount - allowance], + }); + + await walletClient.writeContract(request); + } + } +} + +// NOTE: The bridge action only supports bridge funds between BSC and opBNB for now. We may adding stargate support later. +export const bridgeAction = { + name: "bridge", + description: "Bridge tokens between BSC and opBNB", + handler: async ( + runtime: IAgentRuntime, + message: Memory, + state: State, + _options: any, + callback?: HandlerCallback + ) => { + elizaLogger.log("Starting bridge action..."); + + // Initialize or update state + if (!state) { + state = (await runtime.composeState(message)) as State; + } else { + state = await runtime.updateRecentMessageState(state); + } + + // Compose bridge context + const bridgeContext = composeContext({ + state, + template: bridgeTemplate, + }); + const content = await generateObjectDeprecated({ + runtime, + context: bridgeContext, + modelClass: ModelClass.LARGE, + }); + + const walletProvider = initWalletProvider(runtime); + const action = new BridgeAction(walletProvider); + const paramOptions: BridgeParams = { + fromChain: content.fromChain, + toChain: content.toChain, + fromToken: content.fromToken, + toToken: content.toToken, + amount: content.amount, + toAddress: await walletProvider.formatAddress(content.toAddress), + }; + try { + const bridgeResp = await action.bridge(paramOptions); + callback?.({ + text: `Successfully bridged ${bridgeResp.amount} ${bridgeResp.fromToken} from ${bridgeResp.fromChain} to ${bridgeResp.toChain}\nTransaction Hash: ${bridgeResp.txHash}`, + content: { ...bridgeResp }, + }); + return true; + } catch (error) { + elizaLogger.error("Error during token bridge:", error); + callback?.({ + text: `Bridge failed`, + content: { error: error.message }, + }); + return false; + } + }, + template: bridgeTemplate, + validate: async (runtime: IAgentRuntime) => { + const privateKey = runtime.getSetting("BNB_PRIVATE_KEY"); + return typeof privateKey === "string" && privateKey.startsWith("0x"); + }, + examples: [ + [ + { + user: "user", + content: { + text: "Transfer 1 BNB from BSC to opBNB", + action: "BRIDGE", + }, + }, + ], + [ + { + user: "user", + content: { + text: "Deposit 1 BNB from BSC to opBNB", + action: "DEPOSIT", + }, + }, + ], + [ + { + user: "user", + content: { + text: "Withdraw 1 BNB from opBNB to BSC", + action: "WITHDRAW", + }, + }, + ], + ], + similes: ["BRIDGE", "TOKEN_BRIDGE", "DEPOSIT", "WITHDRAW"], +}; diff --git a/packages/plugin-bnb/src/actions/deploy.ts b/packages/plugin-bnb/src/actions/deploy.ts new file mode 100644 index 00000000000..df773b19c11 --- /dev/null +++ b/packages/plugin-bnb/src/actions/deploy.ts @@ -0,0 +1,373 @@ +import { + composeContext, + elizaLogger, + generateObjectDeprecated, + HandlerCallback, + ModelClass, + type IAgentRuntime, + type Memory, + type State, +} from "@elizaos/core"; +import solc from "solc"; +import { Abi, formatEther, formatUnits, parseUnits } from "viem"; +import { initWalletProvider, WalletProvider } from "../providers/wallet"; +import { ercContractTemplate } from "../templates"; +import { + IDeployERC1155Params, + IDeployERC721Params, + IDeployERC20Params, +} from "../types"; +import { compileSolidity } from "../utils/contracts"; + +export { ercContractTemplate }; + +export class DeployAction { + constructor(private walletProvider: WalletProvider) {} + + async compileSolidity(contractName: string, source: string) { + const solName = `${contractName}.sol`; + const input = { + language: "Solidity", + sources: { + [solName]: { + content: source, + }, + }, + settings: { + outputSelection: { + "*": { + "*": ["*"], + }, + }, + }, + }; + elizaLogger.log("Compiling contract..."); + const output = JSON.parse(solc.compile(JSON.stringify(input))); + + // check compile error + if (output.errors) { + const hasError = output.errors.some( + (error) => error.type === "Error" + ); + if (hasError) { + elizaLogger.error( + `Compilation errors: ${JSON.stringify(output.errors, null, 2)}` + ); + } + } + + const contract = output.contracts[solName][contractName]; + + if (!contract) { + elizaLogger.error("Compilation result is empty"); + } + + elizaLogger.log("Contract compiled successfully"); + return { + abi: contract.abi as Abi, + bytecode: contract.evm.bytecode.object, + }; + } + + async deployERC20(deployTokenParams: IDeployERC20Params) { + elizaLogger.log("deployTokenParams", deployTokenParams); + const { name, symbol, decimals, totalSupply, chain } = + deployTokenParams; + + this.walletProvider.switchChain(chain); + + const chainConfig = this.walletProvider.getChainConfigs(chain); + const publicClient = this.walletProvider.getPublicClient(chain); + const walletClient = this.walletProvider.getWalletClient(chain); + + try { + const { abi, bytecode } = await compileSolidity("ERC20Contract"); + + if (!bytecode) { + throw new Error("Bytecode is empty after compilation"); + } + + // check wallet balance + const balance = await publicClient.getBalance({ + address: this.walletProvider.getAddress(), + }); + elizaLogger.log(`Wallet balance: ${formatEther(balance)} BNB`); + + if (balance === 0n) { + elizaLogger.error("Wallet has no BNB for gas fees"); + } + + const totalSupplyWithDecimals = parseUnits( + totalSupply.toString(), + decimals + ); + const hash = await walletClient.deployContract({ + account: this.walletProvider.getAccount(), + abi, + bytecode, + args: [name, symbol, decimals, totalSupplyWithDecimals], + chain: chainConfig, + }); + + elizaLogger.log("Waiting for deployment transaction...", hash); + const receipt = await publicClient.waitForTransactionReceipt({ + hash, + }); + const contractAddress = receipt.contractAddress; + + elizaLogger.log("Contract deployed successfully!"); + elizaLogger.log("Contract address:", contractAddress); + + elizaLogger.log("\nToken Information:"); + elizaLogger.log("================="); + elizaLogger.log(`Name: ${name}`); + elizaLogger.log(`Symbol: ${symbol}`); + elizaLogger.log(`Decimals: ${decimals}`); + elizaLogger.log( + `Total Supply: ${formatUnits(totalSupplyWithDecimals, decimals)}` + ); + + elizaLogger.log( + `View on BSCScan: https://testnet.bscscan.com/address/${contractAddress}` + ); + + return { + address: contractAddress, + }; + } catch (error) { + elizaLogger.error("Detailed error:", error); + throw error; + } + } + + async deployERC721(deployNftParams: IDeployERC721Params) { + elizaLogger.log("deployNftParams", deployNftParams); + const { baseURI, name, symbol, chain } = deployNftParams; + + this.walletProvider.switchChain(chain); + + const chainConfig = this.walletProvider.getChainConfigs(chain); + const publicClient = this.walletProvider.getPublicClient(chain); + const walletClient = this.walletProvider.getWalletClient(chain); + + try { + const { abi, bytecode } = await compileSolidity("ERC721Contract"); + if (!bytecode) { + throw new Error("Bytecode is empty after compilation"); + } + + // check wallet balance + const balance = await publicClient.getBalance({ + address: this.walletProvider.getAddress(), + }); + elizaLogger.log(`Wallet balance: ${formatEther(balance)} BNB`); + + if (balance === 0n) { + elizaLogger.error("Wallet has no BNB for gas fees"); + } + + const hash = await walletClient.deployContract({ + account: this.walletProvider.getAccount(), + abi, + bytecode, + args: [name, symbol, baseURI], + chain: chainConfig, + }); + + elizaLogger.log("Waiting for deployment transaction...", hash); + const receipt = await publicClient.waitForTransactionReceipt({ + hash, + }); + + const contractAddress = receipt.contractAddress; + + elizaLogger.log("Contract deployed successfully!"); + elizaLogger.log("Contract address:", contractAddress); + + return { + address: contractAddress, + }; + } catch (error) { + elizaLogger.error("Deployment failed:", error); + throw error; + } + } + + async deployERC1155(deploy1155Params: IDeployERC1155Params) { + const { baseURI, name, chain } = deploy1155Params; + + this.walletProvider.switchChain(chain); + + const chainConfig = this.walletProvider.getChainConfigs(chain); + const publicClient = this.walletProvider.getPublicClient(chain); + const walletClient = this.walletProvider.getWalletClient(chain); + + try { + const { bytecode, abi } = await compileSolidity("ERC1155Contract"); + + if (!bytecode) { + throw new Error("Bytecode is empty after compilation"); + } + // check wallet balance + const balance = await publicClient.getBalance({ + address: this.walletProvider.getAddress(), + }); + elizaLogger.log(`Wallet balance: ${formatEther(balance)} BNB`); + + if (balance === 0n) { + elizaLogger.error("Wallet has no BNB for gas fees"); + } + + const hash = await walletClient.deployContract({ + account: this.walletProvider.getAccount(), + abi, + bytecode, + args: [name, baseURI], + chain: chainConfig, + }); + + elizaLogger.log("Waiting for deployment transaction...", hash); + const receipt = await publicClient.waitForTransactionReceipt({ + hash, + }); + const contractAddress = receipt.contractAddress; + elizaLogger.log("Contract deployed successfully!"); + elizaLogger.log("Contract address:", contractAddress); + + return { + address: contractAddress, + name: name, + baseURI: baseURI, + }; + } catch (error) { + elizaLogger.error("Deployment failed:", error); + throw error; + } + } +} + +export const deployAction = { + name: "DEPLOY_TOKEN", + description: + "Deploy token contracts (ERC20/721/1155) based on user specifications", + handler: async ( + runtime: IAgentRuntime, + message: Memory, + state: State, + _options: any, + callback?: HandlerCallback + ) => { + elizaLogger.log("Starting deploy action..."); + + // Initialize or update state + if (!state) { + state = (await runtime.composeState(message)) as State; + } else { + state = await runtime.updateRecentMessageState(state); + } + + // Compose context + const context = composeContext({ + state, + template: ercContractTemplate, + }); + const content = await generateObjectDeprecated({ + runtime, + context: context, + modelClass: ModelClass.LARGE, + }); + + elizaLogger.log("content", content); + + const walletProvider = initWalletProvider(runtime); + const action = new DeployAction(walletProvider); + + const contractType = content.contractType; + let result; + switch (contractType.toLocaleLowerCase()) { + case "erc20": + result = await action.deployERC20({ + chain: content.chain, + decimals: content.decimals, + symbol: content.symbol, + name: content.name, + totalSupply: content.totalSupply, + }); + break; + case "erc721": + result = await action.deployERC721({ + chain: content.chain, + name: content.name, + symbol: content.symbol, + baseURI: content.baseURI, + }); + break; + case "erc1155": + result = await action.deployERC1155({ + chain: content.chain, + name: content.name, + baseURI: content.baseURI, + }); + break; + } + + elizaLogger.log("result: ", result); + if (result) { + callback?.({ + text: `Successfully create contract - ${result?.address}`, + content: { ...result }, + }); + } else { + callback?.({ + text: `Unsuccessfully create contract`, + content: { ...result }, + }); + } + + try { + return true; + } catch (error) { + elizaLogger.error("Error in get balance:", error.message); + callback?.({ + text: `Getting balance failed`, + content: { error: error.message }, + }); + return false; + } + }, + template: ercContractTemplate, + validate: async (_runtime: IAgentRuntime) => { + return true; + }, + examples: [ + [ + { + user: "user", + content: { + text: "deploy an ERC20 token", + action: "DEPLOY_TOKEN", + }, + }, + { + user: "user", + content: { + text: "Deploy an ERC721 NFT contract", + action: "DEPLOY_TOKEN", + }, + }, + { + user: "user", + content: { + text: "Deploy an ERC1155 contract", + action: "DEPLOY_TOKEN", + }, + }, + ], + ], + similes: [ + "DEPLOY_ERC20", + "DEPLOY_ERC721", + "DEPLOY_ERC1155", + "CREATE_TOKEN", + ], +}; diff --git a/packages/plugin-bnb/src/actions/faucet.ts b/packages/plugin-bnb/src/actions/faucet.ts new file mode 100644 index 00000000000..88657f6ef0f --- /dev/null +++ b/packages/plugin-bnb/src/actions/faucet.ts @@ -0,0 +1,172 @@ +import { + composeContext, + elizaLogger, + generateObjectDeprecated, + HandlerCallback, + ModelClass, + type IAgentRuntime, + type Memory, + type State, +} from "@elizaos/core"; +import { type Hex } from "viem"; +import WebSocket, { ClientOptions } from "ws"; + +import { faucetTemplate } from "../templates"; +import { type FaucetParams } from "../types"; +import { initWalletProvider } from "../providers/wallet"; + +export { faucetTemplate }; + +// Exported for tests +export class FaucetAction { + private readonly SUPPORTED_TOKENS: string[] = [ + "BNB", + "BTC", + "BUSD", + "DAI", + "ETH", + "USDC", + ] as const; + private readonly FAUCET_URL = "wss://testnet.bnbchain.org/faucet-smart/api"; + constructor() {} + + async faucet(params: FaucetParams): Promise { + this.validateParams(params); + + return new Promise((resolve, reject) => { + const options: ClientOptions = { + headers: { + Connection: "Upgrade", + Upgrade: "websocket", + }, + }; + const ws = new WebSocket(this.FAUCET_URL, options); + + ws.onopen = () => { + const message = { + tier: 0, + url: params.toAddress, + symbol: params.token || "BNB", + captcha: "noCaptchaToken", + }; + ws.send(JSON.stringify(message)); + }; + + ws.onmessage = (event: WebSocket.MessageEvent) => { + const response = JSON.parse(event.data.toString()); + + // First response: funding request accepted + if (response.success) { + return; // Wait for the next message + } + + // Second response: transaction details + if (response.requests && response.requests.length > 0) { + const txHash = response.requests[0].tx.hash; + if (txHash) { + resolve(txHash as Hex); + ws.close(); + return; + } + } + + // Handle error case + if (response.error) { + reject(new Error(response.error)); + ws.close(); + } + }; + + ws.onerror = (error: WebSocket.ErrorEvent) => { + reject(new Error(`WebSocket error occurred: ${error.message}`)); + }; + + // Add timeout to prevent hanging + setTimeout(() => { + ws.close(); + reject(new Error("Faucet request timeout")); + }, 15000); // 15 seconds timeout + }); + } + + validateParams(params: FaucetParams): void { + if (params.token && !this.SUPPORTED_TOKENS.includes(params.token!)) { + throw new Error("Invalid token"); + } + } +} + +export const faucetAction = { + name: "faucet", + description: "Get test tokens from the faucet", + handler: async ( + runtime: IAgentRuntime, + message: Memory, + state: State, + _options: any, + callback?: HandlerCallback + ) => { + elizaLogger.log("Starting faucet action..."); + + // Initialize or update state + if (!state) { + state = (await runtime.composeState(message)) as State; + } else { + state = await runtime.updateRecentMessageState(state); + } + + // Compose faucet context + const faucetContext = composeContext({ + state, + template: faucetTemplate, + }); + const content = await generateObjectDeprecated({ + runtime, + context: faucetContext, + modelClass: ModelClass.LARGE, + }); + + const walletProvider = initWalletProvider(runtime); + const action = new FaucetAction(); + const paramOptions: FaucetParams = { + token: content.token, + toAddress: await walletProvider.formatAddress(content.toAddress), + }; + try { + const faucetResp = await action.faucet(paramOptions); + callback?.({ + text: `Successfully transferred ${paramOptions.token} to ${paramOptions.toAddress}\nTransaction Hash: ${faucetResp}`, + content: { + hash: faucetResp, + recipient: paramOptions.toAddress, + chain: content.chain, + }, + }); + + return true; + } catch (error) { + elizaLogger.error("Error during get test tokens:", error); + callback?.({ + text: `Error during get test tokens: ${error.message}`, + content: { error: error.message }, + }); + return false; + } + }, + template: faucetTemplate, + validate: async (runtime: IAgentRuntime) => { + return true; + }, + examples: [ + [ + { + user: "user", + content: { + text: "Request some test tokens from the faucet on BSC Testnet", + action: "FAUCET", + }, + }, + ], + ], + similes: ["FAUCET", "GET_TEST_TOKENS"], +}; diff --git a/packages/plugin-bnb/src/actions/getBalance.ts b/packages/plugin-bnb/src/actions/getBalance.ts new file mode 100644 index 00000000000..e5794f6ad4b --- /dev/null +++ b/packages/plugin-bnb/src/actions/getBalance.ts @@ -0,0 +1,197 @@ +import { + composeContext, + elizaLogger, + generateObjectDeprecated, + HandlerCallback, + ModelClass, + type IAgentRuntime, + type Memory, + type State, +} from "@elizaos/core"; +import { + getTokens, + getToken, + getTokenBalance, + getTokenBalances, + ChainId, +} from "@lifi/sdk"; + +import { initWalletProvider, WalletProvider } from "../providers/wallet"; +import { getBalanceTemplate } from "../templates"; +import type { Balance, GetBalanceParams, GetBalanceResponse } from "../types"; +import { Address, formatEther, formatUnits } from "viem"; + +export { getBalanceTemplate }; + +export class GetBalanceAction { + constructor(private walletProvider: WalletProvider) {} + + async getBalance(params: GetBalanceParams): Promise { + let { chain, address, token } = params; + + if (chain == "bscTestnet") { + throw new Error("Testnet is not supported"); + } + + if (!address) { + address = this.walletProvider.getAddress(); + } + + this.walletProvider.switchChain(chain); + const nativeSymbol = + this.walletProvider.getChainConfigs(chain).nativeCurrency.symbol; + const chainId = this.walletProvider.getChainConfigs(chain).id; + + this.walletProvider.configureLiFiSdk(chain); + try { + let resp: GetBalanceResponse = { + chain, + address, + balances: [], + }; + + // If no specific token is requested, get all token balances + if (!token) { + const balances = await this.getTokenBalances(chainId, address); + resp.balances = balances; + } else { + // If specific token is requested and it's not the native token + if (token !== nativeSymbol) { + const balance = await this.getERC20TokenBalance( + chainId, + address, + token! + ); + resp.balances = [{ token: token!, balance }]; + } else { + // If native token is requested + const nativeBalanceWei = await this.walletProvider + .getPublicClient(chain) + .getBalance({ address }); + resp.balances = [ + { + token: nativeSymbol, + balance: formatEther(nativeBalanceWei), + }, + ]; + } + } + + return resp; + } catch (error) { + throw new Error(`Get balance failed: ${error.message}`); + } + } + + async getERC20TokenBalance( + chainId: ChainId, + address: Address, + tokenSymbol: string + ): Promise { + const token = await getToken(chainId, tokenSymbol); + const tokenBalance = await getTokenBalance(address, token); + return formatUnits(tokenBalance?.amount ?? 0n, token.decimals); + } + + async getTokenBalances( + chainId: ChainId, + address: Address + ): Promise { + const tokensResponse = await getTokens(); + const tokens = tokensResponse.tokens[chainId]; + + const tokenBalances = await getTokenBalances(address, tokens); + return tokenBalances + .filter((balance) => balance.amount && balance.amount !== 0n) + .map((balance) => ({ + token: balance.symbol, + balance: formatUnits(balance.amount!, balance.decimals), + })); + } +} + +export const getBalanceAction = { + name: "getBalance", + description: "Get balance of a token or all tokens for the given address", + handler: async ( + runtime: IAgentRuntime, + message: Memory, + state: State, + _options: any, + callback?: HandlerCallback + ) => { + elizaLogger.log("Starting getBalance action..."); + + // Initialize or update state + if (!state) { + state = (await runtime.composeState(message)) as State; + } else { + state = await runtime.updateRecentMessageState(state); + } + + // Compose swap context + const getBalanceContext = composeContext({ + state, + template: getBalanceTemplate, + }); + const content = await generateObjectDeprecated({ + runtime, + context: getBalanceContext, + modelClass: ModelClass.LARGE, + }); + + const walletProvider = initWalletProvider(runtime); + const action = new GetBalanceAction(walletProvider); + const getBalanceOptions: GetBalanceParams = { + chain: content.chain, + address: await walletProvider.formatAddress(content.address), + token: content.token, + }; + try { + const getBalanceResp = await action.getBalance(getBalanceOptions); + if (callback) { + let text = `No balance found for ${getBalanceOptions.address} on ${getBalanceOptions.chain}`; + if (getBalanceResp.balances.length > 0) { + text = `Balance of ${getBalanceResp.address} on ${getBalanceResp.chain}:\n${getBalanceResp.balances + .map(({ token, balance }) => `${token}: ${balance}`) + .join("\n")}`; + } + callback({ + text, + content: { ...getBalanceResp }, + }); + } + return true; + } catch (error) { + elizaLogger.error("Error in get balance:", error.message); + callback?.({ + text: `Getting balance failed`, + content: { error: error.message }, + }); + return false; + } + }, + template: getBalanceTemplate, + validate: async (_runtime: IAgentRuntime) => { + return true; + }, + examples: [ + [ + { + user: "user", + content: { + text: "Check balance of USDC on Bsc", + action: "GET_BALANCE", + }, + }, + { + user: "user", + content: { + text: "Check balance of USDC for 0x742d35Cc6634C0532925a3b844Bc454e4438f44e on Bsc", + action: "CHECK_BALANCE", + }, + }, + ], + ], + similes: ["GET_BALANCE", "CHECK_BALANCE"], +}; diff --git a/packages/plugin-bnb/src/actions/stake.ts b/packages/plugin-bnb/src/actions/stake.ts new file mode 100644 index 00000000000..eeff4edf005 --- /dev/null +++ b/packages/plugin-bnb/src/actions/stake.ts @@ -0,0 +1,291 @@ +import { + composeContext, + elizaLogger, + generateObjectDeprecated, + HandlerCallback, + ModelClass, + type IAgentRuntime, + type Memory, + type State, +} from "@elizaos/core"; + +import { initWalletProvider, WalletProvider } from "../providers/wallet"; +import { stakeTemplate } from "../templates"; +import { + ERC20Abi, + ListaDaoAbi, + SupportedChain, + type StakeParams, + type StakeResponse, +} from "../types"; +import { formatEther, Hex, parseEther } from "viem"; + +export { stakeTemplate }; + +// Exported for tests +export class StakeAction { + private readonly LISTA_DAO = + "0x1adB950d8bB3dA4bE104211D5AB038628e477fE6" as const; + private readonly SLIS_BNB = + "0xB0b84D294e0C75A6abe60171b70edEb2EFd14A1B" as const; + + constructor(private walletProvider: WalletProvider) {} + + async stake(params: StakeParams): Promise { + this.validateStakeParams(params); + + this.walletProvider.switchChain("bsc"); // only BSC is supported + + try { + const actions = { + deposit: async () => await this.doDeposit(params.amount!), + withdraw: async () => await this.doWithdraw(params.amount), + claim: async () => await this.doClaim(), + }; + const resp = await actions[params.action](); + return { response: resp }; + } catch (error) { + throw new Error(`Stake failed: ${error.message}`); + } + } + + validateStakeParams(params: StakeParams) { + if (params.action == "deposit" && !params.amount) { + throw new Error("Amount is required for deposit"); + } + + if (params.action == "withdraw" && !params.amount) { + throw new Error("Amount is required for withdraw"); + } + } + + async doDeposit(amount: string): Promise { + const publicClient = this.walletProvider.getPublicClient( + this.walletProvider.getCurrentChain().name as SupportedChain + ); + const walletClient = this.walletProvider.getWalletClient( + this.walletProvider.getCurrentChain().name as SupportedChain + ); + + const { request } = await publicClient.simulateContract({ + account: walletClient.account, + address: this.LISTA_DAO, + abi: ListaDaoAbi, + functionName: "deposit", + value: parseEther(amount), + }); + const txHash = await walletClient.writeContract(request); + + const slisBNBBalance = await publicClient.readContract({ + address: this.SLIS_BNB, + abi: ERC20Abi, + functionName: "balanceOf", + args: [walletClient.account!.address], + }); + + return `Successfully do deposit. ${formatEther(slisBNBBalance)} slisBNB held. \nTransaction Hash: ${txHash}`; + } + + async doWithdraw(amount?: string): Promise { + const publicClient = this.walletProvider.getPublicClient( + this.walletProvider.getCurrentChain().name as SupportedChain + ); + const walletClient = this.walletProvider.getWalletClient( + this.walletProvider.getCurrentChain().name as SupportedChain + ); + + // If amount is not provided, withdraw all slisBNB + let amountToWithdraw: bigint; + if (!amount) { + amountToWithdraw = await publicClient.readContract({ + address: this.SLIS_BNB, + abi: ERC20Abi, + functionName: "balanceOf", + args: [walletClient.account!.address], + }); + } else { + amountToWithdraw = parseEther(amount); + } + + const { request } = await publicClient.simulateContract({ + account: walletClient.account, + address: this.LISTA_DAO, + abi: ListaDaoAbi, + functionName: "requestWithdraw", + args: [amountToWithdraw], + }); + + const txHash = await walletClient.writeContract(request); + + const slisBNBBalance = await publicClient.readContract({ + address: this.SLIS_BNB, + abi: ERC20Abi, + functionName: "balanceOf", + args: [walletClient.account!.address], + }); + + return `Successfully do withdraw. ${formatEther(slisBNBBalance)} slisBNB left. \nTransaction Hash: ${txHash}`; + } + + async doClaim(): Promise { + const publicClient = this.walletProvider.getPublicClient( + this.walletProvider.getCurrentChain().name as SupportedChain + ); + const walletClient = this.walletProvider.getWalletClient( + this.walletProvider.getCurrentChain().name as SupportedChain + ); + + const address = walletClient.account!.address; + const requests = await publicClient.readContract({ + address: this.LISTA_DAO, + abi: ListaDaoAbi, + functionName: "getUserWithdrawalRequests", + args: [address], + }); + + let totalClaimed = 0n; + for (let idx = 0; idx < requests.length; idx++) { + const [isClaimable, amount] = await publicClient.readContract({ + address: this.LISTA_DAO, + abi: ListaDaoAbi, + functionName: "getUserRequestStatus", + args: [address, BigInt(idx)], + }); + + if (isClaimable) { + const { request } = await publicClient.simulateContract({ + account: walletClient.account, + address: this.LISTA_DAO, + abi: ListaDaoAbi, + functionName: "claimWithdraw", + args: [BigInt(idx)], + }); + + await walletClient.writeContract(request); + + totalClaimed += amount; + } else { + break; + } + } + + return `Successfully do claim. ${formatEther(totalClaimed)} BNB claimed.`; + } +} + +export const stakeAction = { + name: "stake", + description: "Stake related actions through Lista DAO", + handler: async ( + runtime: IAgentRuntime, + message: Memory, + state: State, + _options: any, + callback?: HandlerCallback + ) => { + elizaLogger.log("Starting stake action..."); + + // Validate stake + if (!(message.content.source === "direct")) { + callback?.({ + text: "I can't do that for you.", + content: { error: "Stake not allowed" }, + }); + return false; + } + + // Initialize or update state + if (!state) { + state = (await runtime.composeState(message)) as State; + } else { + state = await runtime.updateRecentMessageState(state); + } + + // Compose stake context + const stakeContext = composeContext({ + state, + template: stakeTemplate, + }); + const content = await generateObjectDeprecated({ + runtime, + context: stakeContext, + modelClass: ModelClass.LARGE, + }); + + const walletProvider = initWalletProvider(runtime); + const action = new StakeAction(walletProvider); + const paramOptions: StakeParams = { + action: content.action, + amount: content.amount, + }; + try { + const stakeResp = await action.stake(paramOptions); + callback?.({ + text: stakeResp.response, + content: { ...stakeResp }, + }); + + return true; + } catch (error) { + elizaLogger.error("Error during transfer:", error); + callback?.({ + text: `Stake failed`, + content: { error: error.message }, + }); + return false; + } + }, + template: stakeTemplate, + validate: async (runtime: IAgentRuntime) => { + const privateKey = runtime.getSetting("BNB_PRIVATE_KEY"); + return typeof privateKey === "string" && privateKey.startsWith("0x"); + }, + examples: [ + [ + { + user: "user", + content: { + text: "Delegate 1 BNB", + action: "delegate", + }, + }, + { + user: "user", + content: { + text: "Stake 1 BNB", + action: "stake", + }, + }, + { + user: "user", + content: { + text: "Deposit 1 BNB to Lista DAO", + action: "deposit", + }, + }, + { + user: "user", + content: { + text: "Withdraw 1 BNB from Lista DAO", + action: "withdraw", + }, + }, + { + user: "user", + content: { + text: "Claim locked BNB", + action: "claim", + }, + }, + ], + ], + similes: [ + "DELEGATE", + "STAKE", + "DEPOSIT", + "UNDELEGATE", + "UNSTAKE", + "WITHDRAW", + "CLAIM", + ], +}; diff --git a/packages/plugin-bnb/src/actions/swap.ts b/packages/plugin-bnb/src/actions/swap.ts new file mode 100644 index 00000000000..e783ce82be8 --- /dev/null +++ b/packages/plugin-bnb/src/actions/swap.ts @@ -0,0 +1,145 @@ +import { + composeContext, + elizaLogger, + generateObjectDeprecated, + HandlerCallback, + ModelClass, + type IAgentRuntime, + type Memory, + type State, +} from "@elizaos/core"; +import { executeRoute, getRoutes } from "@lifi/sdk"; +import { parseEther } from "viem"; + +import { initWalletProvider, WalletProvider } from "../providers/wallet"; +import { swapTemplate } from "../templates"; +import type { SwapParams, SwapResponse } from "../types"; + +export { swapTemplate }; + +export class SwapAction { + constructor(private walletProvider: WalletProvider) {} + + async swap(params: SwapParams): Promise { + if (params.chain == "bscTestnet") { + throw new Error("Testnet is not supported"); + } + + const fromAddress = this.walletProvider.getAddress(); + const chainId = this.walletProvider.getChainConfigs(params.chain).id; + + this.walletProvider.configureLiFiSdk(params.chain); + try { + let resp: SwapResponse = { + chain: params.chain, + txHash: "0x", + fromToken: params.fromToken, + toToken: params.toToken, + amount: params.amount, + }; + + const routes = await getRoutes({ + fromChainId: chainId, + toChainId: chainId, + fromTokenAddress: params.fromToken, + toTokenAddress: params.toToken, + fromAmount: parseEther(params.amount).toString(), + fromAddress: fromAddress, + options: { + slippage: params.slippage, + order: "RECOMMENDED", + }, + }); + + if (!routes.routes.length) throw new Error("No routes found"); + + const execution = await executeRoute(routes.routes[0]); + const process = execution.steps[0]?.execution?.process[0]; + + if (!process?.status || process.status === "FAILED") { + throw new Error("Transaction failed"); + } + + resp.txHash = process.txHash as `0x${string}`; + + return resp; + } catch (error) { + throw new Error(`Swap failed: ${error.message}`); + } + } +} + +export const swapAction = { + name: "swap", + description: "Swap tokens on the same chain", + handler: async ( + runtime: IAgentRuntime, + message: Memory, + state: State, + _options: any, + callback?: HandlerCallback + ) => { + elizaLogger.log("Starting swap action..."); + + // Initialize or update state + if (!state) { + state = (await runtime.composeState(message)) as State; + } else { + state = await runtime.updateRecentMessageState(state); + } + + // Compose swap context + const swapContext = composeContext({ + state, + template: swapTemplate, + }); + const content = await generateObjectDeprecated({ + runtime, + context: swapContext, + modelClass: ModelClass.LARGE, + }); + + const swapOptions: SwapParams = { + chain: content.chain, + fromToken: content.inputToken, + toToken: content.outputToken, + amount: content.amount, + slippage: content.slippage, + }; + + const walletProvider = initWalletProvider(runtime); + const action = new SwapAction(walletProvider); + try { + const swapResp = await action.swap(swapOptions); + callback?.({ + text: `Successfully swap ${swapResp.amount} ${swapResp.fromToken} tokens to ${swapResp.toToken}\nTransaction Hash: ${swapResp.txHash}`, + content: { ...swapResp }, + }); + return true; + } catch (error) { + elizaLogger.error("Error during swap:", error.message); + callback?.({ + text: `Swap failed`, + content: { error: error.message }, + }); + return false; + } + }, + template: swapTemplate, + validate: async (runtime: IAgentRuntime) => { + const privateKey = runtime.getSetting("BNB_PRIVATE_KEY"); + return typeof privateKey === "string" && privateKey.startsWith("0x"); + }, + examples: [ + [ + { + user: "user", + content: { + text: "Swap 1 BNB for USDC on Bsc", + action: "TOKEN_SWAP", + }, + }, + ], + ], + similes: ["TOKEN_SWAP", "EXCHANGE_TOKENS", "TRADE_TOKENS"], +}; diff --git a/packages/plugin-bnb/src/actions/transfer.ts b/packages/plugin-bnb/src/actions/transfer.ts new file mode 100644 index 00000000000..531918065ed --- /dev/null +++ b/packages/plugin-bnb/src/actions/transfer.ts @@ -0,0 +1,221 @@ +import { + composeContext, + elizaLogger, + generateObjectDeprecated, + HandlerCallback, + ModelClass, + type IAgentRuntime, + type Memory, + type State, +} from "@elizaos/core"; +import { + formatEther, + formatUnits, + parseEther, + parseUnits, + type Hex, +} from "viem"; + +import { initWalletProvider, WalletProvider } from "../providers/wallet"; +import { transferTemplate } from "../templates"; +import { ERC20Abi, type TransferParams, type TransferResponse } from "../types"; + +export { transferTemplate }; + +// Exported for tests +export class TransferAction { + private readonly TRANSFER_GAS = 21000n; + private readonly DEFAULT_GAS_PRICE = 3000000000n as const; // 3 Gwei + + constructor(private walletProvider: WalletProvider) {} + + async transfer(params: TransferParams): Promise { + const fromAddress = this.walletProvider.getAddress(); + + this.walletProvider.switchChain(params.chain); + + try { + const nativeToken = + this.walletProvider.chains[params.chain].nativeCurrency.symbol; + + let resp: TransferResponse = { + chain: params.chain, + txHash: "0x", + recipient: params.toAddress, + amount: "", + token: params.token ?? nativeToken, + }; + + if (!params.token || params.token == nativeToken) { + // Native token transfer + let options: { gas?: bigint; gasPrice?: bigint; data?: Hex } = { + data: params.data, + }; + let value: bigint; + if (!params.amount) { + // Transfer all balance minus gas + const publicClient = this.walletProvider.getPublicClient( + params.chain + ); + const balance = await publicClient.getBalance({ + address: fromAddress, + }); + + value = balance - this.DEFAULT_GAS_PRICE * 21000n; + options.gas = this.TRANSFER_GAS; + options.gasPrice = this.DEFAULT_GAS_PRICE; + } else { + value = parseEther(params.amount); + } + + resp.amount = formatEther(value); + resp.txHash = await this.walletProvider.transfer( + params.chain, + params.toAddress, + value, + options + ); + } else { + // ERC20 token transfer + let tokenAddress = params.token; + if (!params.token.startsWith("0x")) { + const resolvedAddress = + await this.walletProvider.getTokenAddress( + params.chain, + params.token + ); + if (!resolvedAddress) { + throw new Error( + `Unknown token symbol ${params.token}. Please provide a valid token address.` + ); + } + tokenAddress = resolvedAddress; + } + + const publicClient = this.walletProvider.getPublicClient( + params.chain + ); + const decimals = await publicClient.readContract({ + address: tokenAddress as `0x${string}`, + abi: ERC20Abi, + functionName: "decimals", + }); + + let value: bigint; + if (!params.amount) { + value = await publicClient.readContract({ + address: tokenAddress as `0x${string}`, + abi: ERC20Abi, + functionName: "balanceOf", + args: [fromAddress], + }); + } else { + value = parseUnits(params.amount, decimals); + } + + resp.amount = formatUnits(value, decimals); + resp.txHash = await this.walletProvider.transferERC20( + params.chain, + tokenAddress as `0x${string}`, + params.toAddress, + value + ); + } + + return resp; + } catch (error) { + throw new Error(`Transfer failed: ${error.message}`); + } + } +} + +export const transferAction = { + name: "transfer", + description: "Transfer tokens between addresses on the same chain", + handler: async ( + runtime: IAgentRuntime, + message: Memory, + state: State, + _options: any, + callback?: HandlerCallback + ) => { + elizaLogger.log("Starting transfer action..."); + + // Validate transfer + if (!(message.content.source === "direct")) { + callback?.({ + text: "I can't do that for you.", + content: { error: "Transfer not allowed" }, + }); + return false; + } + + // 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, + }); + const content = await generateObjectDeprecated({ + runtime, + context: transferContext, + modelClass: ModelClass.LARGE, + }); + + const walletProvider = initWalletProvider(runtime); + const action = new TransferAction(walletProvider); + const paramOptions: TransferParams = { + chain: content.chain, + token: content.token, + amount: content.amount, + toAddress: await walletProvider.formatAddress(content.toAddress), + data: content.data, + }; + try { + const transferResp = await action.transfer(paramOptions); + callback?.({ + text: `Successfully transferred ${transferResp.amount} ${transferResp.token} to ${transferResp.recipient}\nTransaction Hash: ${transferResp.txHash}`, + content: { ...transferResp }, + }); + + return true; + } catch (error) { + elizaLogger.error("Error during transfer:", error); + callback?.({ + text: `Transfer failed`, + content: { error: error.message }, + }); + return false; + } + }, + template: transferTemplate, + validate: async (runtime: IAgentRuntime) => { + const privateKey = runtime.getSetting("BNB_PRIVATE_KEY"); + return typeof privateKey === "string" && privateKey.startsWith("0x"); + }, + examples: [ + [ + { + user: "assistant", + content: { + text: "I'll help you transfer 1 BNB to 0x742d35Cc6634C0532925a3b844Bc454e4438f44e", + action: "SEND_TOKENS", + }, + }, + { + user: "user", + content: { + text: "Transfer 1 BNB to 0x742d35Cc6634C0532925a3b844Bc454e4438f44e", + action: "SEND_TOKENS", + }, + }, + ], + ], + similes: ["SEND_TOKENS", "TOKEN_TRANSFER", "MOVE_TOKENS"], +}; diff --git a/packages/plugin-bnb/src/contracts/Erc1155Contract.sol b/packages/plugin-bnb/src/contracts/Erc1155Contract.sol new file mode 100644 index 00000000000..549b700d17b --- /dev/null +++ b/packages/plugin-bnb/src/contracts/Erc1155Contract.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol"; + +contract ERC1155Contract is ERC1155, Ownable { + string public name; + + constructor( + string memory _name, + string memory _baseURI + ) ERC1155(_baseURI) Ownable(msg.sender) { + name = _name; + } + + function mint( + address to, + uint256 id, + uint256 amount, + bytes memory data + ) public onlyOwner { + _mint(to, id, amount, data); + } + + function mintBatch( + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) public onlyOwner { + _mintBatch(to, ids, amounts, data); + } + + function setURI(string memory newuri) public onlyOwner { + _setURI(newuri); + } +} diff --git a/packages/plugin-bnb/src/contracts/Erc20Contract.sol b/packages/plugin-bnb/src/contracts/Erc20Contract.sol new file mode 100644 index 00000000000..12ddf39ff5d --- /dev/null +++ b/packages/plugin-bnb/src/contracts/Erc20Contract.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +contract ERC20Contract is ERC20, Ownable { + uint8 private immutable _decimals; + + constructor( + string memory name, + string memory symbol, + uint8 decimalsValue, + uint256 initialSupply + ) ERC20(name, symbol) Ownable(msg.sender) { + _decimals = decimalsValue; + _mint(msg.sender, initialSupply); + } + + function decimals() public view override returns (uint8) { + return _decimals; + } +} diff --git a/packages/plugin-bnb/src/contracts/Erc721Contract.sol b/packages/plugin-bnb/src/contracts/Erc721Contract.sol new file mode 100644 index 00000000000..d1f50a9b2fe --- /dev/null +++ b/packages/plugin-bnb/src/contracts/Erc721Contract.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; + +contract ERC721Contract is ERC721, ERC721URIStorage, ERC721Enumerable, Ownable { + string private _baseTokenURI; + + constructor( + string memory name, + string memory symbol, + string memory baseURI + ) ERC721(name, symbol) Ownable(msg.sender) { + _baseTokenURI = baseURI; + } + + function _baseURI() internal view override returns (string memory) { + return _baseTokenURI; + } + + function mint(address to, uint256 tokenId) public onlyOwner { + _safeMint(to, tokenId); + } + + function mintBatch(address to, uint256[] memory tokenIds) public onlyOwner { + for (uint i = 0; i < tokenIds.length; i++) { + _safeMint(to, tokenIds[i]); + } + } + + function setBaseURI(string memory newBaseURI) public onlyOwner { + _baseTokenURI = newBaseURI; + } + + function tokenURI( + uint256 tokenId + ) public view override(ERC721, ERC721URIStorage) returns (string memory) { + return super.tokenURI(tokenId); + } + + function supportsInterface( + bytes4 interfaceId + ) + public + view + override(ERC721, ERC721Enumerable, ERC721URIStorage) + returns (bool) + { + return super.supportsInterface(interfaceId); + } + + function _update( + address to, + uint256 tokenId, + address auth + ) internal override(ERC721, ERC721Enumerable) returns (address) { + return super._update(to, tokenId, auth); + } + + function _increaseBalance( + address account, + uint128 value + ) internal override(ERC721, ERC721Enumerable) { + super._increaseBalance(account, value); + } +} diff --git a/packages/plugin-bnb/src/index.ts b/packages/plugin-bnb/src/index.ts new file mode 100644 index 00000000000..98546eb5218 --- /dev/null +++ b/packages/plugin-bnb/src/index.ts @@ -0,0 +1,33 @@ +export * from "./actions/swap"; +export * from "./actions/transfer"; +export * from "./providers/wallet"; +export * from "./types"; + +import type { Plugin } from "@elizaos/core"; +import { swapAction } from "./actions/swap"; +import { transferAction } from "./actions/transfer"; +import { bnbWalletProvider } from "./providers/wallet"; +import { getBalanceAction } from "./actions/getBalance"; +import { bridgeAction } from "./actions/bridge"; +import { stakeAction } from "./actions/stake"; +import { faucetAction } from "./actions/faucet"; +import { deployAction } from "./actions/deploy"; + +export const bnbPlugin: Plugin = { + name: "bnb", + description: "BNB Smart Chain integration plugin", + providers: [bnbWalletProvider], + evaluators: [], + services: [], + actions: [ + getBalanceAction, + transferAction, + swapAction, + bridgeAction, // NOTE: The bridge action only supports bridge funds between BSC and opBNB for now. We may adding stargate support later. + stakeAction, + faucetAction, + deployAction, + ], +}; + +export default bnbPlugin; diff --git a/packages/plugin-bnb/src/providers/wallet.ts b/packages/plugin-bnb/src/providers/wallet.ts new file mode 100644 index 00000000000..bd2093a6d53 --- /dev/null +++ b/packages/plugin-bnb/src/providers/wallet.ts @@ -0,0 +1,326 @@ +import { + type IAgentRuntime, + type Provider, + type Memory, + type State, + elizaLogger, +} from "@elizaos/core"; +import { EVM, createConfig, getToken } from "@lifi/sdk"; +import type { + Address, + WalletClient, + PublicClient, + Chain, + HttpTransport, + Account, + PrivateKeyAccount, + Hex, +} from "viem"; +import { + createPublicClient, + createWalletClient, + formatUnits, + http, +} from "viem"; +import { privateKeyToAccount } from "viem/accounts"; +import * as viemChains from "viem/chains"; +import { createWeb3Name } from "@web3-name-sdk/core"; + +import type { SupportedChain } from "../types"; +import { ERC20Abi } from "../types"; + +export class WalletProvider { + private currentChain: SupportedChain = "bsc"; + chains: Record = { bsc: viemChains.bsc }; + account: PrivateKeyAccount; + + constructor(privateKey: `0x${string}`, chains?: Record) { + this.setAccount(privateKey); + this.setChains(chains); + + if (chains && Object.keys(chains).length > 0) { + this.setCurrentChain(Object.keys(chains)[0] as SupportedChain); + } + } + + getAccount(): PrivateKeyAccount { + return this.account; + } + + getAddress(): Address { + return this.account.address; + } + + getCurrentChain(): Chain { + return this.chains[this.currentChain]; + } + + getPublicClient( + chainName: SupportedChain + ): PublicClient { + const transport = this.createHttpTransport(chainName); + + const publicClient = createPublicClient({ + chain: this.chains[chainName], + transport, + }); + return publicClient; + } + + getWalletClient(chainName: SupportedChain): WalletClient { + const transport = this.createHttpTransport(chainName); + + const walletClient = createWalletClient({ + chain: this.chains[chainName], + transport, + account: this.account, + }); + + return walletClient; + } + + getChainConfigs(chainName: SupportedChain): Chain { + const chain = viemChains[chainName]; + + if (!chain?.id) { + throw new Error("Invalid chain name"); + } + + return chain; + } + + configureLiFiSdk(chainName: SupportedChain) { + const chains = Object.values(this.chains); + const walletClient = this.getWalletClient(chainName); + + createConfig({ + integrator: "eliza", + providers: [ + EVM({ + getWalletClient: async () => walletClient, + switchChain: async (chainId) => + createWalletClient({ + account: this.account, + chain: chains.find( + (chain) => chain.id == chainId + ) as Chain, + transport: http(), + }), + }), + ], + }); + } + + async formatAddress(address: string): Promise
{ + if (address.startsWith("0x") && address.length === 42) { + return address as Address; + } + const resolvedAddress = await this.resolveWeb3Name(address); + if (resolvedAddress) { + return resolvedAddress as Address; + } else { + throw new Error("Invalid address"); + } + } + + async resolveWeb3Name(name: string): Promise { + const nameService = createWeb3Name(); + return await nameService.getAddress(name); + } + + async transfer( + chain: SupportedChain, + toAddress: Address, + amount: bigint, + options?: { + gas?: bigint; + gasPrice?: bigint; + data?: Hex; + } + ): Promise { + const walletClient = this.getWalletClient(chain); + return walletClient.sendTransaction({ + account: walletClient.account!, + to: toAddress, + value: amount, + chain: this.getChainConfigs(chain), + ...options, + }); + } + + async transferERC20( + chain: SupportedChain, + tokenAddress: Address, + toAddress: Address, + amount: bigint, + options?: { + gas?: bigint; + gasPrice?: bigint; + } + ): Promise { + const publicClient = this.getPublicClient(chain); + const walletClient = this.getWalletClient(chain); + const { request } = await publicClient.simulateContract({ + account: walletClient.account, + address: tokenAddress as `0x${string}`, + abi: ERC20Abi, + functionName: "transfer", + args: [toAddress as `0x${string}`, amount], + ...options, + }); + + return await walletClient.writeContract(request); + } + + // NOTE: Only works for bsc + async getWalletBalance(): Promise { + try { + const client = this.getPublicClient("bsc"); + const balance = await client.getBalance({ + address: this.account.address, + }); + return formatUnits(balance, 18); + } catch (error) { + elizaLogger.error("Error getting wallet balance:", error); + return null; + } + } + + async getTokenAddress( + chainName: SupportedChain, + tokenSymbol: string + ): Promise { + try { + const token = await getToken( + this.getChainConfigs(chainName).id, + tokenSymbol + ); + return token.address; + } catch (error) { + elizaLogger.error("Error getting token address:", error); + return null; + } + } + + addChain(chain: Record) { + this.setChains(chain); + } + + switchChain(chainName: SupportedChain, customRpcUrl?: string) { + if (!this.chains[chainName]) { + const chain = WalletProvider.genChainFromName( + chainName, + customRpcUrl + ); + this.addChain({ [chainName]: chain }); + } + this.setCurrentChain(chainName); + } + + private setAccount = (pk: `0x${string}`) => { + this.account = privateKeyToAccount(pk); + }; + + private setChains = (chains?: Record) => { + if (!chains) { + return; + } + Object.keys(chains).forEach((chain: string) => { + this.chains[chain] = chains[chain]; + }); + }; + + private setCurrentChain = (chain: SupportedChain) => { + this.currentChain = chain; + }; + + private createHttpTransport = (chainName: SupportedChain) => { + const chain = this.chains[chainName]; + + if (chain.rpcUrls.custom) { + return http(chain.rpcUrls.custom.http[0]); + } + return http(chain.rpcUrls.default.http[0]); + }; + + static genChainFromName( + chainName: string, + customRpcUrl?: string | null + ): Chain { + const baseChain = viemChains[chainName]; + + if (!baseChain?.id) { + throw new Error("Invalid chain name"); + } + + const viemChain: Chain = customRpcUrl + ? { + ...baseChain, + rpcUrls: { + ...baseChain.rpcUrls, + custom: { + http: [customRpcUrl], + }, + }, + } + : baseChain; + + return viemChain; + } +} + +const genChainsFromRuntime = ( + runtime: IAgentRuntime +): Record => { + const chainNames = ["bsc", "bscTestnet", "opBNB", "opBNBTestnet"]; + const chains = {}; + + chainNames.forEach((chainName) => { + const chain = WalletProvider.genChainFromName(chainName); + chains[chainName] = chain; + }); + + const mainnet_rpcurl = runtime.getSetting("BSC_PROVIDER_URL"); + if (mainnet_rpcurl) { + const chain = WalletProvider.genChainFromName("bsc", mainnet_rpcurl); + chains["bsc"] = chain; + } + + const opbnb_rpcurl = runtime.getSetting("OPBNB_PROVIDER_URL"); + if (opbnb_rpcurl) { + const chain = WalletProvider.genChainFromName("opBNB", opbnb_rpcurl); + chains["opBNB"] = chain; + } + + return chains; +}; + +export const initWalletProvider = (runtime: IAgentRuntime) => { + const privateKey = runtime.getSetting("BNB_PRIVATE_KEY"); + if (!privateKey) { + throw new Error("BNB_PRIVATE_KEY is missing"); + } + + const chains = genChainsFromRuntime(runtime); + + return new WalletProvider(privateKey as `0x${string}`, chains); +}; + +export const bnbWalletProvider: Provider = { + async get( + runtime: IAgentRuntime, + _message: Memory, + _state?: State + ): Promise { + try { + const walletProvider = initWalletProvider(runtime); + const address = walletProvider.getAddress(); + const balance = await walletProvider.getWalletBalance(); + const chain = walletProvider.getCurrentChain(); + return `BNB chain Wallet Address: ${address}\nBalance: ${balance} ${chain.nativeCurrency.symbol}\nChain ID: ${chain.id}, Name: ${chain.name}`; + } catch (error) { + console.error("Error in BNB chain wallet provider:", error); + return null; + } + }, +}; diff --git a/packages/plugin-bnb/src/templates/index.ts b/packages/plugin-bnb/src/templates/index.ts new file mode 100644 index 00000000000..49afaed6f27 --- /dev/null +++ b/packages/plugin-bnb/src/templates/index.ts @@ -0,0 +1,175 @@ +export const getBalanceTemplate = `Given the recent messages and wallet information below: + +{{recentMessages}} + +{{walletInfo}} + +Extract the following information about the requested check balance: +- Chain to execute on. Must be "bsc". Opbnb, opbnbTestnet and bscTestnet are not supported for now. +- Address to check balance for. Optional, must be a valid Ethereum address starting with "0x" or a web3 domain name. If not provided, return the balance of the wallet. +- Token symbol or address (if not native token). Optional, if not provided, return the balance of all known tokens. + +Respond with a JSON markdown block containing only the extracted values. All fields except 'token' are required: + +\`\`\`json +{ + "chain": "bsc", + "address": string | null, + "token": string | null +} +\`\`\` +`; + +export const transferTemplate = `Given the recent messages and wallet information below: + +{{recentMessages}} + +{{walletInfo}} + +Extract the following information about the requested transfer: +- Chain to execute on. Must be one of ["bsc", "bscTestnet", "opBNB", "opBNBTestnet"]. +- Token symbol or address. Optional, if not provided, transfer native token(BNB). +- Amount to transfer. Optional, if not provided, transfer all available balance. Must be a string representing the amount in ether (only number without coin symbol, e.g., "0.1"). +- Recipient address. Must be a valid Ethereum address starting with "0x" or a web3 domain name. +- Data. Optional, data to be included in the transaction. + +Respond with a JSON markdown block containing only the extracted values: + +\`\`\`json +{ + "chain": SUPPORTED_CHAINS, + "token": string | null, + "amount": string | null, + "toAddress": string, + "data": string | null +} +\`\`\` +`; + +export const swapTemplate = `Given the recent messages and wallet information below: + +{{recentMessages}} + +{{walletInfo}} + +Extract the following information about the requested token swap: +- Input token symbol or address (the token being sold). +- Output token symbol or address (the token being bought). +- Amount to swap. Must be a string representing the amount in ether (only number without coin symbol, e.g., "0.1"). +- Chain to execute on. Must be "bsc". Opbnb, opbnbTestnet and bscTestnet are not supported for now. +- Slippage. Expressed as decimal proportion, 0.03 represents 3%. + +Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined: + +\`\`\`json +{ + "inputToken": string | null, + "outputToken": string | null, + "amount": string | null, + "chain": "bsc", + "slippage": number | null +} +\`\`\` +`; + +export const bridgeTemplate = `Given the recent messages and wallet information below: + +{{recentMessages}} + +{{walletInfo}} + +Extract the following information about the requested token bridge: +- From chain. Must be one of ["bsc", "opBNB"]. +- To chain. Must be one of ["bsc", "opBNB"]. +- From token address. Optional, must be a valid Ethereum address starting with "0x" or a web3 domain name. If not provided, bridge native token(BNB). +- To token address. Optional, must be a valid Ethereum address starting with "0x" or a web3 domain name. If not provided, bridge native token(BNB). If from token is provided, to token must be provided. +- Amount to bridge. Must be a string representing the amount in ether (only number without coin symbol, e.g., "0.1"). +- To address. Optional, must be a valid Ethereum address starting with "0x" or a web3 domain name. If not provided, bridge to the address of the wallet. + +Respond with a JSON markdown block containing only the extracted values: + +\`\`\`json +{ + "fromChain": "bsc" | "opBNB", + "toChain": "bsc" | "opBNB", + "fromToken": string | null, + "toToken": string | null, + "amount": string, + "toAddress": string | null +} +\`\`\` +`; + +export const stakeTemplate = `Given the recent messages and wallet information below: + +{{recentMessages}} + +{{walletInfo}} + +Extract the following information about the requested stake action: +- Action to execute. Must be one of ["deposit", "withdraw", "claim"]. +- Amount to execute. Optional, must be a string representing the amount in ether (only number without coin symbol, e.g., "0.1"). If the action is "deposit", amount is required. + +Respond with a JSON markdown block containing only the extracted values: + +\`\`\`json +{ + "action": "deposit" | "withdraw" | "claim", + "amount": string | null, +} +\`\`\` +`; + +export const faucetTemplate = `Given the recent messages and wallet information below: + +{{recentMessages}} + +{{walletInfo}} + +Extract the following information about the requested faucet request: +- Token. Token to request. Could be one of ["BNB", "BTC", "BUSD", "DAI", "ETH", "USDC"]. Optinal, if not provided, send tBNB by default. +- Recipient address. Must be a valid Ethereum address starting with "0x" or a web3 domain name. + +Respond with a JSON markdown block containing only the extracted values. All fields are required: + +\`\`\`json +{ + "token": string | null, + "toAddress": string +} +\`\`\` +`; + +export const ercContractTemplate = `Given the recent messages and wallet information below: + +{{recentMessages}} + +{{walletInfo}} + +When user wants to deploy any type of token contract (ERC20/721/1155), this will trigger the DEPLOY_TOKEN action. + +Extract the following details for deploying a token contract: +- **contractType** (string): The type of token contract to deploy + - For ERC20: Extract name, symbol, decimals, totalSupply + - For ERC721: Extract name, symbol, baseURI + - For ERC1155: Extract name, baseURI +- **chain** (string): Must be one of: bsc, opBNB, bscTestnet, opBNBTestnet +- **name** (string): The name of the token +- **symbol** (string): The token symbol (only for ERC20/721) +- **decimals** (number): Token decimals (only for ERC20) +- **totalSupply** (string): Total supply with decimals (only for ERC20) +- **baseURI** (string): Base URI for token metadata (only for ERC721/1155) + +Required response format: +\`\`\`json +{ + "contractType": "ERC20" | "ERC721" | "ERC1155", + "chain": "bsc" | "opBNB" | "bscTestnet" | "opBNBTestnet", + "name": string, + "symbol": string, + "decimals": number, // Only for ERC20 + "totalSupply": string, // Only for ERC20 + "baseURI": string // Only for ERC721/1155 +} +\`\`\` +`; diff --git a/packages/plugin-bnb/src/tests/getBalance.test.ts b/packages/plugin-bnb/src/tests/getBalance.test.ts new file mode 100644 index 00000000000..b41b28e7651 --- /dev/null +++ b/packages/plugin-bnb/src/tests/getBalance.test.ts @@ -0,0 +1,54 @@ +import { describe, it, beforeEach } from "vitest"; +import { + generatePrivateKey, + Account, + privateKeyToAccount, +} from "viem/accounts"; + +import { GetBalanceAction } from "../actions/getBalance"; +import { WalletProvider } from "../providers/wallet"; +import { GetBalanceParams } from "../types"; + +describe("GetBalance Action", () => { + let account: Account; + let wp: WalletProvider; + let ga: GetBalanceAction; + + beforeEach(async () => { + const pk = generatePrivateKey(); + account = privateKeyToAccount(pk); + wp = new WalletProvider(pk); + ga = new GetBalanceAction(wp); + }); + + describe("Get Balance", () => { + it("get BNB balance", async () => { + const input: GetBalanceParams = { + chain: "bsc", + address: account.address, + token: "BNB", + }; + const resp = await ga.getBalance(input); + console.log("BNB balance", resp.balances[0]); + }); + + it("get USDC balance", async () => { + const input: GetBalanceParams = { + chain: "bsc", + address: account.address, + token: "USDC", + }; + const resp = await ga.getBalance(input); + console.log("USDC balance", resp.balances[0]); + }); + + it("get all token balances", async () => { + const input: GetBalanceParams = { + chain: "bsc", + address: account.address, + }; + const resp = await ga.getBalance(input); + console.log("token balances", resp.balances); + }, 50000); + }); +}); diff --git a/packages/plugin-bnb/src/tests/wallet.test.ts b/packages/plugin-bnb/src/tests/wallet.test.ts new file mode 100644 index 00000000000..cfe8c12f235 --- /dev/null +++ b/packages/plugin-bnb/src/tests/wallet.test.ts @@ -0,0 +1,102 @@ +import { describe, it, expect, beforeAll } from "vitest"; +import { + Account, + generatePrivateKey, + privateKeyToAccount, +} from "viem/accounts"; +import { bsc, opBNB } from "viem/chains"; + +import { WalletProvider } from "../providers/wallet"; + +const customRpcUrls = { + bsc: "custom-rpc.bsc.io", + opBNB: "custom-rpc.opBNB.io", +}; + +describe("Wallet provider", () => { + let pk: `0x${string}`; + let account: Account; + let walletProvider: WalletProvider; + + beforeAll(() => { + pk = generatePrivateKey(); + account = privateKeyToAccount(pk); + walletProvider = new WalletProvider(pk); + }); + + describe("Constructor", () => { + it("get address", () => { + const expectedAddress = account.address; + + expect(walletProvider.getAddress()).toEqual(expectedAddress); + }); + it("get current chain", () => { + expect(walletProvider.getCurrentChain().id).toEqual(bsc.id); + }); + it("get chain configs", () => { + expect(walletProvider.getChainConfigs("bsc").id).toEqual(bsc.id); + expect(walletProvider.getChainConfigs("opBNB").id).toEqual( + opBNB.id + ); + }); + }); + describe("Clients", () => { + it("generates public client", () => { + const client = walletProvider.getPublicClient("bsc"); + expect(client.chain.id).toEqual(bsc.id); + expect(client.transport.url).toEqual(bsc.rpcUrls.default.http[0]); + }); + it("generates public client with custom rpcurl", () => { + const chain = WalletProvider.genChainFromName( + "bsc", + customRpcUrls.bsc + ); + const wp = new WalletProvider(pk, { ["bsc"]: chain }); + + const client = wp.getPublicClient("bsc"); + expect(client.chain.id).toEqual(bsc.id); + expect(client.chain.rpcUrls.default.http[0]).toEqual( + bsc.rpcUrls.default.http[0] + ); + expect(client.chain.rpcUrls.custom.http[0]).toEqual( + customRpcUrls.bsc + ); + expect(client.transport.url).toEqual(customRpcUrls.bsc); + }); + it("generates wallet client", () => { + const expectedAddress = account.address; + + const client = walletProvider.getWalletClient("bsc"); + + expect(client.account?.address).toEqual(expectedAddress); + expect(client.transport.url).toEqual(bsc.rpcUrls.default.http[0]); + }); + it("generates wallet client with custom rpcurl", () => { + const account = privateKeyToAccount(pk); + const expectedAddress = account.address; + const chain = WalletProvider.genChainFromName( + "bsc", + customRpcUrls.bsc + ); + const wp = new WalletProvider(pk, { ["bsc"]: chain }); + + const client = wp.getWalletClient("bsc"); + + expect(client.account?.address).toEqual(expectedAddress); + expect(client.chain?.id).toEqual(bsc.id); + expect(client.chain?.rpcUrls.default.http[0]).toEqual( + bsc.rpcUrls.default.http[0] + ); + expect(client.chain?.rpcUrls.custom.http[0]).toEqual( + customRpcUrls.bsc + ); + expect(client.transport.url).toEqual(customRpcUrls.bsc); + }); + }); + describe("Balance", () => { + it("should fetch balance", async () => { + let bal = await walletProvider.getWalletBalance(); + expect(bal).toEqual("0"); + }); + }); +}); diff --git a/packages/plugin-bnb/src/types/index.ts b/packages/plugin-bnb/src/types/index.ts new file mode 100644 index 00000000000..5febaba66e2 --- /dev/null +++ b/packages/plugin-bnb/src/types/index.ts @@ -0,0 +1,2824 @@ +import type { Address, Hash } from "viem"; + +export type SupportedChain = "bsc" | "bscTestnet" | "opBNB" | "opBNBTestnet"; +export type StakeAction = "deposit" | "withdraw" | "claim"; + +export interface Balance { + token: string; + balance: string; +} + +// Action parameters +export interface GetBalanceParams { + chain: SupportedChain; + address?: Address; + token?: string; +} + +export interface TransferParams { + chain: SupportedChain; + token?: string; + amount?: string; + toAddress: Address; + data?: `0x${string}`; +} + +export interface SwapParams { + chain: SupportedChain; + fromToken: string; + toToken: string; + amount: string; + slippage?: number; +} + +export interface BridgeParams { + fromChain: SupportedChain; + toChain: SupportedChain; + fromToken?: Address; + toToken?: Address; + amount: string; + toAddress?: Address; +} + +export interface StakeParams { + action: StakeAction; + amount?: string; +} + +export interface FaucetParams { + token?: string; + toAddress: Address; +} + +// Action return types +export interface GetBalanceResponse { + chain: SupportedChain; + address: Address; + balances: Balance[]; +} + +export interface TransferResponse { + chain: SupportedChain; + txHash: Hash; + recipient: Address; + amount: string; + token: string; + data?: `0x${string}`; +} + +export interface SwapResponse { + chain: SupportedChain; + txHash: Hash; + fromToken: string; + toToken: string; + amount: string; +} + +export interface BridgeResponse { + fromChain: SupportedChain; + toChain: SupportedChain; + txHash: Hash; + recipient: Address; + fromToken: string; + toToken: string; + amount: string; +} + +export interface StakeResponse { + response: string; +} + +// Contract ABIs +export const ERC20Abi = [ + { + type: "constructor", + inputs: [ + { + name: "name_", + type: "string", + internalType: "string", + }, + { + name: "symbol_", + type: "string", + internalType: "string", + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "allowance", + inputs: [ + { + name: "owner", + type: "address", + internalType: "address", + }, + { + name: "spender", + type: "address", + internalType: "address", + }, + ], + outputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "approve", + inputs: [ + { + name: "spender", + type: "address", + internalType: "address", + }, + { + name: "amount", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [ + { + name: "", + type: "bool", + internalType: "bool", + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "balanceOf", + inputs: [ + { + name: "account", + type: "address", + internalType: "address", + }, + ], + outputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "decimals", + inputs: [], + outputs: [ + { + name: "", + type: "uint8", + internalType: "uint8", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "decreaseAllowance", + inputs: [ + { + name: "spender", + type: "address", + internalType: "address", + }, + { + name: "subtractedValue", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [ + { + name: "", + type: "bool", + internalType: "bool", + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "increaseAllowance", + inputs: [ + { + name: "spender", + type: "address", + internalType: "address", + }, + { + name: "addedValue", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [ + { + name: "", + type: "bool", + internalType: "bool", + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "name", + inputs: [], + outputs: [ + { + name: "", + type: "string", + internalType: "string", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "symbol", + inputs: [], + outputs: [ + { + name: "", + type: "string", + internalType: "string", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "totalSupply", + inputs: [], + outputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "transfer", + inputs: [ + { + name: "to", + type: "address", + internalType: "address", + }, + { + name: "amount", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [ + { + name: "", + type: "bool", + internalType: "bool", + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "transferFrom", + inputs: [ + { + name: "from", + type: "address", + internalType: "address", + }, + { + name: "to", + type: "address", + internalType: "address", + }, + { + name: "amount", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [ + { + name: "", + type: "bool", + internalType: "bool", + }, + ], + stateMutability: "nonpayable", + }, + { + type: "event", + name: "Approval", + inputs: [ + { + name: "owner", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "spender", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "value", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "Transfer", + inputs: [ + { + name: "from", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "to", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "value", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + ], + anonymous: false, + }, +] as const; + +export const L1StandardBridgeAbi = [ + { + type: "constructor", + inputs: [], + stateMutability: "nonpayable", + }, + { + type: "receive", + stateMutability: "payable", + }, + { + type: "function", + name: "MESSENGER", + inputs: [], + outputs: [ + { + name: "", + type: "address", + internalType: "contract CrossDomainMessenger", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "OTHER_BRIDGE", + inputs: [], + outputs: [ + { + name: "", + type: "address", + internalType: "contract StandardBridge", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "bridgeERC20", + inputs: [ + { + name: "_localToken", + type: "address", + internalType: "address", + }, + { + name: "_remoteToken", + type: "address", + internalType: "address", + }, + { + name: "_amount", + type: "uint256", + internalType: "uint256", + }, + { + name: "_minGasLimit", + type: "uint32", + internalType: "uint32", + }, + { + name: "_extraData", + type: "bytes", + internalType: "bytes", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "bridgeERC20To", + inputs: [ + { + name: "_localToken", + type: "address", + internalType: "address", + }, + { + name: "_remoteToken", + type: "address", + internalType: "address", + }, + { + name: "_to", + type: "address", + internalType: "address", + }, + { + name: "_amount", + type: "uint256", + internalType: "uint256", + }, + { + name: "_minGasLimit", + type: "uint32", + internalType: "uint32", + }, + { + name: "_extraData", + type: "bytes", + internalType: "bytes", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "bridgeETH", + inputs: [ + { + name: "_minGasLimit", + type: "uint32", + internalType: "uint32", + }, + { + name: "_extraData", + type: "bytes", + internalType: "bytes", + }, + ], + outputs: [], + stateMutability: "payable", + }, + { + type: "function", + name: "bridgeETHTo", + inputs: [ + { + name: "_to", + type: "address", + internalType: "address", + }, + { + name: "_minGasLimit", + type: "uint32", + internalType: "uint32", + }, + { + name: "_extraData", + type: "bytes", + internalType: "bytes", + }, + ], + outputs: [], + stateMutability: "payable", + }, + { + type: "function", + name: "depositERC20", + inputs: [ + { + name: "_l1Token", + type: "address", + internalType: "address", + }, + { + name: "_l2Token", + type: "address", + internalType: "address", + }, + { + name: "_amount", + type: "uint256", + internalType: "uint256", + }, + { + name: "_minGasLimit", + type: "uint32", + internalType: "uint32", + }, + { + name: "_extraData", + type: "bytes", + internalType: "bytes", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "depositERC20To", + inputs: [ + { + name: "_l1Token", + type: "address", + internalType: "address", + }, + { + name: "_l2Token", + type: "address", + internalType: "address", + }, + { + name: "_to", + type: "address", + internalType: "address", + }, + { + name: "_amount", + type: "uint256", + internalType: "uint256", + }, + { + name: "_minGasLimit", + type: "uint32", + internalType: "uint32", + }, + { + name: "_extraData", + type: "bytes", + internalType: "bytes", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "depositETH", + inputs: [ + { + name: "_minGasLimit", + type: "uint32", + internalType: "uint32", + }, + { + name: "_extraData", + type: "bytes", + internalType: "bytes", + }, + ], + outputs: [], + stateMutability: "payable", + }, + { + type: "function", + name: "depositETHTo", + inputs: [ + { + name: "_to", + type: "address", + internalType: "address", + }, + { + name: "_minGasLimit", + type: "uint32", + internalType: "uint32", + }, + { + name: "_extraData", + type: "bytes", + internalType: "bytes", + }, + ], + outputs: [], + stateMutability: "payable", + }, + { + type: "function", + name: "deposits", + inputs: [ + { + name: "", + type: "address", + internalType: "address", + }, + { + name: "", + type: "address", + internalType: "address", + }, + ], + outputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "finalizeBridgeERC20", + inputs: [ + { + name: "_localToken", + type: "address", + internalType: "address", + }, + { + name: "_remoteToken", + type: "address", + internalType: "address", + }, + { + name: "_from", + type: "address", + internalType: "address", + }, + { + name: "_to", + type: "address", + internalType: "address", + }, + { + name: "_amount", + type: "uint256", + internalType: "uint256", + }, + { + name: "_extraData", + type: "bytes", + internalType: "bytes", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "finalizeBridgeETH", + inputs: [ + { + name: "_from", + type: "address", + internalType: "address", + }, + { + name: "_to", + type: "address", + internalType: "address", + }, + { + name: "_amount", + type: "uint256", + internalType: "uint256", + }, + { + name: "_extraData", + type: "bytes", + internalType: "bytes", + }, + ], + outputs: [], + stateMutability: "payable", + }, + { + type: "function", + name: "finalizeERC20Withdrawal", + inputs: [ + { + name: "_l1Token", + type: "address", + internalType: "address", + }, + { + name: "_l2Token", + type: "address", + internalType: "address", + }, + { + name: "_from", + type: "address", + internalType: "address", + }, + { + name: "_to", + type: "address", + internalType: "address", + }, + { + name: "_amount", + type: "uint256", + internalType: "uint256", + }, + { + name: "_extraData", + type: "bytes", + internalType: "bytes", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "finalizeETHWithdrawal", + inputs: [ + { + name: "_from", + type: "address", + internalType: "address", + }, + { + name: "_to", + type: "address", + internalType: "address", + }, + { + name: "_amount", + type: "uint256", + internalType: "uint256", + }, + { + name: "_extraData", + type: "bytes", + internalType: "bytes", + }, + ], + outputs: [], + stateMutability: "payable", + }, + { + type: "function", + name: "initialize", + inputs: [ + { + name: "_messenger", + type: "address", + internalType: "contract CrossDomainMessenger", + }, + { + name: "_superchainConfig", + type: "address", + internalType: "contract SuperchainConfig", + }, + { + name: "_systemConfig", + type: "address", + internalType: "contract SystemConfig", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "l2TokenBridge", + inputs: [], + outputs: [ + { + name: "", + type: "address", + internalType: "address", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "messenger", + inputs: [], + outputs: [ + { + name: "", + type: "address", + internalType: "contract CrossDomainMessenger", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "otherBridge", + inputs: [], + outputs: [ + { + name: "", + type: "address", + internalType: "contract StandardBridge", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "paused", + inputs: [], + outputs: [ + { + name: "", + type: "bool", + internalType: "bool", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "superchainConfig", + inputs: [], + outputs: [ + { + name: "", + type: "address", + internalType: "contract SuperchainConfig", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "systemConfig", + inputs: [], + outputs: [ + { + name: "", + type: "address", + internalType: "contract SystemConfig", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "version", + inputs: [], + outputs: [ + { + name: "", + type: "string", + internalType: "string", + }, + ], + stateMutability: "view", + }, + { + type: "event", + name: "ERC20BridgeFinalized", + inputs: [ + { + name: "localToken", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "remoteToken", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "from", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "to", + type: "address", + indexed: false, + internalType: "address", + }, + { + name: "amount", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + { + name: "extraData", + type: "bytes", + indexed: false, + internalType: "bytes", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "ERC20BridgeInitiated", + inputs: [ + { + name: "localToken", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "remoteToken", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "from", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "to", + type: "address", + indexed: false, + internalType: "address", + }, + { + name: "amount", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + { + name: "extraData", + type: "bytes", + indexed: false, + internalType: "bytes", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "ERC20DepositInitiated", + inputs: [ + { + name: "l1Token", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "l2Token", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "from", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "to", + type: "address", + indexed: false, + internalType: "address", + }, + { + name: "amount", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + { + name: "extraData", + type: "bytes", + indexed: false, + internalType: "bytes", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "ERC20WithdrawalFinalized", + inputs: [ + { + name: "l1Token", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "l2Token", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "from", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "to", + type: "address", + indexed: false, + internalType: "address", + }, + { + name: "amount", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + { + name: "extraData", + type: "bytes", + indexed: false, + internalType: "bytes", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "ETHBridgeFinalized", + inputs: [ + { + name: "from", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "to", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "amount", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + { + name: "extraData", + type: "bytes", + indexed: false, + internalType: "bytes", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "ETHBridgeInitiated", + inputs: [ + { + name: "from", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "to", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "amount", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + { + name: "extraData", + type: "bytes", + indexed: false, + internalType: "bytes", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "ETHDepositInitiated", + inputs: [ + { + name: "from", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "to", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "amount", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + { + name: "extraData", + type: "bytes", + indexed: false, + internalType: "bytes", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "ETHWithdrawalFinalized", + inputs: [ + { + name: "from", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "to", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "amount", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + { + name: "extraData", + type: "bytes", + indexed: false, + internalType: "bytes", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "Initialized", + inputs: [ + { + name: "version", + type: "uint8", + indexed: false, + internalType: "uint8", + }, + ], + anonymous: false, + }, +] as const; + +export const L2StandardBridgeAbi = [ + { + type: "constructor", + inputs: [ + { + name: "_owner", + type: "address", + internalType: "address payable", + }, + { + name: "_delegationFee", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "nonpayable", + }, + { + name: "AddressEmptyCode", + type: "error", + inputs: [{ name: "target", type: "address", internalType: "address" }], + }, + { + name: "AddressInsufficientBalance", + type: "error", + inputs: [{ name: "account", type: "address", internalType: "address" }], + }, + { name: "FailedInnerCall", type: "error", inputs: [] }, + { + name: "OwnableInvalidOwner", + type: "error", + inputs: [{ name: "owner", type: "address", internalType: "address" }], + }, + { + name: "OwnableUnauthorizedAccount", + type: "error", + inputs: [{ name: "account", type: "address", internalType: "address" }], + }, + { + name: "SafeERC20FailedOperation", + type: "error", + inputs: [{ name: "token", type: "address", internalType: "address" }], + }, + { + name: "OwnershipTransferred", + type: "event", + inputs: [ + { + name: "previousOwner", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "newOwner", + type: "address", + indexed: true, + internalType: "address", + }, + ], + anonymous: false, + signature: + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + }, + { + name: "SetDelegationFee", + type: "event", + inputs: [ + { + name: "_delegationFee", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + ], + anonymous: false, + signature: + "0x0322f3257c2afe5fe8da7ab561f0d3384148487412fe2751678f2188731c0815", + }, + { + name: "WithdrawTo", + type: "event", + inputs: [ + { + name: "from", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "l2Token", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "to", + type: "address", + indexed: false, + internalType: "address", + }, + { + name: "amount", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + { + name: "minGasLimit", + type: "uint32", + indexed: false, + internalType: "uint32", + }, + { + name: "extraData", + type: "bytes", + indexed: false, + internalType: "bytes", + }, + ], + anonymous: false, + signature: + "0x56f66275d9ebc94b7d6895aa0d96a3783550d0183ba106408d387d19f2e877f1", + }, + { + name: "L2_STANDARD_BRIDGE", + type: "function", + inputs: [], + outputs: [ + { + name: "", + type: "address", + value: "0x4200000000000000000000000000000000000010", + internalType: "contract IL2StandardBridge", + }, + ], + constant: true, + signature: "0x21d12763", + stateMutability: "view", + }, + { + name: "L2_STANDARD_BRIDGE_ADDRESS", + type: "function", + inputs: [], + outputs: [ + { + name: "", + type: "address", + value: "0x4200000000000000000000000000000000000010", + internalType: "address", + }, + ], + constant: true, + signature: "0x2cb7cb06", + stateMutability: "view", + }, + { + name: "delegationFee", + type: "function", + inputs: [], + outputs: [ + { + name: "", + type: "uint256", + value: "2000000000000000", + internalType: "uint256", + }, + ], + constant: true, + signature: "0xc5f0a58f", + stateMutability: "view", + }, + { + name: "owner", + type: "function", + inputs: [], + outputs: [ + { + name: "", + type: "address", + value: "0xCe4750fDc02A07Eb0d99cA798CD5c170D8F8410A", + internalType: "address", + }, + ], + constant: true, + signature: "0x8da5cb5b", + stateMutability: "view", + }, + { + name: "renounceOwnership", + type: "function", + inputs: [], + outputs: [], + signature: "0x715018a6", + stateMutability: "nonpayable", + }, + { + name: "setDelegationFee", + type: "function", + inputs: [ + { + name: "_delegationFee", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [], + signature: "0x55bfc81c", + stateMutability: "nonpayable", + }, + { + name: "transferOwnership", + type: "function", + inputs: [ + { name: "newOwner", type: "address", internalType: "address" }, + ], + outputs: [], + signature: "0xf2fde38b", + stateMutability: "nonpayable", + }, + { + name: "withdraw", + type: "function", + inputs: [ + { name: "_l2Token", type: "address", internalType: "address" }, + { name: "_amount", type: "uint256", internalType: "uint256" }, + { name: "_minGasLimit", type: "uint32", internalType: "uint32" }, + { name: "_extraData", type: "bytes", internalType: "bytes" }, + ], + outputs: [], + payable: true, + signature: "0x32b7006d", + stateMutability: "payable", + }, + { + name: "withdrawFee", + type: "function", + inputs: [ + { name: "_recipient", type: "address", internalType: "address" }, + ], + outputs: [], + signature: "0x1ac3ddeb", + stateMutability: "nonpayable", + }, + { + name: "withdrawFeeToL1", + type: "function", + inputs: [ + { name: "_recipient", type: "address", internalType: "address" }, + { name: "_minGasLimit", type: "uint32", internalType: "uint32" }, + { name: "_extraData", type: "bytes", internalType: "bytes" }, + ], + outputs: [], + signature: "0x244cafe0", + stateMutability: "nonpayable", + }, + { + name: "withdrawTo", + type: "function", + inputs: [ + { name: "_l2Token", type: "address", internalType: "address" }, + { name: "_to", type: "address", internalType: "address" }, + { name: "_amount", type: "uint256", internalType: "uint256" }, + { name: "_minGasLimit", type: "uint32", internalType: "uint32" }, + { name: "_extraData", type: "bytes", internalType: "bytes" }, + ], + outputs: [], + payable: true, + signature: "0xa3a79548", + stateMutability: "payable", + }, +] as const; + +export const ListaDaoAbi = [ + { inputs: [], stateMutability: "nonpayable", type: "constructor" }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "_account", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "_amount", + type: "uint256", + }, + ], + name: "ClaimAllWithdrawals", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint256", + name: "_uuid", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "_amount", + type: "uint256", + }, + ], + name: "ClaimUndelegated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "_validator", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "_uuid", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "_amount", + type: "uint256", + }, + ], + name: "ClaimUndelegatedFrom", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "_account", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "_idx", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "_amount", + type: "uint256", + }, + ], + name: "ClaimWithdrawal", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint256", + name: "_amount", + type: "uint256", + }, + ], + name: "Delegate", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "_validator", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "_amount", + type: "uint256", + }, + { + indexed: false, + internalType: "bool", + name: "_delegateVotePower", + type: "bool", + }, + ], + name: "DelegateTo", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "_delegateTo", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "_votesChange", + type: "uint256", + }, + ], + name: "DelegateVoteTo", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "_src", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "_amount", + type: "uint256", + }, + ], + name: "Deposit", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "_address", + type: "address", + }, + ], + name: "DisableValidator", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint8", + name: "version", + type: "uint8", + }, + ], + name: "Initialized", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "account", + type: "address", + }, + ], + name: "Paused", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "_address", + type: "address", + }, + ], + name: "ProposeManager", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "_src", + type: "address", + }, + { + indexed: false, + internalType: "address", + name: "_dest", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "_amount", + type: "uint256", + }, + ], + name: "ReDelegate", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint256", + name: "_rewardsId", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "_amount", + type: "uint256", + }, + ], + name: "Redelegate", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "_address", + type: "address", + }, + ], + name: "RemoveValidator", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "_account", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "_amountInSlisBnb", + type: "uint256", + }, + ], + name: "RequestWithdraw", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint256", + name: "_amount", + type: "uint256", + }, + ], + name: "RewardsCompounded", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "bytes32", + name: "role", + type: "bytes32", + }, + { + indexed: true, + internalType: "bytes32", + name: "previousAdminRole", + type: "bytes32", + }, + { + indexed: true, + internalType: "bytes32", + name: "newAdminRole", + type: "bytes32", + }, + ], + name: "RoleAdminChanged", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "bytes32", + name: "role", + type: "bytes32", + }, + { + indexed: true, + internalType: "address", + name: "account", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "sender", + type: "address", + }, + ], + name: "RoleGranted", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "bytes32", + name: "role", + type: "bytes32", + }, + { + indexed: true, + internalType: "address", + name: "account", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "sender", + type: "address", + }, + ], + name: "RoleRevoked", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint256", + name: "_annualRate", + type: "uint256", + }, + ], + name: "SetAnnualRate", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "_address", + type: "address", + }, + ], + name: "SetBSCValidator", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "_address", + type: "address", + }, + ], + name: "SetManager", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint256", + name: "_minBnb", + type: "uint256", + }, + ], + name: "SetMinBnb", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "_address", + type: "address", + }, + ], + name: "SetRedirectAddress", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint256", + name: "_amount", + type: "uint256", + }, + ], + name: "SetReserveAmount", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "_address", + type: "address", + }, + ], + name: "SetRevenuePool", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint256", + name: "_synFee", + type: "uint256", + }, + ], + name: "SetSynFee", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "_validator", + type: "address", + }, + { + indexed: false, + internalType: "address", + name: "_credit", + type: "address", + }, + { + indexed: false, + internalType: "bool", + name: "toRemove", + type: "bool", + }, + ], + name: "SyncCreditContract", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint256", + name: "_nextUndelegatedRequestIndex", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "_bnbAmount", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "_shares", + type: "uint256", + }, + ], + name: "Undelegate", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "_operator", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "_bnbAmount", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "_shares", + type: "uint256", + }, + ], + name: "UndelegateFrom", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint256", + name: "_amount", + type: "uint256", + }, + ], + name: "UndelegateReserve", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "account", + type: "address", + }, + ], + name: "Unpaused", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "_address", + type: "address", + }, + ], + name: "WhitelistValidator", + type: "event", + }, + { + inputs: [], + name: "BOT", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "DEFAULT_ADMIN_ROLE", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "GUARDIAN", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "TEN_DECIMALS", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "acceptNewManager", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "amountToDelegate", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "annualRate", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "_bnbAmount", type: "uint256" }, + ], + name: "binarySearchCoveredMaxIndex", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "_validator", type: "address" }, + ], + name: "claimUndelegated", + outputs: [ + { internalType: "uint256", name: "_uuid", type: "uint256" }, + { internalType: "uint256", name: "_amount", type: "uint256" }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "_idx", type: "uint256" }], + name: "claimWithdraw", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "_user", type: "address" }, + { internalType: "uint256", name: "_idx", type: "uint256" }, + ], + name: "claimWithdrawFor", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "compoundRewards", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "_operator", type: "address" }, + { internalType: "uint256", name: "_bnbAmount", type: "uint256" }, + ], + name: "convertBnbToShares", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "_amount", type: "uint256" }], + name: "convertBnbToSnBnb", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "_operator", type: "address" }, + { internalType: "uint256", name: "_shares", type: "uint256" }, + ], + name: "convertSharesToBnb", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_amountInSlisBnb", + type: "uint256", + }, + ], + name: "convertSnBnbToBnb", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "", type: "uint256" }], + name: "creditContracts", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "", type: "address" }], + name: "creditStates", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "_validator", type: "address" }, + { internalType: "uint256", name: "_amount", type: "uint256" }, + ], + name: "delegateTo", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "delegateVotePower", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "_delegateTo", type: "address" }, + ], + name: "delegateVoteTo", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "deposit", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [], + name: "depositReserve", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "_address", type: "address" }, + ], + name: "disableValidator", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "getAmountToUndelegate", + outputs: [ + { + internalType: "uint256", + name: "_amountToUndelegate", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "_uuid", type: "uint256" }], + name: "getBotUndelegateRequest", + outputs: [ + { + components: [ + { + internalType: "uint256", + name: "startTime", + type: "uint256", + }, + { + internalType: "uint256", + name: "endTime", + type: "uint256", + }, + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + { + internalType: "uint256", + name: "amountInSnBnb", + type: "uint256", + }, + ], + internalType: "struct IStakeManager.BotUndelegateRequest", + name: "", + type: "tuple", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "_validator", type: "address" }, + ], + name: "getClaimableAmount", + outputs: [ + { internalType: "uint256", name: "_amount", type: "uint256" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getContracts", + outputs: [ + { internalType: "address", name: "_manager", type: "address" }, + { internalType: "address", name: "_slisBnb", type: "address" }, + { internalType: "address", name: "_bscValidator", type: "address" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "_validator", type: "address" }, + ], + name: "getDelegated", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "_amount", type: "uint256" }], + name: "getRedelegateFee", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "bytes32", name: "role", type: "bytes32" }], + name: "getRoleAdmin", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getSlisBnbWithdrawLimit", + outputs: [ + { + internalType: "uint256", + name: "_slisBnbWithdrawLimit", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getTotalBnbInValidators", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getTotalPooledBnb", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "_user", type: "address" }, + { internalType: "uint256", name: "_idx", type: "uint256" }, + ], + name: "getUserRequestStatus", + outputs: [ + { internalType: "bool", name: "_isClaimable", type: "bool" }, + { internalType: "uint256", name: "_amount", type: "uint256" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "_address", type: "address" }, + ], + name: "getUserWithdrawalRequests", + outputs: [ + { + components: [ + { internalType: "uint256", name: "uuid", type: "uint256" }, + { + internalType: "uint256", + name: "amountInSnBnb", + type: "uint256", + }, + { + internalType: "uint256", + name: "startTime", + type: "uint256", + }, + ], + internalType: "struct IStakeManager.WithdrawalRequest[]", + name: "", + type: "tuple[]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "role", type: "bytes32" }, + { internalType: "address", name: "account", type: "address" }, + ], + name: "grantRole", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "role", type: "bytes32" }, + { internalType: "address", name: "account", type: "address" }, + ], + name: "hasRole", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "_slisBnb", type: "address" }, + { internalType: "address", name: "_admin", type: "address" }, + { internalType: "address", name: "_manager", type: "address" }, + { internalType: "address", name: "_bot", type: "address" }, + { internalType: "uint256", name: "_synFee", type: "uint256" }, + { internalType: "address", name: "_revenuePool", type: "address" }, + { internalType: "address", name: "_validator", type: "address" }, + ], + name: "initialize", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "minBnb", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "nextConfirmedRequestUUID", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "pause", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "paused", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "placeholder", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "_address", type: "address" }, + ], + name: "proposeNewManager", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "srcValidator", type: "address" }, + { internalType: "address", name: "dstValidator", type: "address" }, + { internalType: "uint256", name: "_amount", type: "uint256" }, + ], + name: "redelegate", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "redirectAddress", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "_address", type: "address" }, + ], + name: "removeValidator", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "role", type: "bytes32" }, + { internalType: "address", name: "account", type: "address" }, + ], + name: "renounceRole", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "", type: "uint256" }], + name: "requestIndexMap", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "requestUUID", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_amountInSlisBnb", + type: "uint256", + }, + ], + name: "requestWithdraw", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "reserveAmount", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "revenuePool", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "_address", type: "address" }, + ], + name: "revokeBotRole", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "role", type: "bytes32" }, + { internalType: "address", name: "account", type: "address" }, + ], + name: "revokeRole", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "_annualRate", type: "uint256" }, + ], + name: "setAnnualRate", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "_address", type: "address" }, + ], + name: "setBSCValidator", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "_address", type: "address" }, + ], + name: "setBotRole", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "_amount", type: "uint256" }], + name: "setMinBnb", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "_address", type: "address" }, + ], + name: "setRedirectAddress", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "amount", type: "uint256" }], + name: "setReserveAmount", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "_address", type: "address" }, + ], + name: "setRevenuePool", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "_synFee", type: "uint256" }], + name: "setSynFee", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes4", name: "interfaceId", type: "bytes4" }, + ], + name: "supportsInterface", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "synFee", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "togglePause", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "toggleVote", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "totalDelegated", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "totalReserveAmount", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "unbondingBnb", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "undelegate", + outputs: [ + { internalType: "uint256", name: "_uuid", type: "uint256" }, + { internalType: "uint256", name: "_amount", type: "uint256" }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "_operator", type: "address" }, + { internalType: "uint256", name: "_amount", type: "uint256" }, + ], + name: "undelegateFrom", + outputs: [ + { + internalType: "uint256", + name: "_actualBnbAmount", + type: "uint256", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "undelegatedQuota", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "", type: "address" }], + name: "validators", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "_address", type: "address" }, + ], + name: "whitelistValidator", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "amount", type: "uint256" }], + name: "withdrawReserve", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { stateMutability: "payable", type: "receive" }, +] as const; + +export interface IDeployERC20Params { + chain: SupportedChain; + name: string; + symbol: string; + decimals: number; + totalSupply: number; +} + +export interface IDeployERC721Params { + chain: SupportedChain; + name: string; + symbol: string; + baseURI: string; +} + +export interface IDeployERC1155Params { + chain: SupportedChain; + name: string; + baseURI: string; +} diff --git a/packages/plugin-bnb/src/utils/contracts.ts b/packages/plugin-bnb/src/utils/contracts.ts new file mode 100644 index 00000000000..74bb96519b8 --- /dev/null +++ b/packages/plugin-bnb/src/utils/contracts.ts @@ -0,0 +1,100 @@ +import { elizaLogger } from "@elizaos/core"; +import fs from "node:fs"; +import { createRequire } from "node:module"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import solc from "solc"; + +const require = createRequire(import.meta.url); +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const baseDir = path.resolve(__dirname, "../../plugin-bnb/src/contracts"); + +function getContractSource(contractPath: string) { + return fs.readFileSync(contractPath, "utf8"); +} + +function findImports(importPath: string) { + elizaLogger.log("importPath", importPath); + try { + if (importPath.startsWith("@openzeppelin/")) { + elizaLogger.log("in modPath"); + const modPath = require.resolve(importPath); + elizaLogger.log("modPath", modPath); + return { contents: fs.readFileSync(modPath, "utf8") }; + } + + const localPath = path.resolve("./contracts", importPath); + if (fs.existsSync(localPath)) { + return { contents: fs.readFileSync(localPath, "utf8") }; + } + return { error: "File not found" }; + } catch (e) { + return { error: `File not found: ${importPath}` }; + } +} + +export async function compileSolidity(contractFileName: string) { + const contractPath = path.join(baseDir, contractFileName + ".sol"); + elizaLogger.log("baseDir", __dirname, baseDir, contractPath); + + const source = getContractSource(contractPath); + + const input = { + language: "Solidity", + sources: { + [contractFileName]: { + content: source, + }, + }, + settings: { + optimizer: { + enabled: true, + runs: 200, + }, + outputSelection: { + "*": { + "*": ["*"], + }, + }, + }, + }; + + elizaLogger.log("Compiling contract..."); + + try { + const output = JSON.parse( + solc.compile(JSON.stringify(input), { import: findImports }) + ); + + if (output.errors) { + const hasError = output.errors.some( + (error) => error.type === "Error" + ); + if (hasError) { + throw new Error( + `Compilation errors: ${JSON.stringify(output.errors, null, 2)}` + ); + } + console.warn("Compilation warnings:", output.errors); + } + + const contractName = path.basename(contractFileName, ".sol"); + elizaLogger.log("contractFileName", contractFileName); + elizaLogger.log("contractName", contractName); + const contract = output.contracts[contractFileName][contractName]; + + if (!contract) { + throw new Error("Contract compilation result is empty"); + } + + elizaLogger.log("Contract compiled successfully"); + return { + abi: contract.abi, + bytecode: contract.evm.bytecode.object, + }; + } catch (error) { + console.error("Compilation failed:", error); + throw error; + } +} diff --git a/packages/plugin-bnb/tsconfig.json b/packages/plugin-bnb/tsconfig.json new file mode 100644 index 00000000000..8d95aebdba7 --- /dev/null +++ b/packages/plugin-bnb/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../core/tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "./src", + "typeRoots": [ + "./node_modules/@types", + "./src/types" + ], + "declaration": true, + "strictNullChecks": true + }, + "include": [ + "src" + ] +} \ No newline at end of file diff --git a/packages/plugin-bnb/tsup.config.ts b/packages/plugin-bnb/tsup.config.ts new file mode 100644 index 00000000000..a68ccd636ad --- /dev/null +++ b/packages/plugin-bnb/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", + "viem", + "@lifi/sdk", + ], +});