langchain-insumer 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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 The Insumer Model
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,152 @@
1
+ Metadata-Version: 2.4
2
+ Name: langchain-insumer
3
+ Version: 0.1.0
4
+ Summary: LangChain integration for The Insumer Model — privacy-preserving on-chain attestation across 31 blockchains
5
+ Author-email: The Insumer Model <contact@insumermodel.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://insumermodel.com/for-agents/
8
+ Project-URL: Documentation, https://insumermodel.com/llms-full.txt
9
+ Project-URL: Repository, https://github.com/douglasborthwick-crypto/langchain-insumer
10
+ Project-URL: API Reference, https://insumermodel.com/openapi.yaml
11
+ Project-URL: Bug Tracker, https://github.com/douglasborthwick-crypto/langchain-insumer/issues
12
+ Keywords: langchain,blockchain,attestation,token-gated,nft,web3,privacy,on-chain,verification
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Software Development :: Libraries
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: langchain-core>=0.2.0
26
+ Requires-Dist: requests>=2.28.0
27
+ Requires-Dist: pydantic>=2.0.0
28
+ Dynamic: license-file
29
+
30
+ # langchain-insumer
31
+
32
+ LangChain integration for [The Insumer Model](https://insumermodel.com) Attestation API.
33
+
34
+ Verify on-chain token balances and NFT ownership across 31 blockchains with privacy-preserving attestations. Returns only true/false — never exposes actual wallet balances.
35
+
36
+ ## Installation
37
+
38
+ ```bash
39
+ pip install langchain-insumer
40
+ ```
41
+
42
+ ## Quick Start
43
+
44
+ ```python
45
+ from langchain_insumer import InsumerAPIWrapper, InsumerAttestTool
46
+
47
+ # Initialize the API wrapper
48
+ api = InsumerAPIWrapper(api_key="insr_live_YOUR_KEY_HERE")
49
+
50
+ # Create tools for your agent
51
+ attest_tool = InsumerAttestTool(api_wrapper=api)
52
+
53
+ # Use with a LangChain agent
54
+ from langchain.agents import initialize_agent, AgentType
55
+ from langchain_openai import ChatOpenAI
56
+
57
+ llm = ChatOpenAI(model="gpt-4")
58
+ tools = [attest_tool]
59
+ agent = initialize_agent(tools, llm, agent=AgentType.OPENAI_FUNCTIONS)
60
+
61
+ agent.run("Does wallet 0x1234... hold at least 100 USDC on Ethereum?")
62
+ ```
63
+
64
+ ## Available Tools
65
+
66
+ | Tool | Description | Credits |
67
+ |------|-------------|---------|
68
+ | `InsumerAttestTool` | Verify on-chain conditions with signed attestation | 1/call |
69
+ | `InsumerCheckDiscountTool` | Calculate discount for wallet at merchant | Free |
70
+ | `InsumerListMerchantsTool` | Browse merchant directory | Free |
71
+ | `InsumerListTokensTool` | List registered tokens and NFTs | Free |
72
+ | `InsumerVerifyTool` | Create signed discount code (INSR-XXXXX) | 1/call |
73
+ | `InsumerCreditsTool` | Check API key credit balance | Free |
74
+
75
+ ## Using All Tools
76
+
77
+ ```python
78
+ from langchain_insumer import (
79
+ InsumerAPIWrapper,
80
+ InsumerAttestTool,
81
+ InsumerCheckDiscountTool,
82
+ InsumerCreditsTool,
83
+ InsumerListMerchantsTool,
84
+ InsumerListTokensTool,
85
+ InsumerVerifyTool,
86
+ )
87
+
88
+ api = InsumerAPIWrapper(api_key="insr_live_YOUR_KEY_HERE")
89
+
90
+ tools = [
91
+ InsumerAttestTool(api_wrapper=api),
92
+ InsumerCheckDiscountTool(api_wrapper=api),
93
+ InsumerCreditsTool(api_wrapper=api),
94
+ InsumerListMerchantsTool(api_wrapper=api),
95
+ InsumerListTokensTool(api_wrapper=api),
96
+ InsumerVerifyTool(api_wrapper=api),
97
+ ]
98
+ ```
99
+
100
+ ## Attestation Example
101
+
102
+ ```python
103
+ import json
104
+
105
+ api = InsumerAPIWrapper(api_key="insr_live_YOUR_KEY_HERE")
106
+
107
+ result = api.attest(
108
+ wallet="0x1234567890abcdef1234567890abcdef12345678",
109
+ conditions=[
110
+ {
111
+ "type": "token_balance",
112
+ "contractAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
113
+ "chainId": 1,
114
+ "threshold": 1000,
115
+ "decimals": 6,
116
+ "label": "USDC >= 1000 on Ethereum",
117
+ },
118
+ {
119
+ "type": "nft_ownership",
120
+ "contractAddress": "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D",
121
+ "chainId": 1,
122
+ "label": "Bored Ape Yacht Club holder",
123
+ },
124
+ ],
125
+ )
126
+
127
+ attestation = result["data"]["attestation"]
128
+ print(f"Pass: {attestation['pass']}")
129
+ for r in attestation["results"]:
130
+ print(f" {r['label']}: {'met' if r['met'] else 'not met'}")
131
+ print(f"Signature: {result['data']['sig']}")
132
+ ```
133
+
134
+ ## Supported Chains (31)
135
+
136
+ Ethereum, BNB Chain, Base, Avalanche, Polygon, Arbitrum, Optimism, Solana, Chiliz, Soneium, Plume, Sonic, Gnosis, Mantle, Scroll, Linea, zkSync Era, Blast, Taiko, Ronin, Celo, Moonbeam, Moonriver, Viction, opBNB, World Chain, Unichain, Ink, Sei, Berachain, ApeChain.
137
+
138
+ ## Get an API Key
139
+
140
+ - **Free** (10 credits): [insumermodel.com/developers](https://insumermodel.com/developers/)
141
+ - **Pro** (10,000/day): 29 USDC/month
142
+ - **Enterprise** (100,000/day): 99 USDC/month
143
+
144
+ ## Links
145
+
146
+ - [API Documentation](https://insumermodel.com/for-agents/)
147
+ - [OpenAPI Spec](https://insumermodel.com/openapi.yaml)
148
+ - [Full API Reference](https://insumermodel.com/llms-full.txt)
149
+
150
+ ## License
151
+
152
+ MIT
@@ -0,0 +1,123 @@
1
+ # langchain-insumer
2
+
3
+ LangChain integration for [The Insumer Model](https://insumermodel.com) Attestation API.
4
+
5
+ Verify on-chain token balances and NFT ownership across 31 blockchains with privacy-preserving attestations. Returns only true/false — never exposes actual wallet balances.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pip install langchain-insumer
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```python
16
+ from langchain_insumer import InsumerAPIWrapper, InsumerAttestTool
17
+
18
+ # Initialize the API wrapper
19
+ api = InsumerAPIWrapper(api_key="insr_live_YOUR_KEY_HERE")
20
+
21
+ # Create tools for your agent
22
+ attest_tool = InsumerAttestTool(api_wrapper=api)
23
+
24
+ # Use with a LangChain agent
25
+ from langchain.agents import initialize_agent, AgentType
26
+ from langchain_openai import ChatOpenAI
27
+
28
+ llm = ChatOpenAI(model="gpt-4")
29
+ tools = [attest_tool]
30
+ agent = initialize_agent(tools, llm, agent=AgentType.OPENAI_FUNCTIONS)
31
+
32
+ agent.run("Does wallet 0x1234... hold at least 100 USDC on Ethereum?")
33
+ ```
34
+
35
+ ## Available Tools
36
+
37
+ | Tool | Description | Credits |
38
+ |------|-------------|---------|
39
+ | `InsumerAttestTool` | Verify on-chain conditions with signed attestation | 1/call |
40
+ | `InsumerCheckDiscountTool` | Calculate discount for wallet at merchant | Free |
41
+ | `InsumerListMerchantsTool` | Browse merchant directory | Free |
42
+ | `InsumerListTokensTool` | List registered tokens and NFTs | Free |
43
+ | `InsumerVerifyTool` | Create signed discount code (INSR-XXXXX) | 1/call |
44
+ | `InsumerCreditsTool` | Check API key credit balance | Free |
45
+
46
+ ## Using All Tools
47
+
48
+ ```python
49
+ from langchain_insumer import (
50
+ InsumerAPIWrapper,
51
+ InsumerAttestTool,
52
+ InsumerCheckDiscountTool,
53
+ InsumerCreditsTool,
54
+ InsumerListMerchantsTool,
55
+ InsumerListTokensTool,
56
+ InsumerVerifyTool,
57
+ )
58
+
59
+ api = InsumerAPIWrapper(api_key="insr_live_YOUR_KEY_HERE")
60
+
61
+ tools = [
62
+ InsumerAttestTool(api_wrapper=api),
63
+ InsumerCheckDiscountTool(api_wrapper=api),
64
+ InsumerCreditsTool(api_wrapper=api),
65
+ InsumerListMerchantsTool(api_wrapper=api),
66
+ InsumerListTokensTool(api_wrapper=api),
67
+ InsumerVerifyTool(api_wrapper=api),
68
+ ]
69
+ ```
70
+
71
+ ## Attestation Example
72
+
73
+ ```python
74
+ import json
75
+
76
+ api = InsumerAPIWrapper(api_key="insr_live_YOUR_KEY_HERE")
77
+
78
+ result = api.attest(
79
+ wallet="0x1234567890abcdef1234567890abcdef12345678",
80
+ conditions=[
81
+ {
82
+ "type": "token_balance",
83
+ "contractAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
84
+ "chainId": 1,
85
+ "threshold": 1000,
86
+ "decimals": 6,
87
+ "label": "USDC >= 1000 on Ethereum",
88
+ },
89
+ {
90
+ "type": "nft_ownership",
91
+ "contractAddress": "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D",
92
+ "chainId": 1,
93
+ "label": "Bored Ape Yacht Club holder",
94
+ },
95
+ ],
96
+ )
97
+
98
+ attestation = result["data"]["attestation"]
99
+ print(f"Pass: {attestation['pass']}")
100
+ for r in attestation["results"]:
101
+ print(f" {r['label']}: {'met' if r['met'] else 'not met'}")
102
+ print(f"Signature: {result['data']['sig']}")
103
+ ```
104
+
105
+ ## Supported Chains (31)
106
+
107
+ Ethereum, BNB Chain, Base, Avalanche, Polygon, Arbitrum, Optimism, Solana, Chiliz, Soneium, Plume, Sonic, Gnosis, Mantle, Scroll, Linea, zkSync Era, Blast, Taiko, Ronin, Celo, Moonbeam, Moonriver, Viction, opBNB, World Chain, Unichain, Ink, Sei, Berachain, ApeChain.
108
+
109
+ ## Get an API Key
110
+
111
+ - **Free** (10 credits): [insumermodel.com/developers](https://insumermodel.com/developers/)
112
+ - **Pro** (10,000/day): 29 USDC/month
113
+ - **Enterprise** (100,000/day): 99 USDC/month
114
+
115
+ ## Links
116
+
117
+ - [API Documentation](https://insumermodel.com/for-agents/)
118
+ - [OpenAPI Spec](https://insumermodel.com/openapi.yaml)
119
+ - [Full API Reference](https://insumermodel.com/llms-full.txt)
120
+
121
+ ## License
122
+
123
+ MIT
@@ -0,0 +1,19 @@
1
+ """LangChain integration for The Insumer Model Attestation API."""
2
+
3
+ from langchain_insumer.tools.attest import InsumerAttestTool
4
+ from langchain_insumer.tools.check_discount import InsumerCheckDiscountTool
5
+ from langchain_insumer.tools.credits import InsumerCreditsTool
6
+ from langchain_insumer.tools.list_merchants import InsumerListMerchantsTool
7
+ from langchain_insumer.tools.list_tokens import InsumerListTokensTool
8
+ from langchain_insumer.tools.verify import InsumerVerifyTool
9
+ from langchain_insumer.wrapper import InsumerAPIWrapper
10
+
11
+ __all__ = [
12
+ "InsumerAPIWrapper",
13
+ "InsumerAttestTool",
14
+ "InsumerCheckDiscountTool",
15
+ "InsumerCreditsTool",
16
+ "InsumerListMerchantsTool",
17
+ "InsumerListTokensTool",
18
+ "InsumerVerifyTool",
19
+ ]
@@ -0,0 +1,17 @@
1
+ """Insumer Model tools for LangChain agents."""
2
+
3
+ from langchain_insumer.tools.attest import InsumerAttestTool
4
+ from langchain_insumer.tools.check_discount import InsumerCheckDiscountTool
5
+ from langchain_insumer.tools.credits import InsumerCreditsTool
6
+ from langchain_insumer.tools.list_merchants import InsumerListMerchantsTool
7
+ from langchain_insumer.tools.list_tokens import InsumerListTokensTool
8
+ from langchain_insumer.tools.verify import InsumerVerifyTool
9
+
10
+ __all__ = [
11
+ "InsumerAttestTool",
12
+ "InsumerCheckDiscountTool",
13
+ "InsumerCreditsTool",
14
+ "InsumerListMerchantsTool",
15
+ "InsumerListTokensTool",
16
+ "InsumerVerifyTool",
17
+ ]
@@ -0,0 +1,72 @@
1
+ """Tool for creating privacy-preserving on-chain attestations."""
2
+
3
+ import json
4
+ from typing import Any, Optional, Type
5
+
6
+ from langchain_core.callbacks import CallbackManagerForToolRun
7
+ from langchain_core.tools import BaseTool
8
+ from pydantic import BaseModel, Field
9
+
10
+ from langchain_insumer.wrapper import InsumerAPIWrapper
11
+
12
+
13
+ class AttestSchema(BaseModel):
14
+ """Input for InsumerAttestTool."""
15
+
16
+ wallet: Optional[str] = Field(
17
+ default=None,
18
+ description="EVM wallet address (0x...) to verify.",
19
+ )
20
+ solana_wallet: Optional[str] = Field(
21
+ default=None,
22
+ description="Solana wallet address (base58) to verify.",
23
+ )
24
+ conditions: str = Field(
25
+ description=(
26
+ 'JSON array of conditions. Each condition: {"type": "token_balance" or '
27
+ '"nft_ownership", "contractAddress": "0x...", "chainId": 1, '
28
+ '"threshold": 1000, "decimals": 6, "label": "USDC >= 1000"}. '
29
+ "Supported chains: Ethereum (1), BNB (56), Base (8453), Polygon (137), "
30
+ "Arbitrum (42161), Optimism (10), Avalanche (43114), Solana (\"solana\"), "
31
+ "and 23 more. Max 10 conditions per call."
32
+ ),
33
+ )
34
+
35
+
36
+ class InsumerAttestTool(BaseTool):
37
+ """Verify on-chain token balances or NFT ownership with a signed attestation.
38
+
39
+ Returns only true/false per condition -- never exposes actual balances.
40
+ The response includes an ECDSA P-256 signature for cryptographic proof.
41
+ Costs 1 attestation credit per call.
42
+ """
43
+
44
+ name: str = "insumer_attest"
45
+ description: str = (
46
+ "Verify on-chain conditions (token balances, NFT ownership) across 31 "
47
+ "blockchains. Returns a cryptographically signed true/false attestation "
48
+ "without exposing actual wallet balances. Use this when you need to check "
49
+ "if a wallet holds a specific token or NFT. Costs 1 attestation credit."
50
+ )
51
+ args_schema: Type[AttestSchema] = AttestSchema
52
+
53
+ api_wrapper: InsumerAPIWrapper = Field(..., exclude=True)
54
+
55
+ def __init__(self, api_wrapper: InsumerAPIWrapper) -> None:
56
+ super().__init__(api_wrapper=api_wrapper)
57
+
58
+ def _run(
59
+ self,
60
+ conditions: str,
61
+ wallet: Optional[str] = None,
62
+ solana_wallet: Optional[str] = None,
63
+ run_manager: Optional[CallbackManagerForToolRun] = None,
64
+ ) -> str:
65
+ """Execute the attestation."""
66
+ parsed_conditions: list[dict[str, Any]] = json.loads(conditions)
67
+ result = self.api_wrapper.attest(
68
+ conditions=parsed_conditions,
69
+ wallet=wallet,
70
+ solana_wallet=solana_wallet,
71
+ )
72
+ return json.dumps(result, indent=2)
@@ -0,0 +1,60 @@
1
+ """Tool for checking wallet discount eligibility at a merchant."""
2
+
3
+ import json
4
+ from typing import Optional, Type
5
+
6
+ from langchain_core.callbacks import CallbackManagerForToolRun
7
+ from langchain_core.tools import BaseTool
8
+ from pydantic import BaseModel, Field
9
+
10
+ from langchain_insumer.wrapper import InsumerAPIWrapper
11
+
12
+
13
+ class CheckDiscountSchema(BaseModel):
14
+ """Input for InsumerCheckDiscountTool."""
15
+
16
+ merchant_id: str = Field(description="Merchant ID to check discount at.")
17
+ wallet: Optional[str] = Field(
18
+ default=None,
19
+ description="EVM wallet address (0x...).",
20
+ )
21
+ solana_wallet: Optional[str] = Field(
22
+ default=None,
23
+ description="Solana wallet address (base58).",
24
+ )
25
+
26
+
27
+ class InsumerCheckDiscountTool(BaseTool):
28
+ """Calculate the discount a wallet qualifies for at a specific merchant.
29
+
30
+ Checks on-chain balances server-side and returns the total discount
31
+ percentage with a breakdown by token. Free to call, no credits consumed.
32
+ """
33
+
34
+ name: str = "insumer_check_discount"
35
+ description: str = (
36
+ "Calculate what discount a wallet qualifies for at a specific merchant. "
37
+ "Checks on-chain token balances and returns the discount percentage "
38
+ "with a breakdown by token tier. Free to call, no credits consumed."
39
+ )
40
+ args_schema: Type[CheckDiscountSchema] = CheckDiscountSchema
41
+
42
+ api_wrapper: InsumerAPIWrapper = Field(..., exclude=True)
43
+
44
+ def __init__(self, api_wrapper: InsumerAPIWrapper) -> None:
45
+ super().__init__(api_wrapper=api_wrapper)
46
+
47
+ def _run(
48
+ self,
49
+ merchant_id: str,
50
+ wallet: Optional[str] = None,
51
+ solana_wallet: Optional[str] = None,
52
+ run_manager: Optional[CallbackManagerForToolRun] = None,
53
+ ) -> str:
54
+ """Check the discount."""
55
+ result = self.api_wrapper.check_discount(
56
+ merchant_id=merchant_id,
57
+ wallet=wallet,
58
+ solana_wallet=solana_wallet,
59
+ )
60
+ return json.dumps(result, indent=2)
@@ -0,0 +1,40 @@
1
+ """Tool for checking attestation credit balance."""
2
+
3
+ import json
4
+ from typing import Optional, Type
5
+
6
+ from langchain_core.callbacks import CallbackManagerForToolRun
7
+ from langchain_core.tools import BaseTool
8
+ from pydantic import BaseModel, Field
9
+
10
+ from langchain_insumer.wrapper import InsumerAPIWrapper
11
+
12
+
13
+ class CreditsSchema(BaseModel):
14
+ """Input for InsumerCreditsTool. No parameters required."""
15
+
16
+ pass
17
+
18
+
19
+ class InsumerCreditsTool(BaseTool):
20
+ """Check the attestation credit balance for the current API key."""
21
+
22
+ name: str = "insumer_credits"
23
+ description: str = (
24
+ "Check the attestation credit balance, tier, and daily rate limit "
25
+ "for the current Insumer API key. No parameters needed."
26
+ )
27
+ args_schema: Type[CreditsSchema] = CreditsSchema
28
+
29
+ api_wrapper: InsumerAPIWrapper = Field(..., exclude=True)
30
+
31
+ def __init__(self, api_wrapper: InsumerAPIWrapper) -> None:
32
+ super().__init__(api_wrapper=api_wrapper)
33
+
34
+ def _run(
35
+ self,
36
+ run_manager: Optional[CallbackManagerForToolRun] = None,
37
+ ) -> str:
38
+ """Check credits."""
39
+ result = self.api_wrapper.get_credits()
40
+ return json.dumps(result, indent=2)
@@ -0,0 +1,66 @@
1
+ """Tool for listing merchants in the public directory."""
2
+
3
+ import json
4
+ from typing import Optional, Type
5
+
6
+ from langchain_core.callbacks import CallbackManagerForToolRun
7
+ from langchain_core.tools import BaseTool
8
+ from pydantic import BaseModel, Field
9
+
10
+ from langchain_insumer.wrapper import InsumerAPIWrapper
11
+
12
+
13
+ class ListMerchantsSchema(BaseModel):
14
+ """Input for InsumerListMerchantsTool."""
15
+
16
+ token: Optional[str] = Field(
17
+ default=None,
18
+ description="Filter by accepted token symbol (e.g. UNI, SHIB).",
19
+ )
20
+ verified: Optional[bool] = Field(
21
+ default=None,
22
+ description="Filter by domain verification status.",
23
+ )
24
+ limit: int = Field(
25
+ default=50,
26
+ description="Results per page (max 200).",
27
+ )
28
+ offset: int = Field(
29
+ default=0,
30
+ description="Pagination offset.",
31
+ )
32
+
33
+
34
+ class InsumerListMerchantsTool(BaseTool):
35
+ """Browse merchants that offer token-gated discounts."""
36
+
37
+ name: str = "insumer_list_merchants"
38
+ description: str = (
39
+ "List merchants in The Insumer Model directory that offer discounts "
40
+ "to token holders. Optionally filter by accepted token symbol or "
41
+ "verification status. Returns merchant names, locations, accepted "
42
+ "tokens, and discount structures."
43
+ )
44
+ args_schema: Type[ListMerchantsSchema] = ListMerchantsSchema
45
+
46
+ api_wrapper: InsumerAPIWrapper = Field(..., exclude=True)
47
+
48
+ def __init__(self, api_wrapper: InsumerAPIWrapper) -> None:
49
+ super().__init__(api_wrapper=api_wrapper)
50
+
51
+ def _run(
52
+ self,
53
+ token: Optional[str] = None,
54
+ verified: Optional[bool] = None,
55
+ limit: int = 50,
56
+ offset: int = 0,
57
+ run_manager: Optional[CallbackManagerForToolRun] = None,
58
+ ) -> str:
59
+ """List merchants."""
60
+ result = self.api_wrapper.list_merchants(
61
+ token=token,
62
+ verified=verified,
63
+ limit=limit,
64
+ offset=offset,
65
+ )
66
+ return json.dumps(result, indent=2)
@@ -0,0 +1,59 @@
1
+ """Tool for listing registered tokens and NFT collections."""
2
+
3
+ import json
4
+ from typing import Any, Optional, Type
5
+
6
+ from langchain_core.callbacks import CallbackManagerForToolRun
7
+ from langchain_core.tools import BaseTool
8
+ from pydantic import BaseModel, Field
9
+
10
+ from langchain_insumer.wrapper import InsumerAPIWrapper
11
+
12
+
13
+ class ListTokensSchema(BaseModel):
14
+ """Input for InsumerListTokensTool."""
15
+
16
+ chain: Optional[Any] = Field(
17
+ default=None,
18
+ description='Filter by chain ID (e.g. 1 for Ethereum, "solana" for Solana).',
19
+ )
20
+ symbol: Optional[str] = Field(
21
+ default=None,
22
+ description="Filter by token symbol (e.g. UNI).",
23
+ )
24
+ asset_type: Optional[str] = Field(
25
+ default=None,
26
+ description='Filter by type: "token" or "nft".',
27
+ )
28
+
29
+
30
+ class InsumerListTokensTool(BaseTool):
31
+ """List tokens and NFT collections registered with merchants."""
32
+
33
+ name: str = "insumer_list_tokens"
34
+ description: str = (
35
+ "List all tokens and NFT collections registered in The Insumer Model "
36
+ "ecosystem. Filter by blockchain, symbol, or asset type (token/nft). "
37
+ "Returns contract addresses, chain IDs, and metadata."
38
+ )
39
+ args_schema: Type[ListTokensSchema] = ListTokensSchema
40
+
41
+ api_wrapper: InsumerAPIWrapper = Field(..., exclude=True)
42
+
43
+ def __init__(self, api_wrapper: InsumerAPIWrapper) -> None:
44
+ super().__init__(api_wrapper=api_wrapper)
45
+
46
+ def _run(
47
+ self,
48
+ chain: Optional[Any] = None,
49
+ symbol: Optional[str] = None,
50
+ asset_type: Optional[str] = None,
51
+ run_manager: Optional[CallbackManagerForToolRun] = None,
52
+ ) -> str:
53
+ """List tokens."""
54
+ result = self.api_wrapper.list_tokens(
55
+ chain=chain,
56
+ symbol=symbol,
57
+ asset_type=asset_type,
58
+ )
59
+ return json.dumps(result, indent=2)
@@ -0,0 +1,61 @@
1
+ """Tool for creating signed discount verification codes."""
2
+
3
+ import json
4
+ from typing import Optional, Type
5
+
6
+ from langchain_core.callbacks import CallbackManagerForToolRun
7
+ from langchain_core.tools import BaseTool
8
+ from pydantic import BaseModel, Field
9
+
10
+ from langchain_insumer.wrapper import InsumerAPIWrapper
11
+
12
+
13
+ class VerifySchema(BaseModel):
14
+ """Input for InsumerVerifyTool."""
15
+
16
+ merchant_id: str = Field(description="Merchant ID to generate the discount code for.")
17
+ wallet: Optional[str] = Field(
18
+ default=None,
19
+ description="EVM wallet address (0x...).",
20
+ )
21
+ solana_wallet: Optional[str] = Field(
22
+ default=None,
23
+ description="Solana wallet address (base58).",
24
+ )
25
+
26
+
27
+ class InsumerVerifyTool(BaseTool):
28
+ """Create a signed discount verification code for a wallet at a merchant.
29
+
30
+ The code (INSR-XXXXX) is valid for 30 minutes and can be redeemed
31
+ at the merchant's point of sale. Costs 1 merchant credit.
32
+ """
33
+
34
+ name: str = "insumer_verify"
35
+ description: str = (
36
+ "Create a signed discount verification code (INSR-XXXXX) for a wallet "
37
+ "at a specific merchant. The code is valid for 30 minutes and includes "
38
+ "the discount percentage and an ECDSA signature. Use this after "
39
+ "checking the discount to generate a redeemable code. Costs 1 credit."
40
+ )
41
+ args_schema: Type[VerifySchema] = VerifySchema
42
+
43
+ api_wrapper: InsumerAPIWrapper = Field(..., exclude=True)
44
+
45
+ def __init__(self, api_wrapper: InsumerAPIWrapper) -> None:
46
+ super().__init__(api_wrapper=api_wrapper)
47
+
48
+ def _run(
49
+ self,
50
+ merchant_id: str,
51
+ wallet: Optional[str] = None,
52
+ solana_wallet: Optional[str] = None,
53
+ run_manager: Optional[CallbackManagerForToolRun] = None,
54
+ ) -> str:
55
+ """Create verification code."""
56
+ result = self.api_wrapper.verify(
57
+ merchant_id=merchant_id,
58
+ wallet=wallet,
59
+ solana_wallet=solana_wallet,
60
+ )
61
+ return json.dumps(result, indent=2)
@@ -0,0 +1,151 @@
1
+ """API wrapper for The Insumer Model Attestation API."""
2
+
3
+ from typing import Any, Optional
4
+
5
+ import requests
6
+ from pydantic import BaseModel, Field
7
+
8
+ BASE_URL = "https://us-central1-insumer-merchant.cloudfunctions.net/insumerApi/v1"
9
+
10
+
11
+ class InsumerAPIWrapper(BaseModel):
12
+ """Wrapper around The Insumer Model API.
13
+
14
+ Provides privacy-preserving on-chain attestation and token-gated commerce
15
+ across 31 blockchains. Verifies token balances and NFT ownership without
16
+ exposing actual wallet balances.
17
+
18
+ Args:
19
+ api_key: API key in format ``insr_live_`` followed by 40 hex characters.
20
+ Get a free key at https://insumermodel.com/developers/
21
+ timeout: Request timeout in seconds. Default 30.
22
+ """
23
+
24
+ api_key: str = Field(description="Insumer API key (insr_live_...)")
25
+ timeout: int = Field(default=30, description="Request timeout in seconds")
26
+
27
+ def _headers(self) -> dict:
28
+ return {
29
+ "X-API-Key": self.api_key,
30
+ "Content-Type": "application/json",
31
+ }
32
+
33
+ def _get(self, path: str, params: Optional[dict] = None) -> dict:
34
+ resp = requests.get(
35
+ f"{BASE_URL}{path}",
36
+ headers=self._headers(),
37
+ params=params,
38
+ timeout=self.timeout,
39
+ )
40
+ resp.raise_for_status()
41
+ return resp.json()
42
+
43
+ def _post(self, path: str, json_body: Optional[dict] = None) -> dict:
44
+ resp = requests.post(
45
+ f"{BASE_URL}{path}",
46
+ headers=self._headers(),
47
+ json=json_body or {},
48
+ timeout=self.timeout,
49
+ )
50
+ resp.raise_for_status()
51
+ return resp.json()
52
+
53
+ def attest(
54
+ self,
55
+ conditions: list[dict[str, Any]],
56
+ wallet: Optional[str] = None,
57
+ solana_wallet: Optional[str] = None,
58
+ ) -> dict:
59
+ """Create a privacy-preserving on-chain attestation.
60
+
61
+ Verifies 1-10 conditions (token balances, NFT ownership) and returns
62
+ a cryptographically signed true/false result. Never exposes actual
63
+ balances. Costs 1 attestation credit.
64
+
65
+ Args:
66
+ conditions: List of condition dicts, each with:
67
+ - type: "token_balance" or "nft_ownership"
68
+ - contractAddress: Token/NFT contract address
69
+ - chainId: EVM chain ID (int) or "solana"
70
+ - threshold: Min balance (for token_balance)
71
+ - decimals: Token decimals (default 18)
72
+ - label: Human-readable label
73
+ wallet: EVM wallet address (0x...)
74
+ solana_wallet: Solana wallet address (base58)
75
+
76
+ Returns:
77
+ API response with attestation results and ECDSA signature.
78
+ """
79
+ body: dict[str, Any] = {"conditions": conditions}
80
+ if wallet:
81
+ body["wallet"] = wallet
82
+ if solana_wallet:
83
+ body["solanaWallet"] = solana_wallet
84
+ return self._post("/attest", body)
85
+
86
+ def get_credits(self) -> dict:
87
+ """Check attestation credit balance for the API key."""
88
+ return self._get("/credits")
89
+
90
+ def list_merchants(
91
+ self,
92
+ token: Optional[str] = None,
93
+ verified: Optional[bool] = None,
94
+ limit: int = 50,
95
+ offset: int = 0,
96
+ ) -> dict:
97
+ """List merchants in the public directory."""
98
+ params: dict[str, Any] = {"limit": limit, "offset": offset}
99
+ if token:
100
+ params["token"] = token
101
+ if verified is not None:
102
+ params["verified"] = str(verified).lower()
103
+ return self._get("/merchants", params)
104
+
105
+ def get_merchant(self, merchant_id: str) -> dict:
106
+ """Get full public merchant profile with tier structures."""
107
+ return self._get(f"/merchants/{merchant_id}")
108
+
109
+ def list_tokens(
110
+ self,
111
+ chain: Optional[Any] = None,
112
+ symbol: Optional[str] = None,
113
+ asset_type: Optional[str] = None,
114
+ ) -> dict:
115
+ """List registered tokens and NFT collections."""
116
+ params: dict[str, Any] = {}
117
+ if chain is not None:
118
+ params["chain"] = chain
119
+ if symbol:
120
+ params["symbol"] = symbol
121
+ if asset_type:
122
+ params["type"] = asset_type
123
+ return self._get("/tokens", params)
124
+
125
+ def check_discount(
126
+ self,
127
+ merchant_id: str,
128
+ wallet: Optional[str] = None,
129
+ solana_wallet: Optional[str] = None,
130
+ ) -> dict:
131
+ """Calculate discount for a wallet at a merchant. Free, no credits consumed."""
132
+ params: dict[str, Any] = {"merchant": merchant_id}
133
+ if wallet:
134
+ params["wallet"] = wallet
135
+ if solana_wallet:
136
+ params["solanaWallet"] = solana_wallet
137
+ return self._get("/discount/check", params)
138
+
139
+ def verify(
140
+ self,
141
+ merchant_id: str,
142
+ wallet: Optional[str] = None,
143
+ solana_wallet: Optional[str] = None,
144
+ ) -> dict:
145
+ """Create a signed discount code (INSR-XXXXX), valid 30 minutes. Costs 1 credit."""
146
+ body: dict[str, Any] = {"merchantId": merchant_id}
147
+ if wallet:
148
+ body["wallet"] = wallet
149
+ if solana_wallet:
150
+ body["solanaWallet"] = solana_wallet
151
+ return self._post("/verify", body)
@@ -0,0 +1,152 @@
1
+ Metadata-Version: 2.4
2
+ Name: langchain-insumer
3
+ Version: 0.1.0
4
+ Summary: LangChain integration for The Insumer Model — privacy-preserving on-chain attestation across 31 blockchains
5
+ Author-email: The Insumer Model <contact@insumermodel.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://insumermodel.com/for-agents/
8
+ Project-URL: Documentation, https://insumermodel.com/llms-full.txt
9
+ Project-URL: Repository, https://github.com/douglasborthwick-crypto/langchain-insumer
10
+ Project-URL: API Reference, https://insumermodel.com/openapi.yaml
11
+ Project-URL: Bug Tracker, https://github.com/douglasborthwick-crypto/langchain-insumer/issues
12
+ Keywords: langchain,blockchain,attestation,token-gated,nft,web3,privacy,on-chain,verification
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Software Development :: Libraries
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: langchain-core>=0.2.0
26
+ Requires-Dist: requests>=2.28.0
27
+ Requires-Dist: pydantic>=2.0.0
28
+ Dynamic: license-file
29
+
30
+ # langchain-insumer
31
+
32
+ LangChain integration for [The Insumer Model](https://insumermodel.com) Attestation API.
33
+
34
+ Verify on-chain token balances and NFT ownership across 31 blockchains with privacy-preserving attestations. Returns only true/false — never exposes actual wallet balances.
35
+
36
+ ## Installation
37
+
38
+ ```bash
39
+ pip install langchain-insumer
40
+ ```
41
+
42
+ ## Quick Start
43
+
44
+ ```python
45
+ from langchain_insumer import InsumerAPIWrapper, InsumerAttestTool
46
+
47
+ # Initialize the API wrapper
48
+ api = InsumerAPIWrapper(api_key="insr_live_YOUR_KEY_HERE")
49
+
50
+ # Create tools for your agent
51
+ attest_tool = InsumerAttestTool(api_wrapper=api)
52
+
53
+ # Use with a LangChain agent
54
+ from langchain.agents import initialize_agent, AgentType
55
+ from langchain_openai import ChatOpenAI
56
+
57
+ llm = ChatOpenAI(model="gpt-4")
58
+ tools = [attest_tool]
59
+ agent = initialize_agent(tools, llm, agent=AgentType.OPENAI_FUNCTIONS)
60
+
61
+ agent.run("Does wallet 0x1234... hold at least 100 USDC on Ethereum?")
62
+ ```
63
+
64
+ ## Available Tools
65
+
66
+ | Tool | Description | Credits |
67
+ |------|-------------|---------|
68
+ | `InsumerAttestTool` | Verify on-chain conditions with signed attestation | 1/call |
69
+ | `InsumerCheckDiscountTool` | Calculate discount for wallet at merchant | Free |
70
+ | `InsumerListMerchantsTool` | Browse merchant directory | Free |
71
+ | `InsumerListTokensTool` | List registered tokens and NFTs | Free |
72
+ | `InsumerVerifyTool` | Create signed discount code (INSR-XXXXX) | 1/call |
73
+ | `InsumerCreditsTool` | Check API key credit balance | Free |
74
+
75
+ ## Using All Tools
76
+
77
+ ```python
78
+ from langchain_insumer import (
79
+ InsumerAPIWrapper,
80
+ InsumerAttestTool,
81
+ InsumerCheckDiscountTool,
82
+ InsumerCreditsTool,
83
+ InsumerListMerchantsTool,
84
+ InsumerListTokensTool,
85
+ InsumerVerifyTool,
86
+ )
87
+
88
+ api = InsumerAPIWrapper(api_key="insr_live_YOUR_KEY_HERE")
89
+
90
+ tools = [
91
+ InsumerAttestTool(api_wrapper=api),
92
+ InsumerCheckDiscountTool(api_wrapper=api),
93
+ InsumerCreditsTool(api_wrapper=api),
94
+ InsumerListMerchantsTool(api_wrapper=api),
95
+ InsumerListTokensTool(api_wrapper=api),
96
+ InsumerVerifyTool(api_wrapper=api),
97
+ ]
98
+ ```
99
+
100
+ ## Attestation Example
101
+
102
+ ```python
103
+ import json
104
+
105
+ api = InsumerAPIWrapper(api_key="insr_live_YOUR_KEY_HERE")
106
+
107
+ result = api.attest(
108
+ wallet="0x1234567890abcdef1234567890abcdef12345678",
109
+ conditions=[
110
+ {
111
+ "type": "token_balance",
112
+ "contractAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
113
+ "chainId": 1,
114
+ "threshold": 1000,
115
+ "decimals": 6,
116
+ "label": "USDC >= 1000 on Ethereum",
117
+ },
118
+ {
119
+ "type": "nft_ownership",
120
+ "contractAddress": "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D",
121
+ "chainId": 1,
122
+ "label": "Bored Ape Yacht Club holder",
123
+ },
124
+ ],
125
+ )
126
+
127
+ attestation = result["data"]["attestation"]
128
+ print(f"Pass: {attestation['pass']}")
129
+ for r in attestation["results"]:
130
+ print(f" {r['label']}: {'met' if r['met'] else 'not met'}")
131
+ print(f"Signature: {result['data']['sig']}")
132
+ ```
133
+
134
+ ## Supported Chains (31)
135
+
136
+ Ethereum, BNB Chain, Base, Avalanche, Polygon, Arbitrum, Optimism, Solana, Chiliz, Soneium, Plume, Sonic, Gnosis, Mantle, Scroll, Linea, zkSync Era, Blast, Taiko, Ronin, Celo, Moonbeam, Moonriver, Viction, opBNB, World Chain, Unichain, Ink, Sei, Berachain, ApeChain.
137
+
138
+ ## Get an API Key
139
+
140
+ - **Free** (10 credits): [insumermodel.com/developers](https://insumermodel.com/developers/)
141
+ - **Pro** (10,000/day): 29 USDC/month
142
+ - **Enterprise** (100,000/day): 99 USDC/month
143
+
144
+ ## Links
145
+
146
+ - [API Documentation](https://insumermodel.com/for-agents/)
147
+ - [OpenAPI Spec](https://insumermodel.com/openapi.yaml)
148
+ - [Full API Reference](https://insumermodel.com/llms-full.txt)
149
+
150
+ ## License
151
+
152
+ MIT
@@ -0,0 +1,18 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ langchain_insumer/__init__.py
5
+ langchain_insumer/wrapper.py
6
+ langchain_insumer.egg-info/PKG-INFO
7
+ langchain_insumer.egg-info/SOURCES.txt
8
+ langchain_insumer.egg-info/dependency_links.txt
9
+ langchain_insumer.egg-info/requires.txt
10
+ langchain_insumer.egg-info/top_level.txt
11
+ langchain_insumer/tools/__init__.py
12
+ langchain_insumer/tools/attest.py
13
+ langchain_insumer/tools/check_discount.py
14
+ langchain_insumer/tools/credits.py
15
+ langchain_insumer/tools/list_merchants.py
16
+ langchain_insumer/tools/list_tokens.py
17
+ langchain_insumer/tools/verify.py
18
+ tests/test_tools.py
@@ -0,0 +1,3 @@
1
+ langchain-core>=0.2.0
2
+ requests>=2.28.0
3
+ pydantic>=2.0.0
@@ -0,0 +1 @@
1
+ langchain_insumer
@@ -0,0 +1,51 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "langchain-insumer"
7
+ version = "0.1.0"
8
+ description = "LangChain integration for The Insumer Model — privacy-preserving on-chain attestation across 31 blockchains"
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ requires-python = ">=3.9"
12
+ authors = [
13
+ {name = "The Insumer Model", email = "contact@insumermodel.com"},
14
+ ]
15
+ keywords = [
16
+ "langchain",
17
+ "blockchain",
18
+ "attestation",
19
+ "token-gated",
20
+ "nft",
21
+ "web3",
22
+ "privacy",
23
+ "on-chain",
24
+ "verification",
25
+ ]
26
+ classifiers = [
27
+ "Development Status :: 4 - Beta",
28
+ "Intended Audience :: Developers",
29
+ "License :: OSI Approved :: MIT License",
30
+ "Programming Language :: Python :: 3",
31
+ "Programming Language :: Python :: 3.9",
32
+ "Programming Language :: Python :: 3.10",
33
+ "Programming Language :: Python :: 3.11",
34
+ "Programming Language :: Python :: 3.12",
35
+ "Topic :: Software Development :: Libraries",
36
+ ]
37
+ dependencies = [
38
+ "langchain-core>=0.2.0",
39
+ "requests>=2.28.0",
40
+ "pydantic>=2.0.0",
41
+ ]
42
+
43
+ [project.urls]
44
+ Homepage = "https://insumermodel.com/for-agents/"
45
+ Documentation = "https://insumermodel.com/llms-full.txt"
46
+ Repository = "https://github.com/douglasborthwick-crypto/langchain-insumer"
47
+ "API Reference" = "https://insumermodel.com/openapi.yaml"
48
+ "Bug Tracker" = "https://github.com/douglasborthwick-crypto/langchain-insumer/issues"
49
+
50
+ [tool.setuptools.packages.find]
51
+ include = ["langchain_insumer*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,153 @@
1
+ """Tests for langchain-insumer tools."""
2
+
3
+ import json
4
+ from unittest.mock import MagicMock, patch
5
+
6
+ import pytest
7
+
8
+ from langchain_insumer import (
9
+ InsumerAPIWrapper,
10
+ InsumerAttestTool,
11
+ InsumerCheckDiscountTool,
12
+ InsumerCreditsTool,
13
+ InsumerListMerchantsTool,
14
+ InsumerListTokensTool,
15
+ InsumerVerifyTool,
16
+ )
17
+
18
+
19
+ @pytest.fixture
20
+ def api():
21
+ return InsumerAPIWrapper(api_key="insr_live_0000000000000000000000000000000000000000")
22
+
23
+
24
+ @pytest.fixture
25
+ def mock_response():
26
+ mock = MagicMock()
27
+ mock.status_code = 200
28
+ mock.json.return_value = {
29
+ "ok": True,
30
+ "data": {},
31
+ "meta": {"version": "1.0", "timestamp": "2026-02-22T00:00:00.000Z"},
32
+ }
33
+ mock.raise_for_status = MagicMock()
34
+ return mock
35
+
36
+
37
+ class TestInsumerAPIWrapper:
38
+ def test_headers(self, api):
39
+ headers = api._headers()
40
+ assert headers["X-API-Key"] == "insr_live_0000000000000000000000000000000000000000"
41
+ assert headers["Content-Type"] == "application/json"
42
+
43
+ @patch("langchain_insumer.wrapper.requests.post")
44
+ def test_attest(self, mock_post, api, mock_response):
45
+ mock_response.json.return_value = {
46
+ "ok": True,
47
+ "data": {
48
+ "attestation": {
49
+ "id": "ATST-A7C3E",
50
+ "pass": True,
51
+ "results": [{"condition": 0, "met": True}],
52
+ "passCount": 1,
53
+ "failCount": 0,
54
+ },
55
+ "sig": "base64sig...",
56
+ },
57
+ "meta": {"creditsRemaining": 9},
58
+ }
59
+ mock_post.return_value = mock_response
60
+
61
+ result = api.attest(
62
+ wallet="0x1234567890abcdef1234567890abcdef12345678",
63
+ conditions=[
64
+ {
65
+ "type": "token_balance",
66
+ "contractAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
67
+ "chainId": 1,
68
+ "threshold": 100,
69
+ "decimals": 6,
70
+ }
71
+ ],
72
+ )
73
+
74
+ assert result["ok"] is True
75
+ assert result["data"]["attestation"]["pass"] is True
76
+ mock_post.assert_called_once()
77
+
78
+ @patch("langchain_insumer.wrapper.requests.get")
79
+ def test_get_credits(self, mock_get, api, mock_response):
80
+ mock_response.json.return_value = {
81
+ "ok": True,
82
+ "data": {"apiKeyCredits": 42, "tier": "pro", "dailyLimit": 10000},
83
+ }
84
+ mock_get.return_value = mock_response
85
+
86
+ result = api.get_credits()
87
+ assert result["data"]["apiKeyCredits"] == 42
88
+
89
+ @patch("langchain_insumer.wrapper.requests.get")
90
+ def test_list_merchants(self, mock_get, api, mock_response):
91
+ mock_response.json.return_value = {
92
+ "ok": True,
93
+ "data": [{"id": "test", "companyName": "Test Co"}],
94
+ "meta": {"total": 1, "limit": 50, "offset": 0},
95
+ }
96
+ mock_get.return_value = mock_response
97
+
98
+ result = api.list_merchants(token="UNI", limit=10)
99
+ assert len(result["data"]) == 1
100
+
101
+ @patch("langchain_insumer.wrapper.requests.get")
102
+ def test_check_discount(self, mock_get, api, mock_response):
103
+ mock_response.json.return_value = {
104
+ "ok": True,
105
+ "data": {"eligible": True, "totalDiscount": 15},
106
+ }
107
+ mock_get.return_value = mock_response
108
+
109
+ result = api.check_discount(
110
+ merchant_id="test",
111
+ wallet="0x1234567890abcdef1234567890abcdef12345678",
112
+ )
113
+ assert result["data"]["totalDiscount"] == 15
114
+
115
+
116
+ class TestTools:
117
+ @patch("langchain_insumer.wrapper.requests.post")
118
+ def test_attest_tool(self, mock_post, api, mock_response):
119
+ mock_response.json.return_value = {
120
+ "ok": True,
121
+ "data": {"attestation": {"pass": True}},
122
+ }
123
+ mock_post.return_value = mock_response
124
+
125
+ tool = InsumerAttestTool(api_wrapper=api)
126
+ assert tool.name == "insumer_attest"
127
+
128
+ result = tool._run(
129
+ conditions=json.dumps([{"type": "token_balance", "contractAddress": "0x...", "chainId": 1, "threshold": 100}]),
130
+ wallet="0x1234567890abcdef1234567890abcdef12345678",
131
+ )
132
+ parsed = json.loads(result)
133
+ assert parsed["ok"] is True
134
+
135
+ def test_credits_tool_name(self, api):
136
+ tool = InsumerCreditsTool(api_wrapper=api)
137
+ assert tool.name == "insumer_credits"
138
+
139
+ def test_list_merchants_tool_name(self, api):
140
+ tool = InsumerListMerchantsTool(api_wrapper=api)
141
+ assert tool.name == "insumer_list_merchants"
142
+
143
+ def test_list_tokens_tool_name(self, api):
144
+ tool = InsumerListTokensTool(api_wrapper=api)
145
+ assert tool.name == "insumer_list_tokens"
146
+
147
+ def test_check_discount_tool_name(self, api):
148
+ tool = InsumerCheckDiscountTool(api_wrapper=api)
149
+ assert tool.name == "insumer_check_discount"
150
+
151
+ def test_verify_tool_name(self, api):
152
+ tool = InsumerVerifyTool(api_wrapper=api)
153
+ assert tool.name == "insumer_verify"