diff --git a/python/create-onchain-agent/changelog.d/498.feature.md b/python/create-onchain-agent/changelog.d/498.feature.md new file mode 100644 index 000000000..108af6c60 --- /dev/null +++ b/python/create-onchain-agent/changelog.d/498.feature.md @@ -0,0 +1 @@ +Added revised network selection diff --git a/python/create-onchain-agent/create_onchain_agent/cli.py b/python/create-onchain-agent/create_onchain_agent/cli.py index 4db0b93cf..2e932cef3 100755 --- a/python/create-onchain-agent/create_onchain_agent/cli.py +++ b/python/create-onchain-agent/create_onchain_agent/cli.py @@ -39,18 +39,18 @@ # "selected": "bold cyan", # Selected option }) -NETWORK_CHOICES = [ - ("Ethereum Mainnet", "ethereum-mainnet"), - ("Ethereum Sepolia", "ethereum-sepolia"), - ("Polygon Mainnet", "polygon-mainnet"), - ("Polygon Mumbai", "polygon-mumbai"), - ("Base Mainnet", "base-mainnet"), - ("Base Sepolia (default)", "base-sepolia"), - ("Arbitrum Mainnet", "arbitrum-mainnet"), - ("Arbitrum Sepolia", "arbitrum-sepolia"), - ("Optimism Mainnet", "optimism-mainnet"), - ("Optimism Sepolia", "optimism-sepolia"), - ("Other (Enter EVM Chain ID)", "other"), +# Network constants +EVM_NETWORKS = [ + ("base-mainnet", "Base Mainnet"), + ("base-sepolia", "Base Sepolia"), + ("ethereum-mainnet", "Ethereum Mainnet"), + ("ethereum-sepolia", "Ethereum Sepolia"), + ("arbitrum-mainnet", "Arbitrum Mainnet"), + ("arbitrum-sepolia", "Arbitrum Sepolia"), + ("optimism-mainnet", "Optimism Mainnet"), + ("optimism-sepolia", "Optimism Sepolia"), + ("polygon-mainnet", "Polygon Mainnet"), + ("polygon-mumbai", "Polygon Mumbai"), ] CDP_SUPPORTED_NETWORKS = { @@ -64,8 +64,26 @@ VALID_PACKAGE_NAME_REGEX = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]*$") -def download_and_extract_template(): - """Downloads and extracts the chatbot template to a persistent location.""" +def get_template_path(template_path: str | None = None) -> str: + """Gets the template path either from a local directory or downloaded from GitHub. + + Args: + template_path: Optional path to local template directory + + Returns: + str: Path to the template directory + """ + if template_path: + # Use provided template path + local_template_path = Path(template_path) + if not local_template_path.exists(): + raise FileNotFoundError( + f"Template path not found at {local_template_path}. " + "Please provide a valid template directory path." + ) + return str(local_template_path) + + # No template provided - download from GitHub LOCAL_CACHE_DIR.mkdir(parents=True, exist_ok=True) zip_path = LOCAL_CACHE_DIR / "repo.zip" extract_path = LOCAL_CACHE_DIR / "templates" @@ -94,8 +112,17 @@ def download_and_extract_template(): return str(extract_path) +def get_network_choices(network_type: str) -> list: + """Filter network choices based on network type (mainnet/testnet).""" + return [ + (name, id) for id, name in EVM_NETWORKS + if (network_type == "mainnet" and "mainnet" in id) or + (network_type == "testnet" and any(net in id for net in ["sepolia", "mumbai", "devnet", "testnet"])) + ] + @click.command() -def create_project(): +@click.option('--template', type=str, help='Path to local template directory', default=None) +def create_project(template): """Creates a new onchain agent project with interactive prompts.""" ascii_art = """ @@ -131,56 +158,95 @@ def create_project(): else: package_name = suggested_package_name - # Select network using arrow keys - # console.print("\n[cyan]Select a network:[/cyan]") - - network_name = questionary.select( - "Choose a network network:", - choices=[name for name, _ in NETWORK_CHOICES], - default="Base Sepolia (default)", - style=custom_style # Apply custom styling + # Choose network type + network_type = questionary.select( + "Choose network type:", + choices=[ + "Mainnet", + "Testnet", + "Custom Chain ID", + ], + style=custom_style ).ask() - # Map selection to network key - network = next(n for n in NETWORK_CHOICES if n[0] == network_name)[1] + network = None + chain_id = None + rpc_url = None - # If "Other" is selected, prompt for EVM Chain ID - if network == "other": - network = questionary.text( - "Enter the EVM Chain ID for your custom network:", + if network_type == "Custom Chain ID": + # Handle custom EVM network + chain_id = questionary.text( + "Enter your chain ID:", + validate=lambda text: text.strip().isdigit() or "Chain ID must be a number", style=custom_style - ).ask().strip() - - # Determine wallet provider - if network in CDP_SUPPORTED_NETWORKS: - wallet_provider = questionary.select( - "Select a wallet provider:", - choices=["CDP Wallet Provider", "Ethereum Account Wallet Provider"], - default="CDP Wallet Provider", - style=custom_style # Apply custom styling ).ask() - wallet_provider = "cdp" if wallet_provider.startswith("CDP") else "eth" + rpc_url = questionary.text( + "Enter your RPC URL:", + validate=lambda text: ( + text.strip().startswith(("http://", "https://")) or + "RPC URL must start with http:// or https://" + ), + style=custom_style + ).ask() + + wallet_provider = "eth" # Default to eth wallet provider for custom networks else: - console.print(f"[yellow]⚠️ CDP is not supported on {network}. Defaulting to Ethereum Account Wallet Provider.[/yellow]") - wallet_provider = "eth" + # Filter networks based on mainnet/testnet selection + network_choices = get_network_choices(network_type.lower()) + network_name = questionary.select( + "Choose a network:", + choices=[ + name + (" (default)" if id == "base-sepolia" else "") + for name, id in network_choices + ], + default="Base Sepolia (default)" if network_type == "Testnet" else None, + style=custom_style + ).ask() + # Remove " (default)" suffix if present + network_name = network_name.replace(" (default)", "") + network = next(id for name, id in network_choices if name == network_name) + + # Determine wallet provider + if network: + if network in CDP_SUPPORTED_NETWORKS: + wallet_choices = [ + "CDP Wallet Provider", + "Ethereum Account Wallet Provider" + ] + wallet_selection = questionary.select( + "Select a wallet provider:", + choices=wallet_choices, + default="CDP Wallet Provider", + style=custom_style + ).ask() + wallet_provider = "cdp" if wallet_selection.startswith("CDP") else "eth" + else: + console.print(f"[yellow]⚠️ CDP is not supported on {network}. Defaulting to Ethereum Account Wallet Provider.[/yellow]") + wallet_provider = "eth" console.print(f"\n[blue]Creating your onchain agent project: {project_name}[/blue]") - template_path = download_and_extract_template() - - # Run Copier with collected answers - run_copy( - template_path, - project_path, - data={ - "_project_name": project_name, - "_package_name": package_name, - "_network": network, - "_wallet_provider": wallet_provider, - }, - ) + # Update the Copier data dict to include new fields + copier_data = { + "_project_name": project_name, + "_package_name": package_name, + "_network": network, + "_wallet_provider": wallet_provider, + } + + if chain_id: + copier_data["_chain_id"] = chain_id + if rpc_url: + copier_data["_rpc_url"] = rpc_url + + try: + template_path = get_template_path(template) + run_copy(template_path, project_path, data=copier_data) + except FileNotFoundError as e: + console.print(f"[red]Error: {str(e)}[/red]") + return console.print(f"[bold blue]Successfully created your AgentKit project in {project_path}[/bold blue]") diff --git a/python/create-onchain-agent/templates/chatbot/.env.local.jinja b/python/create-onchain-agent/templates/chatbot/.env.local.jinja index 65653e50a..14d21bf1a 100644 --- a/python/create-onchain-agent/templates/chatbot/.env.local.jinja +++ b/python/create-onchain-agent/templates/chatbot/.env.local.jinja @@ -14,7 +14,15 @@ CDP_API_KEY_PRIVATE_KEY= PRIVATE_KEY= {% endif %} ## Optional +{% if _network %} NETWORK={{ _network }} +{% endif %} +{% if _chain_id %} +CHAIN_ID={{ _chain_id }} +{% endif %} +{% if _rpc_url %} +RPC_URL={{ _rpc_url }} +{% endif %} {% if _wallet_provider == "eth" %} # Place your CDP API key name here if you want to use the CDPApiActionProvider CDP_API_KEY_NAME= diff --git a/python/create-onchain-agent/templates/chatbot/chatbot.py.jinja b/python/create-onchain-agent/templates/chatbot/chatbot.py.jinja index 84d7ba6e7..a415edac9 100644 --- a/python/create-onchain-agent/templates/chatbot/chatbot.py.jinja +++ b/python/create-onchain-agent/templates/chatbot/chatbot.py.jinja @@ -31,6 +31,7 @@ from coinbase_agentkit import ( from coinbase_agentkit_langchain import get_langchain_tools {% if _wallet_provider == "eth" %} from eth_account import Account +from coinbase_agentkit.network import NETWORK_ID_TO_CHAIN_ID, NETWORK_ID_TO_CHAIN, CHAIN_ID_TO_NETWORK_ID {% endif %} """ AgentKit Integration @@ -119,10 +120,41 @@ def initialize_agent(): # Create Ethereum account from private key account = Account.from_key(private_key) + # Get chain configuration + chain_id = os.getenv("CHAIN_ID") + rpc_url = os.getenv("RPC_URL") + network = os.getenv("NETWORK") + + if chain_id and rpc_url: + pass + elif network: + chain_id = NETWORK_ID_TO_CHAIN_ID.get(network) + if not chain_id: + raise ValueError(f"Unknown network ID: {network}") + + if not rpc_url: + chain = NETWORK_ID_TO_CHAIN[network] + rpc_url = chain["rpc_urls"]["default"]["http"][0] + elif chain_id: + # Try to find the network ID from the chain ID + network = CHAIN_ID_TO_NETWORK_ID.get(chain_id) + if network: + chain = NETWORK_ID_TO_CHAIN[network] + rpc_url = chain["rpc_urls"]["default"]["http"][0] + else: + raise ValueError("When using chain_id, you must also provide an RPC_URL if the chain is not recognized") + else: + print("No network configuration provided. Defaulting to Base Sepolia...") + network = "base-sepolia" + chain_id = NETWORK_ID_TO_CHAIN_ID[network] + chain = NETWORK_ID_TO_CHAIN[network] + rpc_url = chain["rpc_urls"]["default"]["http"][0] + wallet_provider = EthAccountWalletProvider( config=EthAccountWalletProviderConfig( account=account, - chain_id="84532" + chain_id=chain_id, + rpc_url=rpc_url ) ) {% endif %} diff --git a/python/create-onchain-agent/templates/chatbot/pyproject.toml.jinja b/python/create-onchain-agent/templates/chatbot/pyproject.toml.jinja index df25fb9a1..9b030ab17 100644 --- a/python/create-onchain-agent/templates/chatbot/pyproject.toml.jinja +++ b/python/create-onchain-agent/templates/chatbot/pyproject.toml.jinja @@ -11,7 +11,7 @@ python = "^3.10" python-dotenv = "^1.0.1" langchain-openai = "^0.2.4" langgraph = "^0.2.39" -coinbase-agentkit = "0.1.2" +coinbase-agentkit = "^0.1.4" coinbase-agentkit-langchain = "0.1.0" [tool.poetry.scripts]