Skip to content

Commit

Permalink
Changes
Browse files Browse the repository at this point in the history
  • Loading branch information
rohan-agarwal-coinbase committed Nov 7, 2024
1 parent 9126e73 commit fae9162
Show file tree
Hide file tree
Showing 10 changed files with 366 additions and 2 deletions.
1 change: 1 addition & 0 deletions cdp-agentkit-core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

- Added `uniswap_v3_create_pool` action.
- Added `uniswap_v3_get_pool`, `uniswap_v3_get_pool_observe`, `uniswap_v3_get_pool_slot0`, and `uniswap_v3_get_pool_liquidity` actions.
- Added `wow_create_token` action.

## [0.0.1] - 2024-11-04

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 @@ -13,6 +13,7 @@
from cdp_agentkit_core.actions.uniswap_v3.get_pool_liquidity import UniswapV3GetPoolLiquidityAction
from cdp_agentkit_core.actions.uniswap_v3.get_pool_observe import UniswapV3GetPoolObserveAction
from cdp_agentkit_core.actions.uniswap_v3.get_pool_slot0 import UniswapV3GetPoolSlot0Action
from cdp_agentkit_core.actions.wow.create_token import WowCreateTokenAction


# WARNING: All new CdpAction subclasses must be imported above, otherwise they will not be discovered
Expand Down Expand Up @@ -43,5 +44,6 @@ def get_all_cdp_actions() -> list[type[CdpAction]]:
"UniswapV3GetPoolSlot0Action",
"UniswapV3GetPoolObserveAction",
"UniswapV3GetPoolLiquidityAction",
"WowCreateTokenAction",
"CDP_ACTIONS",
]
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
ACCOUNT_DETAILS_PROMPT = """
This tool will return account details for the currently authenticated Twitter (X) user context."""


class AccountDetailsInput(BaseModel):
"""Input argument schema for Twitter account details action."""

Expand All @@ -12,6 +13,7 @@ class AccountDetailsInput(BaseModel):
description="No input required, e.g. `` (empty string).",
)


def account_details(client: tweepy.Client) -> str:
"""Get the authenticated Twitter (X) user account details.
Expand Down
Empty file.
190 changes: 190 additions & 0 deletions cdp-agentkit-core/cdp_agentkit_core/actions/wow/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
WOW_FACTORY_ABI = [
{
"type": "constructor",
"inputs": [
{"name": "_tokenImplementation", "type": "address", "internalType": "address"},
{"name": "_bondingCurve", "type": "address", "internalType": "address"},
],
"stateMutability": "nonpayable",
},
{
"type": "function",
"name": "UPGRADE_INTERFACE_VERSION",
"inputs": [],
"outputs": [{"name": "", "type": "string", "internalType": "string"}],
"stateMutability": "view",
},
{
"type": "function",
"name": "bondingCurve",
"inputs": [],
"outputs": [{"name": "", "type": "address", "internalType": "address"}],
"stateMutability": "view",
},
{
"type": "function",
"name": "deploy",
"inputs": [
{"name": "_tokenCreator", "type": "address", "internalType": "address"},
{"name": "_platformReferrer", "type": "address", "internalType": "address"},
{"name": "_tokenURI", "type": "string", "internalType": "string"},
{"name": "_name", "type": "string", "internalType": "string"},
{"name": "_symbol", "type": "string", "internalType": "string"},
],
"outputs": [{"name": "", "type": "address", "internalType": "address"}],
"stateMutability": "payable",
},
{
"type": "function",
"name": "implementation",
"inputs": [],
"outputs": [{"name": "", "type": "address", "internalType": "address"}],
"stateMutability": "view",
},
{
"type": "function",
"name": "initialize",
"inputs": [{"name": "_owner", "type": "address", "internalType": "address"}],
"outputs": [],
"stateMutability": "nonpayable",
},
{
"type": "function",
"name": "owner",
"inputs": [],
"outputs": [{"name": "", "type": "address", "internalType": "address"}],
"stateMutability": "view",
},
{
"type": "function",
"name": "proxiableUUID",
"inputs": [],
"outputs": [{"name": "", "type": "bytes32", "internalType": "bytes32"}],
"stateMutability": "view",
},
{
"type": "function",
"name": "renounceOwnership",
"inputs": [],
"outputs": [],
"stateMutability": "nonpayable",
},
{
"type": "function",
"name": "tokenImplementation",
"inputs": [],
"outputs": [{"name": "", "type": "address", "internalType": "address"}],
"stateMutability": "view",
},
{
"type": "function",
"name": "transferOwnership",
"inputs": [{"name": "newOwner", "type": "address", "internalType": "address"}],
"outputs": [],
"stateMutability": "nonpayable",
},
{
"type": "function",
"name": "upgradeToAndCall",
"inputs": [
{"name": "newImplementation", "type": "address", "internalType": "address"},
{"name": "data", "type": "bytes", "internalType": "bytes"},
],
"outputs": [],
"stateMutability": "payable",
},
{
"type": "event",
"name": "Initialized",
"inputs": [
{"name": "version", "type": "uint64", "indexed": False, "internalType": "uint64"}
],
"anonymous": False,
},
{
"type": "event",
"name": "OwnershipTransferred",
"inputs": [
{
"name": "previousOwner",
"type": "address",
"indexed": True,
"internalType": "address",
},
{"name": "newOwner", "type": "address", "indexed": True, "internalType": "address"},
],
"anonymous": False,
},
{
"type": "event",
"name": "Upgraded",
"inputs": [
{
"name": "implementation",
"type": "address",
"indexed": True,
"internalType": "address",
}
],
"anonymous": False,
},
{
"type": "error",
"name": "AddressEmptyCode",
"inputs": [{"name": "target", "type": "address", "internalType": "address"}],
},
{"type": "error", "name": "ERC1167FailedCreateClone", "inputs": []},
{
"type": "error",
"name": "ERC1967InvalidImplementation",
"inputs": [{"name": "implementation", "type": "address", "internalType": "address"}],
},
{"type": "error", "name": "ERC1967NonPayable", "inputs": []},
{"type": "error", "name": "FailedInnerCall", "inputs": []},
{"type": "error", "name": "InvalidInitialization", "inputs": []},
{"type": "error", "name": "NotInitializing", "inputs": []},
{
"type": "error",
"name": "OwnableInvalidOwner",
"inputs": [{"name": "owner", "type": "address", "internalType": "address"}],
},
{
"type": "error",
"name": "OwnableUnauthorizedAccount",
"inputs": [{"name": "account", "type": "address", "internalType": "address"}],
},
{"type": "error", "name": "ReentrancyGuardReentrantCall", "inputs": []},
{"type": "error", "name": "UUPSUnauthorizedCallContext", "inputs": []},
{
"type": "error",
"name": "UUPSUnsupportedProxiableUUID",
"inputs": [{"name": "slot", "type": "bytes32", "internalType": "bytes32"}],
},
]

WOW_FACTORY_CONTRACT_ADDRESSES = {
"base-sepolia": "0x04870e22fa217Cb16aa00501D7D5253B8838C1eA",
"base-mainnet": "0x997020E5F59cCB79C74D527Be492Cc610CB9fA2B",
}


def get_factory_address(network: str) -> str:
"""Get the Zora Wow ERC20 Factory contract address for the specified network.
Args:
network (str): The network ID to get the contract address for.
Valid networks are: base-sepolia, base-mainnet.
Returns:
str: The contract address for the specified network.
Raises:
ValueError: If the specified network is not supported.
"""
network = network.lower()
if network not in WOW_FACTORY_CONTRACT_ADDRESSES:
raise ValueError(
f"Invalid network: {network}. Valid networks are: {', '.join(WOW_FACTORY_CONTRACT_ADDRESSES.keys())}"
)
return WOW_FACTORY_CONTRACT_ADDRESSES[network]
66 changes: 66 additions & 0 deletions cdp-agentkit-core/cdp_agentkit_core/actions/wow/create_token.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from collections.abc import Callable

from cdp import Wallet
from pydantic import BaseModel, Field

from cdp_agentkit_core.actions import CdpAction
from cdp_agentkit_core.actions.wow.constants import WOW_FACTORY_ABI, get_factory_address

WOW_CREATE_TOKEN_PROMPT = """
This tool will create a Zora Wow ERC20 memecoin using the WoW factory. This tool takes the token name and token symbol. It is only supported on Base Sepolia and Base Mainnet.
"""


class WowCreateTokenInput(BaseModel):
"""Input argument schema for create token action."""

name: str = Field(
...,
description="The name of the token to create.",
)
symbol: str = Field(
...,
description="The symbol of the token to create.",
)


def wow_create_token(wallet: Wallet, name: str, symbol: str) -> str:
"""Create a Zora Wow ERC20 memecoin.
Args:
wallet (Wallet): The wallet to create the token from.
name (str): The name of the token to create.
symbol (str): The symbol of the token to create.
Returns:
str: A message containing the token creation details.
"""
factory_address = get_factory_address(wallet.network_id)

# Generic JSON metadata for all tokens
token_uri = "ipfs://QmY1GqprFYvojCcUEKgqHeDj9uhZD9jmYGrQTfA9vAE78J"

invocation = wallet.invoke_contract(
contract_address=factory_address,
method="deploy",
abi=WOW_FACTORY_ABI,
args={
"_tokenCreator": wallet.default_address.address_id,
"_platformReferrer": "0x0000000000000000000000000000000000000000",
"_tokenURI": token_uri,
"_name": name,
"_symbol": symbol,
},
).wait()

return f"Created WoW ERC20 memecoin {name} with symbol {symbol} on network {wallet.network_id}.\nTransaction hash for the token creation: {invocation.transaction.transaction_hash}\nTransaction link for the token creation: {invocation.transaction.transaction_link}"


class WowCreateTokenAction(CdpAction):
"""Zora Wow create token action."""

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

import pytest

from cdp_agentkit_core.actions.wow.constants import WOW_FACTORY_ABI, get_factory_address
from cdp_agentkit_core.actions.wow.create_token import (
WowCreateTokenInput,
wow_create_token,
)

MOCK_NAME = "Test Token"
MOCK_SYMBOL = "TEST"
MOCK_NETWORK_ID = "base-sepolia"
MOCK_WALLET_ADDRESS = "0x1234567890123456789012345678901234567890"
MOCK_TOKEN_URI = "ipfs://QmY1GqprFYvojCcUEKgqHeDj9uhZD9jmYGrQTfA9vAE78J"


def test_create_token_input_model_valid():
"""Test that CreateTokenInput accepts valid parameters."""
input_model = WowCreateTokenInput(
name=MOCK_NAME,
symbol=MOCK_SYMBOL,
)

assert input_model.name == MOCK_NAME
assert input_model.symbol == MOCK_SYMBOL


def test_create_token_input_model_missing_params():
"""Test that CreateTokenInput raises error when params are missing."""
with pytest.raises(ValueError):
WowCreateTokenInput()


def test_create_token_success(wallet_factory, contract_invocation_factory):
"""Test successful token creation with valid parameters."""
mock_wallet = wallet_factory()
mock_contract_instance = contract_invocation_factory()
mock_wallet.default_address.address_id = MOCK_WALLET_ADDRESS
mock_wallet.network_id = MOCK_NETWORK_ID

with (
patch.object(
mock_wallet, "invoke_contract", return_value=mock_contract_instance
) as mock_invoke,
patch.object(
mock_contract_instance, "wait", return_value=mock_contract_instance
) as mock_contract_wait,
):
action_response = wow_create_token(
mock_wallet,
MOCK_NAME,
MOCK_SYMBOL,
)

expected_response = f"Created WoW ERC20 memecoin {MOCK_NAME} with symbol {MOCK_SYMBOL} on network {MOCK_NETWORK_ID}.\nTransaction hash for the token creation: {mock_contract_instance.transaction.transaction_hash}\nTransaction link for the token creation: {mock_contract_instance.transaction.transaction_link}"
assert action_response == expected_response

mock_invoke.assert_called_once_with(
contract_address=get_factory_address(MOCK_NETWORK_ID),
method="deploy",
abi=WOW_FACTORY_ABI,
args={
"_tokenCreator": MOCK_WALLET_ADDRESS,
"_platformReferrer": "0x0000000000000000000000000000000000000000",
"_tokenURI": MOCK_TOKEN_URI,
"_name": MOCK_NAME,
"_symbol": MOCK_SYMBOL,
},
)
mock_contract_wait.assert_called_once_with()


def test_create_token_api_error(wallet_factory):
"""Test create_token when API error occurs."""
mock_wallet = wallet_factory()
mock_wallet.default_address.address_id = MOCK_WALLET_ADDRESS
mock_wallet.network_id = MOCK_NETWORK_ID

with patch.object(
mock_wallet, "invoke_contract", side_effect=Exception("API error")
) as mock_invoke:
with pytest.raises(Exception, match="API error"):
wow_create_token(
mock_wallet,
MOCK_NAME,
MOCK_SYMBOL,
)

mock_invoke.assert_called_once_with(
contract_address=get_factory_address(MOCK_NETWORK_ID),
method="deploy",
abi=WOW_FACTORY_ABI,
args={
"_tokenCreator": MOCK_WALLET_ADDRESS,
"_platformReferrer": "0x0000000000000000000000000000000000000000",
"_tokenURI": MOCK_TOKEN_URI,
"_name": MOCK_NAME,
"_symbol": MOCK_SYMBOL,
},
)
Loading

0 comments on commit fae9162

Please sign in to comment.