Skip to content

Commit

Permalink
feat: Wrap ETH to WETH on Base Action
Browse files Browse the repository at this point in the history
  • Loading branch information
John-peterson-coinbase committed Jan 12, 2025
1 parent 1335967 commit 81d6b11
Show file tree
Hide file tree
Showing 16 changed files with 210 additions and 31 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ The **Coinbase Developer Platform (CDP) Agentkit for Python** simplifies bringin
- Deploying [ERC-721](https://www.coinbase.com/learn/crypto-glossary/what-is-erc-721) tokens and minting NFTs
- Buying and selling [Zora Wow](https://wow.xyz/) ERC-20 coins
- Deploying tokens on [Zora's Wow Launcher](https://wow.xyz/mechanics) (Bonding Curve)
- Wrapping ETH to WETH on Base

Or [add your own](./CONTRIBUTING.md#adding-an-action-to-agentkit-core)!

Expand Down Expand Up @@ -50,4 +51,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for more information.
## Documentation
- [CDP Agentkit Documentation](https://docs.cdp.coinbase.com/agentkit/docs/welcome)
- [API Reference: CDP Agentkit Core](https://coinbase.github.io/cdp-agentkit/cdp-agentkit-core/index.html)
- [API Reference: CDP Agentkit LangChain Extension](https://coinbase.github.io/cdp-agentkit/cdp-langchain/index.html)
- [API Reference: CDP Agentkit LangChain Extension](https://coinbase.github.io/cdp-agentkit/cdp-langchain/index.html)
4 changes: 4 additions & 0 deletions cdp-agentkit-core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Added

- Added `wrap_eth` action to wrap ETH to WETH on Base.

## [0.0.7] - 2025-01-08

### Added
Expand Down
8 changes: 4 additions & 4 deletions cdp-agentkit-core/Makefile
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
.PHONY: format
format:
ruff format .
poetry run ruff format .

.PHONY: lint
lint:
ruff check .
poetry run ruff check .

.PHONY: lint-fix
lint-fix:
ruff check . --fix
poetry run ruff check . --fix

.PHONY: docs
docs:
sphinx-apidoc -f -o ./docs ./cdp_agentkit_core
poetry run sphinx-apidoc -f -o ./docs ./cdp_agentkit_core

.PHONY: local-docs
local-docs: docs
Expand Down
2 changes: 2 additions & 0 deletions cdp-agentkit-core/cdp_agentkit_core/actions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from cdp_agentkit_core.actions.wow.buy_token import WowBuyTokenAction
from cdp_agentkit_core.actions.wow.create_token import WowCreateTokenAction
from cdp_agentkit_core.actions.wow.sell_token import WowSellTokenAction
from cdp_agentkit_core.actions.wrap_eth import WrapEthAction


# WARNING: All new CdpAction subclasses must be imported above, otherwise they will not be discovered
Expand Down Expand Up @@ -40,4 +41,5 @@ def get_all_cdp_actions() -> list[type[CdpAction]]:
"WowBuyTokenAction",
"WowCreateTokenAction",
"WowSellTokenAction",
"WrapEthAction",
]
1 change: 1 addition & 0 deletions cdp-agentkit-core/cdp_agentkit_core/actions/deploy_nft.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
It takes the name of the NFT collection, the symbol of the NFT collection, and the base URI for the token metadata as inputs.
"""


class DeployNftInput(BaseModel):
"""Input argument schema for deploy NFT action."""

Expand Down
1 change: 1 addition & 0 deletions cdp-agentkit-core/cdp_agentkit_core/actions/get_balance.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
It takes the asset ID as input. Always use 'eth' for the native asset ETH and 'usdc' for USDC.
"""


class GetBalanceInput(BaseModel):
"""Input argument schema for get balance action."""

Expand Down
1 change: 1 addition & 0 deletions cdp-agentkit-core/cdp_agentkit_core/actions/mint_nft.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
Do not use the contract address as the destination address. If you are unsure of the destination address, please ask the user before proceeding.
"""


class MintNftInput(BaseModel):
"""Input argument schema for mint NFT action."""

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,12 @@ def account_details(client: tweepy.Client) -> str:

try:
response = client.get_me()
data = response['data']
data['url'] = f"https://x.com/{data['username']}"
data = response["data"]
data["url"] = f"https://x.com/{data['username']}"

message = f"""Successfully retrieved authenticated user account details:\n{dumps(response)}"""
message = (
f"""Successfully retrieved authenticated user account details:\n{dumps(response)}"""
)
except tweepy.errors.TweepyException as e:
message = f"Error retrieving authenticated user account details:\n{e}"

Expand Down
1 change: 1 addition & 0 deletions cdp-agentkit-core/cdp_agentkit_core/actions/transfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
- When sending native assets (e.g. 'eth' on base-mainnet), ensure there is sufficient balance for the transfer itself AND the gas cost of this transfer
"""


class TransferInput(BaseModel):
"""Input argument schema for transfer action."""

Expand Down
34 changes: 16 additions & 18 deletions cdp-agentkit-core/cdp_agentkit_core/actions/wow/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,14 @@ def get_buy_quote(network_id: str, token_address: str, amount_eth_in_wei: str):
"""
has_graduated = get_has_graduated(network_id, token_address)
token_quote = (
(has_graduated
and (get_uniswap_quote(network_id, token_address, amount_eth_in_wei, "buy")).amount_out)
or SmartContract.read(
network_id,
token_address,
"getEthBuyQuote",
abi=WOW_ABI,
args={"ethOrderSize": str(amount_eth_in_wei)},
)
has_graduated
and (get_uniswap_quote(network_id, token_address, amount_eth_in_wei, "buy")).amount_out
) or SmartContract.read(
network_id,
token_address,
"getEthBuyQuote",
abi=WOW_ABI,
args={"ethOrderSize": str(amount_eth_in_wei)},
)
return token_quote

Expand All @@ -56,14 +55,13 @@ def get_sell_quote(network_id: str, token_address: str, amount_tokens_in_wei: st
"""
has_graduated = get_has_graduated(network_id, token_address)
token_quote = (
(has_graduated
and (get_uniswap_quote(network_id, token_address, amount_tokens_in_wei, "sell")).amount_out)
or SmartContract.read(
network_id,
token_address,
"getTokenSellQuote",
WOW_ABI,
args={"tokenOrderSize": str(amount_tokens_in_wei)},
)
has_graduated
and (get_uniswap_quote(network_id, token_address, amount_tokens_in_wei, "sell")).amount_out
) or SmartContract.read(
network_id,
token_address,
"getTokenSellQuote",
WOW_ABI,
args={"tokenOrderSize": str(amount_tokens_in_wei)},
)
return token_quote
93 changes: 93 additions & 0 deletions cdp-agentkit-core/cdp_agentkit_core/actions/wrap_eth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
from collections.abc import Callable

from cdp import Wallet
from pydantic import BaseModel, Field

from cdp_agentkit_core.actions import CdpAction

WETH_ADDRESS = "0x4200000000000000000000000000000000000006"

WETH_ABI = [
{
"inputs": [],
"name": "deposit",
"outputs": [],
"stateMutability": "payable",
"type": "function",
},
{
"inputs": [
{
"name": "account",
"type": "address",
},
],
"name": "balanceOf",
"outputs": [
{
"type": "uint256",
},
],
"stateMutability": "view",
"type": "function",
},
]

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')
"""


class WrapEthInput(BaseModel):
"""Input argument schema for wrapping ETH to WETH."""

amount_to_wrap: str = Field(
...,
description="Amount of ETH to wrap in wei",
)


def wrap_eth(wallet: Wallet, amount_to_wrap: str) -> str:
"""Wrap ETH to WETH.
Args:
wallet (Wallet): The wallet to wrap ETH from.
amount_to_wrap (str): The amount of ETH to wrap in wei.
Returns:
str: A message containing the wrapped ETH details.
"""
try:
invocation = wallet.invoke_contract(
contract_address=WETH_ADDRESS,
method="deposit",
abi=WETH_ABI,
args={},
amount=amount_to_wrap,
asset_id="wei",
)
result = invocation.wait()
return f"Wrapped ETH with transaction hash: {result.transaction.transaction_hash}"
except Exception as e:
return f"Unexpected error wrapping ETH: {e!s}"


class WrapEthAction(CdpAction):
"""Wrap ETH to WETH action."""

name: str = "wrap_eth"
description: str = WRAP_ETH_PROMPT
args_schema: type[BaseModel] | None = WrapEthInput
func: Callable[..., str] = wrap_eth
74 changes: 74 additions & 0 deletions cdp-agentkit-core/tests/actions/test_wrap_eth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
from unittest.mock import patch

import pytest

from cdp_agentkit_core.actions.wrap_eth import (
WETH_ABI,
WETH_ADDRESS,
WrapEthAction,
WrapEthInput,
wrap_eth,
)


def test_wrap_eth_success(wallet_factory, contract_invocation_factory):
"""Test successful ETH wrapping."""
mock_wallet = wallet_factory()
mock_invocation = contract_invocation_factory()

mock_wallet.invoke_contract.return_value = mock_invocation
mock_invocation.wait.return_value = mock_invocation

amount = "1000000000000000000" # 1 ETH in wei

with (
patch.object(
mock_wallet, "invoke_contract", return_value=mock_invocation
) as mock_invoke_contract,
patch.object(mock_invocation, "wait", return_value=mock_invocation) as mock_invocation_wait,
):
result = wrap_eth(mock_wallet, amount)

mock_invoke_contract.assert_called_once_with(
contract_address=WETH_ADDRESS,
method="deposit",
abi=WETH_ABI,
args={},
amount=amount,
asset_id="wei",
)
mock_invocation_wait.assert_called_once_with()

assert result == f"Wrapped ETH with transaction hash: {mock_invocation.transaction_hash}"


def test_wrap_eth_failure(wallet_factory):
"""Test ETH wrapping failure."""
mock_wallet = wallet_factory()
mock_wallet.invoke_contract.side_effect = Exception("Test error")

amount = "1000000000000000000"
result = wrap_eth(mock_wallet, amount)

assert result == "Unexpected error wrapping ETH: Test error"


def test_wrap_eth_action_initialization():
"""Test WrapEthAction initialization and attributes."""
action = WrapEthAction()

assert action.name == "wrap_eth"
assert action.args_schema == WrapEthInput
assert callable(action.func)


def test_wrap_eth_input_model_valid():
"""Test WrapEthInput accepts valid parameters."""
valid_input = WrapEthInput(amount_to_wrap="1000000000000000000")
assert valid_input.amount_to_wrap == "1000000000000000000"


def test_wrap_eth_input_model_missing_params():
"""Test WrapEthInput raises error when params are missing."""
with pytest.raises(ValueError):
WrapEthInput()
8 changes: 4 additions & 4 deletions cdp-langchain/Makefile
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
.PHONY: format
format:
ruff format .
poetry run ruff format .

.PHONY: lint
lint:
ruff check .
poetry run ruff check .

.PHONY: lint-fix
lint-fix:
ruff check . --fix
poetry run ruff check . --fix

.PHONY: docs
docs:
sphinx-apidoc -f -o ./docs ./cdp_langchain
poetry run sphinx-apidoc -f -o ./docs ./cdp_langchain

.PHONY: local-docs
local-docs: docs
Expand Down
1 change: 1 addition & 0 deletions cdp-langchain/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ The toolkit provides the following tools:
10. **wow_create_token** - Deploy a token using Zora's Wow Launcher (Bonding Curve)
11. **wow_buy_token** - Buy Zora Wow ERC20 memecoin with ETH
12. **wow_sell_token** - Sell Zora Wow ERC20 memecoin for ETH
13. **wrap_eth** - Wrap ETH to WETH

### Using with an Agent

Expand Down
1 change: 1 addition & 0 deletions cdp-langchain/cdp_langchain/agent_toolkits/cdp_toolkit.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ class CdpToolkit(BaseToolkit):
wow_create_token
wow_buy_token
wow_sell_token
wrap_eth
Use within an agent:
.. code-block:: python
Expand Down
1 change: 0 additions & 1 deletion cdp-langchain/examples/chatbot/chatbot.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ def initialize_agent():
"recommend they go to docs.cdp.coinbase.com for more information. Be concise and helpful with your "
"responses. Refrain from restating your tools' descriptions unless it is explicitly requested."
),

), config


Expand Down

0 comments on commit 81d6b11

Please sign in to comment.