@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.
- package/.openclaw/extensions/agent-wallet/dist/index.js +108 -10
- package/.openclaw/extensions/agent-wallet/index.ts +108 -10
- 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 +38 -0
- package/agent-wallet/README.md +5 -1
- package/agent-wallet/agent_wallet/openclaw_adapter.py +306 -4
- package/agent-wallet/agent_wallet/providers/flash_sdk_bridge.py +50 -4
- package/agent-wallet/agent_wallet/providers/x402.py +1323 -0
- package/agent-wallet/agent_wallet/wallet_layer/solana.py +9 -9
- package/agent-wallet/agent_wallet/wallet_layer/wdk_evm.py +30 -0
- package/agent-wallet/pyproject.toml +2 -1
- package/agent-wallet/scripts/flash-sdk-bridge/README.md +2 -2
- package/agent-wallet/scripts/flash-sdk-bridge/bridge.mjs +17 -17
- 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
|
@@ -2600,10 +2600,6 @@ class SolanaWalletBackend(AgentWalletBackend):
|
|
|
2600
2600
|
collateral_symbol,
|
|
2601
2601
|
field_name="collateral_symbol",
|
|
2602
2602
|
)
|
|
2603
|
-
if normalized_collateral_symbol != normalized_market_symbol:
|
|
2604
|
-
raise WalletBackendError(
|
|
2605
|
-
"Phase 2 Flash preview currently supports only same-collateral opens where collateral_symbol matches market_symbol."
|
|
2606
|
-
)
|
|
2607
2603
|
normalized_collateral_amount_raw = _require_positive_integer_string(
|
|
2608
2604
|
collateral_amount_raw,
|
|
2609
2605
|
field_name="collateral_amount_raw",
|
|
@@ -2616,15 +2612,19 @@ class SolanaWalletBackend(AgentWalletBackend):
|
|
|
2616
2612
|
item
|
|
2617
2613
|
for item in market_snapshot["markets"]
|
|
2618
2614
|
if isinstance(item, dict)
|
|
2619
|
-
and str(item.get("symbol") or "").strip().upper()
|
|
2615
|
+
and str(item.get("market_symbol") or item.get("symbol") or "").strip().upper()
|
|
2616
|
+
== normalized_market_symbol
|
|
2617
|
+
and str(item.get("side") or "").strip().lower() == normalized_side
|
|
2618
|
+
and str(item.get("collateral_symbol") or "").strip().upper()
|
|
2619
|
+
== normalized_collateral_symbol
|
|
2620
2620
|
),
|
|
2621
2621
|
None,
|
|
2622
2622
|
)
|
|
2623
2623
|
if matching_market is None:
|
|
2624
2624
|
raise WalletBackendError(
|
|
2625
|
-
"Requested Flash market is not available in the selected pool."
|
|
2625
|
+
"Requested Flash market is not available in the selected pool for the requested collateral and side."
|
|
2626
2626
|
)
|
|
2627
|
-
bridge_preview = await flash_sdk_bridge.
|
|
2627
|
+
bridge_preview = await flash_sdk_bridge.preview_open_position(
|
|
2628
2628
|
owner=owner,
|
|
2629
2629
|
pool_name=normalized_pool_name,
|
|
2630
2630
|
market_symbol=normalized_market_symbol,
|
|
@@ -2927,7 +2927,7 @@ class SolanaWalletBackend(AgentWalletBackend):
|
|
|
2927
2927
|
leverage=leverage,
|
|
2928
2928
|
side=side,
|
|
2929
2929
|
)
|
|
2930
|
-
bridge_prepared = await flash_sdk_bridge.
|
|
2930
|
+
bridge_prepared = await flash_sdk_bridge.prepare_open_position(
|
|
2931
2931
|
owner=str(preview["owner"]),
|
|
2932
2932
|
pool_name=str(preview["pool_name"]),
|
|
2933
2933
|
market_symbol=str(preview["market_symbol"]),
|
|
@@ -2948,7 +2948,7 @@ class SolanaWalletBackend(AgentWalletBackend):
|
|
|
2948
2948
|
self,
|
|
2949
2949
|
preview: dict[str, Any],
|
|
2950
2950
|
) -> dict[str, Any]:
|
|
2951
|
-
bridge_prepared = await flash_sdk_bridge.
|
|
2951
|
+
bridge_prepared = await flash_sdk_bridge.prepare_open_position(
|
|
2952
2952
|
owner=str(preview["owner"]),
|
|
2953
2953
|
pool_name=str(preview["pool_name"]),
|
|
2954
2954
|
market_symbol=str(preview["market_symbol"]),
|
|
@@ -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]
|
|
@@ -13,7 +13,7 @@ Current goals:
|
|
|
13
13
|
- `FLASH_SDK_BRIDGE_MODE=mock`
|
|
14
14
|
Returns deterministic payloads for local smoke checks without installing SDK dependencies.
|
|
15
15
|
- `FLASH_SDK_BRIDGE_MODE=real`
|
|
16
|
-
Loads `flash-sdk` and validates runtime config. This mode now supports market discovery, user-position discovery, open/close previews, and unsigned transaction preparation for the
|
|
16
|
+
Loads `flash-sdk` and validates runtime config. This mode now supports market discovery, user-position discovery, open/close previews, and unsigned transaction preparation for Flash perps using the collateral supported by the selected Flash market.
|
|
17
17
|
|
|
18
18
|
## Command
|
|
19
19
|
|
|
@@ -28,6 +28,6 @@ For local smoke:
|
|
|
28
28
|
```bash
|
|
29
29
|
FLASH_SDK_BRIDGE_MODE=mock \
|
|
30
30
|
node agent-wallet/scripts/flash-sdk-bridge/bridge.mjs <<'EOF'
|
|
31
|
-
{"action":"
|
|
31
|
+
{"action":"preview_open_position","owner":"Fake11111111111111111111111111111111111111111","pool_name":"Crypto.1","market_symbol":"SOL","collateral_symbol":"USDC","collateral_amount_raw":"5000000","leverage":"2","side":"short","network":"mainnet"}
|
|
32
32
|
EOF
|
|
33
33
|
```
|
|
@@ -110,7 +110,7 @@ function mockResponse(normalized) {
|
|
|
110
110
|
pool_name: normalized.poolName ?? "Crypto.1",
|
|
111
111
|
symbol: "SOL",
|
|
112
112
|
market_symbol: "SOL",
|
|
113
|
-
collateral_symbol: "
|
|
113
|
+
collateral_symbol: "USDC",
|
|
114
114
|
side: "short",
|
|
115
115
|
market_address: "MockFlashMarketShort1111111111111111111111111",
|
|
116
116
|
},
|
|
@@ -145,7 +145,10 @@ function mockResponse(normalized) {
|
|
|
145
145
|
};
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
if (
|
|
148
|
+
if (
|
|
149
|
+
normalized.action === "preview_open_position" ||
|
|
150
|
+
normalized.action === "preview_open_position_same_collateral"
|
|
151
|
+
) {
|
|
149
152
|
return {
|
|
150
153
|
ok: true,
|
|
151
154
|
preview: {
|
|
@@ -177,7 +180,10 @@ function mockResponse(normalized) {
|
|
|
177
180
|
};
|
|
178
181
|
}
|
|
179
182
|
|
|
180
|
-
if (
|
|
183
|
+
if (
|
|
184
|
+
normalized.action === "prepare_open_position" ||
|
|
185
|
+
normalized.action === "prepare_open_position_same_collateral"
|
|
186
|
+
) {
|
|
181
187
|
return {
|
|
182
188
|
ok: true,
|
|
183
189
|
prepared: {
|
|
@@ -608,12 +614,6 @@ async function getOpenPositionPreview(runtime, normalized) {
|
|
|
608
614
|
"collateral_symbol, collateral_amount_raw, and leverage are required for open preview",
|
|
609
615
|
);
|
|
610
616
|
}
|
|
611
|
-
if (normalized.collateralSymbol !== normalized.marketSymbol) {
|
|
612
|
-
throw new Error(
|
|
613
|
-
"Current bridge MVP supports only same-collateral opens where collateral_symbol matches market_symbol",
|
|
614
|
-
);
|
|
615
|
-
}
|
|
616
|
-
|
|
617
617
|
const { BN } = runtime;
|
|
618
618
|
const privilege = runtime.flashSdk.Privilege.None;
|
|
619
619
|
const ownerPublicKey = runtime.provider.wallet.publicKey;
|
|
@@ -810,12 +810,6 @@ async function prepareOpenPosition(runtime, normalized) {
|
|
|
810
810
|
"collateral_symbol, collateral_amount_raw, and leverage are required for open prepare",
|
|
811
811
|
);
|
|
812
812
|
}
|
|
813
|
-
if (normalized.collateralSymbol !== normalized.marketSymbol) {
|
|
814
|
-
throw new Error(
|
|
815
|
-
"Current bridge MVP supports only same-collateral opens where collateral_symbol matches market_symbol",
|
|
816
|
-
);
|
|
817
|
-
}
|
|
818
|
-
|
|
819
813
|
const { BN } = runtime;
|
|
820
814
|
const privilege = runtime.flashSdk.Privilege.None;
|
|
821
815
|
const ownerPublicKey = runtime.provider.wallet.publicKey;
|
|
@@ -1113,13 +1107,19 @@ async function realResponse(normalized) {
|
|
|
1113
1107
|
return getPositionsReal(normalized);
|
|
1114
1108
|
}
|
|
1115
1109
|
const runtime = await buildRuntimeContext(normalized);
|
|
1116
|
-
if (
|
|
1110
|
+
if (
|
|
1111
|
+
normalized.action === "preview_open_position" ||
|
|
1112
|
+
normalized.action === "preview_open_position_same_collateral"
|
|
1113
|
+
) {
|
|
1117
1114
|
return getOpenPositionPreview(runtime, normalized);
|
|
1118
1115
|
}
|
|
1119
1116
|
if (normalized.action === "preview_close_position_same_collateral") {
|
|
1120
1117
|
return getClosePositionPreview(runtime, normalized);
|
|
1121
1118
|
}
|
|
1122
|
-
if (
|
|
1119
|
+
if (
|
|
1120
|
+
normalized.action === "prepare_open_position" ||
|
|
1121
|
+
normalized.action === "prepare_open_position_same_collateral"
|
|
1122
|
+
) {
|
|
1123
1123
|
return prepareOpenPosition(runtime, normalized);
|
|
1124
1124
|
}
|
|
1125
1125
|
if (normalized.action === "prepare_close_position_same_collateral") {
|
|
@@ -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];
|