botchain-sdk-py 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.
- botchain_sdk_py-0.1.0/PKG-INFO +58 -0
- botchain_sdk_py-0.1.0/README.md +47 -0
- botchain_sdk_py-0.1.0/pyproject.toml +20 -0
- botchain_sdk_py-0.1.0/setup.cfg +4 -0
- botchain_sdk_py-0.1.0/setup.py +18 -0
- botchain_sdk_py-0.1.0/src/botchain/__init__.py +18 -0
- botchain_sdk_py-0.1.0/src/botchain/agents/__init__.py +4 -0
- botchain_sdk_py-0.1.0/src/botchain/agents/executor.py +92 -0
- botchain_sdk_py-0.1.0/src/botchain/agents/mcp.py +3 -0
- botchain_sdk_py-0.1.0/src/botchain/agents/mcp_server.py +18 -0
- botchain_sdk_py-0.1.0/src/botchain/agents/policy.py +102 -0
- botchain_sdk_py-0.1.0/src/botchain/agents/tools/__init__.py +3 -0
- botchain_sdk_py-0.1.0/src/botchain/agents/tools/base.py +32 -0
- botchain_sdk_py-0.1.0/src/botchain/agents/tools/mcp_server.py +115 -0
- botchain_sdk_py-0.1.0/src/botchain/agents/tools/schemas.py +97 -0
- botchain_sdk_py-0.1.0/src/botchain/client.py +82 -0
- botchain_sdk_py-0.1.0/src/botchain/contracts.py +8 -0
- botchain_sdk_py-0.1.0/src/botchain/dex.py +189 -0
- botchain_sdk_py-0.1.0/src/botchain/dex_alm.py +129 -0
- botchain_sdk_py-0.1.0/src/botchain/exceptions.py +7 -0
- botchain_sdk_py-0.1.0/src/botchain/middleware.py +58 -0
- botchain_sdk_py-0.1.0/src/botchain/tokens.py +87 -0
- botchain_sdk_py-0.1.0/src/botchain_sdk_py.egg-info/PKG-INFO +58 -0
- botchain_sdk_py-0.1.0/src/botchain_sdk_py.egg-info/SOURCES.txt +33 -0
- botchain_sdk_py-0.1.0/src/botchain_sdk_py.egg-info/dependency_links.txt +1 -0
- botchain_sdk_py-0.1.0/src/botchain_sdk_py.egg-info/entry_points.txt +2 -0
- botchain_sdk_py-0.1.0/src/botchain_sdk_py.egg-info/requires.txt +2 -0
- botchain_sdk_py-0.1.0/src/botchain_sdk_py.egg-info/top_level.txt +1 -0
- botchain_sdk_py-0.1.0/tests/test_agents.py +127 -0
- botchain_sdk_py-0.1.0/tests/test_alm_math.py +62 -0
- botchain_sdk_py-0.1.0/tests/test_client.py +51 -0
- botchain_sdk_py-0.1.0/tests/test_dex_liquidity.py +104 -0
- botchain_sdk_py-0.1.0/tests/test_mcp.py +74 -0
- botchain_sdk_py-0.1.0/tests/test_middleware_dex.py +64 -0
- botchain_sdk_py-0.1.0/tests/test_tokens.py +23 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: botchain-sdk-py
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: BOT Chain SDK for Python, featuring Agentic Tooling, MCP, and ALM.
|
|
5
|
+
Author: Bot Chain Team
|
|
6
|
+
Requires-Python: >=3.8
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: web3
|
|
9
|
+
Requires-Dist: eth-account
|
|
10
|
+
Dynamic: requires-python
|
|
11
|
+
|
|
12
|
+
# BotChain Python SDK
|
|
13
|
+
|
|
14
|
+
The official Python SDK for interacting with BOT Chain, an AI-focused Layer 1 ecosystem. This SDK provides complete integration pathways for managing accounts, tokens, concentrated liquidity (BDEX V3), and executing smart contract logic autonomously via our embedded AI Agent integrations.
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- **Core Blockchain Client**: Connect to Mainnet/Testnet easily with full Web3 provider compatibility. Zero-gas transaction sponsorships natively handled.
|
|
19
|
+
- **BDEX V3 Concentrated Liquidity**: Manage advanced DeFi interactions.
|
|
20
|
+
|
|
21
|
+
### AI Agent & MCP Server
|
|
22
|
+
This SDK natively supports Model Context Protocol (MCP) integrations for autonomous agents like OpenClaw or Claude Desktop. The embedded Agent Policy Engine ensures strict on-chain safeguards including:
|
|
23
|
+
- Hourly rate limits and trade cooldowns.
|
|
24
|
+
- Notional USD limits.
|
|
25
|
+
- Asset Allowlists.
|
|
26
|
+
|
|
27
|
+
Agents can discover tools (`get_balance`, `get_quote`, `execute_guarded_swap`) over standard JSON-RPC `stdio`. Run the server locally using the bundled entry point:
|
|
28
|
+
```bash
|
|
29
|
+
botchain-mcp
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Automated Liquidity Management (ALM)
|
|
33
|
+
We support on-chain math utilities required to properly route and rebalance Concentrated Liquidity positions automatically.
|
|
34
|
+
- Exact tick conversions and boundaries.
|
|
35
|
+
- Precise multi-hop math allowing the SDK to compute optimal swap ratios without trusting off-chain actors.
|
|
36
|
+
- Fully atomic rebalances (with Python-level State Recovery for failing swaps).
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
You can install the SDK from source or directly via `pip`:
|
|
41
|
+
```bash
|
|
42
|
+
pip install botchain-sdk-py
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Quick Start
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from botchain.client import BotChain
|
|
49
|
+
from botchain.tokens import TokenManager
|
|
50
|
+
|
|
51
|
+
# Initialize Client
|
|
52
|
+
client = BotChain(network="mainnet")
|
|
53
|
+
|
|
54
|
+
# Check token balances
|
|
55
|
+
token_mgr = TokenManager(client)
|
|
56
|
+
balance = token_mgr.get_balance("0xUSDT_Address", "0xUser_Address")
|
|
57
|
+
print(f"USDT Balance: {balance}")
|
|
58
|
+
```
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# BotChain Python SDK
|
|
2
|
+
|
|
3
|
+
The official Python SDK for interacting with BOT Chain, an AI-focused Layer 1 ecosystem. This SDK provides complete integration pathways for managing accounts, tokens, concentrated liquidity (BDEX V3), and executing smart contract logic autonomously via our embedded AI Agent integrations.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Core Blockchain Client**: Connect to Mainnet/Testnet easily with full Web3 provider compatibility. Zero-gas transaction sponsorships natively handled.
|
|
8
|
+
- **BDEX V3 Concentrated Liquidity**: Manage advanced DeFi interactions.
|
|
9
|
+
|
|
10
|
+
### AI Agent & MCP Server
|
|
11
|
+
This SDK natively supports Model Context Protocol (MCP) integrations for autonomous agents like OpenClaw or Claude Desktop. The embedded Agent Policy Engine ensures strict on-chain safeguards including:
|
|
12
|
+
- Hourly rate limits and trade cooldowns.
|
|
13
|
+
- Notional USD limits.
|
|
14
|
+
- Asset Allowlists.
|
|
15
|
+
|
|
16
|
+
Agents can discover tools (`get_balance`, `get_quote`, `execute_guarded_swap`) over standard JSON-RPC `stdio`. Run the server locally using the bundled entry point:
|
|
17
|
+
```bash
|
|
18
|
+
botchain-mcp
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Automated Liquidity Management (ALM)
|
|
22
|
+
We support on-chain math utilities required to properly route and rebalance Concentrated Liquidity positions automatically.
|
|
23
|
+
- Exact tick conversions and boundaries.
|
|
24
|
+
- Precise multi-hop math allowing the SDK to compute optimal swap ratios without trusting off-chain actors.
|
|
25
|
+
- Fully atomic rebalances (with Python-level State Recovery for failing swaps).
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
You can install the SDK from source or directly via `pip`:
|
|
30
|
+
```bash
|
|
31
|
+
pip install botchain-sdk-py
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from botchain.client import BotChain
|
|
38
|
+
from botchain.tokens import TokenManager
|
|
39
|
+
|
|
40
|
+
# Initialize Client
|
|
41
|
+
client = BotChain(network="mainnet")
|
|
42
|
+
|
|
43
|
+
# Check token balances
|
|
44
|
+
token_mgr = TokenManager(client)
|
|
45
|
+
balance = token_mgr.get_balance("0xUSDT_Address", "0xUser_Address")
|
|
46
|
+
print(f"USDT Balance: {balance}")
|
|
47
|
+
```
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "botchain-sdk-py"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "BOT Chain SDK for Python, featuring Agentic Tooling, MCP, and ALM."
|
|
9
|
+
authors = [
|
|
10
|
+
{name = "Bot Chain Team"}
|
|
11
|
+
]
|
|
12
|
+
dependencies = [
|
|
13
|
+
"web3",
|
|
14
|
+
"eth-account"
|
|
15
|
+
]
|
|
16
|
+
readme = "README.md"
|
|
17
|
+
requires-python = ">=3.8"
|
|
18
|
+
|
|
19
|
+
[project.scripts]
|
|
20
|
+
botchain-mcp = "botchain.agents.mcp_server:main"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name="botchain-sdk",
|
|
5
|
+
version="0.1.0",
|
|
6
|
+
package_dir={"": "src"},
|
|
7
|
+
packages=find_packages(where="src"),
|
|
8
|
+
install_requires=[
|
|
9
|
+
"web3>=6.0.0",
|
|
10
|
+
"eth-account>=0.8.0",
|
|
11
|
+
],
|
|
12
|
+
extras_require={
|
|
13
|
+
"dev": [
|
|
14
|
+
"pytest>=7.0.0",
|
|
15
|
+
],
|
|
16
|
+
},
|
|
17
|
+
python_requires=">=3.8",
|
|
18
|
+
)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from .client import BotChain
|
|
2
|
+
from .exceptions import BotChainLogsDisabledError
|
|
3
|
+
from .dex import BDexManager
|
|
4
|
+
from .tokens import TokenManager
|
|
5
|
+
from .middleware import botchain_paymaster_middleware, check_and_apply_sponsorship
|
|
6
|
+
from .agents import AgentTradePolicy, AgentExecutor
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"BotChain",
|
|
10
|
+
"BotChainLogsDisabledError",
|
|
11
|
+
"BDexManager",
|
|
12
|
+
"TokenManager",
|
|
13
|
+
"botchain_paymaster_middleware",
|
|
14
|
+
"check_and_apply_sponsorship",
|
|
15
|
+
"AgentTradePolicy",
|
|
16
|
+
"AgentExecutor"
|
|
17
|
+
]
|
|
18
|
+
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import time
|
|
3
|
+
from typing import Optional
|
|
4
|
+
from web3 import Web3
|
|
5
|
+
from .policy import AgentTradePolicy
|
|
6
|
+
from ..middleware import check_and_apply_sponsorship
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
class AgentExecutor:
|
|
11
|
+
"""
|
|
12
|
+
Guarded transaction execution engine for AI Agents.
|
|
13
|
+
Validates trade intents against AgentTradePolicy, performs pre-flight eth_call simulations,
|
|
14
|
+
and handles zero-gas Paymaster sponsorship before signing & broadcasting.
|
|
15
|
+
"""
|
|
16
|
+
def __init__(self, botclient, policy: AgentTradePolicy):
|
|
17
|
+
self.client = botclient
|
|
18
|
+
self.w3 = botclient.w3
|
|
19
|
+
self.policy = policy
|
|
20
|
+
|
|
21
|
+
def execute_trade(
|
|
22
|
+
self,
|
|
23
|
+
dex_manager,
|
|
24
|
+
token_in: str,
|
|
25
|
+
token_out: str,
|
|
26
|
+
amount_in: int,
|
|
27
|
+
fee: int = 3000,
|
|
28
|
+
slippage_tolerance: float = 0.01,
|
|
29
|
+
recipient: Optional[str] = None,
|
|
30
|
+
notional_usd: Optional[float] = None
|
|
31
|
+
) -> str:
|
|
32
|
+
"""
|
|
33
|
+
Validates, simulates, sponsors, and executes a single-hop BDEX trade.
|
|
34
|
+
"""
|
|
35
|
+
if not self.client.account:
|
|
36
|
+
raise ValueError("Local account required to execute agent trades.")
|
|
37
|
+
|
|
38
|
+
target_recipient = recipient or self.client.account.address
|
|
39
|
+
router_address = dex_manager.router_contract.address
|
|
40
|
+
|
|
41
|
+
# 1. Policy Validation
|
|
42
|
+
self.policy.validate_trade(
|
|
43
|
+
token_in=token_in,
|
|
44
|
+
token_out=token_out,
|
|
45
|
+
router=router_address,
|
|
46
|
+
amount_in=amount_in,
|
|
47
|
+
slippage=slippage_tolerance,
|
|
48
|
+
notional_usd=notional_usd
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# 2. Get Quote & Calculate minimum output
|
|
52
|
+
expected_out = dex_manager.get_quote(token_in, token_out, amount_in, fee)
|
|
53
|
+
amount_out_minimum = int(expected_out * (1 - slippage_tolerance))
|
|
54
|
+
|
|
55
|
+
# 3. Build Transaction Dictionary
|
|
56
|
+
params = {
|
|
57
|
+
"tokenIn": self.w3.to_checksum_address(token_in),
|
|
58
|
+
"tokenOut": self.w3.to_checksum_address(token_out),
|
|
59
|
+
"fee": fee,
|
|
60
|
+
"recipient": self.w3.to_checksum_address(target_recipient),
|
|
61
|
+
"deadline": int(time.time()) + 600,
|
|
62
|
+
"amountIn": amount_in,
|
|
63
|
+
"amountOutMinimum": amount_out_minimum,
|
|
64
|
+
"sqrtPriceLimitX96": 0
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
tx_dict = dex_manager.router_contract.functions.exactInputSingle(params).build_transaction({
|
|
68
|
+
'from': self.client.account.address,
|
|
69
|
+
'nonce': self.w3.eth.get_transaction_count(self.client.account.address),
|
|
70
|
+
'chainId': self.client.chain_id,
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
# 4. Pre-Flight Simulation (eth_call)
|
|
74
|
+
try:
|
|
75
|
+
logger.info("Executing pre-flight transaction simulation via eth_call...")
|
|
76
|
+
self.w3.eth.call(tx_dict)
|
|
77
|
+
logger.info("Pre-flight simulation successful.")
|
|
78
|
+
except Exception as e:
|
|
79
|
+
logger.error(f"Pre-flight simulation failed: {e}")
|
|
80
|
+
raise RuntimeError(f"Transaction pre-flight simulation failed: {e}") from e
|
|
81
|
+
|
|
82
|
+
# 5. Zero-Gas Paymaster Sponsorship Check
|
|
83
|
+
tx_dict = check_and_apply_sponsorship(self.w3, tx_dict)
|
|
84
|
+
|
|
85
|
+
# 6. Sign and Broadcast Transaction
|
|
86
|
+
signed_tx = self.w3.eth.account.sign_transaction(tx_dict, private_key=self.client.account.key)
|
|
87
|
+
tx_hash = self.w3.eth.send_raw_transaction(signed_tx.rawTransaction)
|
|
88
|
+
|
|
89
|
+
# 7. Record trade in policy state upon success
|
|
90
|
+
self.policy.record_trade()
|
|
91
|
+
|
|
92
|
+
return self.w3.to_hex(tx_hash)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from botchain.client import BotChain
|
|
3
|
+
from botchain.agents.executor import AgentExecutor
|
|
4
|
+
from botchain.agents.policy import AgentTradePolicy
|
|
5
|
+
from botchain.agents.tools.mcp_server import BotChainMCPServer
|
|
6
|
+
|
|
7
|
+
def main():
|
|
8
|
+
"""Entry point for botchain-mcp server."""
|
|
9
|
+
# Simple setup for MCP
|
|
10
|
+
client = BotChain()
|
|
11
|
+
policy = AgentTradePolicy()
|
|
12
|
+
executor = AgentExecutor(policy)
|
|
13
|
+
|
|
14
|
+
server = BotChainMCPServer(client, executor)
|
|
15
|
+
server.run_stdio()
|
|
16
|
+
|
|
17
|
+
if __name__ == "__main__":
|
|
18
|
+
main()
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from typing import List, Optional, Set
|
|
3
|
+
from web3 import Web3
|
|
4
|
+
|
|
5
|
+
class AgentTradePolicy:
|
|
6
|
+
"""
|
|
7
|
+
Safety Policy Engine for AI Agents executing trades on BOT Chain.
|
|
8
|
+
Enforces spending caps, rate limits, cooldowns, and asset allowlists.
|
|
9
|
+
"""
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
allowed_tokens: Optional[List[str]] = None,
|
|
13
|
+
allowed_routers: Optional[List[str]] = None,
|
|
14
|
+
max_slippage_tolerance: float = 0.05,
|
|
15
|
+
max_notional_usd: Optional[float] = 1000.0,
|
|
16
|
+
cooldown_seconds: float = 10.0,
|
|
17
|
+
max_trades_per_hour: int = 10
|
|
18
|
+
):
|
|
19
|
+
self.allowed_tokens: Optional[Set[str]] = (
|
|
20
|
+
{Web3.to_checksum_address(t) for t in allowed_tokens} if allowed_tokens else None
|
|
21
|
+
)
|
|
22
|
+
self.allowed_routers: Optional[Set[str]] = (
|
|
23
|
+
{Web3.to_checksum_address(r) for r in allowed_routers} if allowed_routers else None
|
|
24
|
+
)
|
|
25
|
+
self.max_slippage_tolerance = max_slippage_tolerance
|
|
26
|
+
self.max_notional_usd = max_notional_usd
|
|
27
|
+
self.cooldown_seconds = cooldown_seconds
|
|
28
|
+
self.max_trades_per_hour = max_trades_per_hour
|
|
29
|
+
|
|
30
|
+
# Internal tracking state
|
|
31
|
+
self.last_trade_time: Optional[float] = None
|
|
32
|
+
self.trade_timestamps: List[float] = []
|
|
33
|
+
|
|
34
|
+
def validate_trade(
|
|
35
|
+
self,
|
|
36
|
+
token_in: str,
|
|
37
|
+
token_out: str,
|
|
38
|
+
router: Optional[str] = None,
|
|
39
|
+
amount_in: int = 0,
|
|
40
|
+
slippage: float = 0.01,
|
|
41
|
+
notional_usd: Optional[float] = None,
|
|
42
|
+
current_time: Optional[float] = None
|
|
43
|
+
) -> None:
|
|
44
|
+
"""
|
|
45
|
+
Validates trade parameters against policy rules.
|
|
46
|
+
Raises ValueError if any rule is violated.
|
|
47
|
+
"""
|
|
48
|
+
now = current_time if current_time is not None else time.time()
|
|
49
|
+
|
|
50
|
+
# 1. Token Allowlist Verification
|
|
51
|
+
checksum_in = Web3.to_checksum_address(token_in)
|
|
52
|
+
checksum_out = Web3.to_checksum_address(token_out)
|
|
53
|
+
|
|
54
|
+
if self.allowed_tokens is not None:
|
|
55
|
+
if checksum_in not in self.allowed_tokens:
|
|
56
|
+
raise ValueError(f"Token IN ({token_in}) is not in the allowed tokens list.")
|
|
57
|
+
if checksum_out not in self.allowed_tokens:
|
|
58
|
+
raise ValueError(f"Token OUT ({token_out}) is not in the allowed tokens list.")
|
|
59
|
+
|
|
60
|
+
# 2. Router Allowlist Verification
|
|
61
|
+
if router and self.allowed_routers is not None:
|
|
62
|
+
checksum_router = Web3.to_checksum_address(router)
|
|
63
|
+
if checksum_router not in self.allowed_routers:
|
|
64
|
+
raise ValueError(f"Router ({router}) is not in the allowed routers list.")
|
|
65
|
+
|
|
66
|
+
# 3. Slippage Limit Verification
|
|
67
|
+
if slippage > self.max_slippage_tolerance:
|
|
68
|
+
raise ValueError(
|
|
69
|
+
f"Requested slippage ({slippage:.4f}) exceeds maximum allowed tolerance ({self.max_slippage_tolerance:.4f})."
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# 4. Notional USD Cap Verification
|
|
73
|
+
if self.max_notional_usd is not None and notional_usd is not None:
|
|
74
|
+
if notional_usd > self.max_notional_usd:
|
|
75
|
+
raise ValueError(
|
|
76
|
+
f"Trade value (${notional_usd:.2f}) exceeds maximum notional USD cap (${self.max_notional_usd:.2f})."
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# 5. Cooldown Verification
|
|
80
|
+
if self.last_trade_time is not None:
|
|
81
|
+
elapsed = now - self.last_trade_time
|
|
82
|
+
if elapsed < self.cooldown_seconds:
|
|
83
|
+
remaining = self.cooldown_seconds - elapsed
|
|
84
|
+
raise ValueError(
|
|
85
|
+
f"Trade cooldown active. Please wait {remaining:.2f} seconds before trading again."
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# 6. Hourly Rate Limit Verification
|
|
89
|
+
# Filter timestamps to only keep trades within the last 3600 seconds (1 hour)
|
|
90
|
+
self.trade_timestamps = [t for t in self.trade_timestamps if now - t < 3600]
|
|
91
|
+
if len(self.trade_timestamps) >= self.max_trades_per_hour:
|
|
92
|
+
raise ValueError(
|
|
93
|
+
f"Hourly trade limit reached ({self.max_trades_per_hour} trades/hour)."
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def record_trade(self, timestamp: Optional[float] = None) -> None:
|
|
97
|
+
"""
|
|
98
|
+
Records a successfully executed trade timestamp.
|
|
99
|
+
"""
|
|
100
|
+
now = timestamp if timestamp is not None else time.time()
|
|
101
|
+
self.last_trade_time = now
|
|
102
|
+
self.trade_timestamps.append(now)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from botchain.tokens import TokenManager
|
|
2
|
+
from botchain.dex import BDexManager
|
|
3
|
+
from botchain.agents.executor import AgentExecutor
|
|
4
|
+
|
|
5
|
+
def botchain_get_balance(client, token_address: str, account_address: str = None) -> int:
|
|
6
|
+
token_manager = TokenManager(client)
|
|
7
|
+
return token_manager.get_balance(token_address, account_address)
|
|
8
|
+
|
|
9
|
+
def botchain_get_quote(client, token_in: str, token_out: str, amount_in: int, fee: int = 3000) -> int:
|
|
10
|
+
dex_manager = BDexManager(client)
|
|
11
|
+
return dex_manager.get_quote(token_in, token_out, amount_in, fee)
|
|
12
|
+
|
|
13
|
+
def botchain_execute_swap(
|
|
14
|
+
client,
|
|
15
|
+
executor: AgentExecutor,
|
|
16
|
+
token_in: str,
|
|
17
|
+
token_out: str,
|
|
18
|
+
amount_in: int,
|
|
19
|
+
fee: int = 3000,
|
|
20
|
+
slippage_tolerance: float = 0.01,
|
|
21
|
+
notional_usd: float = None
|
|
22
|
+
) -> str:
|
|
23
|
+
dex_manager = BDexManager(client)
|
|
24
|
+
return executor.execute_trade(
|
|
25
|
+
dex_manager=dex_manager,
|
|
26
|
+
token_in=token_in,
|
|
27
|
+
token_out=token_out,
|
|
28
|
+
amount_in=amount_in,
|
|
29
|
+
fee=fee,
|
|
30
|
+
slippage_tolerance=slippage_tolerance,
|
|
31
|
+
notional_usd=notional_usd
|
|
32
|
+
)
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
from botchain.agents.tools.base import botchain_get_balance, botchain_get_quote, botchain_execute_swap
|
|
5
|
+
from botchain.agents.tools.schemas import BOTCHAIN_TOOL_SCHEMAS
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
class BotChainMCPServer:
|
|
10
|
+
"""
|
|
11
|
+
Model Context Protocol (MCP) Server for BOT Chain Tools.
|
|
12
|
+
Listens on stdio for JSON-RPC 2.0 requests.
|
|
13
|
+
"""
|
|
14
|
+
def __init__(self, client, executor):
|
|
15
|
+
self.client = client
|
|
16
|
+
self.executor = executor
|
|
17
|
+
|
|
18
|
+
# Tools map
|
|
19
|
+
self.tools = {
|
|
20
|
+
"botchain_get_balance": self.handle_get_balance,
|
|
21
|
+
"botchain_get_quote": self.handle_get_quote,
|
|
22
|
+
"botchain_execute_swap": self.handle_execute_swap
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
def handle_get_balance(self, params):
|
|
26
|
+
return botchain_get_balance(
|
|
27
|
+
self.client,
|
|
28
|
+
params.get("token_address"),
|
|
29
|
+
params.get("account_address")
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
def handle_get_quote(self, params):
|
|
33
|
+
return botchain_get_quote(
|
|
34
|
+
self.client,
|
|
35
|
+
params.get("token_in"),
|
|
36
|
+
params.get("token_out"),
|
|
37
|
+
params.get("amount_in"),
|
|
38
|
+
params.get("fee", 3000)
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
def handle_execute_swap(self, params):
|
|
42
|
+
return botchain_execute_swap(
|
|
43
|
+
self.client,
|
|
44
|
+
self.executor,
|
|
45
|
+
params.get("token_in"),
|
|
46
|
+
params.get("token_out"),
|
|
47
|
+
params.get("amount_in"),
|
|
48
|
+
params.get("fee", 3000),
|
|
49
|
+
params.get("slippage_tolerance", 0.01),
|
|
50
|
+
params.get("notional_usd")
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def process_request(self, request_str: str) -> str:
|
|
54
|
+
try:
|
|
55
|
+
req = json.loads(request_str)
|
|
56
|
+
if not isinstance(req, dict) or "jsonrpc" not in req:
|
|
57
|
+
return json.dumps({"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": None})
|
|
58
|
+
|
|
59
|
+
req_id = req.get("id")
|
|
60
|
+
method = req.get("method")
|
|
61
|
+
params = req.get("params", {})
|
|
62
|
+
|
|
63
|
+
# MCP Tool Discovery
|
|
64
|
+
if method == "tools/list":
|
|
65
|
+
return json.dumps({
|
|
66
|
+
"jsonrpc": "2.0",
|
|
67
|
+
"id": req_id,
|
|
68
|
+
"result": {
|
|
69
|
+
"tools": BOTCHAIN_TOOL_SCHEMAS
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
# MCP Tool Call
|
|
74
|
+
if method == "tools/call":
|
|
75
|
+
tool_name = params.get("name")
|
|
76
|
+
tool_args = params.get("arguments", {})
|
|
77
|
+
|
|
78
|
+
if tool_name not in self.tools:
|
|
79
|
+
return json.dumps({
|
|
80
|
+
"jsonrpc": "2.0",
|
|
81
|
+
"id": req_id,
|
|
82
|
+
"error": {"code": -32601, "message": f"Method not found: {tool_name}"}
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
result = self.tools[tool_name](tool_args)
|
|
87
|
+
return json.dumps({
|
|
88
|
+
"jsonrpc": "2.0",
|
|
89
|
+
"id": req_id,
|
|
90
|
+
"result": {
|
|
91
|
+
"content": [{"type": "text", "text": str(result)}]
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
except Exception as e:
|
|
95
|
+
return json.dumps({
|
|
96
|
+
"jsonrpc": "2.0",
|
|
97
|
+
"id": req_id,
|
|
98
|
+
"error": {"code": -32000, "message": str(e)}
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
return json.dumps({
|
|
102
|
+
"jsonrpc": "2.0",
|
|
103
|
+
"id": req_id,
|
|
104
|
+
"error": {"code": -32601, "message": "Method not found"}
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
except json.JSONDecodeError:
|
|
108
|
+
return json.dumps({"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error"}, "id": None})
|
|
109
|
+
|
|
110
|
+
def run_stdio(self):
|
|
111
|
+
"""Runs the MCP server over standard input/output."""
|
|
112
|
+
for line in sys.stdin:
|
|
113
|
+
response = self.process_request(line)
|
|
114
|
+
sys.stdout.write(response + "\n")
|
|
115
|
+
sys.stdout.flush()
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Standardized JSON schemas for LLM Function Calling (OpenAI, Gemini, Anthropic, MCP).
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
BOTCHAIN_TOOL_SCHEMAS = [
|
|
6
|
+
{
|
|
7
|
+
"name": "botchain_get_balance",
|
|
8
|
+
"description": "Get the token balance (native BOT or ERC20/WBOT) for a given account address on BOT Chain.",
|
|
9
|
+
"parameters": {
|
|
10
|
+
"type": "object",
|
|
11
|
+
"properties": {
|
|
12
|
+
"token_address": {
|
|
13
|
+
"type": "string",
|
|
14
|
+
"description": "The contract address of the ERC20 token, or 'NATIVE' / 'WBOT' for BOT Chain native assets."
|
|
15
|
+
},
|
|
16
|
+
"account_address": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"description": "Optional 0x-prefixed account address. Defaults to the connected agent wallet."
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"required": ["token_address"]
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"name": "botchain_get_quote",
|
|
26
|
+
"description": "Query BDEX V3 for the expected output amount for a single-hop token swap.",
|
|
27
|
+
"parameters": {
|
|
28
|
+
"type": "object",
|
|
29
|
+
"properties": {
|
|
30
|
+
"token_in": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"description": "The checksummed contract address of the input token."
|
|
33
|
+
},
|
|
34
|
+
"token_out": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"description": "The checksummed contract address of the output token."
|
|
37
|
+
},
|
|
38
|
+
"amount_in": {
|
|
39
|
+
"type": "integer",
|
|
40
|
+
"description": "Amount of token_in in base units (wei)."
|
|
41
|
+
},
|
|
42
|
+
"fee": {
|
|
43
|
+
"type": "integer",
|
|
44
|
+
"description": "Pool fee tier in hundredths of a bip (default 3000 for 0.3%).",
|
|
45
|
+
"default": 3000
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"required": ["token_in", "token_out", "amount_in"]
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"name": "botchain_execute_swap",
|
|
53
|
+
"description": "Executes a policy-guarded single-hop token swap on BDEX with pre-flight simulation and optional zero-gas Paymaster sponsorship.",
|
|
54
|
+
"parameters": {
|
|
55
|
+
"type": "object",
|
|
56
|
+
"properties": {
|
|
57
|
+
"token_in": {
|
|
58
|
+
"type": "string",
|
|
59
|
+
"description": "Checksummed address of the token to sell."
|
|
60
|
+
},
|
|
61
|
+
"token_out": {
|
|
62
|
+
"type": "string",
|
|
63
|
+
"description": "Checksummed address of the token to buy."
|
|
64
|
+
},
|
|
65
|
+
"amount_in": {
|
|
66
|
+
"type": "integer",
|
|
67
|
+
"description": "Amount of token_in to sell in base units."
|
|
68
|
+
},
|
|
69
|
+
"fee": {
|
|
70
|
+
"type": "integer",
|
|
71
|
+
"description": "Pool fee tier (e.g. 3000 for 0.3%).",
|
|
72
|
+
"default": 3000
|
|
73
|
+
},
|
|
74
|
+
"slippage_tolerance": {
|
|
75
|
+
"type": "number",
|
|
76
|
+
"description": "Maximum allowed price slippage (e.g., 0.01 for 1%).",
|
|
77
|
+
"default": 0.01
|
|
78
|
+
},
|
|
79
|
+
"notional_usd": {
|
|
80
|
+
"type": "number",
|
|
81
|
+
"description": "Estimated USD value of the trade for policy cap verification."
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"required": ["token_in", "token_out", "amount_in"]
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
def get_openai_tool_definitions():
|
|
90
|
+
"""Returns schemas wrapped in OpenAI function tool format."""
|
|
91
|
+
return [
|
|
92
|
+
{
|
|
93
|
+
"type": "function",
|
|
94
|
+
"function": schema
|
|
95
|
+
}
|
|
96
|
+
for schema in BOTCHAIN_TOOL_SCHEMAS
|
|
97
|
+
]
|