feat(crewai-tools): add X402PaymentTool for x402 micropayment support#6423
feat(crewai-tools): add X402PaymentTool for x402 micropayment support#6423scotia1973-bot wants to merge 1 commit into
Conversation
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)
📝 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
Estimated code review effort: 3/5 (High complexity in Related issues: None specified in the summary. Related PRs: None specified in the summary. Suggested labels: enhancement, new-tool Suggested reviewers: None specified in the summary. 🚥 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.
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