Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support creating Zora wow tokens #30

Merged
merged 4 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cdp-agentkit-core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

### Added

- Added `wow_create_token` action.

## [0.0.1] - 2024-11-04

### Added
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 @@ -8,6 +8,7 @@
from cdp_agentkit_core.actions.request_faucet_funds import RequestFaucetFundsAction
from cdp_agentkit_core.actions.trade import TradeAction
from cdp_agentkit_core.actions.transfer import TransferAction
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 All @@ -33,5 +34,6 @@ def get_all_cdp_actions() -> list[type[CdpAction]]:
"RequestFaucetFundsAction",
"TradeAction",
"TransferAction",
"WowCreateTokenAction",
"CDP_ACTIONS",
]
Empty file.
193 changes: 193 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,193 @@
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]


GENERIC_TOKEN_METADATA_URI = "ipfs://QmY1GqprFYvojCcUEKgqHeDj9uhZD9jmYGrQTfA9vAE78J"
67 changes: 67 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,67 @@
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 (
GENERIC_TOKEN_METADATA_URI,
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 uses a bonding curve so there is no need to add liquidity to the pool upfront. 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, e.g. WowCoin",
)
symbol: str = Field(
...,
description="The symbol of the token to create, e.g. WOW",
)


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)

invocation = wallet.invoke_contract(
contract_address=factory_address,
method="deploy",
abi=WOW_FACTORY_ABI,
args={
"_tokenCreator": wallet.default_address.address_id,
"_platformReferrer": "0x0000000000000000000000000000000000000000",
"_tokenURI": GENERIC_TOKEN_METADATA_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
104 changes: 104 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,104 @@
from unittest.mock import patch

import pytest

from cdp_agentkit_core.actions.wow.constants import (
GENERIC_TOKEN_METADATA_URI,
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"


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": GENERIC_TOKEN_METADATA_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": GENERIC_TOKEN_METADATA_URI,
"_name": MOCK_NAME,
"_symbol": MOCK_SYMBOL,
},
)
2 changes: 2 additions & 0 deletions cdp-langchain/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

### Added

- Added `wow_create_token` action to the cdp toolkit.

## [0.0.1] - 2024-11-04

### Added
Expand Down
Loading
Loading