@agentlayer.tech/wallet 0.1.17 → 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.
- 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/.openclaw/extensions/pay-bridge/package.json +1 -1
- package/CHANGELOG.md +24 -0
- package/agent-wallet/README.md +4 -0
- package/agent-wallet/agent_wallet/openclaw_adapter.py +303 -1
- 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/install_openclaw_local_config.py +8 -1
- package/package.json +1 -1
- package/wdk-evm-wallet/src/server.js +6 -0
- package/wdk-evm-wallet/src/wdk_evm_wallet.js +108 -0
|
@@ -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.18"
|
|
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]
|
|
@@ -58,6 +58,13 @@ PAY_BRIDGE_TOOLS = [
|
|
|
58
58
|
"pay_api_request",
|
|
59
59
|
]
|
|
60
60
|
|
|
61
|
+
X402_TOOLS = [
|
|
62
|
+
"x402_search_services",
|
|
63
|
+
"x402_get_service_details",
|
|
64
|
+
"x402_preview_request",
|
|
65
|
+
"x402_pay_request",
|
|
66
|
+
]
|
|
67
|
+
|
|
61
68
|
|
|
62
69
|
def _default_config_path() -> Path:
|
|
63
70
|
return Path(os.path.expanduser("~/.openclaw/openclaw.json"))
|
|
@@ -329,7 +336,7 @@ def main() -> None:
|
|
|
329
336
|
|
|
330
337
|
tools = data.setdefault("tools", {})
|
|
331
338
|
also_allow = tools.setdefault("alsoAllow", [])
|
|
332
|
-
for tool_name in OPTIONAL_TOOLS + PAY_BRIDGE_TOOLS:
|
|
339
|
+
for tool_name in OPTIONAL_TOOLS + PAY_BRIDGE_TOOLS + X402_TOOLS:
|
|
333
340
|
if tool_name not in also_allow:
|
|
334
341
|
also_allow.append(tool_name)
|
|
335
342
|
|
package/package.json
CHANGED
|
@@ -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];
|