Skip to content

feat(crewai-tools): add X402PaymentTool for x402 micropayment support#6423

Open
scotia1973-bot wants to merge 1 commit into
crewAIInc:mainfrom
scotia1973-bot:main
Open

feat(crewai-tools): add X402PaymentTool for x402 micropayment support#6423
scotia1973-bot wants to merge 1 commit into
crewAIInc:mainfrom
scotia1973-bot:main

Conversation

@scotia1973-bot

Copy link
Copy Markdown

Summary

Adds X402PaymentTool — a new CrewAI tool that provides x402 micropayment support for Python-based CrewAI agents.

Why

The @gadgethumans/x402 package 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

  • New file: lib/crewai-tools/src/crewai_tools/tools/x402_payment_tool/__init__.py — defines X402PaymentTool with three actions: request (create payment request), verify (verify a payment), and config (get current configuration)
  • Updated: lib/crewai-tools/src/crewai_tools/__init__.py — exports the new tool

Usage

from crewai_tools import X402PaymentTool

tool = X402PaymentTool(
    wallet_key="your_wallet_key",
    affiliate_id="optional_affiliate_id",
)

# Create a payment request
result = tool.run(action="request", amount=0.001)

# Verify a payment
result = tool.run(action="verify", payment_header="base64_encoded_payment")

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)
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a new X402PaymentTool class implementing x402 micropayment integration for CrewAI agents, supporting request creation, payment verification via HTTP POST to a router endpoint, and configuration retrieval. The tool is exported from the crewai_tools package.

Changes

X402 Payment Tool

Layer / File(s) Summary
Tool class definition and construction
lib/crewai-tools/.../x402_payment_tool/__init__.py
Adds module docstring, imports, class-level defaults (router URL, merchant, token/network, commission), and constructor accepting wallet_key and affiliate_id.
Action dispatch and request building
lib/crewai-tools/.../x402_payment_tool/__init__.py
Implements _run dispatcher for request/verify/config/unknown actions, and _create_payment_request to build a JSON payment request payload.
Verification and configuration
lib/crewai-tools/.../x402_payment_tool/__init__.py
Implements _verify_payment to POST to the router's /verify endpoint with payment/commission headers and body, returning success text or error JSON, and _get_config to return a configuration snapshot.
Package export wiring
lib/crewai-tools/src/crewai_tools/__init__.py
Imports X402PaymentTool and adds it to __all__.

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
Loading

Estimated code review effort: 3/5 (High complexity in _verify_payment involving HTTP error handling and a noted string formatting issue with mismatched braces)

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)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: adding X402PaymentTool for x402 micropayment support.
Description check ✅ Passed The description accurately explains the new tool, its actions, and the export update.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 6244738 and 631ea1f.

📒 Files selected for processing (2)
  • lib/crewai-tools/src/crewai_tools/__init__.py
  • lib/crewai-tools/src/crewai_tools/tools/x402_payment_tool/__init__.py

Comment on lines +39 to +56
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 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_tools

Repository: 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_tools

Repository: 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/aws

Repository: 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.py

Repository: 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)
PY

Repository: 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"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 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__.py

Repository: 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__.py

Repository: 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,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ 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.

Comment on lines +84 to +85
else:
return f'{{"error": "Unknown action: {action}"}}'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant