@agentlayer.tech/wallet 0.1.17 → 0.1.19
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/AGENTS.md +0 -7
- package/.openclaw/extensions/agent-wallet/README.md +3 -2
- package/.openclaw/extensions/agent-wallet/dist/index.js +105 -7
- package/.openclaw/extensions/agent-wallet/index.ts +105 -7
- package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +5 -1
- package/.openclaw/extensions/agent-wallet/package.json +1 -1
- package/CHANGELOG.md +24 -0
- package/README.md +1 -3
- package/RELEASING.md +5 -15
- package/agent-wallet/README.md +7 -0
- package/agent-wallet/agent_wallet/config.py +11 -0
- package/agent-wallet/agent_wallet/evm_user_wallets.py +310 -2
- package/agent-wallet/agent_wallet/openclaw_adapter.py +303 -1
- package/agent-wallet/agent_wallet/openclaw_runtime.py +10 -41
- package/agent-wallet/agent_wallet/providers/wdk_evm_local.py +52 -0
- package/agent-wallet/agent_wallet/providers/x402.py +1323 -0
- package/agent-wallet/agent_wallet/wallet_layer/wdk_evm.py +30 -0
- package/agent-wallet/pyproject.toml +2 -1
- package/agent-wallet/scripts/build_release_bundle.py +1 -0
- package/agent-wallet/scripts/install_agent_wallet.py +3 -0
- package/agent-wallet/scripts/install_openclaw_local_config.py +25 -49
- package/agent-wallet/scripts/install_openclaw_sealed_keys.py +9 -1
- package/package.json +1 -2
- package/wdk-evm-wallet/src/server.js +6 -0
- package/wdk-evm-wallet/src/wdk_evm_wallet.js +108 -0
- package/.openclaw/extensions/pay-bridge/README.md +0 -38
- package/.openclaw/extensions/pay-bridge/core.mjs +0 -287
- package/.openclaw/extensions/pay-bridge/dist/core.mjs +0 -287
- package/.openclaw/extensions/pay-bridge/dist/index.js +0 -196
- package/.openclaw/extensions/pay-bridge/index.ts +0 -196
- package/.openclaw/extensions/pay-bridge/openclaw.plugin.json +0 -34
- package/.openclaw/extensions/pay-bridge/package.json +0 -49
- package/.openclaw/extensions/pay-bridge/skills/pay-operator/SKILL.md +0 -20
- package/.openclaw/extensions/pay-bridge/smoke_pay_bridge.mjs +0 -38
|
@@ -440,6 +440,36 @@ class WdkEvmLocalWalletBackend(AgentWalletBackend):
|
|
|
440
440
|
self.address = address
|
|
441
441
|
return address
|
|
442
442
|
|
|
443
|
+
def sign_x402_evm_exact_typed_data(
|
|
444
|
+
self,
|
|
445
|
+
*,
|
|
446
|
+
domain: dict[str, Any],
|
|
447
|
+
types: dict[str, Any],
|
|
448
|
+
primary_type: str,
|
|
449
|
+
message: dict[str, Any],
|
|
450
|
+
) -> bytes:
|
|
451
|
+
data = self.client.post_sync(
|
|
452
|
+
"/v1/evm/x402/exact/sign",
|
|
453
|
+
{
|
|
454
|
+
"walletId": self.wallet_id,
|
|
455
|
+
"accountIndex": self.account_index,
|
|
456
|
+
"network": self.network,
|
|
457
|
+
"domain": domain,
|
|
458
|
+
"types": types,
|
|
459
|
+
"primaryType": primary_type,
|
|
460
|
+
"message": message,
|
|
461
|
+
},
|
|
462
|
+
)
|
|
463
|
+
signature = str(data.get("signature") or "").strip()
|
|
464
|
+
if not signature:
|
|
465
|
+
raise WalletBackendError("wdk-evm-wallet did not return an x402 EVM signature.")
|
|
466
|
+
if signature.startswith("0x"):
|
|
467
|
+
signature = signature[2:]
|
|
468
|
+
try:
|
|
469
|
+
return bytes.fromhex(signature)
|
|
470
|
+
except ValueError as exc:
|
|
471
|
+
raise WalletBackendError("wdk-evm-wallet returned an invalid x402 EVM signature.") from exc
|
|
472
|
+
|
|
443
473
|
async def get_balance(self, address: str | None = None) -> dict[str, Any]:
|
|
444
474
|
resolved_address = await self.get_address()
|
|
445
475
|
if address is not None and address.strip() and address.strip() != resolved_address:
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "openclaw-agent-wallet"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.19"
|
|
8
8
|
description = "Plugin-friendly wallet backend for OpenClaw agents"
|
|
9
9
|
requires-python = ">=3.10"
|
|
10
10
|
dependencies = [
|
|
@@ -15,6 +15,7 @@ dependencies = [
|
|
|
15
15
|
"python-dotenv>=1.0.0",
|
|
16
16
|
"solana>=0.36.0",
|
|
17
17
|
"solders>=0.27.0",
|
|
18
|
+
"x402[httpx,svm,evm]>=2.10.0",
|
|
18
19
|
]
|
|
19
20
|
|
|
20
21
|
[project.optional-dependencies]
|
|
@@ -201,6 +201,9 @@ def _ignore_runtime_entries(_directory: str, names: list[str]) -> set[str]:
|
|
|
201
201
|
keep_dist = ".openclaw" in directory.parts and "extensions" in directory.parts
|
|
202
202
|
ignored: set[str] = set()
|
|
203
203
|
for name in names:
|
|
204
|
+
if name == "pay-bridge" and directory.parts[-2:] == (".openclaw", "extensions"):
|
|
205
|
+
ignored.add(name)
|
|
206
|
+
continue
|
|
204
207
|
if name == "dist" and keep_dist:
|
|
205
208
|
continue
|
|
206
209
|
if name in EXCLUDED_RUNTIME_DIR_NAMES:
|
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import argparse
|
|
6
6
|
import json
|
|
7
7
|
import os
|
|
8
|
-
import
|
|
8
|
+
import secrets
|
|
9
9
|
import sys
|
|
10
10
|
from datetime import datetime, timezone
|
|
11
11
|
from pathlib import Path
|
|
@@ -49,13 +49,11 @@ OPTIONAL_TOOLS = [
|
|
|
49
49
|
"flash_trade_close_position",
|
|
50
50
|
]
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"pay_get_service_endpoints",
|
|
58
|
-
"pay_api_request",
|
|
52
|
+
X402_TOOLS = [
|
|
53
|
+
"x402_search_services",
|
|
54
|
+
"x402_get_service_details",
|
|
55
|
+
"x402_preview_request",
|
|
56
|
+
"x402_pay_request",
|
|
59
57
|
]
|
|
60
58
|
|
|
61
59
|
|
|
@@ -97,13 +95,6 @@ def _default_extension_path() -> Path:
|
|
|
97
95
|
return _repo_root() / ".openclaw" / "extensions" / "agent-wallet"
|
|
98
96
|
|
|
99
97
|
|
|
100
|
-
def _default_pay_bridge_extension_path() -> Path:
|
|
101
|
-
runtime_root = _trusted_runtime_root()
|
|
102
|
-
if runtime_root is not None:
|
|
103
|
-
return runtime_root / ".openclaw" / "extensions" / PAY_BRIDGE_PLUGIN_ID
|
|
104
|
-
return _repo_root() / ".openclaw" / "extensions" / PAY_BRIDGE_PLUGIN_ID
|
|
105
|
-
|
|
106
|
-
|
|
107
98
|
def _default_package_root() -> Path:
|
|
108
99
|
runtime_root = _trusted_runtime_root()
|
|
109
100
|
if runtime_root is not None:
|
|
@@ -126,14 +117,6 @@ def _default_python_bin() -> str:
|
|
|
126
117
|
return sys.executable
|
|
127
118
|
|
|
128
119
|
|
|
129
|
-
def _default_pay_binary() -> str:
|
|
130
|
-
explicit = os.getenv("OPENCLAW_PAY_BINARY", "").strip()
|
|
131
|
-
if explicit:
|
|
132
|
-
return explicit
|
|
133
|
-
resolved = shutil.which("pay")
|
|
134
|
-
return resolved or "pay"
|
|
135
|
-
|
|
136
|
-
|
|
137
120
|
def _default_user_id() -> str:
|
|
138
121
|
return f"{os.getenv('USER', 'openclaw-user')}-local"
|
|
139
122
|
|
|
@@ -171,10 +154,8 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
171
154
|
default=True,
|
|
172
155
|
)
|
|
173
156
|
parser.add_argument("--extension-path", default=str(_default_extension_path()))
|
|
174
|
-
parser.add_argument("--pay-bridge-extension-path", default=str(_default_pay_bridge_extension_path()))
|
|
175
157
|
parser.add_argument("--package-root", default=str(_default_package_root()))
|
|
176
158
|
parser.add_argument("--python-bin", default=_default_python_bin())
|
|
177
|
-
parser.add_argument("--pay-binary", default=_default_pay_binary())
|
|
178
159
|
parser.add_argument("--write-master-key", action=argparse.BooleanOptionalAction, default=False)
|
|
179
160
|
return parser
|
|
180
161
|
|
|
@@ -184,12 +165,15 @@ def _collect_sealed_secret_updates() -> dict[str, str]:
|
|
|
184
165
|
master_key = os.getenv("AGENT_WALLET_MASTER_KEY", "").strip()
|
|
185
166
|
approval_secret = os.getenv("AGENT_WALLET_APPROVAL_SECRET", "").strip()
|
|
186
167
|
private_key = os.getenv("SOLANA_AGENT_PRIVATE_KEY", "").strip()
|
|
168
|
+
evm_wallet_password = os.getenv("WDK_EVM_WALLET_PASSWORD", "").strip()
|
|
187
169
|
if master_key:
|
|
188
170
|
updates["master_key"] = master_key
|
|
189
171
|
if approval_secret:
|
|
190
172
|
updates["approval_secret"] = approval_secret
|
|
191
173
|
if private_key:
|
|
192
174
|
updates["private_key"] = private_key
|
|
175
|
+
if evm_wallet_password:
|
|
176
|
+
updates["wdk_evm_wallet_password"] = evm_wallet_password
|
|
193
177
|
return updates
|
|
194
178
|
|
|
195
179
|
|
|
@@ -198,10 +182,12 @@ def _maybe_install_sealed_keys() -> str | None:
|
|
|
198
182
|
if not boot_key:
|
|
199
183
|
return None
|
|
200
184
|
updates = _collect_sealed_secret_updates()
|
|
201
|
-
if not updates:
|
|
202
|
-
return None
|
|
203
185
|
sealed_path = resolve_sealed_keys_path()
|
|
204
186
|
existing = unseal_keys(boot_key) if sealed_path.exists() else {}
|
|
187
|
+
if "wdk_evm_wallet_password" not in existing and "wdk_evm_wallet_password" not in updates:
|
|
188
|
+
updates["wdk_evm_wallet_password"] = secrets.token_urlsafe(24)
|
|
189
|
+
if not updates:
|
|
190
|
+
return None
|
|
205
191
|
return str(seal_keys(boot_key, {**existing, **updates}))
|
|
206
192
|
|
|
207
193
|
|
|
@@ -248,17 +234,14 @@ def main() -> None:
|
|
|
248
234
|
allow = plugins.setdefault("allow", [])
|
|
249
235
|
if args.plugin_id not in allow:
|
|
250
236
|
allow.append(args.plugin_id)
|
|
251
|
-
|
|
252
|
-
allow.append(PAY_BRIDGE_PLUGIN_ID)
|
|
237
|
+
allow[:] = [item for item in allow if item != "pay-bridge"]
|
|
253
238
|
|
|
254
239
|
load = plugins.setdefault("load", {})
|
|
255
240
|
paths = load.setdefault("paths", [])
|
|
256
241
|
extension_path_text = str(Path(args.extension_path).expanduser().resolve())
|
|
257
242
|
if extension_path_text not in paths:
|
|
258
243
|
paths.append(extension_path_text)
|
|
259
|
-
|
|
260
|
-
if pay_bridge_extension_path_text not in paths:
|
|
261
|
-
paths.append(pay_bridge_extension_path_text)
|
|
244
|
+
paths[:] = [item for item in paths if "extensions/pay-bridge" not in str(item)]
|
|
262
245
|
|
|
263
246
|
entries = plugins.setdefault("entries", {})
|
|
264
247
|
effective_network = _normalize_network(args.backend, args.network)
|
|
@@ -311,25 +294,19 @@ def main() -> None:
|
|
|
311
294
|
"enabled": True,
|
|
312
295
|
"config": plugin_config,
|
|
313
296
|
}
|
|
314
|
-
|
|
315
|
-
existing_pay_config = (
|
|
316
|
-
dict(existing_pay_entry.get("config"))
|
|
317
|
-
if isinstance(existing_pay_entry.get("config"), dict)
|
|
318
|
-
else {}
|
|
319
|
-
)
|
|
320
|
-
pay_bridge_config = {
|
|
321
|
-
**existing_pay_config,
|
|
322
|
-
"payBinary": args.pay_binary.strip() or _default_pay_binary(),
|
|
323
|
-
"requireHttps": bool(existing_pay_config.get("requireHttps", True)),
|
|
324
|
-
}
|
|
325
|
-
entries[PAY_BRIDGE_PLUGIN_ID] = {
|
|
326
|
-
"enabled": True,
|
|
327
|
-
"config": pay_bridge_config,
|
|
328
|
-
}
|
|
297
|
+
entries.pop("pay-bridge", None)
|
|
329
298
|
|
|
330
299
|
tools = data.setdefault("tools", {})
|
|
331
300
|
also_allow = tools.setdefault("alsoAllow", [])
|
|
332
|
-
|
|
301
|
+
removed_pay_tools = {
|
|
302
|
+
"pay_status",
|
|
303
|
+
"pay_wallet_info",
|
|
304
|
+
"pay_search_services",
|
|
305
|
+
"pay_get_service_endpoints",
|
|
306
|
+
"pay_api_request",
|
|
307
|
+
}
|
|
308
|
+
also_allow[:] = [tool_name for tool_name in also_allow if tool_name not in removed_pay_tools]
|
|
309
|
+
for tool_name in OPTIONAL_TOOLS + X402_TOOLS:
|
|
333
310
|
if tool_name not in also_allow:
|
|
334
311
|
also_allow.append(tool_name)
|
|
335
312
|
|
|
@@ -345,7 +322,6 @@ def main() -> None:
|
|
|
345
322
|
"config_path": str(config_path),
|
|
346
323
|
"backup_path": str(backup_path),
|
|
347
324
|
"extension_path": extension_path_text,
|
|
348
|
-
"pay_bridge_extension_path": pay_bridge_extension_path_text,
|
|
349
325
|
"python_bin": args.python_bin,
|
|
350
326
|
"package_root": plugin_config["packageRoot"],
|
|
351
327
|
"plugin_id": args.plugin_id,
|
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import argparse
|
|
6
6
|
import json
|
|
7
7
|
import os
|
|
8
|
+
import secrets as py_secrets
|
|
8
9
|
import sys
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
|
|
@@ -49,12 +50,15 @@ def _collect_secret_updates() -> dict[str, str]:
|
|
|
49
50
|
master_key = os.getenv("AGENT_WALLET_MASTER_KEY", "").strip()
|
|
50
51
|
approval_secret = os.getenv("AGENT_WALLET_APPROVAL_SECRET", "").strip()
|
|
51
52
|
private_key = os.getenv("SOLANA_AGENT_PRIVATE_KEY", "").strip()
|
|
53
|
+
evm_wallet_password = os.getenv("WDK_EVM_WALLET_PASSWORD", "").strip()
|
|
52
54
|
if master_key:
|
|
53
55
|
updates["master_key"] = master_key
|
|
54
56
|
if approval_secret:
|
|
55
57
|
updates["approval_secret"] = approval_secret
|
|
56
58
|
if private_key:
|
|
57
59
|
updates["private_key"] = private_key
|
|
60
|
+
if evm_wallet_password:
|
|
61
|
+
updates["wdk_evm_wallet_password"] = evm_wallet_password
|
|
58
62
|
return updates
|
|
59
63
|
|
|
60
64
|
|
|
@@ -80,6 +84,10 @@ def main() -> None:
|
|
|
80
84
|
sealed_path = resolve_sealed_keys_path()
|
|
81
85
|
existing = unseal_keys(boot_key) if sealed_path.exists() and not args.replace else {}
|
|
82
86
|
secrets = {**existing, **updates}
|
|
87
|
+
generated_keys: list[str] = []
|
|
88
|
+
if "wdk_evm_wallet_password" not in secrets:
|
|
89
|
+
secrets["wdk_evm_wallet_password"] = py_secrets.token_urlsafe(24)
|
|
90
|
+
generated_keys.append("wdk_evm_wallet_password")
|
|
83
91
|
if not secrets:
|
|
84
92
|
raise SystemExit(
|
|
85
93
|
"No secrets provided. Set AGENT_WALLET_MASTER_KEY, AGENT_WALLET_APPROVAL_SECRET, "
|
|
@@ -93,7 +101,7 @@ def main() -> None:
|
|
|
93
101
|
"ok": True,
|
|
94
102
|
"path": str(path),
|
|
95
103
|
"stored_keys": sorted(secrets.keys()),
|
|
96
|
-
"updated_keys": sorted(updates.keys()),
|
|
104
|
+
"updated_keys": sorted(set(updates.keys()) | set(generated_keys)),
|
|
97
105
|
"replaced": bool(args.replace),
|
|
98
106
|
},
|
|
99
107
|
indent=2,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentlayer.tech/wallet",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.19",
|
|
4
4
|
"description": "NPM installer for the OpenClaw Agent Wallet local runtime.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -40,7 +40,6 @@
|
|
|
40
40
|
"agent-wallet/pyproject.toml",
|
|
41
41
|
".openclaw/AGENTS.md",
|
|
42
42
|
".openclaw/extensions/agent-wallet/",
|
|
43
|
-
".openclaw/extensions/pay-bridge/",
|
|
44
43
|
"hermes/plugins/agent_wallet/",
|
|
45
44
|
"wdk-btc-wallet/src/",
|
|
46
45
|
"wdk-btc-wallet/bootstrap.sh",
|
|
@@ -554,6 +554,12 @@ async function handleRequest(request, response) {
|
|
|
554
554
|
return sendJson(response, 200, { ok: true, data });
|
|
555
555
|
}
|
|
556
556
|
|
|
557
|
+
if (method === "POST" && url.pathname === "/v1/evm/x402/exact/sign") {
|
|
558
|
+
const body = await withResolvedNetwork(await withResolvedSeed(await readJsonBody(request)));
|
|
559
|
+
const data = await service.signX402ExactTypedData(body);
|
|
560
|
+
return sendJson(response, 200, { ok: true, data });
|
|
561
|
+
}
|
|
562
|
+
|
|
557
563
|
return notFound(response);
|
|
558
564
|
} catch (error) {
|
|
559
565
|
const shaped = toErrorResponse(error, new URL(request.url || "/", "http://localhost").pathname, 400);
|
|
@@ -149,6 +149,14 @@ function assertPositiveBigIntString(value, fieldName) {
|
|
|
149
149
|
return parsed;
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
+
function assertNonNegativeBigIntString(value, fieldName) {
|
|
153
|
+
const normalized = String(value ?? "").trim();
|
|
154
|
+
if (!/^[0-9]+$/.test(normalized)) {
|
|
155
|
+
throw new Error(`${fieldName} must be a non-negative base-10 integer string.`);
|
|
156
|
+
}
|
|
157
|
+
return normalized;
|
|
158
|
+
}
|
|
159
|
+
|
|
152
160
|
function normalizeAddress(value, fieldName) {
|
|
153
161
|
const address = assertNonEmptyString(value, fieldName);
|
|
154
162
|
if (!/^0x[a-fA-F0-9]{40}$/.test(address)) {
|
|
@@ -305,6 +313,71 @@ function mergeBridgeLists(...values) {
|
|
|
305
313
|
return items.length > 0 ? items.join(",") : null;
|
|
306
314
|
}
|
|
307
315
|
|
|
316
|
+
function assertPlainObject(value, fieldName) {
|
|
317
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
318
|
+
throw new Error(`${fieldName} must be an object.`);
|
|
319
|
+
}
|
|
320
|
+
return value;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function normalizeX402ExactTypedData({ domain, types, primaryType, message }, runtimeConfig) {
|
|
324
|
+
const normalizedPrimaryType = assertNonEmptyString(primaryType, "primaryType");
|
|
325
|
+
if (normalizedPrimaryType !== "TransferWithAuthorization") {
|
|
326
|
+
throw new Error("primaryType must be TransferWithAuthorization for x402 exact EVM payments.");
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const domainObject = assertPlainObject(domain, "domain");
|
|
330
|
+
const domainChainId = assertNonNegativeInteger(domainObject.chainId, "domain.chainId");
|
|
331
|
+
if (domainChainId !== runtimeConfig.chainId) {
|
|
332
|
+
throw new Error("domain.chainId must match the active network chain id.");
|
|
333
|
+
}
|
|
334
|
+
const normalizedDomain = {
|
|
335
|
+
name: assertNonEmptyString(domainObject.name, "domain.name"),
|
|
336
|
+
version: assertNonEmptyString(domainObject.version, "domain.version"),
|
|
337
|
+
chainId: domainChainId,
|
|
338
|
+
verifyingContract: normalizeAddress(domainObject.verifyingContract, "domain.verifyingContract"),
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
const typesObject = assertPlainObject(types, "types");
|
|
342
|
+
const primaryFields = typesObject[normalizedPrimaryType];
|
|
343
|
+
if (!Array.isArray(primaryFields) || primaryFields.length === 0) {
|
|
344
|
+
throw new Error(`types.${normalizedPrimaryType} must be a non-empty array.`);
|
|
345
|
+
}
|
|
346
|
+
const normalizedTypes = {};
|
|
347
|
+
for (const [typeName, fields] of Object.entries(typesObject)) {
|
|
348
|
+
if (!Array.isArray(fields) || fields.length === 0) {
|
|
349
|
+
throw new Error(`types.${typeName} must be a non-empty array.`);
|
|
350
|
+
}
|
|
351
|
+
normalizedTypes[typeName] = fields.map((field, index) => {
|
|
352
|
+
const normalizedField = assertPlainObject(field, `types.${typeName}[${index}]`);
|
|
353
|
+
return {
|
|
354
|
+
name: assertNonEmptyString(normalizedField.name, `types.${typeName}[${index}].name`),
|
|
355
|
+
type: assertNonEmptyString(normalizedField.type, `types.${typeName}[${index}].type`),
|
|
356
|
+
};
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const messageObject = assertPlainObject(message, "message");
|
|
361
|
+
const normalizedMessage = {
|
|
362
|
+
from: normalizeAddress(messageObject.from, "message.from"),
|
|
363
|
+
to: normalizeAddress(messageObject.to, "message.to"),
|
|
364
|
+
value: assertPositiveBigIntString(messageObject.value, "message.value").toString(),
|
|
365
|
+
validAfter: assertNonNegativeBigIntString(messageObject.validAfter, "message.validAfter"),
|
|
366
|
+
validBefore: assertPositiveBigIntString(messageObject.validBefore, "message.validBefore").toString(),
|
|
367
|
+
nonce: assertNonEmptyString(messageObject.nonce, "message.nonce"),
|
|
368
|
+
};
|
|
369
|
+
if (!/^0x[a-fA-F0-9]{64}$/.test(normalizedMessage.nonce)) {
|
|
370
|
+
throw new Error("message.nonce must be a 32-byte hex string.");
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return {
|
|
374
|
+
domain: normalizedDomain,
|
|
375
|
+
types: normalizedTypes,
|
|
376
|
+
primaryType: normalizedPrimaryType,
|
|
377
|
+
message: normalizedMessage,
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
308
381
|
function buildSwapRequest({ tokenIn, tokenOut, tokenInAmount }) {
|
|
309
382
|
const swapRequest = {
|
|
310
383
|
tokenIn: normalizeAddress(tokenIn, "tokenIn"),
|
|
@@ -2335,6 +2408,41 @@ export class WdkEvmWalletService {
|
|
|
2335
2408
|
});
|
|
2336
2409
|
}
|
|
2337
2410
|
|
|
2411
|
+
async signX402ExactTypedData({
|
|
2412
|
+
seedPhrase,
|
|
2413
|
+
accountIndex = 0,
|
|
2414
|
+
network,
|
|
2415
|
+
domain,
|
|
2416
|
+
types,
|
|
2417
|
+
primaryType,
|
|
2418
|
+
message,
|
|
2419
|
+
}) {
|
|
2420
|
+
return this.#withAccount({ seedPhrase, accountIndex, network }, async (account, runtimeConfig) => {
|
|
2421
|
+
const typedData = normalizeX402ExactTypedData(
|
|
2422
|
+
{ domain, types, primaryType, message },
|
|
2423
|
+
runtimeConfig
|
|
2424
|
+
);
|
|
2425
|
+
const signerAddress = normalizeAddress(await account.getAddress(), "accountAddress");
|
|
2426
|
+
if (typedData.message.from.toLowerCase() !== signerAddress.toLowerCase()) {
|
|
2427
|
+
throw new Error("message.from must match the active wallet account address.");
|
|
2428
|
+
}
|
|
2429
|
+
const signature = await account.signTypedData({
|
|
2430
|
+
domain: typedData.domain,
|
|
2431
|
+
types: typedData.types,
|
|
2432
|
+
message: typedData.message,
|
|
2433
|
+
});
|
|
2434
|
+
return {
|
|
2435
|
+
network: runtimeConfig.network,
|
|
2436
|
+
chainId: runtimeConfig.chainId,
|
|
2437
|
+
accountIndex,
|
|
2438
|
+
address: signerAddress,
|
|
2439
|
+
primaryType: typedData.primaryType,
|
|
2440
|
+
signature,
|
|
2441
|
+
source: "wdk-wallet-evm",
|
|
2442
|
+
};
|
|
2443
|
+
});
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2338
2446
|
#resolveRuntimeConfig(networkOverride) {
|
|
2339
2447
|
const network = assertValidNetwork(networkOverride) || this.config.network;
|
|
2340
2448
|
const profile = this.config.networkProfiles?.[network];
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
# pay-bridge
|
|
2
|
-
|
|
3
|
-
Thin OpenClaw bridge to the locally installed `pay` CLI.
|
|
4
|
-
|
|
5
|
-
External install path:
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
openclaw plugins install clawhub:@agentlayertech/pay-bridge-plugin
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
This plugin is intentionally separate from `agent-wallet`:
|
|
12
|
-
|
|
13
|
-
- `agent-wallet` remains the execution wallet stack for Solana/EVM/BTC
|
|
14
|
-
- `pay-bridge` only discovers and calls paid APIs through `pay`
|
|
15
|
-
- the `pay` wallet stays separate from the AgentLayer wallet runtime
|
|
16
|
-
|
|
17
|
-
## Exposed tools
|
|
18
|
-
|
|
19
|
-
- `pay_status`
|
|
20
|
-
- `pay_wallet_info`
|
|
21
|
-
- `pay_search_services`
|
|
22
|
-
- `pay_get_service_endpoints`
|
|
23
|
-
- `pay_api_request`
|
|
24
|
-
|
|
25
|
-
## Intended workflow
|
|
26
|
-
|
|
27
|
-
1. `pay_status`
|
|
28
|
-
2. `pay_search_services`
|
|
29
|
-
3. `pay_get_service_endpoints`
|
|
30
|
-
4. `pay_api_request`
|
|
31
|
-
|
|
32
|
-
`pay_api_request` is deliberately narrow:
|
|
33
|
-
|
|
34
|
-
- it requires a `service_fqn`, `resource`, and `url`
|
|
35
|
-
- it validates the URL against `pay skills endpoints`
|
|
36
|
-
- it requires `purpose` and `user_confirmed=true`
|
|
37
|
-
|
|
38
|
-
This keeps the bridge thin and prevents it from becoming a generic arbitrary paid-curl launcher.
|