langchain-verdix 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.
@@ -0,0 +1,7 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ .pytest_cache/
5
+ dist/
6
+ build/
7
+ *.egg-info/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Verdix
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,178 @@
1
+ Metadata-Version: 2.5
2
+ Name: langchain-verdix
3
+ Version: 0.1.0
4
+ Summary: LangChain tool: check an EVM address on Base before sending it funds (safe/caution/danger). Pays per call via x402 in USDC from your agent's wallet, within caps you set.
5
+ Project-URL: Homepage, https://api.verdixapi.com
6
+ Project-URL: Repository, https://github.com/verdixapi/langchain-verdix
7
+ Project-URL: Issues, https://github.com/verdixapi/langchain-verdix/issues
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: address,address-poisoning,agents,base,langchain,langgraph,risk,scam,security,tool,usdc,wallet,x402
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Security
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: >=3.10
21
+ Requires-Dist: eth-account>=0.13
22
+ Requires-Dist: httpx<1,>=0.28.1
23
+ Requires-Dist: langchain-core<2,>=1.0
24
+ Requires-Dist: pydantic<3,>=2.7
25
+ Requires-Dist: x402[evm]<3,>=2.22
26
+ Provides-Extra: test
27
+ Requires-Dist: langchain-tests<2,>=1.0; extra == 'test'
28
+ Requires-Dist: langchain<2,>=1.0; extra == 'test'
29
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'test'
30
+ Requires-Dist: pytest>=8; extra == 'test'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # langchain-verdix
34
+
35
+ A [LangChain](https://python.langchain.com) tool that screens an EVM address on Base **before your agent sends it funds**, and answers `safe`, `caution` or `danger` with reasons.
36
+
37
+ It checks OFAC sanctions, scam, phishing and exploit lists, live **address-poisoning lookalikes**, burn addresses, contract and deployer signals, and on-chain behaviour. You don't need an API key or an account. Each check is paid per call via [x402](https://x402.org), in USDC on Base, from your agent's own wallet, under caps you set.
38
+
39
+ ## Install
40
+
41
+ ```bash
42
+ pip install -U langchain-verdix
43
+ ```
44
+
45
+ Payments are real USDC on Base mainnet; there is no testnet mode.
46
+
47
+ ## Usage
48
+
49
+ ```python
50
+ import os
51
+
52
+ from eth_account import Account
53
+ from langchain.agents import create_agent
54
+ from langchain_verdix import VerdixAddressRiskTool
55
+
56
+ check_address_risk = VerdixAddressRiskTool(
57
+ account=Account.from_key(os.environ["AGENT_PRIVATE_KEY"]),
58
+ max_price_per_call_usd=0.10, # never pay more than $0.10 for one check
59
+ max_total_spend_usd=1.00, # and at most $1 in total
60
+ )
61
+
62
+ agent = create_agent("openai:gpt-5-mini", tools=[check_address_risk])
63
+ result = agent.invoke(
64
+ {
65
+ "messages": [
66
+ {
67
+ "role": "user",
68
+ "content": "Send 250 USDC on Base to 0x000000000000000000000000000000000000dEaD.",
69
+ }
70
+ ]
71
+ }
72
+ )
73
+ print(result["messages"][-1].content)
74
+ ```
75
+
76
+ Any chat model that supports tool calling works, as does LangGraph (`ToolNode`) or calling the tool directly:
77
+
78
+ ```python
79
+ check_address_risk.invoke(
80
+ {"address": "0x000000000000000000000000000000000000dEaD", "tier": "quick"}
81
+ )
82
+ ```
83
+
84
+ The model gets one tool, `check_address_risk(address, tier?)`, and sees this result as JSON:
85
+
86
+ ```jsonc
87
+ {
88
+ "verdict": "danger", // "safe" | "caution" | "danger"
89
+ "risk_score": 90,
90
+ "reasons": ["burn_address"],
91
+ "checked": ["ofac", "scam_lists", "poisoning_watch", "burn_list", "..."],
92
+ "advice": "Do not send funds to this address. Show the reasons to the user.",
93
+ "tier": "standard",
94
+ "price_usd": 0.1,
95
+ "complete": true,
96
+ "charged": true,
97
+ "payment": { "transaction": "0x...", "network": "eip155:8453", "payer": "0x..." },
98
+ "retry_after_seconds": null,
99
+ "address": "0x000000000000000000000000000000000000dEaD",
100
+ "chain": "base",
101
+ "as_of": "2026-10-02T12:00:00+00:00"
102
+ }
103
+ ```
104
+
105
+ The same dict is the `artifact` of the `ToolMessage`, so your code can read the payment's transaction hash without parsing the content.
106
+
107
+ ### What the verdicts mean
108
+
109
+ | Verdict | Meaning | What the agent should do |
110
+ |---|---|---|
111
+ | `safe` | None of the checks in `checked` found a risk signal. **Not a guarantee.** | Still confirm the full address and amount with the user. |
112
+ | `caution` | Risk signals were found, or a data source could not be reached (`complete: false`). | Show the reasons and send only if the user explicitly confirms. |
113
+ | `danger` | Strong risk signals: sanctioned, known scam, poisoning lookalike, burn address... | Do not send. |
114
+
115
+ The tool description tells the model the same thing, and every result carries an `advice` string.
116
+
117
+ ### Tiers
118
+
119
+ The model picks a tier by what is at stake. It can only pick the tiers that fit under `max_price_per_call_usd`: the others are left out of the tool's description and input schema.
120
+
121
+ | Tier | List price | When |
122
+ |---|---|---|
123
+ | `quick` | $0.02 | Small, routine transfers (under ~$100) to an address used before. Sanctions, scam lists, poisoning lookalikes, burn addresses, contract/deployer signals and a fast address-age read. |
124
+ | `standard` (default) | $0.10 | ~$100-$1,000, or any address that came from a message, a transaction history or a copy-paste. Adds deeper on-chain behaviour analysis. |
125
+ | `deep` | $0.50 | Over ~$1,000, first-time counterparties, contract interactions. Currently the same checks as `standard`; new counterparty checks land here first. |
126
+
127
+ Each tier has its own URL (`https://api.verdixapi.com/risk/address/{tier}`) with a single price, so the tier the model asks for is exactly the tier that is paid.
128
+
129
+ ## Options
130
+
131
+ ```python
132
+ VerdixAddressRiskTool(
133
+ account=..., # required: an eth_account account (or any x402 ClientEvmSigner)
134
+ max_price_per_call_usd=0.10, # required: refuse any check costing more, before signing
135
+ max_total_spend_usd=1.00, # optional: total budget for this tool instance
136
+ allowed_tiers=["quick", "standard"], # optional (default: all tiers within the cap)
137
+ default_tier="standard", # optional: used when the model gives none
138
+ api_url=None, # optional: default https://api.verdixapi.com
139
+ )
140
+ ```
141
+
142
+ ## Payments and safety
143
+
144
+ - The wallet needs a little **USDC on Base mainnet**. It needs no ETH: x402 payments are gasless signatures that the facilitator settles.
145
+ - The private key only signs the payment locally. It is never sent anywhere.
146
+ - A payment is signed only for **USDC on Base**, only for the tier requested, and only at or under `max_price_per_call_usd`. Anything else is refused before signing.
147
+ - Before each payment is signed, its price is reserved against `max_total_spend_usd`, so parallel tool calls (threads or asyncio) cannot overspend. The reservation is released when the API reports no charge.
148
+ - **You are not charged when a data source fails.** The API then answers `caution` with `complete: false` (and `retry_after_seconds`), and never `safe`.
149
+ - The model cannot change the caps: they are fixed when the tool is created.
150
+ - A refusal (price over the cap, budget used up, malformed address) goes back to the model as the tool's error message instead of ending the run.
151
+
152
+ ## Without a model
153
+
154
+ ```python
155
+ from langchain_verdix import VerdixClient
156
+
157
+ verdix = VerdixClient(account=account, max_price_per_call_usd=0.10)
158
+
159
+ pricing = verdix.get_pricing() # free: reads the unpaid 402 quotes
160
+ result = verdix.check_address("0x...", tier="quick") # or: await verdix.acheck_address(...)
161
+ if result.verdict != "safe":
162
+ ... # stop, or ask the user
163
+ ```
164
+
165
+ The errors to expect are `VerdixError`s: a malformed address, a price over your cap, an exhausted budget or an unexpected API answer.
166
+
167
+ ## Development
168
+
169
+ ```bash
170
+ pip install -e ".[test]"
171
+ pytest # offline: mocked API, throwaway key, sockets disabled, no payment
172
+ ```
173
+
174
+ `examples/check_before_send.py` makes one real paid check.
175
+
176
+ ## License
177
+
178
+ MIT
@@ -0,0 +1,146 @@
1
+ # langchain-verdix
2
+
3
+ A [LangChain](https://python.langchain.com) tool that screens an EVM address on Base **before your agent sends it funds**, and answers `safe`, `caution` or `danger` with reasons.
4
+
5
+ It checks OFAC sanctions, scam, phishing and exploit lists, live **address-poisoning lookalikes**, burn addresses, contract and deployer signals, and on-chain behaviour. You don't need an API key or an account. Each check is paid per call via [x402](https://x402.org), in USDC on Base, from your agent's own wallet, under caps you set.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install -U langchain-verdix
11
+ ```
12
+
13
+ Payments are real USDC on Base mainnet; there is no testnet mode.
14
+
15
+ ## Usage
16
+
17
+ ```python
18
+ import os
19
+
20
+ from eth_account import Account
21
+ from langchain.agents import create_agent
22
+ from langchain_verdix import VerdixAddressRiskTool
23
+
24
+ check_address_risk = VerdixAddressRiskTool(
25
+ account=Account.from_key(os.environ["AGENT_PRIVATE_KEY"]),
26
+ max_price_per_call_usd=0.10, # never pay more than $0.10 for one check
27
+ max_total_spend_usd=1.00, # and at most $1 in total
28
+ )
29
+
30
+ agent = create_agent("openai:gpt-5-mini", tools=[check_address_risk])
31
+ result = agent.invoke(
32
+ {
33
+ "messages": [
34
+ {
35
+ "role": "user",
36
+ "content": "Send 250 USDC on Base to 0x000000000000000000000000000000000000dEaD.",
37
+ }
38
+ ]
39
+ }
40
+ )
41
+ print(result["messages"][-1].content)
42
+ ```
43
+
44
+ Any chat model that supports tool calling works, as does LangGraph (`ToolNode`) or calling the tool directly:
45
+
46
+ ```python
47
+ check_address_risk.invoke(
48
+ {"address": "0x000000000000000000000000000000000000dEaD", "tier": "quick"}
49
+ )
50
+ ```
51
+
52
+ The model gets one tool, `check_address_risk(address, tier?)`, and sees this result as JSON:
53
+
54
+ ```jsonc
55
+ {
56
+ "verdict": "danger", // "safe" | "caution" | "danger"
57
+ "risk_score": 90,
58
+ "reasons": ["burn_address"],
59
+ "checked": ["ofac", "scam_lists", "poisoning_watch", "burn_list", "..."],
60
+ "advice": "Do not send funds to this address. Show the reasons to the user.",
61
+ "tier": "standard",
62
+ "price_usd": 0.1,
63
+ "complete": true,
64
+ "charged": true,
65
+ "payment": { "transaction": "0x...", "network": "eip155:8453", "payer": "0x..." },
66
+ "retry_after_seconds": null,
67
+ "address": "0x000000000000000000000000000000000000dEaD",
68
+ "chain": "base",
69
+ "as_of": "2026-10-02T12:00:00+00:00"
70
+ }
71
+ ```
72
+
73
+ The same dict is the `artifact` of the `ToolMessage`, so your code can read the payment's transaction hash without parsing the content.
74
+
75
+ ### What the verdicts mean
76
+
77
+ | Verdict | Meaning | What the agent should do |
78
+ |---|---|---|
79
+ | `safe` | None of the checks in `checked` found a risk signal. **Not a guarantee.** | Still confirm the full address and amount with the user. |
80
+ | `caution` | Risk signals were found, or a data source could not be reached (`complete: false`). | Show the reasons and send only if the user explicitly confirms. |
81
+ | `danger` | Strong risk signals: sanctioned, known scam, poisoning lookalike, burn address... | Do not send. |
82
+
83
+ The tool description tells the model the same thing, and every result carries an `advice` string.
84
+
85
+ ### Tiers
86
+
87
+ The model picks a tier by what is at stake. It can only pick the tiers that fit under `max_price_per_call_usd`: the others are left out of the tool's description and input schema.
88
+
89
+ | Tier | List price | When |
90
+ |---|---|---|
91
+ | `quick` | $0.02 | Small, routine transfers (under ~$100) to an address used before. Sanctions, scam lists, poisoning lookalikes, burn addresses, contract/deployer signals and a fast address-age read. |
92
+ | `standard` (default) | $0.10 | ~$100-$1,000, or any address that came from a message, a transaction history or a copy-paste. Adds deeper on-chain behaviour analysis. |
93
+ | `deep` | $0.50 | Over ~$1,000, first-time counterparties, contract interactions. Currently the same checks as `standard`; new counterparty checks land here first. |
94
+
95
+ Each tier has its own URL (`https://api.verdixapi.com/risk/address/{tier}`) with a single price, so the tier the model asks for is exactly the tier that is paid.
96
+
97
+ ## Options
98
+
99
+ ```python
100
+ VerdixAddressRiskTool(
101
+ account=..., # required: an eth_account account (or any x402 ClientEvmSigner)
102
+ max_price_per_call_usd=0.10, # required: refuse any check costing more, before signing
103
+ max_total_spend_usd=1.00, # optional: total budget for this tool instance
104
+ allowed_tiers=["quick", "standard"], # optional (default: all tiers within the cap)
105
+ default_tier="standard", # optional: used when the model gives none
106
+ api_url=None, # optional: default https://api.verdixapi.com
107
+ )
108
+ ```
109
+
110
+ ## Payments and safety
111
+
112
+ - The wallet needs a little **USDC on Base mainnet**. It needs no ETH: x402 payments are gasless signatures that the facilitator settles.
113
+ - The private key only signs the payment locally. It is never sent anywhere.
114
+ - A payment is signed only for **USDC on Base**, only for the tier requested, and only at or under `max_price_per_call_usd`. Anything else is refused before signing.
115
+ - Before each payment is signed, its price is reserved against `max_total_spend_usd`, so parallel tool calls (threads or asyncio) cannot overspend. The reservation is released when the API reports no charge.
116
+ - **You are not charged when a data source fails.** The API then answers `caution` with `complete: false` (and `retry_after_seconds`), and never `safe`.
117
+ - The model cannot change the caps: they are fixed when the tool is created.
118
+ - A refusal (price over the cap, budget used up, malformed address) goes back to the model as the tool's error message instead of ending the run.
119
+
120
+ ## Without a model
121
+
122
+ ```python
123
+ from langchain_verdix import VerdixClient
124
+
125
+ verdix = VerdixClient(account=account, max_price_per_call_usd=0.10)
126
+
127
+ pricing = verdix.get_pricing() # free: reads the unpaid 402 quotes
128
+ result = verdix.check_address("0x...", tier="quick") # or: await verdix.acheck_address(...)
129
+ if result.verdict != "safe":
130
+ ... # stop, or ask the user
131
+ ```
132
+
133
+ The errors to expect are `VerdixError`s: a malformed address, a price over your cap, an exhausted budget or an unexpected API answer.
134
+
135
+ ## Development
136
+
137
+ ```bash
138
+ pip install -e ".[test]"
139
+ pytest # offline: mocked API, throwaway key, sockets disabled, no payment
140
+ ```
141
+
142
+ `examples/check_before_send.py` makes one real paid check.
143
+
144
+ ## License
145
+
146
+ MIT
@@ -0,0 +1,33 @@
1
+ """One real, paid Verdix check through the LangChain tool (no model needed).
2
+
3
+ Pays REAL USDC on Base mainnet ($0.02, quick tier) from AGENT_PRIVATE_KEY.
4
+
5
+ AGENT_PRIVATE_KEY=0x... python examples/check_before_send.py [address]
6
+ """
7
+
8
+ import json
9
+ import os
10
+ import sys
11
+
12
+ from eth_account import Account
13
+
14
+ from langchain_verdix import VerdixAddressRiskTool
15
+
16
+ address = sys.argv[1] if len(sys.argv) > 1 else "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
17
+
18
+ tool = VerdixAddressRiskTool(
19
+ account=Account.from_key(os.environ["AGENT_PRIVATE_KEY"]),
20
+ max_price_per_call_usd=0.02, # quick tier only
21
+ max_total_spend_usd=0.02, # one check
22
+ )
23
+
24
+ message = tool.invoke(
25
+ {
26
+ "type": "tool_call",
27
+ "id": "example",
28
+ "name": tool.name,
29
+ "args": {"address": address, "tier": "quick"},
30
+ }
31
+ )
32
+ # A refusal or API error comes back as the message content, with no artifact.
33
+ print(json.dumps(message.artifact, indent=2) if message.artifact else message.content)
@@ -0,0 +1,35 @@
1
+ """LangChain tool: check an EVM address on Base before sending it funds."""
2
+
3
+ from langchain_verdix._constants import (
4
+ DEFAULT_API_URL,
5
+ TIER_LIST_PRICES_USD,
6
+ VERDIX_TIERS,
7
+ VerdixTier,
8
+ )
9
+ from langchain_verdix._version import __version__
10
+ from langchain_verdix.client import (
11
+ VerdixCheckResult,
12
+ VerdixClient,
13
+ VerdixPayment,
14
+ VerdixTierQuote,
15
+ VerdixVerdict,
16
+ tiers_within_cap,
17
+ )
18
+ from langchain_verdix.errors import VerdixError
19
+ from langchain_verdix.tools import VerdixAddressRiskTool
20
+
21
+ __all__ = [
22
+ "DEFAULT_API_URL",
23
+ "TIER_LIST_PRICES_USD",
24
+ "VERDIX_TIERS",
25
+ "VerdixAddressRiskTool",
26
+ "VerdixCheckResult",
27
+ "VerdixClient",
28
+ "VerdixError",
29
+ "VerdixPayment",
30
+ "VerdixTier",
31
+ "VerdixTierQuote",
32
+ "VerdixVerdict",
33
+ "__version__",
34
+ "tiers_within_cap",
35
+ ]
@@ -0,0 +1,24 @@
1
+ from typing import Literal
2
+
3
+ DEFAULT_API_URL = "https://api.verdixapi.com"
4
+
5
+ # CAIP-2 id of Base mainnet, the only network Verdix accepts payment on.
6
+ BASE_MAINNET = "eip155:8453"
7
+
8
+ # Native USDC on Base (lowercase), the only asset Verdix accepts.
9
+ USDC_BASE = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"
10
+
11
+ USDC_DECIMALS = 6
12
+
13
+ VerdixTier = Literal["quick", "standard", "deep"]
14
+
15
+ VERDIX_TIERS: tuple[VerdixTier, ...] = ("quick", "standard", "deep")
16
+
17
+ # List prices in USD, used to pick which tiers fit under your cap and to
18
+ # describe them to the model. The API's own 402 quote is authoritative: a
19
+ # tier is never paid for if its live price is above max_price_per_call_usd.
20
+ TIER_LIST_PRICES_USD: dict[VerdixTier, float] = {
21
+ "quick": 0.02,
22
+ "standard": 0.10,
23
+ "deep": 0.50,
24
+ }
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"