Skip to content

Commit a41112d

Browse files
authored
add engine cloud service (#35)
* Add EngineCloud service for cloud-based engine operations * add google_adk to __init__.py * add EngineCloud support - fix bugs
1 parent 9fe000a commit a41112d

File tree

8 files changed

+324
-15
lines changed

8 files changed

+324
-15
lines changed

README.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ _AI Agents with Onchain Intelligence_
44

55
## 📖 Overview
66

7-
thirdweb AI is thirdweb's comprehensive toolkit for blockchain data analysis, wallet management, and AI agent interaction with blockchains. It simplifies complex blockchain operations into four core components: Insight for data analysis, Engine for wallet and contract operations, Storage for decentralized file management, and Nebula for natural language-powered blockchain interactions.
7+
thirdweb AI is thirdweb's comprehensive toolkit for blockchain data analysis, wallet management, and AI agent interaction with blockchains. It simplifies complex blockchain operations into five core components: Insight for data analysis, Engine for wallet and contract operations, EngineCloud for cloud-based engine operations, Storage for decentralized file management, and Nebula for natural language-powered blockchain interactions.
88

99
## 🌐 Features
1010

@@ -23,6 +23,13 @@ Core blockchain interaction capabilities:
2323
- **Read**: Read operations for smart contracts and blockchain data
2424
- **Write**: Transaction creation and contract interaction
2525

26+
### EngineCloud
27+
Cloud-based engine operations with advanced capabilities:
28+
- **Server Wallets**: Create and manage server wallets with KMS integration
29+
- **Contract Interaction**: Read from and write to smart contracts
30+
- **Transaction Management**: Send transactions and query transaction history
31+
- **Balance Queries**: Check native token balances on various chains
32+
2633
### Storage
2734
Decentralized storage capabilities:
2835
- **Upload**: Upload files, directories, and JSON data to IPFS
@@ -73,12 +80,13 @@ See the list of [supported framework and installation guides](python/thirdweb-ai
7380
#### Basic Usage
7481

7582
```python
76-
from thirdweb_ai import Engine, Insight, Nebula, Storage, Tool
83+
from thirdweb_ai import Engine, EngineCloud, Insight, Nebula, Storage, Tool
7784

7885
# Initialize services
7986
insight = Insight(secret_key=...)
8087
nebula = Nebula(secret_key=...)
8188
engine = Engine(...)
89+
engine_cloud = EngineCloud(secret_key=..., vault_access_token=...) # For cloud-based operations
8290
storage = Storage(secret_key=...)
8391

8492
# Example: Create tools for AI agents
@@ -92,6 +100,7 @@ tools = [
92100
# tools = [
93101
# *insight.get_tools(),
94102
# *engine.get_tools(),
103+
# *engine_cloud.get_tools(),
95104
# *storage.get_tools(),
96105
# ]
97106

@@ -128,7 +137,7 @@ For non-security-related bugs, please use the GitHub issue tracker.
128137

129138
## ⚠️ Important Usage Notes
130139

131-
When using Nebula, do not combine it with other tools (Insight, Engine, Storage) in the same agent implementation as Nebula already calls these tools in the background. Using them together can lead to compatibility issues and unexpected behavior.
140+
When using Nebula, do not combine it with other tools (Insight, Engine, EngineCloud, Storage) in the same agent implementation as Nebula already calls these tools in the background. Using them together can lead to compatibility issues and unexpected behavior.
132141

133142
## 📦 Publishing Workflow
134143

@@ -149,4 +158,4 @@ To publish a new version of thirdweb AI packages:
149158

150159
## 📝 License
151160

152-
thirdweb AI is licensed under the Apache-2.0 License. See the [LICENSE](./LICENSE) file for details.
161+
thirdweb AI is licensed under the Apache-2.0 License. See the [LICENSE](./LICENSE) file for details.

python/thirdweb-ai/README.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,13 @@ pip install "thirdweb-ai[pydantic-ai]" # For Pydantic AI
4949
thirdweb-ai provides a set of tools that can be integrated with various AI agent frameworks. Here's a basic example:
5050

5151
```python
52-
from thirdweb_ai import Engine, Insight, Nebula, Storage, Tool
52+
from thirdweb_ai import Engine, EngineCloud, Insight, Nebula, Storage, Tool
5353

5454
# Initialize thirdweb services
5555
insight = Insight(secret_key=...)
5656
nebula = Nebula(secret_key=...)
5757
engine = Engine(secret_key=...)
58+
engine_cloud = EngineCloud(secret_key=..., vault_access_token=...) # vault_access_token required for server wallet operations
5859
storage = Storage(secret_key=...)
5960

6061
# Get available tools
@@ -64,11 +65,22 @@ tools = [
6465
*insight.get_tools(),
6566
*nebula.get_tools(),
6667
*engine.get_tools(),
68+
*engine_cloud.get_tools(), # Use EngineCloud for cloud-based engine operations
6769
*storage.get_tools(),
6870
# Or pick an individual tool from the services
6971
]
7072
```
7173

74+
### Available Services
75+
76+
thirdweb-ai provides several core services:
77+
78+
- **Engine**: Deploy contracts, manage wallets, execute transactions, and interact with smart contracts
79+
- **EngineCloud**: Cloud-based engine operations for creating server wallets (with KMS integration), executing contract calls, and querying transaction history
80+
- **Insight**: Query blockchain data, retrieve transactions, events, token balances, and contract metadata
81+
- **Nebula**: Advanced onchain analytics and data processing
82+
- **Storage**: Store and retrieve data using IPFS and other decentralized storage solutions
83+
7284
## Framework Integration
7385

7486
thirdweb-ai can be easily integrated with various AI agent frameworks. Below are a few examples of how to integrate with some of the supported frameworks:
@@ -191,4 +203,4 @@ uv run ruff check .
191203

192204
# Run type checking with pyright
193205
uv run pyright
194-
```
206+
```
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .google_adk import get_google_adk_tools
2+
3+
__all__ = ["get_google_adk_tools"]

python/thirdweb-ai/src/thirdweb_ai/adapters/google_adk/google_adk.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,29 @@ def _get_declaration(self) -> types.FunctionDeclaration:
4040
Returns:
4141
A FunctionDeclaration for Google ADK
4242
"""
43-
parameters = self.tool.schema["parameters"]
44-
del parameters["additionalProperties"]
43+
# Deep copy the parameters to avoid modifying the original
44+
import copy
45+
parameters = copy.deepcopy(self.tool.schema["parameters"])
46+
47+
if "additionalProperties" in parameters:
48+
del parameters["additionalProperties"]
49+
50+
def remove_additional_properties(obj: dict[str, Any]):
51+
if "additionalProperties" in obj:
52+
del obj["additionalProperties"]
53+
54+
if "items" in obj and isinstance(obj["items"], dict):
55+
remove_additional_properties(obj["items"])
56+
57+
if "properties" in obj and isinstance(obj["properties"], dict):
58+
for prop in obj["properties"].values():
59+
if isinstance(prop, dict):
60+
remove_additional_properties(prop)
61+
62+
if "properties" in parameters:
63+
for prop in parameters["properties"].values():
64+
remove_additional_properties(prop)
65+
4566
return types.FunctionDeclaration(
4667
name=self.name,
4768
description=self.description,
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from thirdweb_ai.services.engine import Engine
2+
from thirdweb_ai.services.engine_cloud import EngineCloud
3+
from thirdweb_ai.services.insight import Insight
4+
from thirdweb_ai.services.nebula import Nebula
5+
from thirdweb_ai.services.service import Service
6+
from thirdweb_ai.services.storage import Storage
7+
8+
__all__ = ["Engine", "EngineCloud", "Insight", "Nebula", "Service", "Storage"]
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
# At the top of the file, add:
2+
from typing import Annotated, Any, Literal, TypedDict
3+
4+
from thirdweb_ai.services.service import Service
5+
from thirdweb_ai.tools.tool import tool
6+
7+
8+
class FilterField(TypedDict):
9+
field: Literal["id", "batchIndex", "from", "signerAddress", "smartAccountAddress", "chainId"]
10+
11+
12+
class FilterValues(TypedDict):
13+
values: list[int]
14+
15+
16+
class FilterOperator(TypedDict):
17+
operator: Literal["AND", "OR"]
18+
19+
20+
FilterCondition = FilterField | FilterValues | FilterOperator
21+
22+
23+
class EngineCloud(Service):
24+
def __init__(
25+
self,
26+
secret_key: str,
27+
vault_access_token: str,
28+
):
29+
super().__init__(base_url="https://engine.thirdweb.com/v1", secret_key=secret_key)
30+
self.vault_access_token = vault_access_token
31+
32+
def _make_headers(self):
33+
headers = super()._make_headers()
34+
if self.vault_access_token:
35+
headers["x-vault-access-token"] = self.vault_access_token
36+
return headers
37+
38+
@tool(
39+
description="Create a new engine server wallet. This is a helper route for creating a new EOA with your KMS provider, provided as a convenient alternative to creating an EOA directly with your KMS provider."
40+
)
41+
def create_server_wallet(
42+
self,
43+
label: Annotated[
44+
str,
45+
"A human-readable label to identify this wallet.",
46+
],
47+
) -> dict[str, Any]:
48+
payload = {"label": label}
49+
return self._post("accounts", payload)
50+
51+
@tool(
52+
description="Call a write function on a contract. This endpoint allows you to execute state-changing functions on smart contracts, with support for various execution strategies."
53+
)
54+
def write_contract(
55+
self,
56+
from_address: Annotated[
57+
str,
58+
"The address of the account to send the transaction from. Can be the address of a smart account or an EOA.",
59+
],
60+
chain_id: Annotated[
61+
int,
62+
"The numeric blockchain network ID where the contract is deployed (e.g., '1' for Ethereum mainnet, '137' for Polygon).",
63+
],
64+
method: Annotated[str, "The name of the contract function to call on the contract."],
65+
params: Annotated[list[Any], "The arguments to pass to the contract function."],
66+
contract_address: Annotated[str, "The address of the smart contract to interact with."],
67+
abi: Annotated[list[dict[str, Any]], "The ABI (Application Binary Interface) of the contract."],
68+
value: Annotated[str, "The amount of native currency to send with the transaction (in wei)."] = "0",
69+
) -> dict[str, Any]:
70+
payload = {
71+
"executionOptions": {
72+
"from": from_address,
73+
"chainId": chain_id,
74+
},
75+
"params": [
76+
{
77+
"method": method,
78+
"params": params,
79+
"contractAddress": contract_address,
80+
"abi": abi,
81+
"value": value,
82+
}
83+
],
84+
}
85+
return self._post("write/contract", payload)
86+
87+
@tool(
88+
description="Send an encoded transaction or a batch of transactions. This endpoint allows you to execute low-level transactions with raw transaction data."
89+
)
90+
def send_transaction(
91+
self,
92+
from_address: Annotated[str, "The address of the account to send the transaction from."],
93+
chain_id: Annotated[
94+
int,
95+
"The numeric blockchain network ID where the transaction will be sent (e.g., '1' for Ethereum mainnet, '137' for Polygon).",
96+
],
97+
to_address: Annotated[
98+
str,
99+
"The recipient address for the transaction.",
100+
],
101+
data: Annotated[
102+
str,
103+
"The encoded transaction data (hexadecimal).",
104+
],
105+
value: Annotated[
106+
str,
107+
"The amount of native currency to send with the transaction (in wei).",
108+
] = "0",
109+
) -> dict[str, Any]:
110+
payload = {
111+
"executionOptions": {
112+
"from": from_address,
113+
"chainId": chain_id,
114+
},
115+
"params": [
116+
{
117+
"to": to_address,
118+
"data": data,
119+
"value": value,
120+
}
121+
],
122+
}
123+
124+
return self._post("write/transaction", payload)
125+
126+
@tool(
127+
description="Call read-only contract functions or batch read using multicall. This is a gas-efficient way to query data from blockchain contracts without modifying state."
128+
)
129+
def read_contract(
130+
self,
131+
multicall_address: Annotated[
132+
str | None,
133+
"Optional multicall contract address for batching multiple calls. Defaults to the default multicall3 address for the chain",
134+
],
135+
chain_id: Annotated[
136+
int,
137+
"The numeric blockchain network ID where the contract is deployed (e.g., '1' for Ethereum mainnet, '137' for Polygon).",
138+
],
139+
from_address: Annotated[str, "EVM address in hex format"],
140+
method: Annotated[str, "The name of the contract function to call."],
141+
params: Annotated[list[Any], "The arguments to pass to the contract function."],
142+
contract_address: Annotated[
143+
str,
144+
"The address of the smart contract to read from.",
145+
],
146+
abi: Annotated[list[dict[str, Any]], "The ABI (Application Binary Interface) for the contract."],
147+
) -> dict[str, Any]:
148+
payload = {
149+
"readOptions": {
150+
"multicallAddress": multicall_address,
151+
"chainId": chain_id,
152+
"from": from_address,
153+
},
154+
"params": [
155+
{
156+
"method": method,
157+
"params": params,
158+
"contractAddress": contract_address,
159+
"abi": abi,
160+
}
161+
],
162+
}
163+
164+
return self._post("read/contract", payload)
165+
166+
@tool(
167+
description="Fetch the native cryptocurrency balance (e.g., ETH, MATIC) for a given address on a specific blockchain."
168+
)
169+
def get_native_balance(
170+
self,
171+
chain_id: Annotated[
172+
int,
173+
"The numeric blockchain network ID to query (e.g., '1' for Ethereum mainnet, '137' for Polygon).",
174+
],
175+
address: Annotated[str, "The wallet address to check the balance for."],
176+
) -> dict[str, Any]:
177+
payload = {
178+
"chainId": chain_id,
179+
"address": address,
180+
}
181+
182+
return self._post("read/balance", payload)
183+
184+
@tool(
185+
description="Search for transactions with flexible filtering options. Retrieve transaction history with customizable filters for addresses, chains, statuses, and more."
186+
)
187+
def search_transactions(
188+
self,
189+
filters: Annotated[FilterField, "List of filter conditions to apply"],
190+
filters_operation: Annotated[
191+
Literal["AND", "OR"],
192+
"Logical operation to apply between filters. 'AND' means all conditions must match, 'OR' means any condition can match.",
193+
] = "AND",
194+
page: Annotated[
195+
int | None,
196+
"Page number for paginated results, starting from 1.",
197+
] = 1,
198+
limit: Annotated[
199+
int | None,
200+
"Maximum number of transactions to return per page (1-100).",
201+
] = 20,
202+
sort_by: Annotated[
203+
Literal["createdAt", "confirmedAt"],
204+
"Field to sort results by.",
205+
] = "createdAt",
206+
sort_direction: Annotated[
207+
Literal["asc", "desc"],
208+
"Sort direction ('asc' for ascending, 'desc' for descending).",
209+
] = "desc",
210+
) -> dict[str, Any]:
211+
payload = {
212+
"filters": filters,
213+
"filtersOperation": filters_operation,
214+
"page": page,
215+
"limit": limit,
216+
"sortBy": sort_by,
217+
"sortDirection": sort_direction,
218+
}
219+
220+
return self._post("transactions/search", payload)

0 commit comments

Comments
 (0)