crewai-true402 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- crewai_true402-0.1.0/.gitignore +5 -0
- crewai_true402-0.1.0/LICENSE +7 -0
- crewai_true402-0.1.0/PKG-INFO +88 -0
- crewai_true402-0.1.0/README.md +69 -0
- crewai_true402-0.1.0/crewai_true402/__init__.py +26 -0
- crewai_true402-0.1.0/crewai_true402/tools.py +100 -0
- crewai_true402-0.1.0/crewai_true402/x402.py +169 -0
- crewai_true402-0.1.0/pyproject.toml +29 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
MIT No Attribution
|
|
2
|
+
|
|
3
|
+
Copyright 2026 true402
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: crewai-true402
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: true402 tools for CrewAI — pay-per-call on-chain rug/honeypot & address safety for Base AI agents over x402 (USDC, no account, no API key).
|
|
5
|
+
Project-URL: Homepage, https://true402.dev
|
|
6
|
+
Project-URL: Source, https://github.com/true402/crewai-true402
|
|
7
|
+
Project-URL: OpenAPI, https://true402.dev/openapi.json
|
|
8
|
+
Author-email: true402 <contact@true402.dev>
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agent-tools,ai-agent,base,crewai,crewai-tools,crypto,defi,honeypot,rug-check,token-safety,web3,x402
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Requires-Dist: eth-account>=0.11
|
|
14
|
+
Requires-Dist: pydantic>=2.0
|
|
15
|
+
Requires-Dist: requests>=2.28
|
|
16
|
+
Provides-Extra: crewai
|
|
17
|
+
Requires-Dist: crewai>=0.30; extra == 'crewai'
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# crewai-true402
|
|
21
|
+
|
|
22
|
+
**[true402](https://true402.dev) tools for [CrewAI](https://www.crewai.com)** — give a Base trading agent a pre-trade **rug/honeypot check** it pays for per call over [x402](https://x402.org) (USDC on Base). No accounts, no API keys — the wallet is the identity. The safety checks have a **free daily trial**, so the tools work out of the box with no wallet configured.
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install crewai-true402
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Use
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from crewai import Agent
|
|
34
|
+
from crewai_true402 import true402_tools
|
|
35
|
+
|
|
36
|
+
# Reads PAYER_PRIVATE_KEY from the env (a Base wallet holding a little USDC).
|
|
37
|
+
# Omit the key to rely on the free daily trial for the safety stalls.
|
|
38
|
+
tools = true402_tools()
|
|
39
|
+
|
|
40
|
+
trader = Agent(
|
|
41
|
+
role="Base memecoin trader",
|
|
42
|
+
goal="Only buy tokens that pass an on-chain safety check",
|
|
43
|
+
tools=tools,
|
|
44
|
+
backstory="You never ape into a token before rug-checking it.",
|
|
45
|
+
)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The agent gets four tools:
|
|
49
|
+
|
|
50
|
+
| Tool | What | Price |
|
|
51
|
+
|------|------|-------|
|
|
52
|
+
| `true402_token_report` | Composite **avoid/caution/ok** verdict — honeypot buy/sell simulation + liquidity + ownership + recent rug activity. Call **before buying**. | ~$0.01 |
|
|
53
|
+
| `true402_token_safety` | Structural safety score 0–100 + flags (honeypot sim, liquidity, mint/ownership). | ~$0.005 |
|
|
54
|
+
| `true402_address_safety` | Profile + risk for any address before you send/approve/call it (EOA vs contract, balances, proxy detection). | ~$0.005 |
|
|
55
|
+
| `true402_deployer_check` | Deployer wallet reputation — age, contracts shipped, fresh-throwaway flag — to catch serial ruggers. | ~$0.008 |
|
|
56
|
+
|
|
57
|
+
## Configuration
|
|
58
|
+
|
|
59
|
+
`true402_tools()` reads the environment, or pass a `PayOpts`:
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from crewai_true402 import true402_tools, PayOpts
|
|
63
|
+
|
|
64
|
+
tools = true402_tools(PayOpts(
|
|
65
|
+
payer_private_key="0x…", # a Base wallet with a little USDC (gas is sponsored; USDC only)
|
|
66
|
+
max_amount_usd=0.10, # hard per-call ceiling — refuses to sign a 402 demanding more
|
|
67
|
+
))
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
| Env var | Default | Meaning |
|
|
71
|
+
|---------|---------|---------|
|
|
72
|
+
| `PAYER_PRIVATE_KEY` | — | Base wallet key that signs x402 payments (needs USDC, not ETH). Unset → free trial only. |
|
|
73
|
+
| `TRUE402_BASE_URL` | `https://true402.dev/api` | Override to point at a self-hosted instance. |
|
|
74
|
+
| `BASE_RPC_URL` | `https://mainnet.base.org` | Base RPC for the balance pre-check. |
|
|
75
|
+
|
|
76
|
+
## Safety
|
|
77
|
+
|
|
78
|
+
The client **refuses to sign** anything that isn't USDC-on-Base within `max_amount_usd` (default $0.10) — so a rogue or compromised endpoint can't make your agent authorize an unexpected asset, network, or amount. The private key is used only to sign locally; it never leaves the process.
|
|
79
|
+
|
|
80
|
+
## Links
|
|
81
|
+
|
|
82
|
+
- Live check in your browser: <https://true402.dev/check>
|
|
83
|
+
- API reference: <https://true402.dev/docs/api> · OpenAPI: <https://true402.dev/openapi.json>
|
|
84
|
+
- Also available: [LangChain](https://www.npmjs.com/package/@true402.dev/langchain) · [MCP server](https://www.npmjs.com/package/@true402.dev/mcp-server) · [CLI](https://www.npmjs.com/package/@true402.dev/rugcheck)
|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
MIT
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# crewai-true402
|
|
2
|
+
|
|
3
|
+
**[true402](https://true402.dev) tools for [CrewAI](https://www.crewai.com)** — give a Base trading agent a pre-trade **rug/honeypot check** it pays for per call over [x402](https://x402.org) (USDC on Base). No accounts, no API keys — the wallet is the identity. The safety checks have a **free daily trial**, so the tools work out of the box with no wallet configured.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install crewai-true402
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Use
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from crewai import Agent
|
|
15
|
+
from crewai_true402 import true402_tools
|
|
16
|
+
|
|
17
|
+
# Reads PAYER_PRIVATE_KEY from the env (a Base wallet holding a little USDC).
|
|
18
|
+
# Omit the key to rely on the free daily trial for the safety stalls.
|
|
19
|
+
tools = true402_tools()
|
|
20
|
+
|
|
21
|
+
trader = Agent(
|
|
22
|
+
role="Base memecoin trader",
|
|
23
|
+
goal="Only buy tokens that pass an on-chain safety check",
|
|
24
|
+
tools=tools,
|
|
25
|
+
backstory="You never ape into a token before rug-checking it.",
|
|
26
|
+
)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The agent gets four tools:
|
|
30
|
+
|
|
31
|
+
| Tool | What | Price |
|
|
32
|
+
|------|------|-------|
|
|
33
|
+
| `true402_token_report` | Composite **avoid/caution/ok** verdict — honeypot buy/sell simulation + liquidity + ownership + recent rug activity. Call **before buying**. | ~$0.01 |
|
|
34
|
+
| `true402_token_safety` | Structural safety score 0–100 + flags (honeypot sim, liquidity, mint/ownership). | ~$0.005 |
|
|
35
|
+
| `true402_address_safety` | Profile + risk for any address before you send/approve/call it (EOA vs contract, balances, proxy detection). | ~$0.005 |
|
|
36
|
+
| `true402_deployer_check` | Deployer wallet reputation — age, contracts shipped, fresh-throwaway flag — to catch serial ruggers. | ~$0.008 |
|
|
37
|
+
|
|
38
|
+
## Configuration
|
|
39
|
+
|
|
40
|
+
`true402_tools()` reads the environment, or pass a `PayOpts`:
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from crewai_true402 import true402_tools, PayOpts
|
|
44
|
+
|
|
45
|
+
tools = true402_tools(PayOpts(
|
|
46
|
+
payer_private_key="0x…", # a Base wallet with a little USDC (gas is sponsored; USDC only)
|
|
47
|
+
max_amount_usd=0.10, # hard per-call ceiling — refuses to sign a 402 demanding more
|
|
48
|
+
))
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
| Env var | Default | Meaning |
|
|
52
|
+
|---------|---------|---------|
|
|
53
|
+
| `PAYER_PRIVATE_KEY` | — | Base wallet key that signs x402 payments (needs USDC, not ETH). Unset → free trial only. |
|
|
54
|
+
| `TRUE402_BASE_URL` | `https://true402.dev/api` | Override to point at a self-hosted instance. |
|
|
55
|
+
| `BASE_RPC_URL` | `https://mainnet.base.org` | Base RPC for the balance pre-check. |
|
|
56
|
+
|
|
57
|
+
## Safety
|
|
58
|
+
|
|
59
|
+
The client **refuses to sign** anything that isn't USDC-on-Base within `max_amount_usd` (default $0.10) — so a rogue or compromised endpoint can't make your agent authorize an unexpected asset, network, or amount. The private key is used only to sign locally; it never leaves the process.
|
|
60
|
+
|
|
61
|
+
## Links
|
|
62
|
+
|
|
63
|
+
- Live check in your browser: <https://true402.dev/check>
|
|
64
|
+
- API reference: <https://true402.dev/docs/api> · OpenAPI: <https://true402.dev/openapi.json>
|
|
65
|
+
- Also available: [LangChain](https://www.npmjs.com/package/@true402.dev/langchain) · [MCP server](https://www.npmjs.com/package/@true402.dev/mcp-server) · [CLI](https://www.npmjs.com/package/@true402.dev/rugcheck)
|
|
66
|
+
|
|
67
|
+
## License
|
|
68
|
+
|
|
69
|
+
MIT
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""true402 tools for CrewAI — pay-per-call on-chain rug/honeypot & address safety for Base AI agents
|
|
2
|
+
over x402 (USDC on Base, no account, no API key; the wallet is the identity).
|
|
3
|
+
|
|
4
|
+
from crewai_true402 import true402_tools
|
|
5
|
+
tools = true402_tools()
|
|
6
|
+
"""
|
|
7
|
+
from .tools import (
|
|
8
|
+
AddressSafetyTool,
|
|
9
|
+
DeployerCheckTool,
|
|
10
|
+
TokenReportTool,
|
|
11
|
+
TokenSafetyTool,
|
|
12
|
+
true402_tools,
|
|
13
|
+
)
|
|
14
|
+
from .x402 import PayOpts, pay_stall, sign_payment
|
|
15
|
+
|
|
16
|
+
__version__ = "0.1.0"
|
|
17
|
+
__all__ = [
|
|
18
|
+
"true402_tools",
|
|
19
|
+
"TokenReportTool",
|
|
20
|
+
"TokenSafetyTool",
|
|
21
|
+
"AddressSafetyTool",
|
|
22
|
+
"DeployerCheckTool",
|
|
23
|
+
"PayOpts",
|
|
24
|
+
"pay_stall",
|
|
25
|
+
"sign_payment",
|
|
26
|
+
]
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""true402 tools for CrewAI — pay-per-call on-chain safety for Base agents over x402.
|
|
2
|
+
|
|
3
|
+
from crewai_true402 import true402_tools
|
|
4
|
+
tools = true402_tools() # reads PAYER_PRIVATE_KEY from the env
|
|
5
|
+
|
|
6
|
+
agent = Agent(role="Trader", tools=tools, ...)
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
from typing import Optional, Type
|
|
12
|
+
|
|
13
|
+
from crewai.tools import BaseTool
|
|
14
|
+
from pydantic import BaseModel, Field
|
|
15
|
+
|
|
16
|
+
from .x402 import PayOpts, pay_stall
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class _TokenInput(BaseModel):
|
|
20
|
+
token: str = Field(..., description="A Base ERC-20 token contract address (0x…)")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class _AddressInput(BaseModel):
|
|
24
|
+
address: str = Field(..., description="Any Base address (0x…) — an EOA or a contract")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class _StallTool(BaseTool):
|
|
28
|
+
"""Base for a true402 stall exposed as a CrewAI tool."""
|
|
29
|
+
|
|
30
|
+
path: str
|
|
31
|
+
input_key: str
|
|
32
|
+
opts: PayOpts
|
|
33
|
+
|
|
34
|
+
def _run(self, **kwargs) -> str:
|
|
35
|
+
value = kwargs.get(self.input_key)
|
|
36
|
+
result = pay_stall(self.path, {self.input_key: value}, self.opts)
|
|
37
|
+
return json.dumps(result)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class TokenReportTool(_StallTool):
|
|
41
|
+
name: str = "true402_token_report"
|
|
42
|
+
description: str = (
|
|
43
|
+
"Pre-trade rug/honeypot check for a Base ERC-20: a composite avoid/caution/ok verdict from an "
|
|
44
|
+
"on-chain buy/sell honeypot simulation, liquidity depth, ownership/mint inspection, and recent "
|
|
45
|
+
"rug activity. Call BEFORE buying a token. ~$0.01 USDC over x402."
|
|
46
|
+
)
|
|
47
|
+
args_schema: Type[BaseModel] = _TokenInput
|
|
48
|
+
path: str = "/v1/base/token-report"
|
|
49
|
+
input_key: str = "token"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class TokenSafetyTool(_StallTool):
|
|
53
|
+
name: str = "true402_token_safety"
|
|
54
|
+
description: str = (
|
|
55
|
+
"Structural safety score (0–100) + flags for a Base ERC-20: honeypot simulation, liquidity, "
|
|
56
|
+
"mint/ownership/blacklist. Lighter than token_report. ~$0.005 USDC over x402."
|
|
57
|
+
)
|
|
58
|
+
args_schema: Type[BaseModel] = _TokenInput
|
|
59
|
+
path: str = "/v1/token-safety"
|
|
60
|
+
input_key: str = "token"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class AddressSafetyTool(_StallTool):
|
|
64
|
+
name: str = "true402_address_safety"
|
|
65
|
+
description: str = (
|
|
66
|
+
"Profile + risk for any Base address before you send to / approve / call it: EOA-vs-contract, "
|
|
67
|
+
"ETH+USDC balance, activity, ownership, and upgradeable-proxy (EIP-1967) detection. "
|
|
68
|
+
"~$0.005 USDC over x402."
|
|
69
|
+
)
|
|
70
|
+
args_schema: Type[BaseModel] = _AddressInput
|
|
71
|
+
path: str = "/v1/base/address-safety"
|
|
72
|
+
input_key: str = "address"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class DeployerCheckTool(_StallTool):
|
|
76
|
+
name: str = "true402_deployer_check"
|
|
77
|
+
description: str = (
|
|
78
|
+
"Deployer reputation for a Base token: resolves who created it and that wallet's track record "
|
|
79
|
+
"(age, contracts shipped, fresh-throwaway flag) to catch serial ruggers a structural check "
|
|
80
|
+
"can't see. ~$0.008 USDC over x402."
|
|
81
|
+
)
|
|
82
|
+
args_schema: Type[BaseModel] = _TokenInput
|
|
83
|
+
path: str = "/v1/base/deployer-check"
|
|
84
|
+
input_key: str = "token"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def true402_tools(opts: Optional[PayOpts] = None) -> list[BaseTool]:
|
|
88
|
+
"""The four true402 safety tools, ready to hand to a CrewAI Agent.
|
|
89
|
+
|
|
90
|
+
Pass a PayOpts, or leave None to read PAYER_PRIVATE_KEY / TRUE402_BASE_URL / BASE_RPC_URL from the
|
|
91
|
+
environment. Stalls with a free daily trial (token safety/report, address safety) work even with no
|
|
92
|
+
wallet configured — they return a real result until the trial is exhausted, then require payment.
|
|
93
|
+
"""
|
|
94
|
+
o = opts or PayOpts.from_env()
|
|
95
|
+
return [
|
|
96
|
+
TokenReportTool(opts=o),
|
|
97
|
+
TokenSafetyTool(opts=o),
|
|
98
|
+
AddressSafetyTool(opts=o),
|
|
99
|
+
DeployerCheckTool(opts=o),
|
|
100
|
+
]
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""Pay any true402 stall over x402 and return its JSON.
|
|
2
|
+
|
|
3
|
+
The whole protocol, in one function: POST → 402 with payment terms → sign an EIP-3009 USDC
|
|
4
|
+
authorization → retry with an X-PAYMENT header → 200. No accounts, no API keys; the wallet is the
|
|
5
|
+
identity. USDC on Base, gas sponsored by the facilitator (the payer needs only USDC, not ETH).
|
|
6
|
+
|
|
7
|
+
Safety: the client REFUSES to sign anything that isn't USDC-on-Base within a caller-set cap, so a
|
|
8
|
+
rogue/compromised endpoint (or a MITM past TLS) can't make the agent authorize an unexpected
|
|
9
|
+
asset/network or an excessive amount.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import secrets
|
|
16
|
+
import time
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
import requests
|
|
21
|
+
from eth_account import Account
|
|
22
|
+
from eth_account.messages import encode_typed_data
|
|
23
|
+
|
|
24
|
+
BASE_USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
|
|
25
|
+
BASE_NETWORK = "eip155:8453"
|
|
26
|
+
BASE_CHAIN_ID = 8453
|
|
27
|
+
DEFAULT_BASE_URL = "https://true402.dev/api"
|
|
28
|
+
DEFAULT_RPC_URL = "https://mainnet.base.org"
|
|
29
|
+
_BALANCE_OF_SELECTOR = "0x70a08231"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class PayOpts:
|
|
34
|
+
"""Configuration for paying true402 stalls."""
|
|
35
|
+
|
|
36
|
+
payer_private_key: str
|
|
37
|
+
"""A Base wallet private key holding a little USDC (the payer)."""
|
|
38
|
+
base_url: str = DEFAULT_BASE_URL
|
|
39
|
+
rpc_url: str = DEFAULT_RPC_URL
|
|
40
|
+
max_amount_usd: float = 0.10
|
|
41
|
+
"""Hard ceiling, in USDC, on a single signed payment. The client refuses a 402 demanding more."""
|
|
42
|
+
timeout: float = 30.0
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def from_env(cls, **overrides: Any) -> "PayOpts":
|
|
46
|
+
"""Build from PAYER_PRIVATE_KEY / TRUE402_BASE_URL / BASE_RPC_URL env vars."""
|
|
47
|
+
key = overrides.pop("payer_private_key", None) or os.environ.get("PAYER_PRIVATE_KEY", "")
|
|
48
|
+
return cls(
|
|
49
|
+
payer_private_key=key,
|
|
50
|
+
base_url=overrides.pop("base_url", None) or os.environ.get("TRUE402_BASE_URL", DEFAULT_BASE_URL),
|
|
51
|
+
rpc_url=overrides.pop("rpc_url", None) or os.environ.get("BASE_RPC_URL", DEFAULT_RPC_URL),
|
|
52
|
+
**overrides,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _usdc_balance(rpc_url: str, holder: str, timeout: float) -> int:
|
|
57
|
+
data = _BALANCE_OF_SELECTOR + holder[2:].rjust(64, "0")
|
|
58
|
+
body = {"jsonrpc": "2.0", "id": 1, "method": "eth_call", "params": [{"to": BASE_USDC, "data": data}, "latest"]}
|
|
59
|
+
r = requests.post(rpc_url, json=body, timeout=timeout, headers={"content-type": "application/json"})
|
|
60
|
+
r.raise_for_status()
|
|
61
|
+
return int(r.json()["result"], 16)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def sign_payment(accept: dict, opts: PayOpts) -> str:
|
|
65
|
+
"""Sign an EIP-3009 authorization for the given 402 `accepts[]` entry → base64 X-PAYMENT value."""
|
|
66
|
+
key = opts.payer_private_key
|
|
67
|
+
if not key:
|
|
68
|
+
raise ValueError("no payer_private_key set — cannot pay (set PAYER_PRIVATE_KEY or PayOpts.payer_private_key)")
|
|
69
|
+
if not key.startswith("0x"):
|
|
70
|
+
key = "0x" + key
|
|
71
|
+
account = Account.from_key(key)
|
|
72
|
+
|
|
73
|
+
network = accept.get("network")
|
|
74
|
+
if network and network != BASE_NETWORK:
|
|
75
|
+
raise ValueError(f'unexpected payment network "{network}" (expected {BASE_NETWORK}) — refusing to sign')
|
|
76
|
+
asset = accept.get("asset", "")
|
|
77
|
+
if asset.lower() != BASE_USDC.lower():
|
|
78
|
+
raise ValueError(f"unexpected payment asset {asset} (expected Base USDC) — refusing to sign")
|
|
79
|
+
|
|
80
|
+
value = int(accept.get("amount") or accept.get("maxAmountRequired") or "0")
|
|
81
|
+
cap_atomic = round(opts.max_amount_usd * 1e6)
|
|
82
|
+
if value > cap_atomic:
|
|
83
|
+
raise ValueError(f"402 demands {value} USDC base units, over the ${opts.max_amount_usd} cap — refusing to sign")
|
|
84
|
+
|
|
85
|
+
held = _usdc_balance(opts.rpc_url, account.address, opts.timeout)
|
|
86
|
+
if held < value:
|
|
87
|
+
raise ValueError(f"payer {account.address} holds {held} < {value} USDC base units — fund it")
|
|
88
|
+
|
|
89
|
+
now = int(time.time())
|
|
90
|
+
valid_before = now + int(accept.get("maxTimeoutSeconds") or 120)
|
|
91
|
+
nonce = "0x" + secrets.token_hex(32)
|
|
92
|
+
extra = accept.get("extra") or {}
|
|
93
|
+
authorization = {
|
|
94
|
+
"from": account.address,
|
|
95
|
+
"to": accept["payTo"],
|
|
96
|
+
"value": value,
|
|
97
|
+
"validAfter": now - 60,
|
|
98
|
+
"validBefore": valid_before,
|
|
99
|
+
"nonce": bytes.fromhex(nonce[2:]),
|
|
100
|
+
}
|
|
101
|
+
signable = encode_typed_data(
|
|
102
|
+
domain_data={
|
|
103
|
+
"name": extra.get("name", "USD Coin"),
|
|
104
|
+
"version": extra.get("version", "2"),
|
|
105
|
+
"chainId": BASE_CHAIN_ID,
|
|
106
|
+
"verifyingContract": BASE_USDC,
|
|
107
|
+
},
|
|
108
|
+
message_types={
|
|
109
|
+
"TransferWithAuthorization": [
|
|
110
|
+
{"name": "from", "type": "address"},
|
|
111
|
+
{"name": "to", "type": "address"},
|
|
112
|
+
{"name": "value", "type": "uint256"},
|
|
113
|
+
{"name": "validAfter", "type": "uint256"},
|
|
114
|
+
{"name": "validBefore", "type": "uint256"},
|
|
115
|
+
{"name": "nonce", "type": "bytes32"},
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
message_data=authorization,
|
|
119
|
+
)
|
|
120
|
+
signature = Account.sign_message(signable, key).signature.hex()
|
|
121
|
+
if not signature.startswith("0x"):
|
|
122
|
+
signature = "0x" + signature
|
|
123
|
+
|
|
124
|
+
payment = {
|
|
125
|
+
"x402Version": 2,
|
|
126
|
+
"scheme": "exact",
|
|
127
|
+
"network": network,
|
|
128
|
+
"payload": {
|
|
129
|
+
"signature": signature,
|
|
130
|
+
"authorization": {
|
|
131
|
+
"from": authorization["from"],
|
|
132
|
+
"to": authorization["to"],
|
|
133
|
+
"value": str(value),
|
|
134
|
+
"validAfter": str(authorization["validAfter"]),
|
|
135
|
+
"validBefore": str(authorization["validBefore"]),
|
|
136
|
+
"nonce": nonce,
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
}
|
|
140
|
+
import base64
|
|
141
|
+
|
|
142
|
+
return base64.b64encode(json.dumps(payment).encode()).decode()
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def pay_stall(path: str, payload: dict, opts: PayOpts) -> Any:
|
|
146
|
+
"""POST `payload` to a true402 stall path (e.g. '/v1/base/token-report'), paying over x402 if asked.
|
|
147
|
+
|
|
148
|
+
If the first response is 200 (a free-trial call), returns it without paying. If it's 402, signs and
|
|
149
|
+
retries. Raises on any other status or on a refused/insufficient payment.
|
|
150
|
+
"""
|
|
151
|
+
url = opts.base_url.rstrip("/") + path
|
|
152
|
+
headers = {"content-type": "application/json"}
|
|
153
|
+
first = requests.post(url, json=payload, headers=headers, timeout=opts.timeout)
|
|
154
|
+
if first.status_code == 200:
|
|
155
|
+
return first.json() # served free (free trial) — no payment needed
|
|
156
|
+
if first.status_code != 402:
|
|
157
|
+
raise RuntimeError(f"expected HTTP 402 from {path}, got {first.status_code}: {first.text[:200]}")
|
|
158
|
+
|
|
159
|
+
challenge = first.json()
|
|
160
|
+
accepts = challenge.get("accepts") or []
|
|
161
|
+
accept = next((a for a in accepts if a.get("scheme") == "exact"), None)
|
|
162
|
+
if not accept:
|
|
163
|
+
raise RuntimeError('no x402 "exact" payment requirement in the 402')
|
|
164
|
+
|
|
165
|
+
x_payment = sign_payment(accept, opts)
|
|
166
|
+
paid = requests.post(url, json=payload, headers={**headers, "X-PAYMENT": x_payment}, timeout=opts.timeout)
|
|
167
|
+
if paid.status_code != 200:
|
|
168
|
+
raise RuntimeError(f"paid request to {path} failed (HTTP {paid.status_code}): {paid.text[:200]}")
|
|
169
|
+
return paid.json()
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "crewai-true402"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "true402 tools for CrewAI — pay-per-call on-chain rug/honeypot & address safety for Base AI agents over x402 (USDC, no account, no API key)."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
keywords = ["crewai", "crewai-tools", "x402", "base", "rug-check", "honeypot", "token-safety", "crypto", "ai-agent", "agent-tools", "defi", "web3"]
|
|
13
|
+
authors = [{ name = "true402", email = "contact@true402.dev" }]
|
|
14
|
+
dependencies = [
|
|
15
|
+
"requests>=2.28",
|
|
16
|
+
"eth-account>=0.11",
|
|
17
|
+
"pydantic>=2.0",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.optional-dependencies]
|
|
21
|
+
crewai = ["crewai>=0.30"]
|
|
22
|
+
|
|
23
|
+
[project.urls]
|
|
24
|
+
Homepage = "https://true402.dev"
|
|
25
|
+
Source = "https://github.com/true402/crewai-true402"
|
|
26
|
+
"OpenAPI" = "https://true402.dev/openapi.json"
|
|
27
|
+
|
|
28
|
+
[tool.hatch.build.targets.wheel]
|
|
29
|
+
packages = ["crewai_true402"]
|