@agentlayer.tech/wallet 0.1.44 → 0.1.48
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/index.ts +99 -0
- package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +6 -1
- package/.openclaw/extensions/agent-wallet/package.json +1 -1
- package/CHANGELOG.md +62 -0
- package/README.md +2 -2
- package/VERSION +1 -1
- package/agent-wallet/agent_wallet/__init__.py +1 -1
- package/agent-wallet/agent_wallet/openclaw_adapter.py +884 -37
- package/agent-wallet/agent_wallet/providers/jupiter.py +5 -0
- package/agent-wallet/agent_wallet/providers/wdk_evm_local.py +6 -0
- package/agent-wallet/agent_wallet/wallet_layer/base.py +189 -0
- package/agent-wallet/agent_wallet/wallet_layer/solana.py +14 -0
- package/agent-wallet/agent_wallet/wallet_layer/wdk_evm.py +404 -0
- package/agent-wallet/openclaw.plugin.json +1 -1
- package/agent-wallet/pyproject.toml +1 -1
- package/agent-wallet/scripts/install_agent_wallet.py +33 -8
- package/claude-code/plugins/agent-wallet/.claude-plugin/plugin.json +1 -1
- package/codex/plugins/agent-wallet/.codex-plugin/plugin.json +1 -1
- package/hermes/plugins/agent_wallet/plugin.yaml +1 -1
- package/package.json +1 -1
- package/wdk-btc-wallet/package.json +1 -1
- package/wdk-evm-wallet/README.md +31 -0
- package/wdk-evm-wallet/package-lock.json +268 -64
- package/wdk-evm-wallet/package.json +4 -1
- package/wdk-evm-wallet/src/config.js +2 -0
- package/wdk-evm-wallet/src/server.js +66 -0
- package/wdk-evm-wallet/src/wdk_evm_wallet.js +2725 -939
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import hashlib
|
|
6
6
|
import json
|
|
7
|
+
import time
|
|
7
8
|
from typing import Any
|
|
8
9
|
|
|
9
10
|
from agent_wallet.approval import inspect_approval_token, verify_approval_token
|
|
@@ -371,6 +372,24 @@ class OpenClawWalletAdapter:
|
|
|
371
372
|
"spend_policy": payload.get("spend_policy"),
|
|
372
373
|
}
|
|
373
374
|
|
|
375
|
+
if asset_type == "kamino-lend-intent":
|
|
376
|
+
summary = {
|
|
377
|
+
"operation": action_label,
|
|
378
|
+
"network": str(payload.get("network") or getattr(self.backend, "network", "unknown")),
|
|
379
|
+
"owner": payload.get("owner"),
|
|
380
|
+
"kamino_operation": payload.get("kamino_operation"),
|
|
381
|
+
"market": payload.get("market"),
|
|
382
|
+
"reserve": payload.get("reserve"),
|
|
383
|
+
"amount_ui": payload.get("amount_ui"),
|
|
384
|
+
"recipient_policy": payload.get("recipient_policy"),
|
|
385
|
+
"spend_policy": payload.get("spend_policy"),
|
|
386
|
+
"valid_until_epoch_seconds": payload.get("valid_until_epoch_seconds"),
|
|
387
|
+
}
|
|
388
|
+
obligation_address = payload.get("obligation_address")
|
|
389
|
+
if obligation_address is not None:
|
|
390
|
+
summary["obligation_address"] = obligation_address
|
|
391
|
+
return summary
|
|
392
|
+
|
|
374
393
|
if asset_type == "solana-lifi-cross-chain-swap":
|
|
375
394
|
return {
|
|
376
395
|
"operation": action_label,
|
|
@@ -496,6 +515,46 @@ class OpenClawWalletAdapter:
|
|
|
496
515
|
"quote_fingerprint": provided_fingerprint,
|
|
497
516
|
}
|
|
498
517
|
|
|
518
|
+
if asset_type == "evm-morpho-vault":
|
|
519
|
+
return {
|
|
520
|
+
"operation": action_label,
|
|
521
|
+
"network": str(payload.get("network") or getattr(self.backend, "network", "unknown")),
|
|
522
|
+
"wallet": payload.get("wallet"),
|
|
523
|
+
"from_address": payload.get("from_address"),
|
|
524
|
+
"protocol": payload.get("protocol"),
|
|
525
|
+
"morpho_surface": payload.get("surface"),
|
|
526
|
+
"morpho_operation": payload.get("operation"),
|
|
527
|
+
"target": payload.get("target"),
|
|
528
|
+
"token_address": payload.get("token_address"),
|
|
529
|
+
"amount_raw": payload.get("amount_raw"),
|
|
530
|
+
"native_amount_raw": payload.get("native_amount_raw"),
|
|
531
|
+
"estimated_fee_wei": payload.get("estimated_fee_wei"),
|
|
532
|
+
"estimated_operation_fee_wei": payload.get("estimated_operation_fee_wei"),
|
|
533
|
+
"estimated_requirements_fee_wei": payload.get("estimated_requirements_fee_wei"),
|
|
534
|
+
"quote_fingerprint": payload.get("quote_fingerprint"),
|
|
535
|
+
"requirements": payload.get("requirements"),
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if asset_type == "evm-morpho-market":
|
|
539
|
+
return {
|
|
540
|
+
"operation": action_label,
|
|
541
|
+
"network": str(payload.get("network") or getattr(self.backend, "network", "unknown")),
|
|
542
|
+
"wallet": payload.get("wallet"),
|
|
543
|
+
"from_address": payload.get("from_address"),
|
|
544
|
+
"protocol": payload.get("protocol"),
|
|
545
|
+
"morpho_surface": payload.get("surface"),
|
|
546
|
+
"morpho_operation": payload.get("operation"),
|
|
547
|
+
"target": payload.get("target"),
|
|
548
|
+
"token_address": payload.get("token_address"),
|
|
549
|
+
"amount_raw": payload.get("amount_raw"),
|
|
550
|
+
"native_amount_raw": payload.get("native_amount_raw"),
|
|
551
|
+
"estimated_fee_wei": payload.get("estimated_fee_wei"),
|
|
552
|
+
"estimated_operation_fee_wei": payload.get("estimated_operation_fee_wei"),
|
|
553
|
+
"estimated_requirements_fee_wei": payload.get("estimated_requirements_fee_wei"),
|
|
554
|
+
"quote_fingerprint": payload.get("quote_fingerprint"),
|
|
555
|
+
"requirements": payload.get("requirements"),
|
|
556
|
+
}
|
|
557
|
+
|
|
499
558
|
if asset_type == "evm-aave-v3":
|
|
500
559
|
return {
|
|
501
560
|
"operation": action_label,
|
|
@@ -1465,6 +1524,263 @@ class OpenClawWalletAdapter:
|
|
|
1465
1524
|
risk_level="high",
|
|
1466
1525
|
),
|
|
1467
1526
|
)
|
|
1527
|
+
tools.insert(
|
|
1528
|
+
15,
|
|
1529
|
+
AgentToolSpec(
|
|
1530
|
+
name="get_evm_morpho_vaults",
|
|
1531
|
+
description="Get read-only Morpho vault discovery and detail data for the configured EVM network on supported mainnet chains. When listing, results are ordered (default: largest TVL first) and can be filtered by underlying asset.",
|
|
1532
|
+
input_schema={
|
|
1533
|
+
"type": "object",
|
|
1534
|
+
"properties": {
|
|
1535
|
+
"vault_address": {
|
|
1536
|
+
"type": "string",
|
|
1537
|
+
"description": "Optional explicit Morpho vault address for a single-vault lookup.",
|
|
1538
|
+
},
|
|
1539
|
+
"limit": {
|
|
1540
|
+
"type": "integer",
|
|
1541
|
+
"minimum": 1,
|
|
1542
|
+
"maximum": 500,
|
|
1543
|
+
"description": "Optional max number of vaults to return when listing.",
|
|
1544
|
+
},
|
|
1545
|
+
"listed_only": {
|
|
1546
|
+
"type": "boolean",
|
|
1547
|
+
"description": "Filter to listed vaults only. Defaults to true.",
|
|
1548
|
+
},
|
|
1549
|
+
"asset_address": {
|
|
1550
|
+
"type": "string",
|
|
1551
|
+
"description": "Optional underlying asset address to filter vaults (e.g. only USDC vaults).",
|
|
1552
|
+
},
|
|
1553
|
+
"order_by": {
|
|
1554
|
+
"type": "string",
|
|
1555
|
+
"enum": [
|
|
1556
|
+
"TotalAssetsUsd",
|
|
1557
|
+
"TotalAssets",
|
|
1558
|
+
"TotalSupply",
|
|
1559
|
+
"Liquidity",
|
|
1560
|
+
"LiquidityUsd",
|
|
1561
|
+
"Apy",
|
|
1562
|
+
"NetApy",
|
|
1563
|
+
"Address",
|
|
1564
|
+
],
|
|
1565
|
+
"description": "Optional sort field when listing. Defaults to TotalAssetsUsd (largest TVL first).",
|
|
1566
|
+
},
|
|
1567
|
+
"order_direction": {
|
|
1568
|
+
"type": "string",
|
|
1569
|
+
"enum": ["asc", "desc"],
|
|
1570
|
+
"description": "Optional sort direction. Defaults to desc.",
|
|
1571
|
+
},
|
|
1572
|
+
"network": {
|
|
1573
|
+
"type": "string",
|
|
1574
|
+
"enum": ["ethereum", "base"],
|
|
1575
|
+
"description": "Optional EVM network override for this request.",
|
|
1576
|
+
},
|
|
1577
|
+
},
|
|
1578
|
+
"additionalProperties": False,
|
|
1579
|
+
},
|
|
1580
|
+
read_only=True,
|
|
1581
|
+
risk_level="low",
|
|
1582
|
+
),
|
|
1583
|
+
)
|
|
1584
|
+
tools.insert(
|
|
1585
|
+
16,
|
|
1586
|
+
AgentToolSpec(
|
|
1587
|
+
name="get_evm_morpho_markets",
|
|
1588
|
+
description="Get read-only Morpho market discovery and detail data for the configured EVM network on supported mainnet chains. When listing, results are ordered (default: largest supply first) and can be filtered by a free-text search or by collateral/loan asset.",
|
|
1589
|
+
input_schema={
|
|
1590
|
+
"type": "object",
|
|
1591
|
+
"properties": {
|
|
1592
|
+
"market_id": {
|
|
1593
|
+
"type": "string",
|
|
1594
|
+
"description": "Optional explicit Morpho market id (32-byte hex) for a single-market lookup.",
|
|
1595
|
+
},
|
|
1596
|
+
"limit": {
|
|
1597
|
+
"type": "integer",
|
|
1598
|
+
"minimum": 1,
|
|
1599
|
+
"maximum": 500,
|
|
1600
|
+
"description": "Optional max number of markets to return when listing.",
|
|
1601
|
+
},
|
|
1602
|
+
"listed_only": {
|
|
1603
|
+
"type": "boolean",
|
|
1604
|
+
"description": "Filter to listed markets only. Defaults to true.",
|
|
1605
|
+
},
|
|
1606
|
+
"search": {
|
|
1607
|
+
"type": "string",
|
|
1608
|
+
"description": "Optional free-text search over market/asset symbols (e.g. 'wstETH').",
|
|
1609
|
+
},
|
|
1610
|
+
"collateral_asset_address": {
|
|
1611
|
+
"type": "string",
|
|
1612
|
+
"description": "Optional collateral asset address to filter markets.",
|
|
1613
|
+
},
|
|
1614
|
+
"loan_asset_address": {
|
|
1615
|
+
"type": "string",
|
|
1616
|
+
"description": "Optional loan asset address to filter markets.",
|
|
1617
|
+
},
|
|
1618
|
+
"order_by": {
|
|
1619
|
+
"type": "string",
|
|
1620
|
+
"enum": [
|
|
1621
|
+
"SupplyAssetsUsd",
|
|
1622
|
+
"BorrowAssetsUsd",
|
|
1623
|
+
"SupplyApy",
|
|
1624
|
+
"NetSupplyApy",
|
|
1625
|
+
"BorrowApy",
|
|
1626
|
+
"NetBorrowApy",
|
|
1627
|
+
"Utilization",
|
|
1628
|
+
"TotalLiquidityUsd",
|
|
1629
|
+
"Lltv",
|
|
1630
|
+
],
|
|
1631
|
+
"description": "Optional sort field when listing. Defaults to SupplyAssetsUsd (largest supply first).",
|
|
1632
|
+
},
|
|
1633
|
+
"order_direction": {
|
|
1634
|
+
"type": "string",
|
|
1635
|
+
"enum": ["asc", "desc"],
|
|
1636
|
+
"description": "Optional sort direction. Defaults to desc.",
|
|
1637
|
+
},
|
|
1638
|
+
"network": {
|
|
1639
|
+
"type": "string",
|
|
1640
|
+
"enum": ["ethereum", "base"],
|
|
1641
|
+
"description": "Optional EVM network override for this request.",
|
|
1642
|
+
},
|
|
1643
|
+
},
|
|
1644
|
+
"additionalProperties": False,
|
|
1645
|
+
},
|
|
1646
|
+
read_only=True,
|
|
1647
|
+
risk_level="low",
|
|
1648
|
+
),
|
|
1649
|
+
)
|
|
1650
|
+
tools.insert(
|
|
1651
|
+
17,
|
|
1652
|
+
AgentToolSpec(
|
|
1653
|
+
name="get_evm_morpho_positions",
|
|
1654
|
+
description="Get read-only Morpho vault and market positions for the configured EVM wallet on supported mainnet chains.",
|
|
1655
|
+
input_schema={
|
|
1656
|
+
"type": "object",
|
|
1657
|
+
"properties": {
|
|
1658
|
+
"network": {
|
|
1659
|
+
"type": "string",
|
|
1660
|
+
"enum": ["ethereum", "base"],
|
|
1661
|
+
"description": "Optional EVM network override for this request.",
|
|
1662
|
+
},
|
|
1663
|
+
},
|
|
1664
|
+
"additionalProperties": False,
|
|
1665
|
+
},
|
|
1666
|
+
read_only=True,
|
|
1667
|
+
risk_level="low",
|
|
1668
|
+
),
|
|
1669
|
+
)
|
|
1670
|
+
tools.insert(
|
|
1671
|
+
18,
|
|
1672
|
+
AgentToolSpec(
|
|
1673
|
+
name="manage_evm_morpho_vault_position",
|
|
1674
|
+
description=(
|
|
1675
|
+
"Preview, prepare, or execute a narrow Morpho vault operation on supported EVM mainnet networks. "
|
|
1676
|
+
"Supported operations are supply and withdraw. Prepare returns an execution plan only, "
|
|
1677
|
+
"and execute requires a host-issued approval token bound to the previewed operation."
|
|
1678
|
+
),
|
|
1679
|
+
input_schema={
|
|
1680
|
+
"type": "object",
|
|
1681
|
+
"properties": {
|
|
1682
|
+
"operation": {
|
|
1683
|
+
"type": "string",
|
|
1684
|
+
"enum": ["supply", "withdraw"],
|
|
1685
|
+
},
|
|
1686
|
+
"token_address": {
|
|
1687
|
+
"type": "string",
|
|
1688
|
+
"description": "Vault asset token address.",
|
|
1689
|
+
},
|
|
1690
|
+
"vault_address": {
|
|
1691
|
+
"type": "string",
|
|
1692
|
+
"description": "Optional explicit Morpho vault address.",
|
|
1693
|
+
},
|
|
1694
|
+
"vault_preset": {
|
|
1695
|
+
"type": "string",
|
|
1696
|
+
"description": "Optional Morpho vault preset name.",
|
|
1697
|
+
},
|
|
1698
|
+
"amount_raw": {
|
|
1699
|
+
"type": "string",
|
|
1700
|
+
"description": "Asset amount in base units. Required for withdraw and optional for supply when native_amount_raw is used.",
|
|
1701
|
+
},
|
|
1702
|
+
"native_amount_raw": {
|
|
1703
|
+
"type": "string",
|
|
1704
|
+
"description": "Optional native-wrap amount in base units for supply.",
|
|
1705
|
+
},
|
|
1706
|
+
"mode": {
|
|
1707
|
+
"type": "string",
|
|
1708
|
+
"enum": ["preview", "prepare", "execute"],
|
|
1709
|
+
},
|
|
1710
|
+
"purpose": {"type": "string"},
|
|
1711
|
+
"user_intent": {"type": "boolean"},
|
|
1712
|
+
"approval_token": {"type": "string"},
|
|
1713
|
+
"network": {
|
|
1714
|
+
"type": "string",
|
|
1715
|
+
"enum": ["ethereum", "base"],
|
|
1716
|
+
"description": "Optional EVM network override for this request.",
|
|
1717
|
+
},
|
|
1718
|
+
},
|
|
1719
|
+
"required": ["operation", "token_address", "mode", "purpose"],
|
|
1720
|
+
"additionalProperties": False,
|
|
1721
|
+
},
|
|
1722
|
+
read_only=False,
|
|
1723
|
+
requires_explicit_user_intent=True,
|
|
1724
|
+
risk_level="high",
|
|
1725
|
+
),
|
|
1726
|
+
)
|
|
1727
|
+
tools.insert(
|
|
1728
|
+
19,
|
|
1729
|
+
AgentToolSpec(
|
|
1730
|
+
name="manage_evm_morpho_market_position",
|
|
1731
|
+
description=(
|
|
1732
|
+
"Preview, prepare, or execute a narrow Morpho market operation on supported EVM mainnet networks. "
|
|
1733
|
+
"Supported operations are supply_collateral, borrow, repay, and withdraw_collateral. "
|
|
1734
|
+
"Prepare returns an execution plan only, and execute requires a host-issued approval token bound to the previewed operation."
|
|
1735
|
+
),
|
|
1736
|
+
input_schema={
|
|
1737
|
+
"type": "object",
|
|
1738
|
+
"properties": {
|
|
1739
|
+
"operation": {
|
|
1740
|
+
"type": "string",
|
|
1741
|
+
"enum": ["supply_collateral", "borrow", "repay", "withdraw_collateral"],
|
|
1742
|
+
},
|
|
1743
|
+
"token_address": {
|
|
1744
|
+
"type": "string",
|
|
1745
|
+
"description": "Loan token or collateral token address for the selected operation.",
|
|
1746
|
+
},
|
|
1747
|
+
"market_id": {
|
|
1748
|
+
"type": "string",
|
|
1749
|
+
"description": "Optional explicit Morpho market id.",
|
|
1750
|
+
},
|
|
1751
|
+
"market_preset": {
|
|
1752
|
+
"type": "string",
|
|
1753
|
+
"description": "Optional Morpho market preset name.",
|
|
1754
|
+
},
|
|
1755
|
+
"amount_raw": {
|
|
1756
|
+
"type": "string",
|
|
1757
|
+
"description": "Operation amount in base units. For repay, use a positive integer string or the literal max.",
|
|
1758
|
+
},
|
|
1759
|
+
"native_amount_raw": {
|
|
1760
|
+
"type": "string",
|
|
1761
|
+
"description": "Optional native-wrap amount in base units for supply_collateral.",
|
|
1762
|
+
},
|
|
1763
|
+
"mode": {
|
|
1764
|
+
"type": "string",
|
|
1765
|
+
"enum": ["preview", "prepare", "execute"],
|
|
1766
|
+
},
|
|
1767
|
+
"purpose": {"type": "string"},
|
|
1768
|
+
"user_intent": {"type": "boolean"},
|
|
1769
|
+
"approval_token": {"type": "string"},
|
|
1770
|
+
"network": {
|
|
1771
|
+
"type": "string",
|
|
1772
|
+
"enum": ["ethereum", "base"],
|
|
1773
|
+
"description": "Optional EVM network override for this request.",
|
|
1774
|
+
},
|
|
1775
|
+
},
|
|
1776
|
+
"required": ["operation", "token_address", "mode", "purpose"],
|
|
1777
|
+
"additionalProperties": False,
|
|
1778
|
+
},
|
|
1779
|
+
read_only=False,
|
|
1780
|
+
requires_explicit_user_intent=True,
|
|
1781
|
+
risk_level="high",
|
|
1782
|
+
),
|
|
1783
|
+
)
|
|
1468
1784
|
tools.insert(
|
|
1469
1785
|
8,
|
|
1470
1786
|
AgentToolSpec(
|
|
@@ -2703,12 +3019,16 @@ class OpenClawWalletAdapter:
|
|
|
2703
3019
|
},
|
|
2704
3020
|
"mode": {
|
|
2705
3021
|
"type": "string",
|
|
2706
|
-
"enum": ["preview", "prepare", "execute"],
|
|
2707
|
-
"description": "
|
|
3022
|
+
"enum": ["preview", "prepare", "execute", "intent_preview", "intent_execute"],
|
|
3023
|
+
"description": "Prefer intent_preview then intent_execute after explicit chat confirmation: intent_execute re-derives the Kamino transaction and executes within the approved parameters without round-tripping the preview payload. Legacy preview/prepare/execute remains supported.",
|
|
3024
|
+
},
|
|
3025
|
+
"valid_for_seconds": {
|
|
3026
|
+
"type": "integer",
|
|
3027
|
+
"description": "Optional intent validity window in seconds for intent_preview (1-300, default 120).",
|
|
2708
3028
|
},
|
|
2709
3029
|
"purpose": {"type": "string", "description": "Short explanation of why the deposit is being made."},
|
|
2710
3030
|
"user_intent": {"type": "boolean", "description": "Must be true for prepare mode."},
|
|
2711
|
-
"approval_token": {"type": "string", "description": "Host-issued approval token required for execute mode."},
|
|
3031
|
+
"approval_token": {"type": "string", "description": "Host-issued approval token required for execute/intent_execute mode."},
|
|
2712
3032
|
},
|
|
2713
3033
|
"required": ["market", "reserve", "amount_ui", "mode", "purpose"],
|
|
2714
3034
|
"additionalProperties": False,
|
|
@@ -2741,12 +3061,16 @@ class OpenClawWalletAdapter:
|
|
|
2741
3061
|
},
|
|
2742
3062
|
"mode": {
|
|
2743
3063
|
"type": "string",
|
|
2744
|
-
"enum": ["preview", "prepare", "execute"],
|
|
2745
|
-
"description": "
|
|
3064
|
+
"enum": ["preview", "prepare", "execute", "intent_preview", "intent_execute"],
|
|
3065
|
+
"description": "Prefer intent_preview then intent_execute after explicit chat confirmation: intent_execute re-derives the Kamino transaction and executes within the approved parameters without round-tripping the preview payload. Legacy preview/prepare/execute remains supported.",
|
|
3066
|
+
},
|
|
3067
|
+
"valid_for_seconds": {
|
|
3068
|
+
"type": "integer",
|
|
3069
|
+
"description": "Optional intent validity window in seconds for intent_preview (1-300, default 120).",
|
|
2746
3070
|
},
|
|
2747
3071
|
"purpose": {"type": "string", "description": "Short explanation of why the withdraw is being made."},
|
|
2748
3072
|
"user_intent": {"type": "boolean", "description": "Must be true for prepare mode."},
|
|
2749
|
-
"approval_token": {"type": "string", "description": "Host-issued approval token required for execute mode."},
|
|
3073
|
+
"approval_token": {"type": "string", "description": "Host-issued approval token required for execute/intent_execute mode."},
|
|
2750
3074
|
},
|
|
2751
3075
|
"required": ["market", "reserve", "amount_ui", "mode", "purpose"],
|
|
2752
3076
|
"additionalProperties": False,
|
|
@@ -2779,12 +3103,16 @@ class OpenClawWalletAdapter:
|
|
|
2779
3103
|
},
|
|
2780
3104
|
"mode": {
|
|
2781
3105
|
"type": "string",
|
|
2782
|
-
"enum": ["preview", "prepare", "execute"],
|
|
2783
|
-
"description": "
|
|
3106
|
+
"enum": ["preview", "prepare", "execute", "intent_preview", "intent_execute"],
|
|
3107
|
+
"description": "Prefer intent_preview then intent_execute after explicit chat confirmation: intent_execute re-derives the Kamino transaction and executes within the approved parameters without round-tripping the preview payload. Legacy preview/prepare/execute remains supported.",
|
|
3108
|
+
},
|
|
3109
|
+
"valid_for_seconds": {
|
|
3110
|
+
"type": "integer",
|
|
3111
|
+
"description": "Optional intent validity window in seconds for intent_preview (1-300, default 120).",
|
|
2784
3112
|
},
|
|
2785
3113
|
"purpose": {"type": "string", "description": "Short explanation of why the borrow is being made."},
|
|
2786
3114
|
"user_intent": {"type": "boolean", "description": "Must be true for prepare mode."},
|
|
2787
|
-
"approval_token": {"type": "string", "description": "Host-issued approval token required for execute mode."},
|
|
3115
|
+
"approval_token": {"type": "string", "description": "Host-issued approval token required for execute/intent_execute mode."},
|
|
2788
3116
|
},
|
|
2789
3117
|
"required": ["market", "reserve", "amount_ui", "mode", "purpose"],
|
|
2790
3118
|
"additionalProperties": False,
|
|
@@ -2817,12 +3145,16 @@ class OpenClawWalletAdapter:
|
|
|
2817
3145
|
},
|
|
2818
3146
|
"mode": {
|
|
2819
3147
|
"type": "string",
|
|
2820
|
-
"enum": ["preview", "prepare", "execute"],
|
|
2821
|
-
"description": "
|
|
3148
|
+
"enum": ["preview", "prepare", "execute", "intent_preview", "intent_execute"],
|
|
3149
|
+
"description": "Prefer intent_preview then intent_execute after explicit chat confirmation: intent_execute re-derives the Kamino transaction and executes within the approved parameters without round-tripping the preview payload. Legacy preview/prepare/execute remains supported.",
|
|
3150
|
+
},
|
|
3151
|
+
"valid_for_seconds": {
|
|
3152
|
+
"type": "integer",
|
|
3153
|
+
"description": "Optional intent validity window in seconds for intent_preview (1-300, default 120).",
|
|
2822
3154
|
},
|
|
2823
3155
|
"purpose": {"type": "string", "description": "Short explanation of why the repay is being made."},
|
|
2824
3156
|
"user_intent": {"type": "boolean", "description": "Must be true for prepare mode."},
|
|
2825
|
-
"approval_token": {"type": "string", "description": "Host-issued approval token required for execute mode."},
|
|
3157
|
+
"approval_token": {"type": "string", "description": "Host-issued approval token required for execute/intent_execute mode."},
|
|
2826
3158
|
},
|
|
2827
3159
|
"required": ["market", "reserve", "amount_ui", "mode", "purpose"],
|
|
2828
3160
|
"additionalProperties": False,
|
|
@@ -3607,32 +3939,421 @@ class OpenClawWalletAdapter:
|
|
|
3607
3939
|
),
|
|
3608
3940
|
)
|
|
3609
3941
|
|
|
3610
|
-
if tool_name == "
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3942
|
+
if tool_name == "get_evm_morpho_vaults":
|
|
3943
|
+
data = await active_backend.get_evm_morpho_vaults(
|
|
3944
|
+
vault_address=(
|
|
3945
|
+
str(args.get("vault_address")).strip()
|
|
3946
|
+
if isinstance(args.get("vault_address"), str) and args.get("vault_address").strip()
|
|
3947
|
+
else None
|
|
3948
|
+
),
|
|
3949
|
+
limit=int(args.get("limit")) if args.get("limit") is not None else None,
|
|
3950
|
+
listed_only=bool(args.get("listed_only", True)),
|
|
3951
|
+
asset_address=(
|
|
3952
|
+
str(args.get("asset_address")).strip()
|
|
3953
|
+
if isinstance(args.get("asset_address"), str) and args.get("asset_address").strip()
|
|
3954
|
+
else None
|
|
3955
|
+
),
|
|
3956
|
+
order_by=(
|
|
3957
|
+
str(args.get("order_by")).strip()
|
|
3958
|
+
if isinstance(args.get("order_by"), str) and args.get("order_by").strip()
|
|
3959
|
+
else None
|
|
3960
|
+
),
|
|
3961
|
+
order_direction=(
|
|
3962
|
+
str(args.get("order_direction")).strip()
|
|
3963
|
+
if isinstance(args.get("order_direction"), str) and args.get("order_direction").strip()
|
|
3964
|
+
else None
|
|
3965
|
+
),
|
|
3626
3966
|
)
|
|
3627
3967
|
return AgentToolResult(tool=tool_name, ok=True, data=data)
|
|
3628
3968
|
|
|
3629
|
-
if tool_name == "
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3969
|
+
if tool_name == "get_evm_morpho_markets":
|
|
3970
|
+
data = await active_backend.get_evm_morpho_markets(
|
|
3971
|
+
market_id=(
|
|
3972
|
+
str(args.get("market_id")).strip()
|
|
3973
|
+
if isinstance(args.get("market_id"), str) and args.get("market_id").strip()
|
|
3974
|
+
else None
|
|
3975
|
+
),
|
|
3976
|
+
limit=int(args.get("limit")) if args.get("limit") is not None else None,
|
|
3977
|
+
listed_only=bool(args.get("listed_only", True)),
|
|
3978
|
+
search=(
|
|
3979
|
+
str(args.get("search")).strip()
|
|
3980
|
+
if isinstance(args.get("search"), str) and args.get("search").strip()
|
|
3981
|
+
else None
|
|
3982
|
+
),
|
|
3983
|
+
collateral_asset_address=(
|
|
3984
|
+
str(args.get("collateral_asset_address")).strip()
|
|
3985
|
+
if isinstance(args.get("collateral_asset_address"), str)
|
|
3986
|
+
and args.get("collateral_asset_address").strip()
|
|
3987
|
+
else None
|
|
3988
|
+
),
|
|
3989
|
+
loan_asset_address=(
|
|
3990
|
+
str(args.get("loan_asset_address")).strip()
|
|
3991
|
+
if isinstance(args.get("loan_asset_address"), str)
|
|
3992
|
+
and args.get("loan_asset_address").strip()
|
|
3993
|
+
else None
|
|
3994
|
+
),
|
|
3995
|
+
order_by=(
|
|
3996
|
+
str(args.get("order_by")).strip()
|
|
3997
|
+
if isinstance(args.get("order_by"), str) and args.get("order_by").strip()
|
|
3998
|
+
else None
|
|
3999
|
+
),
|
|
4000
|
+
order_direction=(
|
|
4001
|
+
str(args.get("order_direction")).strip()
|
|
4002
|
+
if isinstance(args.get("order_direction"), str) and args.get("order_direction").strip()
|
|
4003
|
+
else None
|
|
4004
|
+
),
|
|
4005
|
+
)
|
|
4006
|
+
return AgentToolResult(tool=tool_name, ok=True, data=data)
|
|
4007
|
+
|
|
4008
|
+
if tool_name == "get_evm_morpho_positions":
|
|
4009
|
+
data = await active_backend.get_evm_morpho_positions()
|
|
4010
|
+
return AgentToolResult(tool=tool_name, ok=True, data=data)
|
|
4011
|
+
|
|
4012
|
+
if tool_name == "manage_evm_morpho_vault_position":
|
|
4013
|
+
operation = args.get("operation")
|
|
4014
|
+
token_address = args.get("token_address")
|
|
4015
|
+
vault_address = args.get("vault_address")
|
|
4016
|
+
vault_preset = args.get("vault_preset")
|
|
4017
|
+
amount_raw = args.get("amount_raw")
|
|
4018
|
+
native_amount_raw = args.get("native_amount_raw")
|
|
4019
|
+
mode = args.get("mode")
|
|
4020
|
+
purpose = args.get("purpose")
|
|
4021
|
+
user_intent = args.get("user_intent", False)
|
|
4022
|
+
approval_token = args.get("approval_token")
|
|
4023
|
+
|
|
4024
|
+
if operation not in {"supply", "withdraw"}:
|
|
4025
|
+
raise WalletBackendError("operation must be one of: supply, withdraw.")
|
|
4026
|
+
if not isinstance(token_address, str) or not token_address.strip():
|
|
4027
|
+
raise WalletBackendError("token_address is required.")
|
|
4028
|
+
if bool(vault_address) == bool(vault_preset):
|
|
4029
|
+
raise WalletBackendError("Provide exactly one of vault_address or vault_preset.")
|
|
4030
|
+
if operation == "supply":
|
|
4031
|
+
has_amount = isinstance(amount_raw, str) and amount_raw.strip().isdigit() and int(amount_raw.strip()) > 0
|
|
4032
|
+
has_native_amount = (
|
|
4033
|
+
isinstance(native_amount_raw, str)
|
|
4034
|
+
and native_amount_raw.strip().isdigit()
|
|
4035
|
+
and int(native_amount_raw.strip()) > 0
|
|
4036
|
+
)
|
|
4037
|
+
if not has_amount and not has_native_amount:
|
|
4038
|
+
raise WalletBackendError(
|
|
4039
|
+
"amount_raw or native_amount_raw must be a positive integer string for Morpho vault supply."
|
|
4040
|
+
)
|
|
4041
|
+
else:
|
|
4042
|
+
if not isinstance(amount_raw, str) or not amount_raw.strip().isdigit():
|
|
4043
|
+
raise WalletBackendError("amount_raw must be a positive integer string.")
|
|
4044
|
+
if int(amount_raw.strip()) <= 0:
|
|
4045
|
+
raise WalletBackendError("amount_raw must be greater than zero.")
|
|
4046
|
+
if mode not in {"preview", "prepare", "execute"}:
|
|
4047
|
+
raise WalletBackendError("mode must be 'preview', 'prepare' or 'execute'.")
|
|
4048
|
+
if not isinstance(purpose, str) or not purpose.strip():
|
|
4049
|
+
raise WalletBackendError("purpose is required.")
|
|
4050
|
+
|
|
4051
|
+
preview_kwargs = {
|
|
4052
|
+
"operation": str(operation),
|
|
4053
|
+
"token_address": token_address.strip(),
|
|
4054
|
+
**({"vault_address": vault_address.strip()} if isinstance(vault_address, str) and vault_address.strip() else {}),
|
|
4055
|
+
**({"vault_preset": vault_preset.strip()} if isinstance(vault_preset, str) and vault_preset.strip() else {}),
|
|
4056
|
+
**({"amount_raw": amount_raw.strip()} if isinstance(amount_raw, str) and amount_raw.strip() else {}),
|
|
4057
|
+
**(
|
|
4058
|
+
{"native_amount_raw": native_amount_raw.strip()}
|
|
4059
|
+
if isinstance(native_amount_raw, str) and native_amount_raw.strip()
|
|
4060
|
+
else {}
|
|
4061
|
+
),
|
|
4062
|
+
}
|
|
4063
|
+
|
|
4064
|
+
if mode == "preview":
|
|
4065
|
+
preview = await active_backend.preview_evm_morpho_vault_operation(**preview_kwargs)
|
|
4066
|
+
return AgentToolResult(
|
|
4067
|
+
tool=tool_name,
|
|
4068
|
+
ok=True,
|
|
4069
|
+
data=self._annotate_sensitive_payload(
|
|
4070
|
+
preview,
|
|
4071
|
+
action_label="EVM Morpho vault operation",
|
|
4072
|
+
mode="preview",
|
|
4073
|
+
),
|
|
4074
|
+
)
|
|
4075
|
+
|
|
4076
|
+
if mode == "prepare":
|
|
4077
|
+
self._require_prepare_intent(user_intent)
|
|
4078
|
+
preview = await active_backend.preview_evm_morpho_vault_operation(**preview_kwargs)
|
|
4079
|
+
return AgentToolResult(
|
|
4080
|
+
tool=tool_name,
|
|
4081
|
+
ok=True,
|
|
4082
|
+
data=self._annotate_sensitive_payload(
|
|
4083
|
+
self._build_prepare_plan(
|
|
4084
|
+
preview_payload=preview,
|
|
4085
|
+
action_label="EVM Morpho vault operation",
|
|
4086
|
+
),
|
|
4087
|
+
action_label="EVM Morpho vault operation",
|
|
4088
|
+
mode="prepare",
|
|
4089
|
+
),
|
|
4090
|
+
)
|
|
4091
|
+
|
|
4092
|
+
approval_payload = inspect_approval_token(
|
|
4093
|
+
approval_token,
|
|
4094
|
+
tool_name=tool_name,
|
|
4095
|
+
network=str(getattr(active_backend, "network", "unknown")),
|
|
4096
|
+
require_mainnet_confirmation=self._is_mainnet_for_backend(active_backend),
|
|
4097
|
+
)
|
|
4098
|
+
approval_summary = approval_payload.get("binding", {}).get("summary")
|
|
4099
|
+
if not isinstance(approval_summary, dict):
|
|
4100
|
+
raise WalletBackendError(
|
|
4101
|
+
"approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
|
|
4102
|
+
)
|
|
4103
|
+
expected_summary = {
|
|
4104
|
+
"operation": "EVM Morpho vault operation",
|
|
4105
|
+
"network": str(getattr(active_backend, "network", "unknown")),
|
|
4106
|
+
"morpho_surface": "vault",
|
|
4107
|
+
"morpho_operation": str(operation),
|
|
4108
|
+
"token_address": token_address.strip(),
|
|
4109
|
+
}
|
|
4110
|
+
if isinstance(amount_raw, str) and amount_raw.strip():
|
|
4111
|
+
expected_summary["amount_raw"] = amount_raw.strip()
|
|
4112
|
+
if isinstance(native_amount_raw, str) and native_amount_raw.strip():
|
|
4113
|
+
expected_summary["native_amount_raw"] = native_amount_raw.strip()
|
|
4114
|
+
target = approval_summary.get("target")
|
|
4115
|
+
if not isinstance(target, dict):
|
|
4116
|
+
raise WalletBackendError(
|
|
4117
|
+
"approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
|
|
4118
|
+
)
|
|
4119
|
+
expected_target_key = "vaultAddress" if vault_address else "vaultPreset"
|
|
4120
|
+
expected_target_value = vault_address.strip() if vault_address else vault_preset.strip()
|
|
4121
|
+
for key, expected_value in expected_summary.items():
|
|
4122
|
+
if approval_summary.get(key) != expected_value:
|
|
4123
|
+
raise WalletBackendError(
|
|
4124
|
+
"approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
|
|
4125
|
+
)
|
|
4126
|
+
actual_target_value = target.get(expected_target_key)
|
|
4127
|
+
if vault_address:
|
|
4128
|
+
# The runtime lowercases the resolved vault address in the
|
|
4129
|
+
# approval binding, so compare addresses case-insensitively.
|
|
4130
|
+
target_matches = (
|
|
4131
|
+
isinstance(actual_target_value, str)
|
|
4132
|
+
and actual_target_value.lower() == expected_target_value.lower()
|
|
4133
|
+
)
|
|
4134
|
+
else:
|
|
4135
|
+
target_matches = actual_target_value == expected_target_value
|
|
4136
|
+
if not target_matches:
|
|
4137
|
+
raise WalletBackendError(
|
|
4138
|
+
"approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
|
|
4139
|
+
)
|
|
4140
|
+
|
|
4141
|
+
approval_summary_copy = dict(approval_summary)
|
|
4142
|
+
self._require_execute_approval(
|
|
4143
|
+
approval_token=approval_token,
|
|
4144
|
+
tool_name=tool_name,
|
|
4145
|
+
summary=approval_summary_copy,
|
|
4146
|
+
action_label="EVM Morpho vault operation",
|
|
4147
|
+
backend=active_backend,
|
|
4148
|
+
)
|
|
4149
|
+
result = await active_backend.send_evm_morpho_vault_operation(
|
|
4150
|
+
**preview_kwargs,
|
|
4151
|
+
expected_quote_fingerprint=(
|
|
4152
|
+
str(approval_summary_copy.get("quote_fingerprint")).strip()
|
|
4153
|
+
if approval_summary_copy.get("quote_fingerprint") is not None
|
|
4154
|
+
else None
|
|
4155
|
+
),
|
|
4156
|
+
)
|
|
4157
|
+
return AgentToolResult(
|
|
4158
|
+
tool=tool_name,
|
|
4159
|
+
ok=True,
|
|
4160
|
+
data=self._annotate_sensitive_payload(
|
|
4161
|
+
result,
|
|
4162
|
+
action_label="EVM Morpho vault operation",
|
|
4163
|
+
mode="execute",
|
|
4164
|
+
),
|
|
4165
|
+
)
|
|
4166
|
+
|
|
4167
|
+
if tool_name == "manage_evm_morpho_market_position":
|
|
4168
|
+
operation = args.get("operation")
|
|
4169
|
+
token_address = args.get("token_address")
|
|
4170
|
+
market_id = args.get("market_id")
|
|
4171
|
+
market_preset = args.get("market_preset")
|
|
4172
|
+
amount_raw = args.get("amount_raw")
|
|
4173
|
+
native_amount_raw = args.get("native_amount_raw")
|
|
4174
|
+
mode = args.get("mode")
|
|
4175
|
+
purpose = args.get("purpose")
|
|
4176
|
+
user_intent = args.get("user_intent", False)
|
|
4177
|
+
approval_token = args.get("approval_token")
|
|
4178
|
+
|
|
4179
|
+
if operation not in {"supply_collateral", "borrow", "repay", "withdraw_collateral"}:
|
|
4180
|
+
raise WalletBackendError(
|
|
4181
|
+
"operation must be one of: supply_collateral, borrow, repay, withdraw_collateral."
|
|
4182
|
+
)
|
|
4183
|
+
if not isinstance(token_address, str) or not token_address.strip():
|
|
4184
|
+
raise WalletBackendError("token_address is required.")
|
|
4185
|
+
if bool(market_id) == bool(market_preset):
|
|
4186
|
+
raise WalletBackendError("Provide exactly one of market_id or market_preset.")
|
|
4187
|
+
if operation == "supply_collateral":
|
|
4188
|
+
has_amount = isinstance(amount_raw, str) and amount_raw.strip().isdigit() and int(amount_raw.strip()) > 0
|
|
4189
|
+
has_native_amount = (
|
|
4190
|
+
isinstance(native_amount_raw, str)
|
|
4191
|
+
and native_amount_raw.strip().isdigit()
|
|
4192
|
+
and int(native_amount_raw.strip()) > 0
|
|
4193
|
+
)
|
|
4194
|
+
if not has_amount and not has_native_amount:
|
|
4195
|
+
raise WalletBackendError(
|
|
4196
|
+
"amount_raw or native_amount_raw must be a positive integer string for Morpho supply_collateral."
|
|
4197
|
+
)
|
|
4198
|
+
elif operation == "repay":
|
|
4199
|
+
normalized_amount = str(amount_raw or "").strip().lower()
|
|
4200
|
+
if normalized_amount != "max":
|
|
4201
|
+
if not normalized_amount.isdigit() or int(normalized_amount) <= 0:
|
|
4202
|
+
raise WalletBackendError(
|
|
4203
|
+
"amount_raw must be a positive integer string or the literal max for Morpho repay."
|
|
4204
|
+
)
|
|
4205
|
+
else:
|
|
4206
|
+
if not isinstance(amount_raw, str) or not amount_raw.strip().isdigit():
|
|
4207
|
+
raise WalletBackendError("amount_raw must be a positive integer string.")
|
|
4208
|
+
if int(amount_raw.strip()) <= 0:
|
|
4209
|
+
raise WalletBackendError("amount_raw must be greater than zero.")
|
|
4210
|
+
if mode not in {"preview", "prepare", "execute"}:
|
|
4211
|
+
raise WalletBackendError("mode must be 'preview', 'prepare' or 'execute'.")
|
|
4212
|
+
if not isinstance(purpose, str) or not purpose.strip():
|
|
4213
|
+
raise WalletBackendError("purpose is required.")
|
|
4214
|
+
|
|
4215
|
+
preview_kwargs = {
|
|
4216
|
+
"operation": str(operation),
|
|
4217
|
+
"token_address": token_address.strip(),
|
|
4218
|
+
**({"market_id": market_id.strip()} if isinstance(market_id, str) and market_id.strip() else {}),
|
|
4219
|
+
**({"market_preset": market_preset.strip()} if isinstance(market_preset, str) and market_preset.strip() else {}),
|
|
4220
|
+
**({"amount_raw": amount_raw.strip()} if isinstance(amount_raw, str) and amount_raw.strip() else {}),
|
|
4221
|
+
**(
|
|
4222
|
+
{"native_amount_raw": native_amount_raw.strip()}
|
|
4223
|
+
if isinstance(native_amount_raw, str) and native_amount_raw.strip()
|
|
4224
|
+
else {}
|
|
4225
|
+
),
|
|
4226
|
+
}
|
|
4227
|
+
|
|
4228
|
+
if mode == "preview":
|
|
4229
|
+
preview = await active_backend.preview_evm_morpho_market_operation(**preview_kwargs)
|
|
4230
|
+
return AgentToolResult(
|
|
4231
|
+
tool=tool_name,
|
|
4232
|
+
ok=True,
|
|
4233
|
+
data=self._annotate_sensitive_payload(
|
|
4234
|
+
preview,
|
|
4235
|
+
action_label="EVM Morpho market operation",
|
|
4236
|
+
mode="preview",
|
|
4237
|
+
),
|
|
4238
|
+
)
|
|
4239
|
+
|
|
4240
|
+
if mode == "prepare":
|
|
4241
|
+
self._require_prepare_intent(user_intent)
|
|
4242
|
+
preview = await active_backend.preview_evm_morpho_market_operation(**preview_kwargs)
|
|
4243
|
+
return AgentToolResult(
|
|
4244
|
+
tool=tool_name,
|
|
4245
|
+
ok=True,
|
|
4246
|
+
data=self._annotate_sensitive_payload(
|
|
4247
|
+
self._build_prepare_plan(
|
|
4248
|
+
preview_payload=preview,
|
|
4249
|
+
action_label="EVM Morpho market operation",
|
|
4250
|
+
),
|
|
4251
|
+
action_label="EVM Morpho market operation",
|
|
4252
|
+
mode="prepare",
|
|
4253
|
+
),
|
|
4254
|
+
)
|
|
4255
|
+
|
|
4256
|
+
approval_payload = inspect_approval_token(
|
|
4257
|
+
approval_token,
|
|
4258
|
+
tool_name=tool_name,
|
|
4259
|
+
network=str(getattr(active_backend, "network", "unknown")),
|
|
4260
|
+
require_mainnet_confirmation=self._is_mainnet_for_backend(active_backend),
|
|
4261
|
+
)
|
|
4262
|
+
approval_summary = approval_payload.get("binding", {}).get("summary")
|
|
4263
|
+
if not isinstance(approval_summary, dict):
|
|
4264
|
+
raise WalletBackendError(
|
|
4265
|
+
"approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
|
|
4266
|
+
)
|
|
4267
|
+
expected_summary = {
|
|
4268
|
+
"operation": "EVM Morpho market operation",
|
|
4269
|
+
"network": str(getattr(active_backend, "network", "unknown")),
|
|
4270
|
+
"morpho_surface": "market",
|
|
4271
|
+
"morpho_operation": str(operation),
|
|
4272
|
+
"token_address": token_address.strip(),
|
|
4273
|
+
}
|
|
4274
|
+
if isinstance(amount_raw, str) and amount_raw.strip():
|
|
4275
|
+
expected_summary["amount_raw"] = amount_raw.strip()
|
|
4276
|
+
if isinstance(native_amount_raw, str) and native_amount_raw.strip():
|
|
4277
|
+
expected_summary["native_amount_raw"] = native_amount_raw.strip()
|
|
4278
|
+
target = approval_summary.get("target")
|
|
4279
|
+
if not isinstance(target, dict):
|
|
4280
|
+
raise WalletBackendError(
|
|
4281
|
+
"approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
|
|
4282
|
+
)
|
|
4283
|
+
expected_target_key = "marketId" if market_id else "marketPreset"
|
|
4284
|
+
expected_target_value = market_id.strip() if market_id else market_preset.strip()
|
|
4285
|
+
for key, expected_value in expected_summary.items():
|
|
4286
|
+
if approval_summary.get(key) != expected_value:
|
|
4287
|
+
raise WalletBackendError(
|
|
4288
|
+
"approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
|
|
4289
|
+
)
|
|
4290
|
+
actual_target_value = target.get(expected_target_key)
|
|
4291
|
+
if market_id:
|
|
4292
|
+
# Market ids are hex hashes; the runtime may normalize their
|
|
4293
|
+
# case in the approval binding, so compare case-insensitively.
|
|
4294
|
+
target_matches = (
|
|
4295
|
+
isinstance(actual_target_value, str)
|
|
4296
|
+
and actual_target_value.lower() == expected_target_value.lower()
|
|
4297
|
+
)
|
|
4298
|
+
else:
|
|
4299
|
+
target_matches = actual_target_value == expected_target_value
|
|
4300
|
+
if not target_matches:
|
|
4301
|
+
raise WalletBackendError(
|
|
4302
|
+
"approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
|
|
4303
|
+
)
|
|
4304
|
+
|
|
4305
|
+
approval_summary_copy = dict(approval_summary)
|
|
4306
|
+
self._require_execute_approval(
|
|
4307
|
+
approval_token=approval_token,
|
|
4308
|
+
tool_name=tool_name,
|
|
4309
|
+
summary=approval_summary_copy,
|
|
4310
|
+
action_label="EVM Morpho market operation",
|
|
4311
|
+
backend=active_backend,
|
|
4312
|
+
)
|
|
4313
|
+
result = await active_backend.send_evm_morpho_market_operation(
|
|
4314
|
+
**preview_kwargs,
|
|
4315
|
+
expected_quote_fingerprint=(
|
|
4316
|
+
str(approval_summary_copy.get("quote_fingerprint")).strip()
|
|
4317
|
+
if approval_summary_copy.get("quote_fingerprint") is not None
|
|
4318
|
+
else None
|
|
4319
|
+
),
|
|
4320
|
+
)
|
|
4321
|
+
return AgentToolResult(
|
|
4322
|
+
tool=tool_name,
|
|
4323
|
+
ok=True,
|
|
4324
|
+
data=self._annotate_sensitive_payload(
|
|
4325
|
+
result,
|
|
4326
|
+
action_label="EVM Morpho market operation",
|
|
4327
|
+
mode="execute",
|
|
4328
|
+
),
|
|
4329
|
+
)
|
|
4330
|
+
|
|
4331
|
+
if tool_name == "get_evm_swap_quote":
|
|
4332
|
+
token_in = args.get("token_in")
|
|
4333
|
+
token_out = args.get("token_out")
|
|
4334
|
+
amount_in_raw = args.get("amount_in_raw")
|
|
4335
|
+
if not isinstance(token_in, str) or not token_in.strip():
|
|
4336
|
+
raise WalletBackendError("token_in is required.")
|
|
4337
|
+
if not isinstance(token_out, str) or not token_out.strip():
|
|
4338
|
+
raise WalletBackendError("token_out is required.")
|
|
4339
|
+
if not isinstance(amount_in_raw, str) or not amount_in_raw.strip().isdigit():
|
|
4340
|
+
raise WalletBackendError("amount_in_raw must be a positive integer string.")
|
|
4341
|
+
if int(amount_in_raw.strip()) <= 0:
|
|
4342
|
+
raise WalletBackendError("amount_in_raw must be greater than zero.")
|
|
4343
|
+
data = await active_backend.get_evm_swap_quote(
|
|
4344
|
+
token_in=self._canonicalize_velora_token_identifier(token_in),
|
|
4345
|
+
token_out=self._canonicalize_velora_token_identifier(token_out),
|
|
4346
|
+
amount_in_raw=amount_in_raw.strip(),
|
|
4347
|
+
)
|
|
4348
|
+
return AgentToolResult(tool=tool_name, ok=True, data=data)
|
|
4349
|
+
|
|
4350
|
+
if tool_name == "swap_evm_tokens":
|
|
4351
|
+
token_in = args.get("token_in")
|
|
4352
|
+
token_out = args.get("token_out")
|
|
4353
|
+
amount_in_raw = args.get("amount_in_raw")
|
|
4354
|
+
mode = args.get("mode")
|
|
4355
|
+
purpose = args.get("purpose")
|
|
4356
|
+
user_intent = args.get("user_intent", False)
|
|
3636
4357
|
approval_token = args.get("approval_token")
|
|
3637
4358
|
|
|
3638
4359
|
if not isinstance(token_in, str) or not token_in.strip():
|
|
@@ -4794,6 +5515,7 @@ class OpenClawWalletAdapter:
|
|
|
4794
5515
|
purpose = args.get("purpose")
|
|
4795
5516
|
user_intent = args.get("user_intent", False)
|
|
4796
5517
|
approval_token = args.get("approval_token")
|
|
5518
|
+
valid_for_seconds = args.get("valid_for_seconds", 120)
|
|
4797
5519
|
|
|
4798
5520
|
if not isinstance(market, str) or not market.strip():
|
|
4799
5521
|
raise WalletBackendError("market is required.")
|
|
@@ -4803,10 +5525,18 @@ class OpenClawWalletAdapter:
|
|
|
4803
5525
|
raise WalletBackendError("amount_ui is required.")
|
|
4804
5526
|
if obligation_address is not None and not isinstance(obligation_address, str):
|
|
4805
5527
|
raise WalletBackendError("obligation_address must be a string when provided.")
|
|
4806
|
-
if mode not in {"preview", "prepare", "execute"}:
|
|
4807
|
-
raise WalletBackendError(
|
|
5528
|
+
if mode not in {"preview", "prepare", "execute", "intent_preview", "intent_execute"}:
|
|
5529
|
+
raise WalletBackendError(
|
|
5530
|
+
"mode must be 'preview', 'prepare', 'execute', 'intent_preview' or 'intent_execute'."
|
|
5531
|
+
)
|
|
4808
5532
|
if not isinstance(purpose, str) or not purpose.strip():
|
|
4809
5533
|
raise WalletBackendError("purpose is required.")
|
|
5534
|
+
if mode == "intent_preview" and (
|
|
5535
|
+
not isinstance(valid_for_seconds, int)
|
|
5536
|
+
or valid_for_seconds <= 0
|
|
5537
|
+
or valid_for_seconds > 300
|
|
5538
|
+
):
|
|
5539
|
+
raise WalletBackendError("valid_for_seconds must be an integer between 1 and 300.")
|
|
4810
5540
|
|
|
4811
5541
|
action_label_map = {
|
|
4812
5542
|
"kamino_lend_deposit": "Kamino deposit",
|
|
@@ -4814,12 +5544,24 @@ class OpenClawWalletAdapter:
|
|
|
4814
5544
|
"kamino_lend_borrow": "Kamino borrow",
|
|
4815
5545
|
"kamino_lend_repay": "Kamino repay",
|
|
4816
5546
|
}
|
|
5547
|
+
intent_action_label_map = {
|
|
5548
|
+
"kamino_lend_deposit": "Kamino deposit intent",
|
|
5549
|
+
"kamino_lend_withdraw": "Kamino withdraw intent",
|
|
5550
|
+
"kamino_lend_borrow": "Kamino borrow intent",
|
|
5551
|
+
"kamino_lend_repay": "Kamino repay intent",
|
|
5552
|
+
}
|
|
4817
5553
|
preview_method_map = {
|
|
4818
5554
|
"kamino_lend_deposit": self.backend.preview_kamino_lend_deposit,
|
|
4819
5555
|
"kamino_lend_withdraw": self.backend.preview_kamino_lend_withdraw,
|
|
4820
5556
|
"kamino_lend_borrow": self.backend.preview_kamino_lend_borrow,
|
|
4821
5557
|
"kamino_lend_repay": self.backend.preview_kamino_lend_repay,
|
|
4822
5558
|
}
|
|
5559
|
+
intent_preview_method_map = {
|
|
5560
|
+
"kamino_lend_deposit": self.backend.preview_kamino_lend_deposit_intent,
|
|
5561
|
+
"kamino_lend_withdraw": self.backend.preview_kamino_lend_withdraw_intent,
|
|
5562
|
+
"kamino_lend_borrow": self.backend.preview_kamino_lend_borrow_intent,
|
|
5563
|
+
"kamino_lend_repay": self.backend.preview_kamino_lend_repay_intent,
|
|
5564
|
+
}
|
|
4823
5565
|
execute_method_map = {
|
|
4824
5566
|
"kamino_lend_deposit": self.backend.execute_kamino_lend_deposit,
|
|
4825
5567
|
"kamino_lend_withdraw": self.backend.execute_kamino_lend_withdraw,
|
|
@@ -4827,8 +5569,113 @@ class OpenClawWalletAdapter:
|
|
|
4827
5569
|
"kamino_lend_repay": self.backend.execute_kamino_lend_repay,
|
|
4828
5570
|
}
|
|
4829
5571
|
action_label = action_label_map[tool_name]
|
|
5572
|
+
intent_action_label = intent_action_label_map[tool_name]
|
|
4830
5573
|
preview_method = preview_method_map[tool_name]
|
|
5574
|
+
intent_preview_method = intent_preview_method_map[tool_name]
|
|
4831
5575
|
execute_method = execute_method_map[tool_name]
|
|
5576
|
+
normalized_obligation_address = (
|
|
5577
|
+
obligation_address.strip()
|
|
5578
|
+
if isinstance(obligation_address, str) and obligation_address.strip()
|
|
5579
|
+
else None
|
|
5580
|
+
)
|
|
5581
|
+
|
|
5582
|
+
if mode == "intent_preview":
|
|
5583
|
+
intent_preview = await intent_preview_method(
|
|
5584
|
+
market=market.strip(),
|
|
5585
|
+
reserve=reserve.strip(),
|
|
5586
|
+
amount_ui=amount_ui.strip(),
|
|
5587
|
+
obligation_address=normalized_obligation_address,
|
|
5588
|
+
valid_for_seconds=valid_for_seconds,
|
|
5589
|
+
)
|
|
5590
|
+
if bool(intent_preview.get("requires_obligation_address")):
|
|
5591
|
+
raise WalletBackendError(
|
|
5592
|
+
f"{action_label} requires obligation_address when multiple Kamino obligations match the selected position."
|
|
5593
|
+
)
|
|
5594
|
+
return AgentToolResult(
|
|
5595
|
+
tool=tool_name,
|
|
5596
|
+
ok=True,
|
|
5597
|
+
data=self._annotate_sensitive_payload(
|
|
5598
|
+
intent_preview,
|
|
5599
|
+
action_label=intent_action_label,
|
|
5600
|
+
mode="preview",
|
|
5601
|
+
),
|
|
5602
|
+
)
|
|
5603
|
+
|
|
5604
|
+
if mode == "intent_execute":
|
|
5605
|
+
approval_payload = inspect_approval_token(
|
|
5606
|
+
approval_token,
|
|
5607
|
+
tool_name=tool_name,
|
|
5608
|
+
network=str(getattr(self.backend, "network", "unknown")),
|
|
5609
|
+
require_mainnet_confirmation=self._is_mainnet_for_backend(self.backend),
|
|
5610
|
+
)
|
|
5611
|
+
approval_summary = approval_payload.get("binding", {}).get("summary")
|
|
5612
|
+
if not isinstance(approval_summary, dict):
|
|
5613
|
+
raise WalletBackendError(
|
|
5614
|
+
"approval_token does not match the requested operation. Generate a new intent preview and approval before execute."
|
|
5615
|
+
)
|
|
5616
|
+
expected_summary = {
|
|
5617
|
+
"operation": intent_action_label,
|
|
5618
|
+
"network": str(getattr(self.backend, "network", "unknown")),
|
|
5619
|
+
"market": market.strip(),
|
|
5620
|
+
"reserve": reserve.strip(),
|
|
5621
|
+
"amount_ui": amount_ui.strip(),
|
|
5622
|
+
}
|
|
5623
|
+
for key, expected_value in expected_summary.items():
|
|
5624
|
+
if approval_summary.get(key) != expected_value:
|
|
5625
|
+
raise WalletBackendError(
|
|
5626
|
+
f"approval_token does not match the requested {action_label} intent. Generate a fresh intent preview and approval before execute."
|
|
5627
|
+
)
|
|
5628
|
+
if approval_summary.get("recipient_policy") != "owner-only":
|
|
5629
|
+
raise WalletBackendError("approved Kamino intent recipient policy is invalid.")
|
|
5630
|
+
if approval_summary.get("spend_policy") != "exact-amount":
|
|
5631
|
+
raise WalletBackendError("approved Kamino intent spend policy is invalid.")
|
|
5632
|
+
current_owner = await self.backend.get_address()
|
|
5633
|
+
approved_owner = approval_summary.get("owner")
|
|
5634
|
+
if approved_owner and current_owner and str(approved_owner) != str(current_owner):
|
|
5635
|
+
raise WalletBackendError(
|
|
5636
|
+
"approval_token does not match the active wallet owner. Generate a fresh intent preview and approval before execute."
|
|
5637
|
+
)
|
|
5638
|
+
approved_obligation = approval_summary.get("obligation_address")
|
|
5639
|
+
if (
|
|
5640
|
+
normalized_obligation_address is not None
|
|
5641
|
+
and approved_obligation is not None
|
|
5642
|
+
and str(approved_obligation) != normalized_obligation_address
|
|
5643
|
+
):
|
|
5644
|
+
raise WalletBackendError(
|
|
5645
|
+
"approval_token does not match the requested obligation. Generate a fresh intent preview and approval before execute."
|
|
5646
|
+
)
|
|
5647
|
+
valid_until = approval_summary.get("valid_until_epoch_seconds")
|
|
5648
|
+
if valid_until is not None and int(time.time()) > int(valid_until):
|
|
5649
|
+
raise WalletBackendError(
|
|
5650
|
+
"Approved Kamino intent has expired. Create a fresh intent preview."
|
|
5651
|
+
)
|
|
5652
|
+
approval_summary_copy = dict(approval_summary)
|
|
5653
|
+
self._require_execute_approval(
|
|
5654
|
+
approval_token=approval_token,
|
|
5655
|
+
tool_name=tool_name,
|
|
5656
|
+
summary=approval_summary_copy,
|
|
5657
|
+
action_label=intent_action_label,
|
|
5658
|
+
)
|
|
5659
|
+
result = await execute_method(
|
|
5660
|
+
market=market.strip(),
|
|
5661
|
+
reserve=reserve.strip(),
|
|
5662
|
+
amount_ui=amount_ui.strip(),
|
|
5663
|
+
obligation_address=(
|
|
5664
|
+
str(approved_obligation).strip()
|
|
5665
|
+
if approved_obligation
|
|
5666
|
+
else normalized_obligation_address
|
|
5667
|
+
),
|
|
5668
|
+
approved_preview=None,
|
|
5669
|
+
)
|
|
5670
|
+
return AgentToolResult(
|
|
5671
|
+
tool=tool_name,
|
|
5672
|
+
ok=True,
|
|
5673
|
+
data=self._annotate_sensitive_payload(
|
|
5674
|
+
result,
|
|
5675
|
+
action_label=action_label,
|
|
5676
|
+
mode="execute",
|
|
5677
|
+
),
|
|
5678
|
+
)
|
|
4832
5679
|
|
|
4833
5680
|
if mode == "preview":
|
|
4834
5681
|
preview = await preview_method(
|