diff --git a/cdp-agentkit-core/src/actions/cdp/index.ts b/cdp-agentkit-core/src/actions/cdp/index.ts index 8387c95..837652e 100644 --- a/cdp-agentkit-core/src/actions/cdp/index.ts +++ b/cdp-agentkit-core/src/actions/cdp/index.ts @@ -8,6 +8,7 @@ import { RegisterBasenameAction } from "./register_basename"; import { RequestFaucetFundsAction } from "./request_faucet_funds"; import { TradeAction } from "./trade"; import { TransferAction } from "./transfer"; +import { WrapEthAction } from "./wrap_eth"; import { WOW_ACTIONS } from "./defi/wow"; /** @@ -27,6 +28,7 @@ export function getAllCdpActions(): CdpAction[] { new RequestFaucetFundsAction(), new TradeAction(), new TransferAction(), + new WrapEthAction(), ]; } @@ -44,4 +46,5 @@ export { RequestFaucetFundsAction, TradeAction, TransferAction, + WrapEthAction, }; diff --git a/cdp-agentkit-core/src/actions/cdp/wrap_eth.ts b/cdp-agentkit-core/src/actions/cdp/wrap_eth.ts new file mode 100644 index 0000000..bfb1fba --- /dev/null +++ b/cdp-agentkit-core/src/actions/cdp/wrap_eth.ts @@ -0,0 +1,89 @@ +import { CdpAction } from "./cdp_action"; +import { Wallet } from "@coinbase/coinbase-sdk"; +import { z } from "zod"; + +export const WETH_ADDRESS = "0x4200000000000000000000000000000000000006"; + +export const WETH_ABI = [ + { + inputs: [], + name: "deposit", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + name: "account", + type: "address", + }, + ], + name: "balanceOf", + outputs: [ + { + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, +]; + +const WRAP_ETH_PROMPT = ` +This tool can only be used to wrap ETH to WETH. +Do not use this tool for any other purpose, or trading other assets. + +Inputs: +- Amount of ETH to wrap. + +Important notes: +- The amount is a string and cannot have any decimal points, since the unit of measurement is wei. +- Make sure to use the exact amount provided, and if there's any doubt, check by getting more information before continuing with the action. +- 1 wei = 0.000000000000000001 WETH +- Minimum purchase amount is 100000000000000 wei (0.0000001 WETH) +- Only supported on the following networks: + - Base Sepolia (ie, 'base-sepolia') + - Base Mainnet (ie, 'base', 'base-mainnnet') +`; + +export const WrapEthInput = z + .object({ + amountToWrap: z.string().describe("Amount of ETH to wrap in wei"), + }) + .strip() + .describe("Instructions for wrapping ETH to WETH"); + +/** + * Wraps ETH to WETH + * + * @param wallet - The wallet to create the token from. + * @param args - The input arguments for the action. + * @returns A message containing the wrapped ETH details. + */ +export async function wrapEth(wallet: Wallet, args: z.infer): Promise { + try { + const invocation = await wallet.invokeContract({ + contractAddress: WETH_ADDRESS, + method: "deposit", + abi: WETH_ABI, + args: {}, + amount: BigInt(args.amountToWrap), + assetId: "wei", + }); + const result = await invocation.wait(); + return `Wrapped ETH with transaction hash: ${result.getTransaction().getTransactionHash()}`; + } catch (error) { + return `Error wrapping ETH: ${error}`; + } +} + +/** + * Wrap ETH to WETH on Base action. + */ +export class WrapEthAction implements CdpAction { + public name = "wrap_eth"; + public description = WRAP_ETH_PROMPT; + public argsSchema = WrapEthInput; + public func = wrapEth; +} diff --git a/cdp-agentkit-core/src/tests/wrap_eth_test.ts b/cdp-agentkit-core/src/tests/wrap_eth_test.ts new file mode 100644 index 0000000..0d13eeb --- /dev/null +++ b/cdp-agentkit-core/src/tests/wrap_eth_test.ts @@ -0,0 +1,80 @@ +import { ContractInvocation, Wallet } from "@coinbase/coinbase-sdk"; + +import { WETH_ABI, WETH_ADDRESS, wrapEth, WrapEthInput } from "../actions/cdp/wrap_eth"; + +const MOCK_AMOUNT_TO_WRAP = "100000000000000000"; + +describe("Wrap Eth", () => { + it("should successfully parse valid input", () => { + const validInput = { + amountToWrap: MOCK_AMOUNT_TO_WRAP, + }; + + const result = WrapEthInput.safeParse(validInput); + + expect(result.success).toBe(true); + expect(result.data).toEqual(validInput); + }); + + it("should fail parsing empty input", () => { + const emptyInput = {}; + const result = WrapEthInput.safeParse(emptyInput); + + expect(result.success).toBe(false); + }); +}); + +describe("Wrap Eth Action", () => { + const TRANSACTION_HASH = "0xghijkl987654321"; + + let mockContractInvocation: jest.Mocked; + let mockWallet: jest.Mocked; + + beforeEach(() => { + mockWallet = { + invokeContract: jest.fn(), + } as unknown as jest.Mocked; + + mockContractInvocation = { + wait: jest.fn().mockResolvedValue({ + getTransaction: jest.fn().mockReturnValue({ + getTransactionHash: jest.fn().mockReturnValue(TRANSACTION_HASH), + }), + }), + } as unknown as jest.Mocked; + + mockWallet.invokeContract.mockResolvedValue(mockContractInvocation); + }); + + it("should successfully wrap ETH", async () => { + const args = { + amountToWrap: MOCK_AMOUNT_TO_WRAP, + }; + + const response = await wrapEth(mockWallet, args); + + expect(mockWallet.invokeContract).toHaveBeenCalledWith({ + contractAddress: WETH_ADDRESS, + method: "deposit", + abi: WETH_ABI, + args: {}, + amount: BigInt(args.amountToWrap), + assetId: "wei", + }); + expect(response).toContain(`Wrapped ETH with transaction hash: ${TRANSACTION_HASH}`); + }); + + it("should fail with an error", async () => { + const args = { + amountToWrap: MOCK_AMOUNT_TO_WRAP, + }; + + const error = new Error("Failed to execute transfer"); + mockWallet.invokeContract.mockRejectedValue(error); + + const response = await wrapEth(mockWallet, args); + + expect(mockWallet.invokeContract).toHaveBeenCalled(); + expect(response).toContain(`Error wrapping ETH: ${error}`); + }); +}); diff --git a/cdp-langchain/README.md b/cdp-langchain/README.md index 8423fdc..e257c9c 100644 --- a/cdp-langchain/README.md +++ b/cdp-langchain/README.md @@ -61,6 +61,7 @@ The toolkit provides the following tools: 10. **wow_create_token** - Deploy a token using [Zora's Wow Launcher](https://wow.xyz/mechanics) (Bonding Curve) 11. **wow_buy_token** - Buy [Zora Wow](https://wow.xyz/) ERC-20 memecoin with ETH 12. **wow_sell_token** - Sell [Zora Wow](https://wow.xyz/) ERC-20 memecoin for ETH +13. **wrap_eth** - Wrap ETH as WETH ### Using with an Agent diff --git a/cdp-langchain/package.json b/cdp-langchain/package.json index 677109f..19d1226 100644 --- a/cdp-langchain/package.json +++ b/cdp-langchain/package.json @@ -7,7 +7,9 @@ "license": "Apache-2.0", "main": "dist/index.js", "types": "dist/index.d.ts", - "files": ["dist"], + "files": [ + "dist" + ], "scripts": { "build": "tsc", "lint": "npx --yes eslint -c .eslintrc.json src/**/*.ts", diff --git a/cdp-langchain/src/toolkits/cdp_toolkit.ts b/cdp-langchain/src/toolkits/cdp_toolkit.ts index 97b32b8..126b146 100644 --- a/cdp-langchain/src/toolkits/cdp_toolkit.ts +++ b/cdp-langchain/src/toolkits/cdp_toolkit.ts @@ -39,6 +39,7 @@ import { CdpTool } from "../tools/cdp_tool"; * // - wow_create_token * // - wow_buy_token * // - wow_sell_token + * // - wrap_eth * ``` */ export class CdpToolkit extends Toolkit {