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.
- langchain_insumer-0.1.0/LICENSE +21 -0
- langchain_insumer-0.1.0/PKG-INFO +152 -0
- langchain_insumer-0.1.0/README.md +123 -0
- langchain_insumer-0.1.0/langchain_insumer/__init__.py +19 -0
- langchain_insumer-0.1.0/langchain_insumer/tools/__init__.py +17 -0
- langchain_insumer-0.1.0/langchain_insumer/tools/attest.py +72 -0
- langchain_insumer-0.1.0/langchain_insumer/tools/check_discount.py +60 -0
- langchain_insumer-0.1.0/langchain_insumer/tools/credits.py +40 -0
- langchain_insumer-0.1.0/langchain_insumer/tools/list_merchants.py +66 -0
- langchain_insumer-0.1.0/langchain_insumer/tools/list_tokens.py +59 -0
- langchain_insumer-0.1.0/langchain_insumer/tools/verify.py +61 -0
- langchain_insumer-0.1.0/langchain_insumer/wrapper.py +151 -0
- langchain_insumer-0.1.0/langchain_insumer.egg-info/PKG-INFO +152 -0
- langchain_insumer-0.1.0/langchain_insumer.egg-info/SOURCES.txt +18 -0
- langchain_insumer-0.1.0/langchain_insumer.egg-info/dependency_links.txt +1 -0
- langchain_insumer-0.1.0/langchain_insumer.egg-info/requires.txt +3 -0
- langchain_insumer-0.1.0/langchain_insumer.egg-info/top_level.txt +1 -0
- langchain_insumer-0.1.0/pyproject.toml +51 -0
- langchain_insumer-0.1.0/setup.cfg +4 -0
- langchain_insumer-0.1.0/tests/test_tools.py +153 -0
|
@@ -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 @@
|
|
|
1
|
+
|
|
@@ -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,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"
|