clicks-langchain 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,175 @@
1
+ Metadata-Version: 2.4
2
+ Name: clicks-langchain
3
+ Version: 0.1.0
4
+ Summary: Clicks Protocol yield management tools for LangChain agents on Base
5
+ Author-email: Clicks Protocol <dev@clicksprotocol.xyz>
6
+ License: MIT
7
+ Project-URL: Homepage, https://clicksprotocol.xyz
8
+ Project-URL: Repository, https://github.com/clicks-protocol/clicks-langchain
9
+ Project-URL: Documentation, https://docs.clicksprotocol.xyz/integrations/langchain
10
+ Keywords: langchain,defi,yield,base,usdc,ai-agents,clicks-protocol
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Software Development :: Libraries
20
+ Requires-Python: >=3.9
21
+ Description-Content-Type: text/markdown
22
+ Requires-Dist: langchain>=0.1.0
23
+ Requires-Dist: pydantic>=2.0
24
+
25
+ # Clicks Protocol — LangChain Integration
26
+
27
+ On-chain yield management tools for LangChain agents on Base.
28
+
29
+ Powered by [Clicks Protocol](https://clicksprotocol.xyz)
30
+
31
+ ## What It Does
32
+
33
+ Gives your LangChain agent the ability to earn yield on idle USDC. The agent can autonomously split funds 80/20: 80% stays liquid for operations, 20% earns 4-8% APY on-chain via Clicks Protocol on Base.
34
+
35
+ ## Installation
36
+
37
+ ```bash
38
+ pip install clicks-langchain
39
+ ```
40
+
41
+ Or from source:
42
+
43
+ ```bash
44
+ git clone https://github.com/clicks-protocol/clicks-langchain
45
+ cd clicks-langchain
46
+ pip install -e .
47
+ ```
48
+
49
+ ### Prerequisites
50
+
51
+ - Python >= 3.9
52
+ - Node.js >= 18 (for SDK bridge)
53
+ - `@clicks-protocol/sdk` installed: `npm install @clicks-protocol/sdk`
54
+
55
+ ### Environment Variables
56
+
57
+ ```bash
58
+ export CLICKS_PRIVATE_KEY="your-agent-wallet-private-key"
59
+ export CLICKS_RPC_URL="https://mainnet.base.org" # optional, defaults to Base mainnet
60
+ export CLICKS_SDK_PATH="/path/to/sdk" # optional, defaults to cwd
61
+ ```
62
+
63
+ ## Quick Start
64
+
65
+ ```python
66
+ from langchain.chat_models import ChatOpenAI
67
+ from langchain.agents import initialize_agent, AgentType
68
+ from clicks_langchain import get_clicks_tools
69
+
70
+ # Get all Clicks Protocol tools
71
+ tools = get_clicks_tools()
72
+
73
+ # Create an agent with yield management capabilities
74
+ llm = ChatOpenAI(model="gpt-4", temperature=0)
75
+ agent = initialize_agent(
76
+ tools,
77
+ llm,
78
+ agent=AgentType.OPENAI_FUNCTIONS,
79
+ verbose=True,
80
+ )
81
+
82
+ # The agent can now manage its own treasury
83
+ agent.run("Check my USDC balance and yield status for address 0xYourAgent...")
84
+ agent.run("I have 5000 idle USDC. Activate yield on it.")
85
+ agent.run("What's the current APY on Clicks Protocol?")
86
+ ```
87
+
88
+ ## Available Tools
89
+
90
+ | Tool | Description | When to Use |
91
+ |------|-------------|-------------|
92
+ | `clicks_activate_yield` | Split USDC 80/20 to earn yield | Agent has idle USDC not needed immediately |
93
+ | `clicks_withdraw_yield` | Withdraw all yield + deposits | Agent needs maximum liquidity |
94
+ | `clicks_check_balance` | View liquid vs. yield balances | Before spending decisions |
95
+ | `clicks_get_apy` | Get current APY rate | Evaluating whether to activate yield |
96
+ | `clicks_simulate_split` | Preview split without moving funds | Planning treasury allocation |
97
+
98
+ ## Example: Treasury Management Agent
99
+
100
+ ```python
101
+ from langchain.chat_models import ChatOpenAI
102
+ from langchain.agents import initialize_agent, AgentType
103
+ from langchain.memory import ConversationBufferMemory
104
+ from clicks_langchain import get_clicks_tools
105
+
106
+ AGENT_ADDRESS = "0xYourAgentAddress"
107
+
108
+ SYSTEM_PROMPT = """You are a treasury management agent. Your job is to maximize
109
+ yield on idle USDC while keeping enough liquid for operations.
110
+
111
+ Rules:
112
+ 1. Always check balance before activating yield
113
+ 2. Keep at least 1000 USDC liquid at all times
114
+ 3. Activate yield on any idle amount above 1000 USDC
115
+ 4. Check APY weekly and report changes
116
+ 5. Withdraw yield if APY drops below 2%
117
+ """
118
+
119
+ tools = get_clicks_tools()
120
+ memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
121
+
122
+ agent = initialize_agent(
123
+ tools,
124
+ ChatOpenAI(model="gpt-4", temperature=0),
125
+ agent=AgentType.OPENAI_FUNCTIONS,
126
+ memory=memory,
127
+ system_message=SYSTEM_PROMPT,
128
+ verbose=True,
129
+ )
130
+
131
+ # Run the agent
132
+ agent.run(f"Check the treasury status for {AGENT_ADDRESS} and optimize yield allocation.")
133
+ ```
134
+
135
+ ## Using Individual Tools
136
+
137
+ ```python
138
+ from clicks_langchain import (
139
+ ClicksActivateYieldTool,
140
+ ClicksCheckBalanceTool,
141
+ ClicksGetAPYTool,
142
+ ClicksSDKBridge,
143
+ )
144
+
145
+ # Create SDK bridge with explicit config
146
+ sdk = ClicksSDKBridge(
147
+ rpc_url="https://mainnet.base.org",
148
+ private_key="your-key",
149
+ )
150
+
151
+ # Use individual tools
152
+ balance_tool = ClicksCheckBalanceTool(sdk=sdk)
153
+ print(balance_tool.run({"agent_address": "0xYourAgent"}))
154
+
155
+ apy_tool = ClicksGetAPYTool(sdk=sdk)
156
+ print(apy_tool.run({}))
157
+ ```
158
+
159
+ ## Contract Addresses (Base Mainnet)
160
+
161
+ | Contract | Address |
162
+ |----------|---------|
163
+ | Registry | `0x23bb0Ea69b2BD2e527D5DbA6093155A6E1D0C0a3` |
164
+ | Splitter | `0xc96C1a566a8ed7A39040a34927fEe952bAB8Ad1D` |
165
+ | YieldRouter | `0x053167a233d18E05Bc65a8d5F3F8808782a3EECD` |
166
+ | ClicksReferral | `0x1E5Ab896D3b3A542C5E91852e221b2D849944ccC` |
167
+ | USDC | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` |
168
+
169
+ ## License
170
+
171
+ MIT
172
+
173
+ ---
174
+
175
+ Built for autonomous AI agents. Powered by [Clicks Protocol](https://clicksprotocol.xyz).
@@ -0,0 +1,151 @@
1
+ # Clicks Protocol — LangChain Integration
2
+
3
+ On-chain yield management tools for LangChain agents on Base.
4
+
5
+ Powered by [Clicks Protocol](https://clicksprotocol.xyz)
6
+
7
+ ## What It Does
8
+
9
+ Gives your LangChain agent the ability to earn yield on idle USDC. The agent can autonomously split funds 80/20: 80% stays liquid for operations, 20% earns 4-8% APY on-chain via Clicks Protocol on Base.
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ pip install clicks-langchain
15
+ ```
16
+
17
+ Or from source:
18
+
19
+ ```bash
20
+ git clone https://github.com/clicks-protocol/clicks-langchain
21
+ cd clicks-langchain
22
+ pip install -e .
23
+ ```
24
+
25
+ ### Prerequisites
26
+
27
+ - Python >= 3.9
28
+ - Node.js >= 18 (for SDK bridge)
29
+ - `@clicks-protocol/sdk` installed: `npm install @clicks-protocol/sdk`
30
+
31
+ ### Environment Variables
32
+
33
+ ```bash
34
+ export CLICKS_PRIVATE_KEY="your-agent-wallet-private-key"
35
+ export CLICKS_RPC_URL="https://mainnet.base.org" # optional, defaults to Base mainnet
36
+ export CLICKS_SDK_PATH="/path/to/sdk" # optional, defaults to cwd
37
+ ```
38
+
39
+ ## Quick Start
40
+
41
+ ```python
42
+ from langchain.chat_models import ChatOpenAI
43
+ from langchain.agents import initialize_agent, AgentType
44
+ from clicks_langchain import get_clicks_tools
45
+
46
+ # Get all Clicks Protocol tools
47
+ tools = get_clicks_tools()
48
+
49
+ # Create an agent with yield management capabilities
50
+ llm = ChatOpenAI(model="gpt-4", temperature=0)
51
+ agent = initialize_agent(
52
+ tools,
53
+ llm,
54
+ agent=AgentType.OPENAI_FUNCTIONS,
55
+ verbose=True,
56
+ )
57
+
58
+ # The agent can now manage its own treasury
59
+ agent.run("Check my USDC balance and yield status for address 0xYourAgent...")
60
+ agent.run("I have 5000 idle USDC. Activate yield on it.")
61
+ agent.run("What's the current APY on Clicks Protocol?")
62
+ ```
63
+
64
+ ## Available Tools
65
+
66
+ | Tool | Description | When to Use |
67
+ |------|-------------|-------------|
68
+ | `clicks_activate_yield` | Split USDC 80/20 to earn yield | Agent has idle USDC not needed immediately |
69
+ | `clicks_withdraw_yield` | Withdraw all yield + deposits | Agent needs maximum liquidity |
70
+ | `clicks_check_balance` | View liquid vs. yield balances | Before spending decisions |
71
+ | `clicks_get_apy` | Get current APY rate | Evaluating whether to activate yield |
72
+ | `clicks_simulate_split` | Preview split without moving funds | Planning treasury allocation |
73
+
74
+ ## Example: Treasury Management Agent
75
+
76
+ ```python
77
+ from langchain.chat_models import ChatOpenAI
78
+ from langchain.agents import initialize_agent, AgentType
79
+ from langchain.memory import ConversationBufferMemory
80
+ from clicks_langchain import get_clicks_tools
81
+
82
+ AGENT_ADDRESS = "0xYourAgentAddress"
83
+
84
+ SYSTEM_PROMPT = """You are a treasury management agent. Your job is to maximize
85
+ yield on idle USDC while keeping enough liquid for operations.
86
+
87
+ Rules:
88
+ 1. Always check balance before activating yield
89
+ 2. Keep at least 1000 USDC liquid at all times
90
+ 3. Activate yield on any idle amount above 1000 USDC
91
+ 4. Check APY weekly and report changes
92
+ 5. Withdraw yield if APY drops below 2%
93
+ """
94
+
95
+ tools = get_clicks_tools()
96
+ memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
97
+
98
+ agent = initialize_agent(
99
+ tools,
100
+ ChatOpenAI(model="gpt-4", temperature=0),
101
+ agent=AgentType.OPENAI_FUNCTIONS,
102
+ memory=memory,
103
+ system_message=SYSTEM_PROMPT,
104
+ verbose=True,
105
+ )
106
+
107
+ # Run the agent
108
+ agent.run(f"Check the treasury status for {AGENT_ADDRESS} and optimize yield allocation.")
109
+ ```
110
+
111
+ ## Using Individual Tools
112
+
113
+ ```python
114
+ from clicks_langchain import (
115
+ ClicksActivateYieldTool,
116
+ ClicksCheckBalanceTool,
117
+ ClicksGetAPYTool,
118
+ ClicksSDKBridge,
119
+ )
120
+
121
+ # Create SDK bridge with explicit config
122
+ sdk = ClicksSDKBridge(
123
+ rpc_url="https://mainnet.base.org",
124
+ private_key="your-key",
125
+ )
126
+
127
+ # Use individual tools
128
+ balance_tool = ClicksCheckBalanceTool(sdk=sdk)
129
+ print(balance_tool.run({"agent_address": "0xYourAgent"}))
130
+
131
+ apy_tool = ClicksGetAPYTool(sdk=sdk)
132
+ print(apy_tool.run({}))
133
+ ```
134
+
135
+ ## Contract Addresses (Base Mainnet)
136
+
137
+ | Contract | Address |
138
+ |----------|---------|
139
+ | Registry | `0x23bb0Ea69b2BD2e527D5DbA6093155A6E1D0C0a3` |
140
+ | Splitter | `0xc96C1a566a8ed7A39040a34927fEe952bAB8Ad1D` |
141
+ | YieldRouter | `0x053167a233d18E05Bc65a8d5F3F8808782a3EECD` |
142
+ | ClicksReferral | `0x1E5Ab896D3b3A542C5E91852e221b2D849944ccC` |
143
+ | USDC | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` |
144
+
145
+ ## License
146
+
147
+ MIT
148
+
149
+ ---
150
+
151
+ Built for autonomous AI agents. Powered by [Clicks Protocol](https://clicksprotocol.xyz).
@@ -0,0 +1,175 @@
1
+ Metadata-Version: 2.4
2
+ Name: clicks-langchain
3
+ Version: 0.1.0
4
+ Summary: Clicks Protocol yield management tools for LangChain agents on Base
5
+ Author-email: Clicks Protocol <dev@clicksprotocol.xyz>
6
+ License: MIT
7
+ Project-URL: Homepage, https://clicksprotocol.xyz
8
+ Project-URL: Repository, https://github.com/clicks-protocol/clicks-langchain
9
+ Project-URL: Documentation, https://docs.clicksprotocol.xyz/integrations/langchain
10
+ Keywords: langchain,defi,yield,base,usdc,ai-agents,clicks-protocol
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Software Development :: Libraries
20
+ Requires-Python: >=3.9
21
+ Description-Content-Type: text/markdown
22
+ Requires-Dist: langchain>=0.1.0
23
+ Requires-Dist: pydantic>=2.0
24
+
25
+ # Clicks Protocol — LangChain Integration
26
+
27
+ On-chain yield management tools for LangChain agents on Base.
28
+
29
+ Powered by [Clicks Protocol](https://clicksprotocol.xyz)
30
+
31
+ ## What It Does
32
+
33
+ Gives your LangChain agent the ability to earn yield on idle USDC. The agent can autonomously split funds 80/20: 80% stays liquid for operations, 20% earns 4-8% APY on-chain via Clicks Protocol on Base.
34
+
35
+ ## Installation
36
+
37
+ ```bash
38
+ pip install clicks-langchain
39
+ ```
40
+
41
+ Or from source:
42
+
43
+ ```bash
44
+ git clone https://github.com/clicks-protocol/clicks-langchain
45
+ cd clicks-langchain
46
+ pip install -e .
47
+ ```
48
+
49
+ ### Prerequisites
50
+
51
+ - Python >= 3.9
52
+ - Node.js >= 18 (for SDK bridge)
53
+ - `@clicks-protocol/sdk` installed: `npm install @clicks-protocol/sdk`
54
+
55
+ ### Environment Variables
56
+
57
+ ```bash
58
+ export CLICKS_PRIVATE_KEY="your-agent-wallet-private-key"
59
+ export CLICKS_RPC_URL="https://mainnet.base.org" # optional, defaults to Base mainnet
60
+ export CLICKS_SDK_PATH="/path/to/sdk" # optional, defaults to cwd
61
+ ```
62
+
63
+ ## Quick Start
64
+
65
+ ```python
66
+ from langchain.chat_models import ChatOpenAI
67
+ from langchain.agents import initialize_agent, AgentType
68
+ from clicks_langchain import get_clicks_tools
69
+
70
+ # Get all Clicks Protocol tools
71
+ tools = get_clicks_tools()
72
+
73
+ # Create an agent with yield management capabilities
74
+ llm = ChatOpenAI(model="gpt-4", temperature=0)
75
+ agent = initialize_agent(
76
+ tools,
77
+ llm,
78
+ agent=AgentType.OPENAI_FUNCTIONS,
79
+ verbose=True,
80
+ )
81
+
82
+ # The agent can now manage its own treasury
83
+ agent.run("Check my USDC balance and yield status for address 0xYourAgent...")
84
+ agent.run("I have 5000 idle USDC. Activate yield on it.")
85
+ agent.run("What's the current APY on Clicks Protocol?")
86
+ ```
87
+
88
+ ## Available Tools
89
+
90
+ | Tool | Description | When to Use |
91
+ |------|-------------|-------------|
92
+ | `clicks_activate_yield` | Split USDC 80/20 to earn yield | Agent has idle USDC not needed immediately |
93
+ | `clicks_withdraw_yield` | Withdraw all yield + deposits | Agent needs maximum liquidity |
94
+ | `clicks_check_balance` | View liquid vs. yield balances | Before spending decisions |
95
+ | `clicks_get_apy` | Get current APY rate | Evaluating whether to activate yield |
96
+ | `clicks_simulate_split` | Preview split without moving funds | Planning treasury allocation |
97
+
98
+ ## Example: Treasury Management Agent
99
+
100
+ ```python
101
+ from langchain.chat_models import ChatOpenAI
102
+ from langchain.agents import initialize_agent, AgentType
103
+ from langchain.memory import ConversationBufferMemory
104
+ from clicks_langchain import get_clicks_tools
105
+
106
+ AGENT_ADDRESS = "0xYourAgentAddress"
107
+
108
+ SYSTEM_PROMPT = """You are a treasury management agent. Your job is to maximize
109
+ yield on idle USDC while keeping enough liquid for operations.
110
+
111
+ Rules:
112
+ 1. Always check balance before activating yield
113
+ 2. Keep at least 1000 USDC liquid at all times
114
+ 3. Activate yield on any idle amount above 1000 USDC
115
+ 4. Check APY weekly and report changes
116
+ 5. Withdraw yield if APY drops below 2%
117
+ """
118
+
119
+ tools = get_clicks_tools()
120
+ memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
121
+
122
+ agent = initialize_agent(
123
+ tools,
124
+ ChatOpenAI(model="gpt-4", temperature=0),
125
+ agent=AgentType.OPENAI_FUNCTIONS,
126
+ memory=memory,
127
+ system_message=SYSTEM_PROMPT,
128
+ verbose=True,
129
+ )
130
+
131
+ # Run the agent
132
+ agent.run(f"Check the treasury status for {AGENT_ADDRESS} and optimize yield allocation.")
133
+ ```
134
+
135
+ ## Using Individual Tools
136
+
137
+ ```python
138
+ from clicks_langchain import (
139
+ ClicksActivateYieldTool,
140
+ ClicksCheckBalanceTool,
141
+ ClicksGetAPYTool,
142
+ ClicksSDKBridge,
143
+ )
144
+
145
+ # Create SDK bridge with explicit config
146
+ sdk = ClicksSDKBridge(
147
+ rpc_url="https://mainnet.base.org",
148
+ private_key="your-key",
149
+ )
150
+
151
+ # Use individual tools
152
+ balance_tool = ClicksCheckBalanceTool(sdk=sdk)
153
+ print(balance_tool.run({"agent_address": "0xYourAgent"}))
154
+
155
+ apy_tool = ClicksGetAPYTool(sdk=sdk)
156
+ print(apy_tool.run({}))
157
+ ```
158
+
159
+ ## Contract Addresses (Base Mainnet)
160
+
161
+ | Contract | Address |
162
+ |----------|---------|
163
+ | Registry | `0x23bb0Ea69b2BD2e527D5DbA6093155A6E1D0C0a3` |
164
+ | Splitter | `0xc96C1a566a8ed7A39040a34927fEe952bAB8Ad1D` |
165
+ | YieldRouter | `0x053167a233d18E05Bc65a8d5F3F8808782a3EECD` |
166
+ | ClicksReferral | `0x1E5Ab896D3b3A542C5E91852e221b2D849944ccC` |
167
+ | USDC | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` |
168
+
169
+ ## License
170
+
171
+ MIT
172
+
173
+ ---
174
+
175
+ Built for autonomous AI agents. Powered by [Clicks Protocol](https://clicksprotocol.xyz).
@@ -0,0 +1,8 @@
1
+ README.md
2
+ clicks_langchain.py
3
+ pyproject.toml
4
+ clicks_langchain.egg-info/PKG-INFO
5
+ clicks_langchain.egg-info/SOURCES.txt
6
+ clicks_langchain.egg-info/dependency_links.txt
7
+ clicks_langchain.egg-info/requires.txt
8
+ clicks_langchain.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ langchain>=0.1.0
2
+ pydantic>=2.0
@@ -0,0 +1 @@
1
+ clicks_langchain
@@ -0,0 +1,505 @@
1
+ """
2
+ Clicks Protocol LangChain Tools
3
+ ================================
4
+ On-chain yield management for AI agents on Base.
5
+ Powered by Clicks Protocol (clicksprotocol.xyz)
6
+
7
+ These tools let LangChain agents autonomously manage idle USDC by splitting
8
+ funds 80/20: 80% stays liquid for operations, 20% earns 4-8% APY on-chain.
9
+
10
+ Usage:
11
+ from clicks_langchain import ClicksYieldTool, ClicksBalanceTool
12
+ tools = [ClicksYieldTool(), ClicksBalanceTool()]
13
+ agent = initialize_agent(tools, llm, agent=AgentType.OPENAI_FUNCTIONS)
14
+ """
15
+
16
+ import os
17
+ import json
18
+ import subprocess
19
+ from typing import Optional, Type
20
+ from pydantic import BaseModel, Field
21
+ from langchain.tools import BaseTool
22
+ from langchain.callbacks.manager import CallbackManagerForToolRun
23
+
24
+
25
+ # ---------------------------------------------------------------------------
26
+ # Contract addresses (Base Mainnet)
27
+ # ---------------------------------------------------------------------------
28
+ CONTRACTS = {
29
+ "registry": "0x23bb0Ea69b2BD2e527D5DbA6093155A6E1D0C0a3",
30
+ "splitter": "0xc96C1a566a8ed7A39040a34927fEe952bAB8Ad1D",
31
+ "yield_router": "0x053167a233d18E05Bc65a8d5F3F8808782a3EECD",
32
+ "fee": "0xc47B162D3c456B6C56a3cE6EE89A828CFd34E6bE",
33
+ "referral": "0x1E5Ab896D3b3A542C5E91852e221b2D849944ccC",
34
+ "usdc": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
35
+ }
36
+
37
+ # Default split: 80% liquid, 20% yield
38
+ DEFAULT_LIQUID_PERCENT = 80
39
+ DEFAULT_YIELD_PERCENT = 20
40
+
41
+
42
+ class ClicksSDKBridge:
43
+ """
44
+ Bridge to the @clicks-protocol/sdk Node.js package.
45
+ Executes SDK methods via a subprocess call to a thin JS wrapper.
46
+
47
+ Requires:
48
+ - Node.js >= 18
49
+ - @clicks-protocol/sdk installed (npm install @clicks-protocol/sdk)
50
+ - CLICKS_RPC_URL env var (Base RPC endpoint)
51
+ - CLICKS_PRIVATE_KEY env var (agent wallet private key)
52
+ """
53
+
54
+ def __init__(
55
+ self,
56
+ rpc_url: Optional[str] = None,
57
+ private_key: Optional[str] = None,
58
+ sdk_path: Optional[str] = None,
59
+ ):
60
+ self.rpc_url = rpc_url or os.environ.get("CLICKS_RPC_URL", "https://mainnet.base.org")
61
+ self.private_key = private_key or os.environ.get("CLICKS_PRIVATE_KEY", "")
62
+ self.sdk_path = sdk_path or os.environ.get("CLICKS_SDK_PATH", ".")
63
+
64
+ if not self.private_key:
65
+ raise ValueError(
66
+ "No private key provided. Set CLICKS_PRIVATE_KEY env var "
67
+ "or pass private_key to the constructor."
68
+ )
69
+
70
+ def _call_sdk(self, method: str, **kwargs) -> dict:
71
+ """Execute an SDK method via the Node.js bridge script."""
72
+ script = f"""
73
+ const {{ ClicksClient }} = require('@clicks-protocol/sdk');
74
+ const {{ ethers }} = require('ethers');
75
+
76
+ (async () => {{
77
+ const provider = new ethers.JsonRpcProvider('{self.rpc_url}');
78
+ const wallet = new ethers.Wallet('{self.private_key}', provider);
79
+ const sdk = new ClicksClient(wallet);
80
+
81
+ try {{
82
+ const result = await sdk.{method}({json.dumps(kwargs).strip('{}')});
83
+ console.log(JSON.stringify({{ success: true, data: result }}, (k, v) => typeof v === 'bigint' ? v.toString() : v));
84
+ }} catch (e) {{
85
+ console.log(JSON.stringify({{ success: false, error: e.message }}));
86
+ }}
87
+ }})();
88
+ """
89
+ try:
90
+ result = subprocess.run(
91
+ ["node", "-e", script],
92
+ capture_output=True,
93
+ text=True,
94
+ timeout=60,
95
+ cwd=self.sdk_path,
96
+ )
97
+ if result.returncode != 0:
98
+ return {"success": False, "error": result.stderr.strip()}
99
+ return json.loads(result.stdout.strip())
100
+ except subprocess.TimeoutExpired:
101
+ return {"success": False, "error": "SDK call timed out after 60s"}
102
+ except json.JSONDecodeError:
103
+ return {"success": False, "error": f"Invalid SDK response: {result.stdout[:200]}"}
104
+
105
+ def quick_start(self, amount: str, agent_address: str) -> dict:
106
+ return self._call_sdk("quickStart", amount=amount, agentAddress=agent_address)
107
+
108
+ def withdraw_yield(self, agent_address: str) -> dict:
109
+ return self._call_sdk("withdrawYield", agentAddress=agent_address)
110
+
111
+ def simulate_split(self, amount: str, agent_address: str) -> dict:
112
+ return self._call_sdk("simulateSplit", amount=amount, agentAddress=agent_address)
113
+
114
+ def get_agent_info(self, agent_address: str) -> dict:
115
+ """Get agent registration info + yield balance combined."""
116
+ script = f"""
117
+ const {{ ClicksClient }} = require('@clicks-protocol/sdk');
118
+ const {{ ethers }} = require('ethers');
119
+ (async () => {{
120
+ const provider = new ethers.JsonRpcProvider('{self.rpc_url}');
121
+ const wallet = new ethers.Wallet('{self.private_key}', provider);
122
+ const sdk = new ClicksClient(wallet);
123
+ try {{
124
+ const [info, yieldBal, usdcBal] = await Promise.all([
125
+ sdk.getAgentInfo('{agent_address}'),
126
+ sdk.getAgentYieldBalance('{agent_address}'),
127
+ sdk.getUSDCBalance('{agent_address}'),
128
+ ]);
129
+ console.log(JSON.stringify({{ success: true, data: {{
130
+ isRegistered: info.isRegistered,
131
+ operator: info.operator,
132
+ liquidBalance: usdcBal.toString(),
133
+ depositedBalance: yieldBal.deposited.toString(),
134
+ accruedYield: yieldBal.yieldEarned.toString(),
135
+ yieldPct: info.yieldPct.toString(),
136
+ }} }}));
137
+ }} catch (e) {{
138
+ console.log(JSON.stringify({{ success: false, error: e.message }}));
139
+ }}
140
+ }})();
141
+ """
142
+ try:
143
+ result = subprocess.run(
144
+ ["node", "-e", script],
145
+ capture_output=True, text=True, timeout=60, cwd=self.sdk_path,
146
+ )
147
+ if result.returncode != 0:
148
+ return {"success": False, "error": result.stderr.strip()}
149
+ return json.loads(result.stdout.strip())
150
+ except subprocess.TimeoutExpired:
151
+ return {"success": False, "error": "SDK call timed out after 60s"}
152
+ except json.JSONDecodeError:
153
+ return {"success": False, "error": f"Invalid SDK response: {result.stdout[:200]}"}
154
+
155
+ def get_yield_info(self) -> dict:
156
+ return self._call_sdk("getYieldInfo")
157
+
158
+ def get_current_apy(self) -> dict:
159
+ """Get current APY from the active yield protocol."""
160
+ script = f"""
161
+ const {{ ClicksClient }} = require('@clicks-protocol/sdk');
162
+ const {{ ethers }} = require('ethers');
163
+ (async () => {{
164
+ const provider = new ethers.JsonRpcProvider('{self.rpc_url}');
165
+ const wallet = new ethers.Wallet('{self.private_key}', provider);
166
+ const sdk = new ClicksClient(wallet);
167
+ try {{
168
+ const info = await sdk.getYieldInfo();
169
+ const apy = info.activeProtocol === 1 ? info.aaveAPY : info.morphoAPY;
170
+ const apyPct = (Number(apy) / 100).toFixed(2);
171
+ console.log(JSON.stringify({{ success: true, data: apyPct }}));
172
+ }} catch (e) {{
173
+ console.log(JSON.stringify({{ success: false, error: e.message }}));
174
+ }}
175
+ }})();
176
+ """
177
+ try:
178
+ result = subprocess.run(
179
+ ["node", "-e", script],
180
+ capture_output=True, text=True, timeout=60, cwd=self.sdk_path,
181
+ )
182
+ if result.returncode != 0:
183
+ return {"success": False, "error": result.stderr.strip()}
184
+ return json.loads(result.stdout.strip())
185
+ except subprocess.TimeoutExpired:
186
+ return {"success": False, "error": "SDK call timed out after 60s"}
187
+ except json.JSONDecodeError:
188
+ return {"success": False, "error": f"Invalid SDK response: {result.stdout[:200]}"}
189
+
190
+
191
+ # ---------------------------------------------------------------------------
192
+ # Input schemas
193
+ # ---------------------------------------------------------------------------
194
+
195
+ class ActivateYieldInput(BaseModel):
196
+ amount: str = Field(
197
+ description="Amount of USDC to split (in human-readable units, e.g. '1000' for 1000 USDC)"
198
+ )
199
+ agent_address: str = Field(
200
+ description="The Ethereum address of the agent wallet on Base"
201
+ )
202
+
203
+
204
+ class WithdrawYieldInput(BaseModel):
205
+ agent_address: str = Field(
206
+ description="The Ethereum address of the agent wallet on Base"
207
+ )
208
+
209
+
210
+ class CheckBalanceInput(BaseModel):
211
+ agent_address: str = Field(
212
+ description="The Ethereum address of the agent wallet on Base"
213
+ )
214
+
215
+
216
+ class SimulateSplitInput(BaseModel):
217
+ amount: str = Field(
218
+ description="Amount of USDC to simulate splitting"
219
+ )
220
+ agent_address: str = Field(
221
+ description="The Ethereum address of the agent wallet on Base"
222
+ )
223
+
224
+
225
+ # ---------------------------------------------------------------------------
226
+ # LangChain Tools
227
+ # ---------------------------------------------------------------------------
228
+
229
+ class ClicksActivateYieldTool(BaseTool):
230
+ """
231
+ Activate yield on idle USDC via Clicks Protocol.
232
+
233
+ USE THIS TOOL WHEN:
234
+ - The agent has idle USDC sitting in its wallet doing nothing
235
+ - The agent wants to earn passive yield (4-8% APY) on unused funds
236
+ - After receiving a payment that won't be spent immediately
237
+
238
+ HOW IT WORKS:
239
+ - Splits the specified USDC amount 80/20
240
+ - 80% stays liquid in the agent's wallet (available immediately)
241
+ - 20% is deposited into an on-chain yield strategy on Base
242
+ - The yield portion earns 4-8% APY automatically
243
+
244
+ DO NOT USE WHEN:
245
+ - The agent needs 100% of its USDC for an imminent transaction
246
+ - The amount is very small (gas costs may exceed yield)
247
+ - The agent is on a network other than Base
248
+ """
249
+
250
+ name: str = "clicks_activate_yield"
251
+ description: str = (
252
+ "Split idle USDC 80/20 to earn yield on the idle portion. "
253
+ "80% stays liquid, 20% earns 4-8% APY on Base via Clicks Protocol. "
254
+ "Input: amount (USDC as string) and agent_address (Ethereum address)."
255
+ )
256
+ args_schema: Type[BaseModel] = ActivateYieldInput
257
+ sdk: Optional[ClicksSDKBridge] = None
258
+
259
+ class Config:
260
+ arbitrary_types_allowed = True
261
+
262
+ def __init__(self, sdk: Optional[ClicksSDKBridge] = None, **kwargs):
263
+ super().__init__(**kwargs)
264
+ self.sdk = sdk or ClicksSDKBridge()
265
+
266
+ def _run(
267
+ self,
268
+ amount: str,
269
+ agent_address: str,
270
+ run_manager: Optional[CallbackManagerForToolRun] = None,
271
+ ) -> str:
272
+ # First simulate to show the user what will happen
273
+ sim = self.sdk.simulate_split(amount, agent_address)
274
+ if not sim.get("success"):
275
+ return f"Simulation failed: {sim.get('error', 'Unknown error')}"
276
+
277
+ result = self.sdk.quick_start(amount, agent_address)
278
+ if result.get("success"):
279
+ data = result.get("data", {})
280
+ return (
281
+ f"Yield activated successfully.\n"
282
+ f"Total: {amount} USDC\n"
283
+ f"Liquid (80%): {float(amount) * 0.8:.2f} USDC (available now)\n"
284
+ f"Yield (20%): {float(amount) * 0.2:.2f} USDC (earning APY)\n"
285
+ f"Transaction: {data.get('txHash', 'N/A')}"
286
+ )
287
+ return f"Failed to activate yield: {result.get('error', 'Unknown error')}"
288
+
289
+
290
+ class ClicksWithdrawYieldTool(BaseTool):
291
+ """
292
+ Withdraw earned yield from Clicks Protocol.
293
+
294
+ USE THIS TOOL WHEN:
295
+ - The agent needs to access its yield earnings
296
+ - The agent wants to consolidate all funds back to liquid
297
+ - Before a large purchase that requires maximum available funds
298
+
299
+ WHAT HAPPENS:
300
+ - All accrued yield is withdrawn to the agent's wallet
301
+ - The previously deposited 20% is returned to the wallet
302
+ - After withdrawal, no more yield is earned until re-activated
303
+ """
304
+
305
+ name: str = "clicks_withdraw_yield"
306
+ description: str = (
307
+ "Withdraw all earned yield and deposited funds from Clicks Protocol "
308
+ "back to the agent's wallet. Use when the agent needs maximum liquidity."
309
+ )
310
+ args_schema: Type[BaseModel] = WithdrawYieldInput
311
+ sdk: Optional[ClicksSDKBridge] = None
312
+
313
+ class Config:
314
+ arbitrary_types_allowed = True
315
+
316
+ def __init__(self, sdk: Optional[ClicksSDKBridge] = None, **kwargs):
317
+ super().__init__(**kwargs)
318
+ self.sdk = sdk or ClicksSDKBridge()
319
+
320
+ def _run(
321
+ self,
322
+ agent_address: str,
323
+ run_manager: Optional[CallbackManagerForToolRun] = None,
324
+ ) -> str:
325
+ result = self.sdk.withdraw_yield(agent_address)
326
+ if result.get("success"):
327
+ data = result.get("data", {})
328
+ return (
329
+ f"Yield withdrawn successfully.\n"
330
+ f"Amount returned: {data.get('amount', 'N/A')} USDC\n"
331
+ f"Transaction: {data.get('txHash', 'N/A')}"
332
+ )
333
+ return f"Withdrawal failed: {result.get('error', 'Unknown error')}"
334
+
335
+
336
+ class ClicksCheckBalanceTool(BaseTool):
337
+ """
338
+ Check agent balance and yield status on Clicks Protocol.
339
+
340
+ USE THIS TOOL WHEN:
341
+ - The agent needs to know how much USDC is liquid vs. earning yield
342
+ - Before making spending decisions to understand available funds
343
+ - To monitor yield performance and accrued earnings
344
+ - As a periodic health check on treasury status
345
+
346
+ RETURNS:
347
+ - Total USDC balance (liquid + yield)
348
+ - Liquid amount (immediately available)
349
+ - Deposited amount (earning yield)
350
+ - Accrued yield (earnings so far)
351
+ - Current APY rate
352
+ """
353
+
354
+ name: str = "clicks_check_balance"
355
+ description: str = (
356
+ "Check the agent's USDC balance split: liquid funds, deposited yield amount, "
357
+ "accrued earnings, and current APY. Use to understand treasury status before "
358
+ "spending decisions."
359
+ )
360
+ args_schema: Type[BaseModel] = CheckBalanceInput
361
+ sdk: Optional[ClicksSDKBridge] = None
362
+
363
+ class Config:
364
+ arbitrary_types_allowed = True
365
+
366
+ def __init__(self, sdk: Optional[ClicksSDKBridge] = None, **kwargs):
367
+ super().__init__(**kwargs)
368
+ self.sdk = sdk or ClicksSDKBridge()
369
+
370
+ def _run(
371
+ self,
372
+ agent_address: str,
373
+ run_manager: Optional[CallbackManagerForToolRun] = None,
374
+ ) -> str:
375
+ info = self.sdk.get_agent_info(agent_address)
376
+ apy = self.sdk.get_current_apy()
377
+
378
+ if not info.get("success"):
379
+ return f"Balance check failed: {info.get('error', 'Unknown error')}"
380
+
381
+ data = info.get("data", {})
382
+ apy_rate = apy.get("data", "N/A") if apy.get("success") else "N/A"
383
+
384
+ return (
385
+ f"Agent Treasury Status:\n"
386
+ f" Liquid USDC: {data.get('liquidBalance', '0')} (available now)\n"
387
+ f" Deposited: {data.get('depositedBalance', '0')} (earning yield)\n"
388
+ f" Accrued: {data.get('accruedYield', '0')} (yield earned)\n"
389
+ f" Current APY: {apy_rate}%\n"
390
+ f" Registered: {data.get('isRegistered', False)}"
391
+ )
392
+
393
+
394
+ class ClicksGetAPYTool(BaseTool):
395
+ """
396
+ Get the current APY rate from Clicks Protocol.
397
+
398
+ USE THIS TOOL WHEN:
399
+ - The agent wants to evaluate whether activating yield is worthwhile
400
+ - Comparing yield rates before making treasury decisions
401
+ - Reporting current earnings potential to the user
402
+
403
+ Typically returns 4-8% APY depending on market conditions.
404
+ """
405
+
406
+ name: str = "clicks_get_apy"
407
+ description: str = (
408
+ "Get the current annual percentage yield (APY) from Clicks Protocol. "
409
+ "Returns the current rate (typically 4-8%). Use to evaluate yield potential."
410
+ )
411
+ sdk: Optional[ClicksSDKBridge] = None
412
+
413
+ class Config:
414
+ arbitrary_types_allowed = True
415
+
416
+ def __init__(self, sdk: Optional[ClicksSDKBridge] = None, **kwargs):
417
+ super().__init__(**kwargs)
418
+ self.sdk = sdk or ClicksSDKBridge()
419
+
420
+ def _run(
421
+ self,
422
+ run_manager: Optional[CallbackManagerForToolRun] = None,
423
+ ) -> str:
424
+ result = self.sdk.get_current_apy()
425
+ if result.get("success"):
426
+ return f"Current Clicks Protocol APY: {result['data']}%"
427
+ return f"Could not fetch APY: {result.get('error', 'Unknown error')}"
428
+
429
+
430
+ class ClicksSimulateSplitTool(BaseTool):
431
+ """
432
+ Preview how a USDC amount would be split before committing.
433
+
434
+ USE THIS TOOL WHEN:
435
+ - The agent wants to see the 80/20 breakdown before activating
436
+ - Planning treasury allocation
437
+ - The user asks "what would happen if I deposited X?"
438
+
439
+ This is a read-only operation. No funds are moved.
440
+ """
441
+
442
+ name: str = "clicks_simulate_split"
443
+ description: str = (
444
+ "Preview how USDC would be split 80/20 without moving funds. "
445
+ "Shows liquid amount, yield amount, and estimated earnings. "
446
+ "Use before clicks_activate_yield to preview the outcome."
447
+ )
448
+ args_schema: Type[BaseModel] = SimulateSplitInput
449
+ sdk: Optional[ClicksSDKBridge] = None
450
+
451
+ class Config:
452
+ arbitrary_types_allowed = True
453
+
454
+ def __init__(self, sdk: Optional[ClicksSDKBridge] = None, **kwargs):
455
+ super().__init__(**kwargs)
456
+ self.sdk = sdk or ClicksSDKBridge()
457
+
458
+ def _run(
459
+ self,
460
+ amount: str,
461
+ agent_address: str,
462
+ run_manager: Optional[CallbackManagerForToolRun] = None,
463
+ ) -> str:
464
+ result = self.sdk.simulate_split(amount, agent_address)
465
+ if result.get("success"):
466
+ data = result.get("data", {})
467
+ return (
468
+ f"Split Preview for {amount} USDC:\n"
469
+ f" Liquid (80%): {data.get('liquidAmount', float(amount) * 0.8):.2f} USDC\n"
470
+ f" Yield (20%): {data.get('yieldAmount', float(amount) * 0.2):.2f} USDC\n"
471
+ f" Est. Annual: {data.get('estimatedYield', 'N/A')} USDC\n"
472
+ f" No funds moved. Use clicks_activate_yield to proceed."
473
+ )
474
+ return f"Simulation failed: {result.get('error', 'Unknown error')}"
475
+
476
+
477
+ def get_clicks_tools(
478
+ rpc_url: Optional[str] = None,
479
+ private_key: Optional[str] = None,
480
+ sdk_path: Optional[str] = None,
481
+ ) -> list:
482
+ """
483
+ Get all Clicks Protocol tools for a LangChain agent.
484
+
485
+ Args:
486
+ rpc_url: Base RPC URL (default: CLICKS_RPC_URL env var)
487
+ private_key: Agent wallet private key (default: CLICKS_PRIVATE_KEY env var)
488
+ sdk_path: Path to installed @clicks-protocol/sdk (default: CLICKS_SDK_PATH env var)
489
+
490
+ Returns:
491
+ List of LangChain tools for yield management
492
+
493
+ Example:
494
+ from clicks_langchain import get_clicks_tools
495
+ tools = get_clicks_tools()
496
+ agent = initialize_agent(tools, llm, agent=AgentType.OPENAI_FUNCTIONS)
497
+ """
498
+ sdk = ClicksSDKBridge(rpc_url=rpc_url, private_key=private_key, sdk_path=sdk_path)
499
+ return [
500
+ ClicksActivateYieldTool(sdk=sdk),
501
+ ClicksWithdrawYieldTool(sdk=sdk),
502
+ ClicksCheckBalanceTool(sdk=sdk),
503
+ ClicksGetAPYTool(sdk=sdk),
504
+ ClicksSimulateSplitTool(sdk=sdk),
505
+ ]
@@ -0,0 +1,38 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "clicks-langchain"
7
+ version = "0.1.0"
8
+ description = "Clicks Protocol yield management tools for LangChain agents on Base"
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ requires-python = ">=3.9"
12
+ authors = [
13
+ {name = "Clicks Protocol", email = "dev@clicksprotocol.xyz"},
14
+ ]
15
+ keywords = ["langchain", "defi", "yield", "base", "usdc", "ai-agents", "clicks-protocol"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.9",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Topic :: Software Development :: Libraries",
26
+ ]
27
+ dependencies = [
28
+ "langchain>=0.1.0",
29
+ "pydantic>=2.0",
30
+ ]
31
+
32
+ [project.urls]
33
+ Homepage = "https://clicksprotocol.xyz"
34
+ Repository = "https://github.com/clicks-protocol/clicks-langchain"
35
+ Documentation = "https://docs.clicksprotocol.xyz/integrations/langchain"
36
+
37
+ [tool.setuptools]
38
+ py-modules = ["clicks_langchain"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+