@agentlayer.tech/wallet 0.1.67 → 0.1.68

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.
@@ -2,7 +2,7 @@
2
2
  "id": "agent-wallet",
3
3
  "name": "Agent Wallet",
4
4
  "description": "Official OpenClaw plugin bridge for the agent-wallet backends, including Solana, local BTC, and local EVM.",
5
- "version": "0.1.67",
5
+ "version": "0.1.68",
6
6
  "contracts": {
7
7
  "tools": [
8
8
  "agentlayer_autonomous_approve",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentlayertech/agent-wallet-plugin",
3
- "version": "0.1.67",
3
+ "version": "0.1.68",
4
4
  "description": "OpenClaw plugin bridge for the AgentLayer wallet runtime.",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN ../../../LICENSE",
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.67
1
+ 0.1.68
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Keep in sync with package.json, pyproject.toml, and the npm installer version.
4
4
  # scripts/check_release_version.mjs enforces this on release.
5
- __version__ = "0.1.67"
5
+ __version__ = "0.1.68"
6
6
 
7
7
  __all__ = [
8
8
  "config",
@@ -7,6 +7,7 @@ import hashlib
7
7
  import json
8
8
  import logging
9
9
  import time
10
+ from types import SimpleNamespace
10
11
  from typing import Any
11
12
  from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
12
13
 
@@ -428,11 +429,16 @@ def _evm_exact_execution_supported(backend: AgentWalletBackend) -> bool:
428
429
 
429
430
 
430
431
  def _evm_payment_requirement_supported(requirement: dict[str, Any]) -> bool:
431
- if _trim(requirement.get("scheme")).lower() != "exact":
432
- return False
432
+ scheme = _trim(requirement.get("scheme")).lower()
433
433
  extra = _extract_requirement_extra(requirement)
434
- transfer_method = _trim(extra.get("assetTransferMethod")).lower()
435
- return transfer_method in {"", "eip3009", "transferwithauthorization"}
434
+ if scheme == "exact":
435
+ transfer_method = _trim(extra.get("assetTransferMethod")).lower()
436
+ return transfer_method in {"", "eip3009", "transferwithauthorization"}
437
+ if scheme == "upto":
438
+ # The upto Permit2 payload needs the facilitator's address up front;
439
+ # without it the SDK signer raises instead of falling back cleanly.
440
+ return bool(_trim(extra.get("facilitatorAddress")))
441
+ return False
436
442
 
437
443
 
438
444
  def _wallet_x402_support_summary(backend: AgentWalletBackend) -> dict[str, Any]:
@@ -478,7 +484,7 @@ def _requirement_compatibility(requirement: dict[str, Any], backend: AgentWallet
478
484
  )
479
485
  elif chain == "evm":
480
486
  planned_execution_supported = (
481
- scheme == "exact"
487
+ scheme in {"exact", "upto"}
482
488
  and network in set(wallet_summary["planned_execution_networks"])
483
489
  and _evm_payment_requirement_supported(requirement)
484
490
  )
@@ -491,10 +497,12 @@ def _requirement_compatibility(requirement: dict[str, Any], backend: AgentWallet
491
497
  reason = (
492
498
  "Executable now through the local Solana exact buyer flow."
493
499
  if chain == "solana"
494
- else "Executable now through the local EVM exact buyer flow."
500
+ else f"Executable now through the local EVM {scheme or 'exact'} buyer flow."
495
501
  )
496
502
  elif chain == "evm" and scheme == "exact" and not _evm_payment_requirement_supported(requirement):
497
503
  reason = "This EVM exact payment requires a transfer method that is not enabled in the current wallet runtime."
504
+ elif chain == "evm" and scheme == "upto" and not _evm_payment_requirement_supported(requirement):
505
+ reason = "This EVM upto payment is missing a facilitatorAddress in its extra data, so it cannot be signed."
498
506
  elif planned_execution_supported and wallet_network_matches:
499
507
  reason = "Wallet network matches, but this backend does not yet expose a supported x402 signer path."
500
508
  elif planned_execution_supported:
@@ -527,11 +535,16 @@ def _select_preferred_requirement(
527
535
  if not candidates:
528
536
  return None
529
537
 
530
- def sort_key(item: dict[str, Any]) -> tuple[int, int]:
538
+ def sort_key(item: dict[str, Any]) -> tuple[int, int, int]:
539
+ # Prefer upto over exact: upto settles for actual usage against a
540
+ # signed cap, which fits metered/usage-priced endpoints better than
541
+ # exact's fixed charge. Ties within a scheme still favor the cheapest
542
+ # declared amount.
543
+ scheme_rank = 0 if _trim(item.get("scheme")).lower() == "upto" else 1
531
544
  amount = _trim(item.get("amount"))
532
545
  if amount.isdigit():
533
- return (0, int(amount))
534
- return (1, 0)
546
+ return (scheme_rank, 0, int(amount))
547
+ return (scheme_rank, 1, 0)
535
548
 
536
549
  return sorted(candidates, key=sort_key)[0]
537
550
 
@@ -728,10 +741,10 @@ def _validate_payment_requirement(
728
741
  )
729
742
 
730
743
  scheme = _trim(selected.get("scheme")).lower()
731
- if scheme != "exact":
744
+ if scheme not in {"exact", "upto"}:
732
745
  raise ProviderError(
733
746
  "x402-validate",
734
- f"Unsupported x402 payment scheme '{scheme or 'unknown'}'. Only 'exact' is supported.",
747
+ f"Unsupported x402 payment scheme '{scheme or 'unknown'}'. Only 'exact' and 'upto' are supported.",
735
748
  details={"request_url": request_url, "selected_payment": selected},
736
749
  )
737
750
 
@@ -846,6 +859,24 @@ def _select_sdk_payment_requirement(
846
859
  )
847
860
 
848
861
 
862
+ # The `upto` Permit2 signature authorizes the (merchant + facilitator) pair to
863
+ # settle any amount in [0, cap] until `max_timeout_seconds` elapses, with no
864
+ # on-chain check beyond the deadline itself. A merchant-declared timeout with
865
+ # no upper bound would extend that facilitator-discretion window indefinitely,
866
+ # so cap it defensively before signing. `exact` amounts are fixed in the
867
+ # signature, so this cap only applies to `upto`.
868
+ MAX_UPTO_DEADLINE_SECONDS = 7 * 24 * 60 * 60 # 7 days
869
+
870
+
871
+ def _cap_upto_deadline(requirement: Any) -> Any:
872
+ if _trim(getattr(requirement, "scheme", "")).lower() != "upto":
873
+ return requirement
874
+ timeout = getattr(requirement, "max_timeout_seconds", None)
875
+ if not isinstance(timeout, int) or timeout <= MAX_UPTO_DEADLINE_SECONDS:
876
+ return requirement
877
+ return requirement.model_copy(update={"max_timeout_seconds": MAX_UPTO_DEADLINE_SECONDS})
878
+
879
+
849
880
  def _build_selected_payment_required_payload(
850
881
  payment_required: Any,
851
882
  *,
@@ -855,6 +886,7 @@ def _build_selected_payment_required_payload(
855
886
  payment_required,
856
887
  selected_payment=selected_payment,
857
888
  )
889
+ selected_requirement = _cap_upto_deadline(selected_requirement)
858
890
  return payment_required.model_copy(update={"accepts": [selected_requirement]})
859
891
 
860
892
 
@@ -904,9 +936,18 @@ def _load_x402_evm_sdk() -> dict[str, Any]:
904
936
  "x402 EVM execution requires EVM support.",
905
937
  details={"hint": 'Install dependencies so `x402[httpx,evm]` is available in the wallet runtime.'},
906
938
  ) from exc
939
+ try:
940
+ from x402.mechanisms.evm.upto import UptoEvmScheme
941
+ except ImportError as exc:
942
+ raise ProviderError(
943
+ "x402-sdk",
944
+ "x402 EVM upto-scheme execution requires the upto mechanism module.",
945
+ details={"hint": 'Install dependencies so `x402[httpx,evm]` is available in the wallet runtime.'},
946
+ ) from exc
907
947
  sdk.update(
908
948
  {
909
949
  "register_exact_evm_client": register_exact_evm_client,
950
+ "UptoEvmScheme": UptoEvmScheme,
910
951
  }
911
952
  )
912
953
  return sdk
@@ -916,6 +957,67 @@ def _load_x402_sdk() -> dict[str, Any]:
916
957
  return _load_x402_common_sdk()
917
958
 
918
959
 
960
+ def _load_x402_siwx_sdk() -> dict[str, Any]:
961
+ try:
962
+ from x402.extensions.sign_in_with_x import (
963
+ SIGN_IN_WITH_X,
964
+ CreateSIWxClientExtensionOptions,
965
+ create_siwx_client_extension,
966
+ )
967
+ except ImportError as exc:
968
+ raise ProviderError(
969
+ "x402-sdk",
970
+ "x402 sign-in-with-x execution requires the SIWx extension module.",
971
+ details={"hint": 'Install dependencies so `x402[httpx,evm,svm]` is available in the wallet runtime.'},
972
+ ) from exc
973
+ return {
974
+ "SIGN_IN_WITH_X": SIGN_IN_WITH_X,
975
+ "CreateSIWxClientExtensionOptions": CreateSIWxClientExtensionOptions,
976
+ "create_siwx_client_extension": create_siwx_client_extension,
977
+ }
978
+
979
+
980
+ async def _maybe_attach_siwx_header(
981
+ *,
982
+ payment_required: Any,
983
+ signer: Any,
984
+ headers: dict[str, str],
985
+ ) -> dict[str, str]:
986
+ """Attach a Sign-In-With-X header when the merchant declares that extension.
987
+
988
+ Some x402 gateways require proving wallet ownership via SIWx alongside the
989
+ payment signature; without it those requests are rejected even with a
990
+ valid payment. Best-effort: any failure to build the SIWx credential falls
991
+ back to paying without it, matching the upstream SDK's own
992
+ on_payment_required hook contract (which swallows exceptions the same way).
993
+ """
994
+ extensions = getattr(payment_required, "extensions", None) or {}
995
+ if not extensions:
996
+ return headers
997
+ try:
998
+ sdk = _load_x402_siwx_sdk()
999
+ except ProviderError:
1000
+ return headers
1001
+ if sdk["SIGN_IN_WITH_X"] not in extensions:
1002
+ return headers
1003
+ extension = sdk["create_siwx_client_extension"](
1004
+ sdk["CreateSIWxClientExtensionOptions"](signers=[signer])
1005
+ )
1006
+ context = SimpleNamespace(payment_required=payment_required)
1007
+ try:
1008
+ result = await extension.transport_hooks.http.on_payment_required(None, context)
1009
+ except Exception as exc:
1010
+ log.warning(
1011
+ "x402 SIWx header build failed; continuing without it",
1012
+ extra={"error_type": type(exc).__name__, "error": str(exc) or None},
1013
+ )
1014
+ return headers
1015
+ siwx_headers = getattr(result, "headers", None) if result is not None else None
1016
+ if not siwx_headers:
1017
+ return headers
1018
+ return {**headers, **siwx_headers}
1019
+
1020
+
919
1021
  def _build_solana_sdk_signer(backend: AgentWalletBackend) -> Any:
920
1022
  signer = getattr(backend, "signer", None)
921
1023
  if signer is None or not hasattr(signer, "export_keypair_bytes"):
@@ -959,15 +1061,30 @@ def _build_evm_sdk_signer(backend: AgentWalletBackend, address: str) -> Any:
959
1061
  "The active EVM backend does not expose an x402 exact typed-data signer.",
960
1062
  )
961
1063
 
1064
+ class _NoEthAccountSentinel:
1065
+ """Truthy placeholder with neither `.sign_message` nor `.address`.
1066
+
1067
+ x402's SIWx sign_evm_message() treats a signer with `.account` unset
1068
+ but both `.sign_message` and `.address` present as an eth_account-style
1069
+ wallet, and calls `.sign_message(SignableMessage)` positionally instead
1070
+ of our keyword-based `sign_message(message=..., account=...)`. Exposing
1071
+ a benign `.account` here short-circuits that fallback so the SDK
1072
+ reaches our intended call path.
1073
+ """
1074
+
962
1075
  class _OpenClawEvmX402Signer:
963
1076
  def __init__(self, wallet_backend: AgentWalletBackend, wallet_address: str):
964
1077
  self._wallet_backend = wallet_backend
965
1078
  self._address = wallet_address
1079
+ self.account = _NoEthAccountSentinel()
966
1080
 
967
1081
  @property
968
1082
  def address(self) -> str:
969
1083
  return self._address
970
1084
 
1085
+ async def sign_message(self, *, message: str, account: Any = None) -> str:
1086
+ return await self._wallet_backend.sign_message(message)
1087
+
971
1088
  def sign_typed_data(
972
1089
  self,
973
1090
  domain: Any,
@@ -1022,9 +1139,10 @@ async def _create_payment_headers(
1022
1139
  "No direct Solana RPC URL is available for the x402 SDK signer path.",
1023
1140
  details={"network": _backend_network(backend)},
1024
1141
  )
1142
+ solana_signer = _build_solana_sdk_signer(backend)
1025
1143
  sdk["register_exact_svm_client"](
1026
1144
  client,
1027
- _build_solana_sdk_signer(backend),
1145
+ solana_signer,
1028
1146
  networks=str(selected_payment["network"]),
1029
1147
  rpc_url=sdk_rpc_url,
1030
1148
  )
@@ -1041,7 +1159,10 @@ async def _create_payment_headers(
1041
1159
  "error": str(exc) or None,
1042
1160
  },
1043
1161
  ) from exc
1044
- return sdk["x402HTTPClientBase"]().encode_payment_signature_header(payment_payload)
1162
+ headers = sdk["x402HTTPClientBase"]().encode_payment_signature_header(payment_payload)
1163
+ return await _maybe_attach_siwx_header(
1164
+ payment_required=payment_required, signer=solana_signer, headers=headers
1165
+ )
1045
1166
 
1046
1167
  if chain == "evm":
1047
1168
  sdk = _load_x402_evm_sdk()
@@ -1054,13 +1175,18 @@ async def _create_payment_headers(
1054
1175
  address = await backend.get_address()
1055
1176
  if not isinstance(address, str) or not address.strip():
1056
1177
  raise ProviderError("x402-evm", "The active EVM backend did not resolve a payer address.")
1057
- sdk["register_exact_evm_client"](
1058
- client,
1059
- _build_evm_sdk_signer(backend, address.strip()),
1060
- networks=str(selected_payment["network"]),
1061
- )
1178
+ evm_signer = _build_evm_sdk_signer(backend, address.strip())
1179
+ network = str(selected_payment["network"])
1180
+ sdk["register_exact_evm_client"](client, evm_signer, networks=network)
1181
+ # `upto` reuses the same typed-data signer as `exact`; registering both
1182
+ # lets the SDK settle whichever scheme the merchant's accepted
1183
+ # requirement (already narrowed to one entry above) turns out to use.
1184
+ client.register(network, sdk["UptoEvmScheme"](evm_signer))
1062
1185
  payment_payload = await client.create_payment_payload(selected_payload)
1063
- return sdk["x402HTTPClientBase"]().encode_payment_signature_header(payment_payload)
1186
+ headers = sdk["x402HTTPClientBase"]().encode_payment_signature_header(payment_payload)
1187
+ return await _maybe_attach_siwx_header(
1188
+ payment_required=payment_required, signer=evm_signer, headers=headers
1189
+ )
1064
1190
 
1065
1191
  raise ProviderError(
1066
1192
  "x402-http",
@@ -568,6 +568,24 @@ class WdkEvmLocalWalletBackend(AgentWalletBackend):
568
568
  except ValueError as exc:
569
569
  raise WalletBackendError("wdk-evm-wallet returned an invalid x402 EVM signature.") from exc
570
570
 
571
+ async def sign_message(self, message: bytes | str) -> str:
572
+ text = message.decode("utf-8") if isinstance(message, bytes) else str(message)
573
+ if not text:
574
+ raise WalletBackendError("message must be a non-empty string.")
575
+ data = await self.client.post(
576
+ "/v1/evm/message/sign",
577
+ {
578
+ "walletId": self.wallet_id,
579
+ "accountIndex": self.account_index,
580
+ "network": self.network,
581
+ "message": text,
582
+ },
583
+ )
584
+ signature = str(data.get("signature") or "").strip()
585
+ if not signature:
586
+ raise WalletBackendError("wdk-evm-wallet did not return a message signature.")
587
+ return signature
588
+
571
589
  async def get_balance(self, address: str | None = None) -> dict[str, Any]:
572
590
  resolved_address = await self.get_address()
573
591
  if address is not None and address.strip() and address.strip() != resolved_address:
@@ -2,7 +2,7 @@
2
2
  "id": "agent-wallet",
3
3
  "name": "Agent Wallet",
4
4
  "description": "Plugin-friendly wallet backend for OpenClaw agents with safe wallet tools and runtime instructions across Solana, local BTC, and local EVM.",
5
- "version": "0.1.67",
5
+ "version": "0.1.68",
6
6
  "skills": ["skills/wallet-operator"],
7
7
  "configSchema": {
8
8
  "type": "object",
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "openclaw-agent-wallet"
7
- version = "0.1.67"
7
+ version = "0.1.68"
8
8
  description = "Plugin-friendly wallet backend for OpenClaw agents"
9
9
  requires-python = ">=3.10"
10
10
  dependencies = [
@@ -16,7 +16,7 @@ dependencies = [
16
16
  "python-dotenv>=1.0.0",
17
17
  "solana>=0.36.0",
18
18
  "solders>=0.27.0",
19
- "x402[httpx,svm,evm]>=2.10.0",
19
+ "x402[httpx,svm,evm,extensions]>=2.10.0",
20
20
  ]
21
21
 
22
22
  [project.optional-dependencies]
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "agent-wallet",
3
3
  "displayName": "Agent Wallet",
4
- "version": "0.1.67",
4
+ "version": "0.1.68",
5
5
  "description": "Claude Code bridge for the existing AgentLayer wallet runtime. Connects to Solana, Bitcoin, and EVM wallets without creating a new one.",
6
6
  "author": {
7
7
  "name": "AgentLayer"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-wallet",
3
- "version": "0.1.67",
3
+ "version": "0.1.68",
4
4
  "description": "Codex plugin bridge for the AgentLayer wallet runtime.",
5
5
  "author": {
6
6
  "name": "AgentLayer"
@@ -1,5 +1,5 @@
1
1
  name: agent-wallet
2
- version: 0.1.67
2
+ version: 0.1.68
3
3
  description: Thin Hermes Agent bridge to the existing AgentLayer/OpenClaw wallet backend
4
4
  provides_tools:
5
5
  - agent_wallet_tools
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentlayer.tech/wallet",
3
- "version": "0.1.67",
3
+ "version": "0.1.68",
4
4
  "description": "NPM installer for the OpenClaw Agent Wallet local runtime.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wdk-btc-wallet",
3
- "version": "0.1.67",
3
+ "version": "0.1.68",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "Separate BTC-only wallet service built on Tether WDK.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wdk-evm-wallet",
3
- "version": "0.1.67",
3
+ "version": "0.1.68",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "Separate EVM wallet service built on Tether WDK.",
@@ -649,6 +649,12 @@ async function handleRequest(request, response) {
649
649
  return sendJson(response, 200, { ok: true, data });
650
650
  }
651
651
 
652
+ if (method === "POST" && url.pathname === "/v1/evm/message/sign") {
653
+ const body = await withResolvedNetwork(await withResolvedSeed(await readJsonBody(request)));
654
+ const data = await service.signPersonalMessage(body);
655
+ return sendJson(response, 200, { ok: true, data });
656
+ }
657
+
652
658
  return notFound(response);
653
659
  } catch (error) {
654
660
  const shaped = toErrorResponse(error, new URL(request.url || "/", "http://localhost").pathname, 400);
@@ -4068,6 +4068,29 @@ export class WdkEvmWalletService {
4068
4068
  });
4069
4069
  }
4070
4070
 
4071
+ async signPersonalMessage({
4072
+ seedPhrase,
4073
+ accountIndex = 0,
4074
+ network,
4075
+ message,
4076
+ }) {
4077
+ return this.#withAccount({ seedPhrase, accountIndex, network }, async (account, runtimeConfig) => {
4078
+ if (typeof message !== "string" || message.length === 0) {
4079
+ throw new Error("message must be a non-empty string.");
4080
+ }
4081
+ const signerAddress = normalizeAddress(await account.getAddress(), "accountAddress");
4082
+ const signature = await account.sign(message);
4083
+ return {
4084
+ network: runtimeConfig.network,
4085
+ chainId: runtimeConfig.chainId,
4086
+ accountIndex,
4087
+ address: signerAddress,
4088
+ signature,
4089
+ source: "wdk-wallet-evm",
4090
+ };
4091
+ });
4092
+ }
4093
+
4071
4094
  #resolveRuntimeConfig(networkOverride) {
4072
4095
  const network = assertValidNetwork(networkOverride) || this.config.network;
4073
4096
  const profile = this.config.networkProfiles?.[network];