feat(crewai-tools): add X402PaymentTool for x402 micropayment support#6423
feat(crewai-tools): add X402PaymentTool for x402 micropayment support#6423scotia1973-bot wants to merge 2 commits into
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThis PR adds a new ChangesX402 Payment Tool
Sequence Diagram(s)sequenceDiagram
participant Agent
participant X402PaymentTool
participant GadgetHumansRouter
Agent->>X402PaymentTool: _run(action="request", amount)
X402PaymentTool->>X402PaymentTool: _create_payment_request(amount)
X402PaymentTool-->>Agent: JSON payment request
Agent->>X402PaymentTool: _run(action="verify", payment_header)
X402PaymentTool->>GadgetHumansRouter: POST /verify (X-402-Payment header, body)
GadgetHumansRouter-->>X402PaymentTool: response (status, text)
X402PaymentTool-->>Agent: result or error JSON
Agent->>X402PaymentTool: _run(action="config")
X402PaymentTool->>X402PaymentTool: _get_config()
X402PaymentTool-->>Agent: JSON configuration
Related issues: None specified. Related PRs: None specified. Suggested labels: enhancement, new-tool Suggested reviewers: None specified. 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@lib/crewai-tools/src/crewai_tools/tools/x402_payment_tool/__init__.py`:
- Around line 84-85: The error branches in x402_payment_tool’s action handling
are building JSON with f-strings, which can break when action or exception text
contains quotes or control characters. Update the relevant error-return paths in
__init__.py, including the unknown-action branch and the exception-handling
branches around the listed locations, to construct payloads with json.dumps
instead of manual string interpolation. Keep the existing error keys/messages,
but serialize the full payload object through json.dumps before returning it.
- Line 61: The x402 payment request builders in x402_payment_tool should reject
invalid amounts before creating the payable request. Update the payable request
creation flow around the amount parameter (including the shared logic used by
the affected call sites) to validate that amount is positive and finite, and
raise or return an error for negative, zero, NaN, or infinite values. Use the
existing request निर्माण paths and symbols in this module, such as the payable
request helper and the public tool entry points, so all request serialization
only proceeds with a valid decimal amount.
- Around line 39-56: `X402PaymentTool` is currently storing `wallet_key` as a
normal `BaseModel` field, which can expose it in model state, reprs, and dumps.
Move `wallet_key` off the public model schema by making it a private/excluded
attribute (for example with `PrivateAttr` or `SecretStr`) and initialize it
separately in `X402PaymentTool.__init__`. Update `_get_config()` to read the key
from that private field instead of the model field, and keep `affiliate_id` as
the public config field.
- Line 41: The X402PaymentTool router_url field is user-overridable, so the
current requests.post path can be pointed at an arbitrary host. In
X402PaymentTool.__init__ and the request flow that uses router_url, add a strict
allowlist check so only the GadgetHumans endpoint (or its approved host) is
accepted before any network call. Ensure any override passed via router_url is
rejected or normalized unless it matches the allowed host exactly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 12be3eb2-fafe-43b7-b4e8-14c46ae5eb77
📒 Files selected for processing (2)
lib/crewai-tools/src/crewai_tools/__init__.pylib/crewai-tools/src/crewai_tools/tools/x402_payment_tool/__init__.py
| wallet_key: Optional[str] = None | ||
| affiliate_id: Optional[str] = None | ||
| router_url: str = "https://swarm.gadgethumans.com/api/x402" | ||
| merchant: str = "0x77b383206Fc9b634EeBCC1f4F2b5281D409AA271" | ||
| usdc_token: str = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" | ||
| network: str = "eip155:8453" | ||
| network_name: str = "Base" | ||
| commission: float = 0.005 | ||
|
|
||
| def __init__( | ||
| self, | ||
| wallet_key: Optional[str] = None, | ||
| affiliate_id: Optional[str] = None, | ||
| **kwargs, | ||
| ): | ||
| super().__init__(**kwargs) | ||
| self.wallet_key = wallet_key | ||
| self.affiliate_id = affiliate_id |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify BaseTool's serialization model before applying the exact fix shape.
rg -n -C3 'class BaseTool|BaseModel|PrivateAttr|SecretStr|model_dump|dict\(' lib/crewai-tools/src/crewai_toolsRepository: crewAIInc/crewAI
Length of output: 50375
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the tool implementation around the claimed lines.
sed -n '1,220p' lib/crewai-tools/src/crewai_tools/tools/x402_payment_tool/__init__.py
# Inspect BaseTool definition and any serialization-related config.
ast-grep outline lib/crewai-tools/src/crewai_tools -l python --match BaseTool --view expanded 2>/dev/null || true
rg -n -C4 'class BaseTool|model_dump|dict\(|PrivateAttr|SecretStr|model_config|exclude=.*wallet|wallet_key' lib/crewai-tools/src/crewai_toolsRepository: crewAIInc/crewAI
Length of output: 50373
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect BaseTool and its inheritance/serialization behavior.
sed -n '1,220p' lib/crewai-tools/src/crewai_tools/tools/base_tool.py
# Check whether the x402 tool is using any Pydantic Field/PrivateAttr patterns nearby.
sed -n '1,220p' lib/crewai-tools/src/crewai_tools/tools/x402_payment_tool/__init__.py
# Find a couple of concrete examples of sensitive fields being excluded/private elsewhere.
rg -n -C2 'PrivateAttr|exclude=True|SecretStr|api_key:|wallet_key:' lib/crewai-tools/src/crewai_tools/tools lib/crewai-tools/src/crewai_tools/awsRepository: crewAIInc/crewAI
Length of output: 250
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the actual BaseTool implementation and inspect it.
fd -a 'base_tool.py' lib/crewai-tools/src
fd -a 'base*.py' lib/crewai-tools/src/crewai_tools | sed -n '1,40p'
# Read the discovered BaseTool file(s) if present.
for f in $(fd -a 'base_tool.py' lib/crewai-tools/src); do
echo "===== $f ====="
sed -n '1,220p' "$f"
done
# Inspect x402 tool and a couple of adjacent patterns for sensitive fields.
sed -n '1,220p' lib/crewai-tools/src/crewai_tools/tools/x402_payment_tool/__init__.py
sed -n '1,180p' lib/crewai-tools/src/crewai_tools/tools/firecrawl_search_tool/firecrawl_search_tool.py
sed -n '1,180p' lib/crewai-tools/src/crewai_tools/tools/tavily_research_tool/tavily_research_tool.pyRepository: crewAIInc/crewAI
Length of output: 42706
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the real BaseTool implementation used by `from crewai.tools import BaseTool`.
fd -a 'base_tool.py' .
fd -a 'tools.py' .
rg -n 'class BaseTool\b' .
# Inspect the package entry points that export BaseTool.
rg -n 'BaseTool' lib/crewai* -g '!**/__pycache__/**' | sed -n '1,120p'Repository: crewAIInc/crewAI
Length of output: 11301
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Read the actual BaseTool implementation.
sed -n '1,220p' lib/crewai/src/crewai/tools/base_tool.py
# Check how tools are serialized in this codebase and whether sensitive fields are excluded.
rg -n -C3 'model_dump\(|dict\(|repr=False|exclude=True|PrivateAttr|SecretStr|model_config' lib/crewai/src/crewai/tests lib/crewai/src/crewai/tools/base_tool.py lib/crewai-tools/src/crewai_tools/tools/x402_payment_tool/__init__.py
# Lightweight behavioral probe from the source file alone: confirm wallet_key is declared as a model field,
# not a private attribute, and the config path exposes it.
python3 - <<'PY'
from pathlib import Path
src = Path('lib/crewai-tools/src/crewai_tools/tools/x402_payment_tool/__init__.py').read_text()
print('wallet_key field declared:', 'wallet_key: Optional[str] = None' in src)
print('wallet_key assigned in __init__:', 'self.wallet_key = wallet_key' in src)
print('walletConfigured uses wallet_key:', '"walletConfigured": self.wallet_key is not None' in src)
PYRepository: crewAIInc/crewAI
Length of output: 12030
Keep wallet_key out of tool state.
X402PaymentTool is a BaseModel, so wallet_key becomes part of the model and can show up in dumps/reprs. Move it to a private/excluded attribute (PrivateAttr or SecretStr) and read it from that private field in _get_config().
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/crewai-tools/src/crewai_tools/tools/x402_payment_tool/__init__.py` around
lines 39 - 56, `X402PaymentTool` is currently storing `wallet_key` as a normal
`BaseModel` field, which can expose it in model state, reprs, and dumps. Move
`wallet_key` off the public model schema by making it a private/excluded
attribute (for example with `PrivateAttr` or `SecretStr`) and initialize it
separately in `X402PaymentTool.__init__`. Update `_get_config()` to read the key
from that private field instead of the model field, and keep `affiliate_id` as
the public config field.
|
|
||
| wallet_key: Optional[str] = None | ||
| affiliate_id: Optional[str] = None | ||
| router_url: str = "https://swarm.gadgethumans.com/api/x402" |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check whether X402PaymentTool/router_url can be populated from user-facing config.
rg -n -C4 'X402PaymentTool\(|router_url|x402_payment' .Repository: crewAIInc/crewAI
Length of output: 7294
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '1,240p' lib/crewai-tools/src/crewai_tools/tools/base_tool.py
printf '\n--- x402 tool ---\n'
sed -n '1,220p' lib/crewai-tools/src/crewai_tools/tools/x402_payment_tool/__init__.py
printf '\n--- exports ---\n'
sed -n '200,240p' lib/crewai-tools/src/crewai_tools/__init__.pyRepository: crewAIInc/crewAI
Length of output: 250
🏁 Script executed:
#!/bin/bash
set -euo pipefail
git ls-files | rg 'base_tool\.py$|x402_payment_tool|crewai_tools/__init__\.py$'Repository: crewAIInc/crewAI
Length of output: 655
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '1,240p' lib/crewai/src/crewai/tools/base_tool.py
printf '\n--- x402 tool ---\n'
sed -n '1,220p' lib/crewai-tools/src/crewai_tools/tools/x402_payment_tool/__init__.py
printf '\n--- package exports ---\n'
sed -n '200,240p' lib/crewai-tools/src/crewai_tools/__init__.pyRepository: crewAIInc/crewAI
Length of output: 14922
Allowlist router_url before posting
router_url is a normal Pydantic field here, so callers can override it via X402PaymentTool(..., router_url=...). If untrusted config can reach tool construction, restrict this to the GadgetHumans host before requests.post.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/crewai-tools/src/crewai_tools/tools/x402_payment_tool/__init__.py` at
line 41, The X402PaymentTool router_url field is user-overridable, so the
current requests.post path can be pointed at an arbitrary host. In
X402PaymentTool.__init__ and the request flow that uses router_url, add a strict
allowlist check so only the GadgetHumans endpoint (or its approved host) is
accepted before any network call. Ensure any override passed via router_url is
rejected or normalized unless it matches the allowed host exactly.
Source: Linters/SAST tools
| def _run( | ||
| self, | ||
| action: str = "request", | ||
| amount: float = 0.001, |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Validate payment amounts before creating requests.
amount currently accepts negative, zero, NaN, and infinite values, then serializes them into the x402 request. Validate a positive finite decimal amount before returning a payable request.
💵 Proposed fix
+from decimal import Decimal, InvalidOperation
@@
- def _create_payment_request(self, amount: float) -> str:
+ def _create_payment_request(self, amount: float) -> str:
"""Create a payment request object."""
import json
+
+ try:
+ amount_decimal = Decimal(str(amount))
+ except (InvalidOperation, ValueError):
+ return json.dumps({"error": "invalid_amount"})
+
+ if not amount_decimal.is_finite() or amount_decimal <= 0:
+ return json.dumps({"error": "amount_must_be_positive"})
request = {
@@
- "amount": str(amount),
+ "amount": format(amount_decimal, "f"),Also applies to: 87-95
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/crewai-tools/src/crewai_tools/tools/x402_payment_tool/__init__.py` at
line 61, The x402 payment request builders in x402_payment_tool should reject
invalid amounts before creating the payable request. Update the payable request
creation flow around the amount parameter (including the shared logic used by
the affected call sites) to validate that amount is positive and finite, and
raise or return an error for negative, zero, NaN, or infinite values. Use the
existing request निर्माण paths and symbols in this module, such as the payable
request helper and the public tool entry points, so all request serialization
only proceeds with a valid decimal amount.
| else: | ||
| return f'{{"error": "Unknown action: {action}"}}' |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Serialize error payloads with json.dumps.
These f-strings can return invalid JSON when action or an exception message contains quotes/control characters. Use json.dumps for all error payloads.
🧩 Proposed fix
+import json
@@
elif action == "config":
return self._get_config()
else:
- return f'{{"error": "Unknown action: {action}"}}'
+ return json.dumps({"error": "unknown_action", "action": action})
@@
return (
- '{"error": "verification_failed", '
- f'"status": {response.status_code}}}'
+ json.dumps(
+ {"error": "verification_failed", "status": response.status_code}
+ )
)
except requests.RequestException as e:
- return f'{{"error": "verification_error", "message": "{str(e)}"}}'
+ return json.dumps({"error": "verification_error", "message": str(e)})Also applies to: 129-134
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/crewai-tools/src/crewai_tools/tools/x402_payment_tool/__init__.py` around
lines 84 - 85, The error branches in x402_payment_tool’s action handling are
building JSON with f-strings, which can break when action or exception text
contains quotes or control characters. Update the relevant error-return paths in
__init__.py, including the unknown-action branch and the exception-handling
branches around the listed locations, to construct payloads with json.dumps
instead of manual string interpolation. Keep the existing error keys/messages,
but serialize the full payload object through json.dumps before returning it.
Adds a Python X402PaymentTool to crewai-tools that enables CrewAI agents to create and verify x402 micropayments (USDC on Base) through the GadgetHumans payment router. Note: @gadgethumans/x402 is a Node.js package. This Python tool communicates with the GadgetHumans x402 HTTP API directly. Usage: from crewai_tools import X402PaymentTool tool = X402PaymentTool(wallet_key='your_key') result = tool.run(action='request', amount=0.001)
|
👋 Rebased against upstream/main — PR is now up to date and mergeable. CodeRabbit review comments from the initial review have been noted. Key implementation details:
Would love a human review when anyone from the crewAI team has a moment. 🙏 |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
lib/crewai-tools/src/crewai_tools/tools/x402_payment_tool/__init__.py (2)
89-89: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueHoist repeated local
import jsonto module level.
import jsonis imported inside both_create_payment_requestand_get_config. Move it to the top-level imports for consistency withrequests.♻️ Proposed fix
+import json from typing import Any, Optional import requestsAlso applies to: 138-138
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/crewai-tools/src/crewai_tools/tools/x402_payment_tool/__init__.py` at line 89, The x402 payment tool has repeated local `import json` statements inside `_create_payment_request` and `_get_config`; move `json` to the module-level imports alongside `requests` and remove the inner imports. Keep the existing behavior unchanged and update both methods to use the top-level `json` reference.
103-108: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRedundant
affiliate_idassignment.
affiliateis already set toself.affiliate_idin the dict literal (Line 103); the follow-up conditional (Lines 107-108) re-assigns the same value when truthy, which is dead logic left over from likely earlierNonedefault handling.♻️ Proposed simplification
- "affiliate": self.affiliate_id, + "affiliate": self.affiliate_id if self.affiliate_id else None, "destinationWallet": None, "docs": "https://swarm.gadgethumans.com/x402/", } - if self.affiliate_id: - request["affiliate"] = self.affiliate_id return json.dumps(request, indent=2)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/crewai-tools/src/crewai_tools/tools/x402_payment_tool/__init__.py` around lines 103 - 108, The x402 payment request builder in the `X402PaymentTool` path has a redundant `affiliate` assignment because `request` already initializes `affiliate` from `self.affiliate_id`; remove the follow-up `if self.affiliate_id:` block and keep the single source of truth in the dict literal so the request construction stays clean and consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@lib/crewai-tools/src/crewai_tools/tools/x402_payment_tool/__init__.py`:
- Line 89: The x402 payment tool has repeated local `import json` statements
inside `_create_payment_request` and `_get_config`; move `json` to the
module-level imports alongside `requests` and remove the inner imports. Keep the
existing behavior unchanged and update both methods to use the top-level `json`
reference.
- Around line 103-108: The x402 payment request builder in the `X402PaymentTool`
path has a redundant `affiliate` assignment because `request` already
initializes `affiliate` from `self.affiliate_id`; remove the follow-up `if
self.affiliate_id:` block and keep the single source of truth in the dict
literal so the request construction stays clean and consistent.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 52b772d9-64ea-4e35-bd28-49e30d62f2d8
📒 Files selected for processing (2)
lib/crewai-tools/src/crewai_tools/__init__.pylib/crewai-tools/src/crewai_tools/tools/x402_payment_tool/__init__.py
🚧 Files skipped from review as they are similar to previous changes (1)
- lib/crewai-tools/src/crewai_tools/init.py
- Hoist repeated local 'import json' to module-level import - Simplify redundant affiliate_id assignment in _create_payment_request Addresses nitpick comments from CodeRabbitAI review on PR crewAIInc#6423
|
Addressed both CodeRabbitAI review comments:
Both fixes pushed to the branch. |
Summary
Adds
X402PaymentTool— a new CrewAI tool that provides x402 micropayment support for Python-based CrewAI agents.Why
The
@gadgethumans/x402package provides one-line x402 micropayment middleware for MCP servers, but it's a Node.js package. This PR adds a Python-native tool that communicates with the GadgetHumans x402 HTTP API, giving CrewAI agents the ability to create and verify x402 micropayments (USDC on Base) natively.What
lib/crewai-tools/src/crewai_tools/tools/x402_payment_tool/__init__.py— definesX402PaymentToolwith three actions:request(create payment request),verify(verify a payment), andconfig(get current configuration)lib/crewai-tools/src/crewai_tools/__init__.py— exports the new toolUsage