Skip to content

Commit

Permalink
chore: migrate wrap eth action (#189)
Browse files Browse the repository at this point in the history
  • Loading branch information
0xRAG authored and John-peterson-coinbase committed Feb 1, 2025
1 parent 1888ced commit f5ae1f4
Show file tree
Hide file tree
Showing 5 changed files with 227 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
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",
},
] as const;
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { z } from "zod";

export const WrapEthSchema = z
.object({
amountToWrap: z.string().describe("Amount of ETH to wrap in wei"),
})
.strip()
.describe("Instructions for wrapping ETH to WETH");
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { WrapEthSchema } from "./schemas";
import { EvmWalletProvider } from "../../wallet_providers";
import { encodeFunctionData } from "viem";
import { WETH_ABI, WETH_ADDRESS } from "./constants";
import { wethActionProvider } from "./wethActionProvider";

const MOCK_AMOUNT = "15";
const MOCK_ADDRESS = "0x1234567890123456789012345678901234543210";

describe("Wrap Eth Schema", () => {
it("should successfully parse valid input", () => {
const validInput = {
amountToWrap: MOCK_AMOUNT,
};

const result = WrapEthSchema.safeParse(validInput);

expect(result.success).toBe(true);
expect(result.data).toEqual(validInput);
});

it("should fail parsing empty input", () => {
const emptyInput = {};
const result = WrapEthSchema.safeParse(emptyInput);

expect(result.success).toBe(false);
});
});

describe("Wrap Eth Action", () => {
let mockWallet: jest.Mocked<EvmWalletProvider>;
const actionProvider = wethActionProvider();

beforeEach(async () => {
mockWallet = {
getAddress: jest.fn().mockReturnValue(MOCK_ADDRESS),
sendTransaction: jest.fn(),
waitForTransactionReceipt: jest.fn(),
} as unknown as jest.Mocked<EvmWalletProvider>;
});

it("should successfully respond", async () => {
const args = {
amountToWrap: MOCK_AMOUNT,
};

const hash = "0x1234567890123456789012345678901234567890";
mockWallet.sendTransaction.mockResolvedValue(hash);

const response = await actionProvider.wrapEth(mockWallet, args);

expect(mockWallet.sendTransaction).toHaveBeenCalledWith({
to: WETH_ADDRESS,
data: encodeFunctionData({
abi: WETH_ABI,
functionName: "deposit",
}),
value: BigInt(MOCK_AMOUNT),
});
expect(response).toContain(`Wrapped ETH with transaction hash: ${hash}`);
});

it("should fail with an error", async () => {
const args = {
amountToWrap: MOCK_AMOUNT,
};

const error = new Error("Failed to wrap ETH");
mockWallet.sendTransaction.mockRejectedValue(error);

const response = await actionProvider.wrapEth(mockWallet, args);

expect(mockWallet.sendTransaction).toHaveBeenCalledWith({
to: WETH_ADDRESS,
data: encodeFunctionData({
abi: WETH_ABI,
functionName: "deposit",
}),
value: BigInt(MOCK_AMOUNT),
});

expect(response).toContain(`Error wrapping ETH: ${error}`);
});
});

describe("supportsNetwork", () => {
const actionProvider = wethActionProvider();

it("should return true for base-mainnet", () => {
const result = actionProvider.supportsNetwork({
protocolFamily: "evm",
networkId: "base-mainnet",
});
expect(result).toBe(true);
});

it("should return true for base-sepolia", () => {
const result = actionProvider.supportsNetwork({
protocolFamily: "evm",
networkId: "base-sepolia",
});
expect(result).toBe(true);
});

it("should return false for non-base networks", () => {
const result = actionProvider.supportsNetwork({
protocolFamily: "evm",
networkId: "ethereum-mainnet",
});
expect(result).toBe(false);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { z } from "zod";
import { ActionProvider } from "../action_provider";
import { Network } from "../../wallet_providers/wallet_provider";
import { CreateAction } from "../action_decorator";
import { WrapEthSchema } from "./schemas";
import { WETH_ABI, WETH_ADDRESS } from "./constants";
import { encodeFunctionData, Hex } from "viem";
import { EvmWalletProvider } from "../../wallet_providers";

/**
* WethActionProvider is an action provider for WETH.
*/
export class WethActionProvider extends ActionProvider {
/**
* Constructor for the WethActionProvider.
*/
constructor() {
super("weth", []);
}

/**
* Wraps ETH to WETH.
*
* @param walletProvider - The wallet provider to use for the action.
* @param args - The input arguments for the action.
* @returns A message containing the transaction hash.
*/
@CreateAction({
name: "wrap_eth",
description: `
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-mainnet')
`,
schema: WrapEthSchema,
})
async wrapEth(
walletProvider: EvmWalletProvider,
args: z.infer<typeof WrapEthSchema>,
): Promise<string> {
try {
const hash = await walletProvider.sendTransaction({
to: WETH_ADDRESS as Hex,
data: encodeFunctionData({
abi: WETH_ABI,
functionName: "deposit",
}),
value: BigInt(args.amountToWrap),
});

await walletProvider.waitForTransactionReceipt(hash);

return `Wrapped ETH with transaction hash: ${hash}`;
} catch (error) {
return `Error wrapping ETH: ${error}`;
}
}

/**
* Checks if the Weth action provider supports the given network.
*
* @param network - The network to check.
* @returns True if the Weth action provider supports the network, false otherwise.
*/
supportsNetwork = (network: Network) =>
network.networkId === "base-mainnet" || network.networkId === "base-sepolia";
}

export const wethActionProvider = () => new WethActionProvider();

0 comments on commit f5ae1f4

Please sign in to comment.