@agentlayer.tech/wallet 0.1.16 → 0.1.18

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.
@@ -7,7 +7,9 @@ import json
7
7
  from typing import Any
8
8
 
9
9
  from agent_wallet.approval import inspect_approval_token, verify_approval_token
10
+ from agent_wallet.exceptions import ProviderError
10
11
  from agent_wallet.models import AgentToolResult, AgentToolSpec
12
+ from agent_wallet.providers import x402
11
13
  from agent_wallet.wallet_layer.base import AgentWalletBackend, WalletBackendError
12
14
 
13
15
 
@@ -75,7 +77,9 @@ class OpenClawWalletAdapter:
75
77
  if chain == "bitcoin":
76
78
  return normalized == "bitcoin"
77
79
  if chain == "evm":
78
- return normalized in {"ethereum", "base"}
80
+ return normalized in {"ethereum", "base", "eip155:1", "eip155:8453"}
81
+ if chain == "solana":
82
+ return normalized in {"mainnet", "solana:5eykt4usfv8p8njdtrepy1vzkqzkvdp"}
79
83
  return normalized == "mainnet"
80
84
 
81
85
  def _is_mainnet(self) -> bool:
@@ -168,6 +172,115 @@ class OpenClawWalletAdapter:
168
172
  "Prepare mode requires explicit user intent confirmation."
169
173
  )
170
174
 
175
+ def _x402_tool_specs(self) -> list[AgentToolSpec]:
176
+ return [
177
+ AgentToolSpec(
178
+ name="x402_search_services",
179
+ description=(
180
+ "Search x402-paid services through CDP Bazaar or Agentic Market. "
181
+ "This is read-only discovery and does not spend funds."
182
+ ),
183
+ input_schema={
184
+ "type": "object",
185
+ "properties": {
186
+ "query": {"type": "string"},
187
+ "discovery_provider": {
188
+ "type": "string",
189
+ "enum": ["auto", "cdp_bazaar", "agentic_market"],
190
+ },
191
+ "network": {"type": "string"},
192
+ "asset": {"type": "string"},
193
+ "scheme": {"type": "string"},
194
+ "max_usd_price": {"type": "string"},
195
+ "limit": {"type": "integer"},
196
+ },
197
+ "additionalProperties": False,
198
+ },
199
+ read_only=True,
200
+ risk_level="low",
201
+ ),
202
+ AgentToolSpec(
203
+ name="x402_get_service_details",
204
+ description=(
205
+ "Resolve one x402 service or resource into a normalized details payload. "
206
+ "Use a resource URL for CDP Bazaar or a domain/service id for Agentic Market."
207
+ ),
208
+ input_schema={
209
+ "type": "object",
210
+ "properties": {
211
+ "reference": {"type": "string"},
212
+ "discovery_provider": {
213
+ "type": "string",
214
+ "enum": ["auto", "cdp_bazaar", "agentic_market"],
215
+ },
216
+ },
217
+ "required": ["reference"],
218
+ "additionalProperties": False,
219
+ },
220
+ read_only=True,
221
+ risk_level="low",
222
+ ),
223
+ AgentToolSpec(
224
+ name="x402_preview_request",
225
+ description=(
226
+ "Make an unpaid HTTP request to an x402 endpoint, detect HTTP 402, parse "
227
+ "PAYMENT-REQUIRED, and summarize the payment options. This does not pay or execute."
228
+ ),
229
+ input_schema={
230
+ "type": "object",
231
+ "properties": {
232
+ "url": {"type": "string"},
233
+ "method": {"type": "string"},
234
+ "headers": {"type": "object", "additionalProperties": {"type": "string"}},
235
+ "query": {"type": "object", "additionalProperties": True},
236
+ "json_body": {},
237
+ "text_body": {"type": "string"},
238
+ },
239
+ "required": ["url"],
240
+ "additionalProperties": False,
241
+ },
242
+ read_only=True,
243
+ risk_level="low",
244
+ ),
245
+ AgentToolSpec(
246
+ name="x402_pay_request",
247
+ description=(
248
+ "Prepare or execute an x402 paid request using the active wallet backend. "
249
+ "This milestone executes the Solana exact buyer flow and keeps EVM as prepare-only."
250
+ ),
251
+ input_schema={
252
+ "type": "object",
253
+ "properties": {
254
+ "url": {"type": "string"},
255
+ "method": {"type": "string"},
256
+ "headers": {"type": "object", "additionalProperties": {"type": "string"}},
257
+ "query": {"type": "object", "additionalProperties": True},
258
+ "json_body": {},
259
+ "text_body": {"type": "string"},
260
+ "mode": {
261
+ "type": "string",
262
+ "enum": ["prepare", "execute"],
263
+ "description": "prepare validates the payment plan; execute sends the paid retry.",
264
+ },
265
+ "purpose": {"type": "string"},
266
+ "user_intent": {
267
+ "type": "boolean",
268
+ "description": "Must be true for prepare mode.",
269
+ },
270
+ "approval_token": {
271
+ "type": "string",
272
+ "description": "Required for execute mode and must be issued against the exact x402 payment summary.",
273
+ },
274
+ },
275
+ "required": ["url", "mode", "purpose"],
276
+ "additionalProperties": False,
277
+ },
278
+ read_only=False,
279
+ requires_explicit_user_intent=True,
280
+ risk_level="high",
281
+ ),
282
+ ]
283
+
171
284
  def _require_execute_approval(
172
285
  self,
173
286
  *,
@@ -550,6 +663,23 @@ class OpenClawWalletAdapter:
550
663
  "btc_transfer_fingerprint": btc_fingerprint,
551
664
  }
552
665
 
666
+ if asset_type == "x402-request":
667
+ return {
668
+ "operation": action_label,
669
+ "network": str(payload.get("network") or getattr(self.backend, "network", "unknown")),
670
+ "wallet": payload.get("wallet"),
671
+ "request_url": payload.get("request_url"),
672
+ "method": payload.get("method"),
673
+ "request_fingerprint": payload.get("request_fingerprint"),
674
+ "body_hash": payload.get("body_hash"),
675
+ "x402_network": payload.get("x402_network"),
676
+ "x402_scheme": payload.get("x402_scheme"),
677
+ "x402_asset": payload.get("x402_asset"),
678
+ "x402_amount": payload.get("x402_amount"),
679
+ "x402_amount_display": payload.get("x402_amount_display"),
680
+ "x402_pay_to": payload.get("x402_pay_to"),
681
+ }
682
+
553
683
  if asset_type == "evm-native-transfer":
554
684
  evm_binding = {
555
685
  "recipient": payload.get("recipient"),
@@ -1956,7 +2086,7 @@ class OpenClawWalletAdapter:
1956
2086
  AgentToolSpec(
1957
2087
  name="flash_trade_open_position",
1958
2088
  description=(
1959
- "Preview, prepare, or execute a Flash Trade same-collateral perpetual open on Solana mainnet."
2089
+ "Preview, prepare, or execute a Flash Trade perpetual open on Solana mainnet using a supported Flash collateral."
1960
2090
  ),
1961
2091
  input_schema={
1962
2092
  "type": "object",
@@ -1971,7 +2101,7 @@ class OpenClawWalletAdapter:
1971
2101
  },
1972
2102
  "collateral_symbol": {
1973
2103
  "type": "string",
1974
- "description": "Collateral symbol. Phase 2 requires the same symbol as market_symbol.",
2104
+ "description": "Flash collateral symbol, for example SOL for SOL longs or USDC for SOL shorts.",
1975
2105
  },
1976
2106
  "collateral_amount_raw": {
1977
2107
  "type": "string",
@@ -2023,7 +2153,7 @@ class OpenClawWalletAdapter:
2023
2153
  AgentToolSpec(
2024
2154
  name="flash_trade_close_position",
2025
2155
  description=(
2026
- "Preview, prepare, or execute a Flash Trade same-collateral perpetual close on Solana mainnet."
2156
+ "Preview, prepare, or execute a Flash Trade perpetual close on Solana mainnet."
2027
2157
  ),
2028
2158
  input_schema={
2029
2159
  "type": "object",
@@ -3038,6 +3168,7 @@ class OpenClawWalletAdapter:
3038
3168
  )
3039
3169
  )
3040
3170
 
3171
+ tools.extend(self._x402_tool_specs())
3041
3172
  return [tool for tool in tools if tool.name not in TEMPORARILY_DISABLED_TOOLS]
3042
3173
 
3043
3174
  def get_runtime_instructions(self) -> str:
@@ -3054,6 +3185,169 @@ class OpenClawWalletAdapter:
3054
3185
  f"{tool_name} is temporarily disabled. The implementation remains in the repo but this tool is currently turned off."
3055
3186
  )
3056
3187
 
3188
+ if tool_name == "x402_search_services":
3189
+ query = args.get("query")
3190
+ discovery_provider = args.get("discovery_provider", "auto")
3191
+ network = args.get("network")
3192
+ asset = args.get("asset")
3193
+ scheme = args.get("scheme")
3194
+ max_usd_price = args.get("max_usd_price")
3195
+ limit = args.get("limit", 10)
3196
+ for field_name, value in (
3197
+ ("query", query),
3198
+ ("discovery_provider", discovery_provider),
3199
+ ("network", network),
3200
+ ("asset", asset),
3201
+ ("scheme", scheme),
3202
+ ("max_usd_price", max_usd_price),
3203
+ ):
3204
+ if value is not None and not isinstance(value, str):
3205
+ raise WalletBackendError(f"{field_name} must be a string when provided.")
3206
+ if not isinstance(limit, int) or limit <= 0:
3207
+ raise WalletBackendError("limit must be a positive integer.")
3208
+ data = await x402.search_services(
3209
+ query=query,
3210
+ discovery_provider=discovery_provider,
3211
+ network=network,
3212
+ asset=asset,
3213
+ scheme=scheme,
3214
+ max_usd_price=max_usd_price,
3215
+ limit=limit,
3216
+ )
3217
+ return AgentToolResult(tool=tool_name, ok=True, data=data)
3218
+
3219
+ if tool_name == "x402_get_service_details":
3220
+ reference = args.get("reference")
3221
+ discovery_provider = args.get("discovery_provider", "auto")
3222
+ if not isinstance(reference, str) or not reference.strip():
3223
+ raise WalletBackendError("reference is required.")
3224
+ if discovery_provider is not None and not isinstance(discovery_provider, str):
3225
+ raise WalletBackendError("discovery_provider must be a string when provided.")
3226
+ data = await x402.get_service_details(
3227
+ reference=reference.strip(),
3228
+ discovery_provider=discovery_provider,
3229
+ )
3230
+ return AgentToolResult(tool=tool_name, ok=True, data=data)
3231
+
3232
+ if tool_name == "x402_preview_request":
3233
+ url = args.get("url")
3234
+ method = args.get("method", "GET")
3235
+ headers = args.get("headers")
3236
+ query = args.get("query")
3237
+ json_body = args.get("json_body")
3238
+ text_body = args.get("text_body")
3239
+ if not isinstance(url, str) or not url.strip():
3240
+ raise WalletBackendError("url is required.")
3241
+ if method is not None and not isinstance(method, str):
3242
+ raise WalletBackendError("method must be a string when provided.")
3243
+ if headers is not None and not isinstance(headers, dict):
3244
+ raise WalletBackendError("headers must be an object when provided.")
3245
+ if query is not None and not isinstance(query, dict):
3246
+ raise WalletBackendError("query must be an object when provided.")
3247
+ if text_body is not None and not isinstance(text_body, str):
3248
+ raise WalletBackendError("text_body must be a string when provided.")
3249
+ data = await x402.preview_request(
3250
+ backend=active_backend,
3251
+ url=url.strip(),
3252
+ method=method,
3253
+ headers=headers,
3254
+ query=query,
3255
+ json_body=json_body,
3256
+ text_body=text_body,
3257
+ )
3258
+ if data.get("payment_required"):
3259
+ data = self._annotate_sensitive_payload(
3260
+ data,
3261
+ action_label="x402 paid request",
3262
+ mode="preview",
3263
+ )
3264
+ approval_hint = dict(data.get("approval_hint") or {})
3265
+ approval_hint["tool_name"] = "x402_pay_request"
3266
+ data["approval_hint"] = approval_hint
3267
+ return AgentToolResult(tool=tool_name, ok=True, data=data)
3268
+
3269
+ if tool_name == "x402_pay_request":
3270
+ url = args.get("url")
3271
+ method = args.get("method", "GET")
3272
+ headers = args.get("headers")
3273
+ query = args.get("query")
3274
+ json_body = args.get("json_body")
3275
+ text_body = args.get("text_body")
3276
+ mode = str(args.get("mode") or "").strip().lower()
3277
+ purpose = args.get("purpose")
3278
+ user_intent = args.get("user_intent")
3279
+ approval_token = args.get("approval_token")
3280
+ if not isinstance(url, str) or not url.strip():
3281
+ raise WalletBackendError("url is required.")
3282
+ if method is not None and not isinstance(method, str):
3283
+ raise WalletBackendError("method must be a string when provided.")
3284
+ if headers is not None and not isinstance(headers, dict):
3285
+ raise WalletBackendError("headers must be an object when provided.")
3286
+ if query is not None and not isinstance(query, dict):
3287
+ raise WalletBackendError("query must be an object when provided.")
3288
+ if text_body is not None and not isinstance(text_body, str):
3289
+ raise WalletBackendError("text_body must be a string when provided.")
3290
+ if mode not in {"prepare", "execute"}:
3291
+ raise WalletBackendError("mode must be 'prepare' or 'execute'.")
3292
+ if not isinstance(purpose, str) or not purpose.strip():
3293
+ raise WalletBackendError("purpose is required.")
3294
+ if mode == "prepare":
3295
+ self._require_prepare_intent(user_intent)
3296
+ data = await x402.prepare_request(
3297
+ backend=active_backend,
3298
+ url=url.strip(),
3299
+ method=method,
3300
+ headers=headers,
3301
+ query=query,
3302
+ json_body=json_body,
3303
+ text_body=text_body,
3304
+ )
3305
+ data["purpose"] = purpose.strip()
3306
+ data = self._annotate_sensitive_payload(
3307
+ data,
3308
+ action_label="x402 paid request",
3309
+ mode="prepare",
3310
+ )
3311
+ return AgentToolResult(tool=tool_name, ok=True, data=data)
3312
+ preview = await x402.prepare_request(
3313
+ backend=active_backend,
3314
+ url=url.strip(),
3315
+ method=method,
3316
+ headers=headers,
3317
+ query=query,
3318
+ json_body=json_body,
3319
+ text_body=text_body,
3320
+ )
3321
+ preview["purpose"] = purpose.strip()
3322
+ preview = self._annotate_sensitive_payload(
3323
+ preview,
3324
+ action_label="x402 paid request",
3325
+ mode="execute",
3326
+ )
3327
+ self._require_execute_approval(
3328
+ approval_token=approval_token,
3329
+ tool_name=tool_name,
3330
+ summary=preview["confirmation_summary"],
3331
+ action_label="x402 paid request",
3332
+ backend=active_backend,
3333
+ )
3334
+ data = await x402.execute_request(
3335
+ backend=active_backend,
3336
+ url=url.strip(),
3337
+ method=method,
3338
+ headers=headers,
3339
+ query=query,
3340
+ json_body=json_body,
3341
+ text_body=text_body,
3342
+ )
3343
+ data["purpose"] = purpose.strip()
3344
+ data = self._annotate_sensitive_payload(
3345
+ data,
3346
+ action_label="x402 paid request",
3347
+ mode="execute",
3348
+ )
3349
+ return AgentToolResult(tool=tool_name, ok=True, data=data)
3350
+
3057
3351
  if tool_name == "get_wallet_capabilities":
3058
3352
  data = active_backend.get_capabilities().to_dict()
3059
3353
  data["network"] = str(getattr(active_backend, "network", "unknown"))
@@ -6053,4 +6347,12 @@ class OpenClawWalletAdapter:
6053
6347
  error_code=exc.code,
6054
6348
  error_details=exc.details,
6055
6349
  )
6350
+ if isinstance(exc, ProviderError):
6351
+ return AgentToolResult(
6352
+ tool=tool_name,
6353
+ ok=False,
6354
+ error=str(exc),
6355
+ error_code=exc.provider,
6356
+ error_details=exc.details,
6357
+ )
6056
6358
  return AgentToolResult(tool=tool_name, ok=False, error=str(exc))
@@ -127,7 +127,7 @@ async def _call_bridge(payload: dict[str, Any]) -> dict[str, Any]:
127
127
  return decoded
128
128
 
129
129
 
130
- async def preview_open_position_same_collateral(
130
+ async def preview_open_position(
131
131
  *,
132
132
  owner: str,
133
133
  pool_name: str,
@@ -139,7 +139,7 @@ async def preview_open_position_same_collateral(
139
139
  network: str,
140
140
  ) -> dict[str, Any]:
141
141
  payload = {
142
- "action": "preview_open_position_same_collateral",
142
+ "action": "preview_open_position",
143
143
  "owner": owner,
144
144
  "pool_name": pool_name,
145
145
  "market_symbol": market_symbol,
@@ -153,6 +153,29 @@ async def preview_open_position_same_collateral(
153
153
  return _unwrap_bridge_payload(response, operation="Flash open-position preview")
154
154
 
155
155
 
156
+ async def preview_open_position_same_collateral(
157
+ *,
158
+ owner: str,
159
+ pool_name: str,
160
+ market_symbol: str,
161
+ collateral_symbol: str,
162
+ collateral_amount_raw: str,
163
+ leverage: str,
164
+ side: str,
165
+ network: str,
166
+ ) -> dict[str, Any]:
167
+ return await preview_open_position(
168
+ owner=owner,
169
+ pool_name=pool_name,
170
+ market_symbol=market_symbol,
171
+ collateral_symbol=collateral_symbol,
172
+ collateral_amount_raw=collateral_amount_raw,
173
+ leverage=leverage,
174
+ side=side,
175
+ network=network,
176
+ )
177
+
178
+
156
179
  async def get_markets(
157
180
  *,
158
181
  pool_name: str | None,
@@ -205,7 +228,7 @@ async def preview_close_position_same_collateral(
205
228
  return _unwrap_bridge_payload(response, operation="Flash close-position preview")
206
229
 
207
230
 
208
- async def prepare_open_position_same_collateral(
231
+ async def prepare_open_position(
209
232
  *,
210
233
  owner: str,
211
234
  pool_name: str,
@@ -217,7 +240,7 @@ async def prepare_open_position_same_collateral(
217
240
  network: str,
218
241
  ) -> dict[str, Any]:
219
242
  payload = {
220
- "action": "prepare_open_position_same_collateral",
243
+ "action": "prepare_open_position",
221
244
  "owner": owner,
222
245
  "pool_name": pool_name,
223
246
  "market_symbol": market_symbol,
@@ -231,6 +254,29 @@ async def prepare_open_position_same_collateral(
231
254
  return _unwrap_bridge_payload(response, operation="Flash open-position prepare")
232
255
 
233
256
 
257
+ async def prepare_open_position_same_collateral(
258
+ *,
259
+ owner: str,
260
+ pool_name: str,
261
+ market_symbol: str,
262
+ collateral_symbol: str,
263
+ collateral_amount_raw: str,
264
+ leverage: str,
265
+ side: str,
266
+ network: str,
267
+ ) -> dict[str, Any]:
268
+ return await prepare_open_position(
269
+ owner=owner,
270
+ pool_name=pool_name,
271
+ market_symbol=market_symbol,
272
+ collateral_symbol=collateral_symbol,
273
+ collateral_amount_raw=collateral_amount_raw,
274
+ leverage=leverage,
275
+ side=side,
276
+ network=network,
277
+ )
278
+
279
+
234
280
  async def prepare_close_position_same_collateral(
235
281
  *,
236
282
  owner: str,