@agentlayer.tech/wallet 0.1.65 → 0.1.67
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.
- package/.openclaw/extensions/agent-wallet/index.ts +82 -0
- package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +7 -1
- package/.openclaw/extensions/agent-wallet/package.json +1 -1
- package/VERSION +1 -1
- package/agent-wallet/README.md +6 -0
- package/agent-wallet/agent_wallet/__init__.py +1 -1
- package/agent-wallet/agent_wallet/openclaw_adapter.py +485 -4
- package/agent-wallet/agent_wallet/providers/kamino.py +169 -3
- package/agent-wallet/agent_wallet/providers/x402.py +229 -148
- package/agent-wallet/agent_wallet/transaction_policy.py +60 -0
- package/agent-wallet/agent_wallet/wallet_layer/base.py +124 -0
- package/agent-wallet/agent_wallet/wallet_layer/solana.py +563 -0
- package/agent-wallet/agent_wallet/wallet_layer/wdk_evm.py +16 -0
- package/agent-wallet/openclaw.plugin.json +2 -2
- package/agent-wallet/pyproject.toml +1 -1
- package/agent-wallet/scripts/install_openclaw_local_config.py +6 -0
- package/agent-wallet/skills/wallet-operator/SKILL.md +6 -4
- package/claude-code/plugins/agent-wallet/.claude-plugin/plugin.json +1 -1
- package/claude-code/plugins/agent-wallet/skills/wallet-operator/SKILL.md +1 -1
- package/codex/plugins/agent-wallet/.codex-plugin/plugin.json +1 -1
- package/codex/plugins/agent-wallet/server.py +52 -3
- package/codex/plugins/agent-wallet/skills/wallet-operator/SKILL.md +1 -1
- package/hermes/plugins/agent_wallet/plugin.yaml +1 -1
- package/install-from-github.sh +6 -0
- package/package.json +1 -1
- package/wdk-btc-wallet/package.json +1 -1
- package/wdk-evm-wallet/package.json +1 -1
- package/wdk-evm-wallet/src/wdk_evm_wallet.js +126 -19
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import json
|
|
6
|
+
import time
|
|
5
7
|
from typing import Any
|
|
6
8
|
|
|
7
9
|
from agent_wallet.config import normalize_solana_network, settings
|
|
@@ -9,6 +11,41 @@ from agent_wallet.exceptions import ProviderError
|
|
|
9
11
|
from agent_wallet.http_client import get_client
|
|
10
12
|
|
|
11
13
|
KAMINO_BUILD_TIMEOUT_SECONDS = 20.0
|
|
14
|
+
# Discovery data only (market/vault catalogs and rate metrics) — never
|
|
15
|
+
# user-specific data such as obligations, positions, rewards, or portfolios.
|
|
16
|
+
# Catalogs change when Kamino ships a market/vault (days); rate metrics drift
|
|
17
|
+
# slower than the API's own aggregation window over these TTLs. The cache is
|
|
18
|
+
# long-lived only inside the resident read worker; cold CLI runs start empty.
|
|
19
|
+
MARKETS_CACHE_TTL_SECONDS = 600.0
|
|
20
|
+
RESERVES_CACHE_TTL_SECONDS = 180.0
|
|
21
|
+
EARN_VAULTS_CACHE_TTL_SECONDS = 600.0
|
|
22
|
+
EARN_VAULT_METRICS_CACHE_TTL_SECONDS = 180.0
|
|
23
|
+
_DISCOVERY_CACHE_MAX_ENTRIES = 128
|
|
24
|
+
_discovery_cache: dict[str, tuple[float, str]] = {}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _discovery_cache_get(cache_key: str) -> Any | None:
|
|
28
|
+
entry = _discovery_cache.get(cache_key)
|
|
29
|
+
if entry is None:
|
|
30
|
+
return None
|
|
31
|
+
expires_at, payload_text = entry
|
|
32
|
+
if expires_at <= time.monotonic():
|
|
33
|
+
_discovery_cache.pop(cache_key, None)
|
|
34
|
+
return None
|
|
35
|
+
return json.loads(payload_text)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _discovery_cache_set(cache_key: str, payload: Any, ttl_seconds: float) -> None:
|
|
39
|
+
try:
|
|
40
|
+
payload_text = json.dumps(payload)
|
|
41
|
+
except (TypeError, ValueError):
|
|
42
|
+
return
|
|
43
|
+
if len(_discovery_cache) >= _DISCOVERY_CACHE_MAX_ENTRIES:
|
|
44
|
+
for stale_key in sorted(_discovery_cache, key=lambda key: _discovery_cache[key][0])[
|
|
45
|
+
: len(_discovery_cache) - _DISCOVERY_CACHE_MAX_ENTRIES + 1
|
|
46
|
+
]:
|
|
47
|
+
_discovery_cache.pop(stale_key, None)
|
|
48
|
+
_discovery_cache[cache_key] = (time.monotonic() + ttl_seconds, payload_text)
|
|
12
49
|
|
|
13
50
|
|
|
14
51
|
def _normalized_api_base() -> str:
|
|
@@ -48,6 +85,10 @@ def _env_name(network: str) -> str:
|
|
|
48
85
|
|
|
49
86
|
async def fetch_lend_markets() -> dict[str, Any]:
|
|
50
87
|
"""Fetch Kamino lending markets for the configured program id."""
|
|
88
|
+
cache_key = f"markets:{settings.kamino_program_id}"
|
|
89
|
+
cached = _discovery_cache_get(cache_key)
|
|
90
|
+
if cached is not None:
|
|
91
|
+
return cached
|
|
51
92
|
client = get_client()
|
|
52
93
|
response = await client.get(
|
|
53
94
|
f"{_normalized_api_base()}/v2/kamino-market",
|
|
@@ -55,11 +96,85 @@ async def fetch_lend_markets() -> dict[str, Any]:
|
|
|
55
96
|
)
|
|
56
97
|
if response.status_code != 200:
|
|
57
98
|
raise ProviderError("kamino", f"HTTP {response.status_code}: {response.text[:300]}")
|
|
58
|
-
|
|
99
|
+
result = _normalize_named_list_response(
|
|
59
100
|
response.json(),
|
|
60
101
|
key="markets",
|
|
61
102
|
provider_name="kamino",
|
|
62
103
|
)
|
|
104
|
+
_discovery_cache_set(cache_key, result, MARKETS_CACHE_TTL_SECONDS)
|
|
105
|
+
return result
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
async def fetch_portfolio(*, user: str) -> dict[str, Any]:
|
|
109
|
+
"""Fetch the unified Kamino product portfolio for one wallet."""
|
|
110
|
+
client = get_client()
|
|
111
|
+
response = await client.get(f"{_normalized_api_base()}/portfolio/{user}")
|
|
112
|
+
if response.status_code != 200:
|
|
113
|
+
raise ProviderError("kamino", f"HTTP {response.status_code}: {response.text[:300]}")
|
|
114
|
+
data = response.json()
|
|
115
|
+
if not isinstance(data, dict):
|
|
116
|
+
raise ProviderError("kamino", "Unexpected portfolio response from Kamino.")
|
|
117
|
+
return data
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
async def fetch_earn_vaults() -> dict[str, Any]:
|
|
121
|
+
"""Fetch the list of Kamino Earn vaults."""
|
|
122
|
+
cache_key = "earn_vaults"
|
|
123
|
+
cached = _discovery_cache_get(cache_key)
|
|
124
|
+
if cached is not None:
|
|
125
|
+
return cached
|
|
126
|
+
client = get_client()
|
|
127
|
+
response = await client.get(f"{_normalized_api_base()}/kvaults/vaults")
|
|
128
|
+
if response.status_code != 200:
|
|
129
|
+
raise ProviderError("kamino", f"HTTP {response.status_code}: {response.text[:300]}")
|
|
130
|
+
result = _normalize_named_list_response(
|
|
131
|
+
response.json(),
|
|
132
|
+
key="vaults",
|
|
133
|
+
provider_name="kamino",
|
|
134
|
+
)
|
|
135
|
+
_discovery_cache_set(cache_key, result, EARN_VAULTS_CACHE_TTL_SECONDS)
|
|
136
|
+
return result
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
async def fetch_earn_vault_metrics(*, vault: str, network: str) -> dict[str, Any]:
|
|
140
|
+
"""Fetch APY/TVL metrics for one Kamino Earn vault."""
|
|
141
|
+
env = _env_name(network)
|
|
142
|
+
cache_key = f"earn_vault_metrics:{vault}:{env}"
|
|
143
|
+
cached = _discovery_cache_get(cache_key)
|
|
144
|
+
if cached is not None:
|
|
145
|
+
return cached
|
|
146
|
+
client = get_client()
|
|
147
|
+
response = await client.get(
|
|
148
|
+
f"{_normalized_api_base()}/kvaults/{vault}/metrics",
|
|
149
|
+
params={"env": env},
|
|
150
|
+
)
|
|
151
|
+
if response.status_code != 200:
|
|
152
|
+
raise ProviderError("kamino", f"HTTP {response.status_code}: {response.text[:300]}")
|
|
153
|
+
data = response.json()
|
|
154
|
+
if not isinstance(data, dict):
|
|
155
|
+
raise ProviderError("kamino", "Unexpected vault metrics response from Kamino.")
|
|
156
|
+
_discovery_cache_set(cache_key, data, EARN_VAULT_METRICS_CACHE_TTL_SECONDS)
|
|
157
|
+
return data
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
async def fetch_earn_user_positions(
|
|
161
|
+
*,
|
|
162
|
+
user: str,
|
|
163
|
+
network: str,
|
|
164
|
+
) -> dict[str, Any]:
|
|
165
|
+
"""Fetch all Kamino Earn vault positions for a wallet."""
|
|
166
|
+
client = get_client()
|
|
167
|
+
response = await client.get(
|
|
168
|
+
f"{_normalized_api_base()}/kvaults/users/{user}/positions",
|
|
169
|
+
params={"env": _env_name(network)},
|
|
170
|
+
)
|
|
171
|
+
if response.status_code != 200:
|
|
172
|
+
raise ProviderError("kamino", f"HTTP {response.status_code}: {response.text[:300]}")
|
|
173
|
+
return _normalize_named_list_response(
|
|
174
|
+
response.json(),
|
|
175
|
+
key="positions",
|
|
176
|
+
provider_name="kamino",
|
|
177
|
+
)
|
|
63
178
|
|
|
64
179
|
|
|
65
180
|
async def fetch_lend_market_reserves(
|
|
@@ -68,18 +183,25 @@ async def fetch_lend_market_reserves(
|
|
|
68
183
|
network: str,
|
|
69
184
|
) -> dict[str, Any]:
|
|
70
185
|
"""Fetch reserve metrics for one Kamino lending market."""
|
|
186
|
+
env = _env_name(network)
|
|
187
|
+
cache_key = f"reserves:{market}:{env}"
|
|
188
|
+
cached = _discovery_cache_get(cache_key)
|
|
189
|
+
if cached is not None:
|
|
190
|
+
return cached
|
|
71
191
|
client = get_client()
|
|
72
192
|
response = await client.get(
|
|
73
193
|
f"{_normalized_api_base()}/kamino-market/{market}/reserves/metrics",
|
|
74
|
-
params={"env":
|
|
194
|
+
params={"env": env},
|
|
75
195
|
)
|
|
76
196
|
if response.status_code != 200:
|
|
77
197
|
raise ProviderError("kamino", f"HTTP {response.status_code}: {response.text[:300]}")
|
|
78
|
-
|
|
198
|
+
result = _normalize_named_list_response(
|
|
79
199
|
response.json(),
|
|
80
200
|
key="reserves",
|
|
81
201
|
provider_name="kamino",
|
|
82
202
|
)
|
|
203
|
+
_discovery_cache_set(cache_key, result, RESERVES_CACHE_TTL_SECONDS)
|
|
204
|
+
return result
|
|
83
205
|
|
|
84
206
|
|
|
85
207
|
async def fetch_lend_user_obligations(
|
|
@@ -236,3 +358,47 @@ async def build_lend_repay_transaction(
|
|
|
236
358
|
if response.status_code != 200:
|
|
237
359
|
raise ProviderError("kamino", f"HTTP {response.status_code}: {response.text[:300]}")
|
|
238
360
|
return _normalized_tx_response(response.json(), provider_name="kamino")
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
async def build_earn_deposit_transaction(
|
|
364
|
+
*,
|
|
365
|
+
wallet: str,
|
|
366
|
+
kvault: str,
|
|
367
|
+
amount_ui: str,
|
|
368
|
+
) -> dict[str, Any]:
|
|
369
|
+
"""Build an unsigned Kamino Earn deposit transaction."""
|
|
370
|
+
client = get_client()
|
|
371
|
+
response = await client.post(
|
|
372
|
+
f"{_normalized_api_base()}/ktx/kvault/deposit",
|
|
373
|
+
json={
|
|
374
|
+
"wallet": wallet,
|
|
375
|
+
"kvault": kvault,
|
|
376
|
+
"amount": amount_ui,
|
|
377
|
+
},
|
|
378
|
+
timeout=KAMINO_BUILD_TIMEOUT_SECONDS,
|
|
379
|
+
)
|
|
380
|
+
if response.status_code != 200:
|
|
381
|
+
raise ProviderError("kamino", f"HTTP {response.status_code}: {response.text[:300]}")
|
|
382
|
+
return _normalized_tx_response(response.json(), provider_name="kamino")
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
async def build_earn_withdraw_transaction(
|
|
386
|
+
*,
|
|
387
|
+
wallet: str,
|
|
388
|
+
kvault: str,
|
|
389
|
+
amount_ui: str,
|
|
390
|
+
) -> dict[str, Any]:
|
|
391
|
+
"""Build an unsigned Kamino Earn withdraw transaction."""
|
|
392
|
+
client = get_client()
|
|
393
|
+
response = await client.post(
|
|
394
|
+
f"{_normalized_api_base()}/ktx/kvault/withdraw",
|
|
395
|
+
json={
|
|
396
|
+
"wallet": wallet,
|
|
397
|
+
"kvault": kvault,
|
|
398
|
+
"amount": amount_ui,
|
|
399
|
+
},
|
|
400
|
+
timeout=KAMINO_BUILD_TIMEOUT_SECONDS,
|
|
401
|
+
)
|
|
402
|
+
if response.status_code != 200:
|
|
403
|
+
raise ProviderError("kamino", f"HTTP {response.status_code}: {response.text[:300]}")
|
|
404
|
+
return _normalized_tx_response(response.json(), provider_name="kamino")
|