@agentlayer.tech/wallet 0.1.66 → 0.1.67
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 +82 -0
- package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +7 -1
- package/.openclaw/extensions/agent-wallet/package.json +1 -1
- package/VERSION +1 -1
- package/agent-wallet/README.md +6 -0
- package/agent-wallet/agent_wallet/__init__.py +1 -1
- package/agent-wallet/agent_wallet/openclaw_adapter.py +481 -4
- package/agent-wallet/agent_wallet/providers/kamino.py +169 -3
- package/agent-wallet/agent_wallet/transaction_policy.py +60 -0
- package/agent-wallet/agent_wallet/wallet_layer/base.py +124 -0
- package/agent-wallet/agent_wallet/wallet_layer/solana.py +563 -0
- package/agent-wallet/agent_wallet/wallet_layer/wdk_evm.py +16 -0
- package/agent-wallet/openclaw.plugin.json +2 -2
- package/agent-wallet/pyproject.toml +1 -1
- package/agent-wallet/scripts/install_openclaw_local_config.py +6 -0
- package/agent-wallet/skills/wallet-operator/SKILL.md +5 -3
- 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/package.json +1 -1
- package/wdk-evm-wallet/src/wdk_evm_wallet.js +126 -19
|
@@ -116,6 +116,17 @@ class OpenClawWalletAdapter:
|
|
|
116
116
|
raise WalletBackendError("network must be a non-empty string when provided.")
|
|
117
117
|
return self.backend.with_network(self._normalize_evm_tool_network(requested_network))
|
|
118
118
|
|
|
119
|
+
def _normalize_optional_non_negative_number(
|
|
120
|
+
self, value: Any, *, field_name: str
|
|
121
|
+
) -> float | int | None:
|
|
122
|
+
if value is None:
|
|
123
|
+
return None
|
|
124
|
+
if isinstance(value, bool) or not isinstance(value, (int, float)):
|
|
125
|
+
raise WalletBackendError(f"{field_name} must be a number when provided.")
|
|
126
|
+
if value < 0:
|
|
127
|
+
raise WalletBackendError(f"{field_name} must not be negative.")
|
|
128
|
+
return value
|
|
129
|
+
|
|
119
130
|
def _normalize_positive_limit(self, value: Any, *, field_name: str, default: int, maximum: int) -> int:
|
|
120
131
|
if value is None:
|
|
121
132
|
return default
|
|
@@ -583,6 +594,19 @@ class OpenClawWalletAdapter:
|
|
|
583
594
|
summary["obligation_address"] = obligation_address
|
|
584
595
|
return summary
|
|
585
596
|
|
|
597
|
+
if asset_type == "kamino-earn-intent":
|
|
598
|
+
return {
|
|
599
|
+
"operation": action_label,
|
|
600
|
+
"network": str(payload.get("network") or getattr(self.backend, "network", "unknown")),
|
|
601
|
+
"owner": payload.get("owner"),
|
|
602
|
+
"kamino_operation": payload.get("kamino_operation"),
|
|
603
|
+
"kvault": payload.get("kvault"),
|
|
604
|
+
"amount_ui": payload.get("amount_ui"),
|
|
605
|
+
"recipient_policy": payload.get("recipient_policy"),
|
|
606
|
+
"spend_policy": payload.get("spend_policy"),
|
|
607
|
+
"valid_until_epoch_seconds": payload.get("valid_until_epoch_seconds"),
|
|
608
|
+
}
|
|
609
|
+
|
|
586
610
|
if asset_type == "solana-lifi-cross-chain-swap":
|
|
587
611
|
return {
|
|
588
612
|
"operation": action_label,
|
|
@@ -985,6 +1009,7 @@ class OpenClawWalletAdapter:
|
|
|
985
1009
|
"owner",
|
|
986
1010
|
"authority",
|
|
987
1011
|
"address",
|
|
1012
|
+
"kvault",
|
|
988
1013
|
"obligation_address",
|
|
989
1014
|
"market",
|
|
990
1015
|
"reserve",
|
|
@@ -1743,6 +1768,16 @@ class OpenClawWalletAdapter:
|
|
|
1743
1768
|
"type": "string",
|
|
1744
1769
|
"description": "Optional underlying asset address to filter vaults (e.g. only USDC vaults).",
|
|
1745
1770
|
},
|
|
1771
|
+
"min_tvl_usd": {
|
|
1772
|
+
"type": "number",
|
|
1773
|
+
"minimum": 0,
|
|
1774
|
+
"description": "Optional minimum vault TVL in USD. Recommended when sorting by APY, so dust vaults with inflated yields are excluded (e.g. 1000000).",
|
|
1775
|
+
},
|
|
1776
|
+
"min_net_apy": {
|
|
1777
|
+
"type": "number",
|
|
1778
|
+
"minimum": 0,
|
|
1779
|
+
"description": "Optional minimum net APY as a fraction (e.g. 0.03 for 3%).",
|
|
1780
|
+
},
|
|
1746
1781
|
"order_by": {
|
|
1747
1782
|
"type": "string",
|
|
1748
1783
|
"enum": [
|
|
@@ -1755,7 +1790,7 @@ class OpenClawWalletAdapter:
|
|
|
1755
1790
|
"NetApy",
|
|
1756
1791
|
"Address",
|
|
1757
1792
|
],
|
|
1758
|
-
"description": "Optional sort field when listing. Defaults to TotalAssetsUsd (largest TVL first).",
|
|
1793
|
+
"description": "Optional sort field when listing. Defaults to TotalAssetsUsd (largest TVL first). When sorting by Apy/NetApy, pair with min_tvl_usd to avoid dust vaults.",
|
|
1759
1794
|
},
|
|
1760
1795
|
"order_direction": {
|
|
1761
1796
|
"type": "string",
|
|
@@ -1808,6 +1843,16 @@ class OpenClawWalletAdapter:
|
|
|
1808
1843
|
"type": "string",
|
|
1809
1844
|
"description": "Optional loan asset address to filter markets.",
|
|
1810
1845
|
},
|
|
1846
|
+
"min_supply_usd": {
|
|
1847
|
+
"type": "number",
|
|
1848
|
+
"minimum": 0,
|
|
1849
|
+
"description": "Optional minimum market supply in USD. Recommended when sorting by APY, so dust markets with inflated yields are excluded (e.g. 1000000).",
|
|
1850
|
+
},
|
|
1851
|
+
"min_net_supply_apy": {
|
|
1852
|
+
"type": "number",
|
|
1853
|
+
"minimum": 0,
|
|
1854
|
+
"description": "Optional minimum net supply APY as a fraction (e.g. 0.03 for 3%).",
|
|
1855
|
+
},
|
|
1811
1856
|
"order_by": {
|
|
1812
1857
|
"type": "string",
|
|
1813
1858
|
"enum": [
|
|
@@ -1821,7 +1866,7 @@ class OpenClawWalletAdapter:
|
|
|
1821
1866
|
"TotalLiquidityUsd",
|
|
1822
1867
|
"Lltv",
|
|
1823
1868
|
],
|
|
1824
|
-
"description": "Optional sort field when listing. Defaults to SupplyAssetsUsd (largest supply first).",
|
|
1869
|
+
"description": "Optional sort field when listing. Defaults to SupplyAssetsUsd (largest supply first). When sorting by APY, pair with min_supply_usd to avoid dust markets.",
|
|
1825
1870
|
},
|
|
1826
1871
|
"order_direction": {
|
|
1827
1872
|
"type": "string",
|
|
@@ -2720,9 +2765,97 @@ class OpenClawWalletAdapter:
|
|
|
2720
2765
|
requires_explicit_user_intent=True,
|
|
2721
2766
|
risk_level="high",
|
|
2722
2767
|
),
|
|
2768
|
+
AgentToolSpec(
|
|
2769
|
+
name="get_kamino_portfolio",
|
|
2770
|
+
description="Get the unified Kamino portfolio view for a Solana wallet on mainnet across lending, multiply, leverage, liquidity, earn, and staking.",
|
|
2771
|
+
input_schema={
|
|
2772
|
+
"type": "object",
|
|
2773
|
+
"properties": {
|
|
2774
|
+
"user": {
|
|
2775
|
+
"type": "string",
|
|
2776
|
+
"description": "Optional Solana wallet address override. If omitted, use the configured wallet.",
|
|
2777
|
+
},
|
|
2778
|
+
},
|
|
2779
|
+
"additionalProperties": False,
|
|
2780
|
+
},
|
|
2781
|
+
read_only=True,
|
|
2782
|
+
risk_level="low",
|
|
2783
|
+
),
|
|
2784
|
+
AgentToolSpec(
|
|
2785
|
+
name="get_kamino_vaults",
|
|
2786
|
+
description=(
|
|
2787
|
+
"Discover Kamino Earn vaults on Solana mainnet. Returns compact vault "
|
|
2788
|
+
"summaries pre-ranked by AUM; pass include_metrics=true to fetch APY/TVL "
|
|
2789
|
+
"metrics for the top vaults (sorted by APY), token_mint to filter by "
|
|
2790
|
+
"deposit token, or vault_address for one vault's detail with metrics. "
|
|
2791
|
+
"For yield search, combine token_mint with include_metrics."
|
|
2792
|
+
),
|
|
2793
|
+
input_schema={
|
|
2794
|
+
"type": "object",
|
|
2795
|
+
"properties": {
|
|
2796
|
+
"vault_address": {
|
|
2797
|
+
"type": "string",
|
|
2798
|
+
"description": "Optional vault address for a single-vault lookup with APY/TVL metrics.",
|
|
2799
|
+
},
|
|
2800
|
+
"token_mint": {
|
|
2801
|
+
"type": "string",
|
|
2802
|
+
"description": "Optional deposit token mint to filter vaults (e.g. EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v for USDC).",
|
|
2803
|
+
},
|
|
2804
|
+
"include_metrics": {
|
|
2805
|
+
"type": "boolean",
|
|
2806
|
+
"description": "Fetch APY/TVL metrics for the top vaults and sort by APY. Defaults to false. Metrics are fetched for at most 20 vaults per call.",
|
|
2807
|
+
},
|
|
2808
|
+
"limit": {
|
|
2809
|
+
"type": "integer",
|
|
2810
|
+
"minimum": 1,
|
|
2811
|
+
"maximum": 100,
|
|
2812
|
+
"description": "Optional max number of vaults to return. Defaults to 50 (capped at 20 when include_metrics is true).",
|
|
2813
|
+
},
|
|
2814
|
+
},
|
|
2815
|
+
"additionalProperties": False,
|
|
2816
|
+
},
|
|
2817
|
+
read_only=True,
|
|
2818
|
+
risk_level="low",
|
|
2819
|
+
),
|
|
2820
|
+
AgentToolSpec(
|
|
2821
|
+
name="get_kamino_earn_positions",
|
|
2822
|
+
description="Get Kamino Earn vault positions for a Solana wallet on mainnet.",
|
|
2823
|
+
input_schema={
|
|
2824
|
+
"type": "object",
|
|
2825
|
+
"properties": {
|
|
2826
|
+
"user": {
|
|
2827
|
+
"type": "string",
|
|
2828
|
+
"description": "Optional Solana wallet address override. If omitted, use the configured wallet.",
|
|
2829
|
+
},
|
|
2830
|
+
},
|
|
2831
|
+
"additionalProperties": False,
|
|
2832
|
+
},
|
|
2833
|
+
read_only=True,
|
|
2834
|
+
risk_level="low",
|
|
2835
|
+
),
|
|
2836
|
+
AgentToolSpec(
|
|
2837
|
+
name="get_kamino_liquidity_positions",
|
|
2838
|
+
description="Get Kamino Liquidity strategy positions for a Solana wallet on mainnet.",
|
|
2839
|
+
input_schema={
|
|
2840
|
+
"type": "object",
|
|
2841
|
+
"properties": {
|
|
2842
|
+
"user": {
|
|
2843
|
+
"type": "string",
|
|
2844
|
+
"description": "Optional Solana wallet address override. If omitted, use the configured wallet.",
|
|
2845
|
+
},
|
|
2846
|
+
},
|
|
2847
|
+
"additionalProperties": False,
|
|
2848
|
+
},
|
|
2849
|
+
read_only=True,
|
|
2850
|
+
risk_level="low",
|
|
2851
|
+
),
|
|
2723
2852
|
AgentToolSpec(
|
|
2724
2853
|
name="get_kamino_lend_markets",
|
|
2725
|
-
description=
|
|
2854
|
+
description=(
|
|
2855
|
+
"List Kamino lending markets currently available on Solana mainnet. "
|
|
2856
|
+
"The list has no yield data; markets flagged isPrimary (Main Market) hold "
|
|
2857
|
+
"most TVL — start there and call get_kamino_lend_market_reserves for APYs."
|
|
2858
|
+
),
|
|
2726
2859
|
input_schema={
|
|
2727
2860
|
"type": "object",
|
|
2728
2861
|
"properties": {},
|
|
@@ -2733,7 +2866,11 @@ class OpenClawWalletAdapter:
|
|
|
2733
2866
|
),
|
|
2734
2867
|
AgentToolSpec(
|
|
2735
2868
|
name="get_kamino_lend_market_reserves",
|
|
2736
|
-
description=
|
|
2869
|
+
description=(
|
|
2870
|
+
"Get reserve metrics for one Kamino lending market on Solana mainnet: "
|
|
2871
|
+
"supplyApy, borrowApy, TVL in USD, and maxLtv per token — the data to use "
|
|
2872
|
+
"for lend/borrow yield comparisons within a market."
|
|
2873
|
+
),
|
|
2737
2874
|
input_schema={
|
|
2738
2875
|
"type": "object",
|
|
2739
2876
|
"properties": {
|
|
@@ -3361,6 +3498,80 @@ class OpenClawWalletAdapter:
|
|
|
3361
3498
|
)
|
|
3362
3499
|
)
|
|
3363
3500
|
|
|
3501
|
+
tools.append(
|
|
3502
|
+
AgentToolSpec(
|
|
3503
|
+
name="kamino_earn_deposit",
|
|
3504
|
+
description=(
|
|
3505
|
+
"Preview, prepare, or execute a Kamino Earn vault deposit using a decimal token amount. "
|
|
3506
|
+
"Prepare returns an execution plan only, and execute requires a host-issued approval token bound to the previewed operation."
|
|
3507
|
+
),
|
|
3508
|
+
input_schema={
|
|
3509
|
+
"type": "object",
|
|
3510
|
+
"properties": {
|
|
3511
|
+
"kvault": {"type": "string", "description": "Kamino Earn vault address."},
|
|
3512
|
+
"amount_ui": {
|
|
3513
|
+
"type": "string",
|
|
3514
|
+
"description": "Decimal token amount to deposit, as a string.",
|
|
3515
|
+
},
|
|
3516
|
+
"mode": {
|
|
3517
|
+
"type": "string",
|
|
3518
|
+
"enum": ["preview", "prepare", "execute", "intent_preview", "intent_execute"],
|
|
3519
|
+
"description": "Prefer intent_preview then intent_execute after explicit chat confirmation: intent_execute re-derives the Kamino Earn transaction and executes within the approved parameters without round-tripping the preview payload. Legacy preview/prepare/execute remains supported.",
|
|
3520
|
+
},
|
|
3521
|
+
"valid_for_seconds": {
|
|
3522
|
+
"type": "integer",
|
|
3523
|
+
"description": "Optional intent validity window in seconds for intent_preview (1-300, default 120).",
|
|
3524
|
+
},
|
|
3525
|
+
"purpose": {"type": "string", "description": "Short explanation of why the deposit is being made."},
|
|
3526
|
+
"user_intent": {"type": "boolean", "description": "Must be true for prepare mode."},
|
|
3527
|
+
"approval_token": {"type": "string", "description": "Host-issued approval token required for execute/intent_execute mode."},
|
|
3528
|
+
},
|
|
3529
|
+
"required": ["kvault", "amount_ui", "mode", "purpose"],
|
|
3530
|
+
"additionalProperties": False,
|
|
3531
|
+
},
|
|
3532
|
+
read_only=False,
|
|
3533
|
+
requires_explicit_user_intent=True,
|
|
3534
|
+
risk_level="high",
|
|
3535
|
+
)
|
|
3536
|
+
)
|
|
3537
|
+
|
|
3538
|
+
tools.append(
|
|
3539
|
+
AgentToolSpec(
|
|
3540
|
+
name="kamino_earn_withdraw",
|
|
3541
|
+
description=(
|
|
3542
|
+
"Preview, prepare, or execute a Kamino Earn vault withdraw using a decimal token amount. "
|
|
3543
|
+
"Prepare returns an execution plan only, and execute requires a host-issued approval token bound to the previewed operation."
|
|
3544
|
+
),
|
|
3545
|
+
input_schema={
|
|
3546
|
+
"type": "object",
|
|
3547
|
+
"properties": {
|
|
3548
|
+
"kvault": {"type": "string", "description": "Kamino Earn vault address."},
|
|
3549
|
+
"amount_ui": {
|
|
3550
|
+
"type": "string",
|
|
3551
|
+
"description": "Decimal token amount to withdraw, as a string.",
|
|
3552
|
+
},
|
|
3553
|
+
"mode": {
|
|
3554
|
+
"type": "string",
|
|
3555
|
+
"enum": ["preview", "prepare", "execute", "intent_preview", "intent_execute"],
|
|
3556
|
+
"description": "Prefer intent_preview then intent_execute after explicit chat confirmation: intent_execute re-derives the Kamino Earn transaction and executes within the approved parameters without round-tripping the preview payload. Legacy preview/prepare/execute remains supported.",
|
|
3557
|
+
},
|
|
3558
|
+
"valid_for_seconds": {
|
|
3559
|
+
"type": "integer",
|
|
3560
|
+
"description": "Optional intent validity window in seconds for intent_preview (1-300, default 120).",
|
|
3561
|
+
},
|
|
3562
|
+
"purpose": {"type": "string", "description": "Short explanation of why the withdraw is being made."},
|
|
3563
|
+
"user_intent": {"type": "boolean", "description": "Must be true for prepare mode."},
|
|
3564
|
+
"approval_token": {"type": "string", "description": "Host-issued approval token required for execute/intent_execute mode."},
|
|
3565
|
+
},
|
|
3566
|
+
"required": ["kvault", "amount_ui", "mode", "purpose"],
|
|
3567
|
+
"additionalProperties": False,
|
|
3568
|
+
},
|
|
3569
|
+
read_only=False,
|
|
3570
|
+
requires_explicit_user_intent=True,
|
|
3571
|
+
risk_level="high",
|
|
3572
|
+
)
|
|
3573
|
+
)
|
|
3574
|
+
|
|
3364
3575
|
tools.append(
|
|
3365
3576
|
AgentToolSpec(
|
|
3366
3577
|
name="close_empty_token_accounts",
|
|
@@ -4511,6 +4722,12 @@ class OpenClawWalletAdapter:
|
|
|
4511
4722
|
if isinstance(args.get("asset_address"), str) and args.get("asset_address").strip()
|
|
4512
4723
|
else None
|
|
4513
4724
|
),
|
|
4725
|
+
min_tvl_usd=self._normalize_optional_non_negative_number(
|
|
4726
|
+
args.get("min_tvl_usd"), field_name="min_tvl_usd"
|
|
4727
|
+
),
|
|
4728
|
+
min_net_apy=self._normalize_optional_non_negative_number(
|
|
4729
|
+
args.get("min_net_apy"), field_name="min_net_apy"
|
|
4730
|
+
),
|
|
4514
4731
|
order_by=(
|
|
4515
4732
|
str(args.get("order_by")).strip()
|
|
4516
4733
|
if isinstance(args.get("order_by"), str) and args.get("order_by").strip()
|
|
@@ -4550,6 +4767,12 @@ class OpenClawWalletAdapter:
|
|
|
4550
4767
|
and args.get("loan_asset_address").strip()
|
|
4551
4768
|
else None
|
|
4552
4769
|
),
|
|
4770
|
+
min_supply_usd=self._normalize_optional_non_negative_number(
|
|
4771
|
+
args.get("min_supply_usd"), field_name="min_supply_usd"
|
|
4772
|
+
),
|
|
4773
|
+
min_net_supply_apy=self._normalize_optional_non_negative_number(
|
|
4774
|
+
args.get("min_net_supply_apy"), field_name="min_net_supply_apy"
|
|
4775
|
+
),
|
|
4553
4776
|
order_by=(
|
|
4554
4777
|
str(args.get("order_by")).strip()
|
|
4555
4778
|
if isinstance(args.get("order_by"), str) and args.get("order_by").strip()
|
|
@@ -5723,6 +5946,45 @@ class OpenClawWalletAdapter:
|
|
|
5723
5946
|
data = await self.backend.get_kamino_lend_markets()
|
|
5724
5947
|
return AgentToolResult(tool=tool_name, ok=True, data=data)
|
|
5725
5948
|
|
|
5949
|
+
if tool_name == "get_kamino_portfolio":
|
|
5950
|
+
user = args.get("user")
|
|
5951
|
+
if user is not None and not isinstance(user, str):
|
|
5952
|
+
raise WalletBackendError("user must be a string when provided.")
|
|
5953
|
+
data = await self.backend.get_kamino_portfolio(user=user)
|
|
5954
|
+
return AgentToolResult(tool=tool_name, ok=True, data=data)
|
|
5955
|
+
|
|
5956
|
+
if tool_name == "get_kamino_vaults":
|
|
5957
|
+
vault_address = args.get("vault_address")
|
|
5958
|
+
if vault_address is not None and not isinstance(vault_address, str):
|
|
5959
|
+
raise WalletBackendError("vault_address must be a string when provided.")
|
|
5960
|
+
token_mint = args.get("token_mint")
|
|
5961
|
+
if token_mint is not None and not isinstance(token_mint, str):
|
|
5962
|
+
raise WalletBackendError("token_mint must be a string when provided.")
|
|
5963
|
+
limit = args.get("limit")
|
|
5964
|
+
if limit is not None and (not isinstance(limit, int) or isinstance(limit, bool)):
|
|
5965
|
+
raise WalletBackendError("limit must be an integer when provided.")
|
|
5966
|
+
data = await self.backend.get_kamino_vaults(
|
|
5967
|
+
vault_address=vault_address,
|
|
5968
|
+
token_mint=token_mint,
|
|
5969
|
+
include_metrics=bool(args.get("include_metrics", False)),
|
|
5970
|
+
limit=limit,
|
|
5971
|
+
)
|
|
5972
|
+
return AgentToolResult(tool=tool_name, ok=True, data=data)
|
|
5973
|
+
|
|
5974
|
+
if tool_name == "get_kamino_earn_positions":
|
|
5975
|
+
user = args.get("user")
|
|
5976
|
+
if user is not None and not isinstance(user, str):
|
|
5977
|
+
raise WalletBackendError("user must be a string when provided.")
|
|
5978
|
+
data = await self.backend.get_kamino_earn_positions(user=user)
|
|
5979
|
+
return AgentToolResult(tool=tool_name, ok=True, data=data)
|
|
5980
|
+
|
|
5981
|
+
if tool_name == "get_kamino_liquidity_positions":
|
|
5982
|
+
user = args.get("user")
|
|
5983
|
+
if user is not None and not isinstance(user, str):
|
|
5984
|
+
raise WalletBackendError("user must be a string when provided.")
|
|
5985
|
+
data = await self.backend.get_kamino_liquidity_positions(user=user)
|
|
5986
|
+
return AgentToolResult(tool=tool_name, ok=True, data=data)
|
|
5987
|
+
|
|
5726
5988
|
if tool_name == "get_kamino_lend_market_reserves":
|
|
5727
5989
|
market = args.get("market")
|
|
5728
5990
|
if not isinstance(market, str) or not market.strip():
|
|
@@ -6095,6 +6357,221 @@ class OpenClawWalletAdapter:
|
|
|
6095
6357
|
),
|
|
6096
6358
|
)
|
|
6097
6359
|
|
|
6360
|
+
if tool_name in {
|
|
6361
|
+
"kamino_earn_deposit",
|
|
6362
|
+
"kamino_earn_withdraw",
|
|
6363
|
+
}:
|
|
6364
|
+
kvault = args.get("kvault")
|
|
6365
|
+
amount_ui = args.get("amount_ui")
|
|
6366
|
+
mode = args.get("mode")
|
|
6367
|
+
purpose = args.get("purpose")
|
|
6368
|
+
user_intent = args.get("user_intent", False)
|
|
6369
|
+
approval_token = args.get("approval_token")
|
|
6370
|
+
valid_for_seconds = args.get("valid_for_seconds", 120)
|
|
6371
|
+
|
|
6372
|
+
if not isinstance(kvault, str) or not kvault.strip():
|
|
6373
|
+
raise WalletBackendError("kvault is required.")
|
|
6374
|
+
if not isinstance(amount_ui, str) or not amount_ui.strip():
|
|
6375
|
+
raise WalletBackendError("amount_ui is required.")
|
|
6376
|
+
if mode not in {"preview", "prepare", "execute", "intent_preview", "intent_execute"}:
|
|
6377
|
+
raise WalletBackendError(
|
|
6378
|
+
"mode must be 'preview', 'prepare', 'execute', 'intent_preview' or 'intent_execute'."
|
|
6379
|
+
)
|
|
6380
|
+
if not isinstance(purpose, str) or not purpose.strip():
|
|
6381
|
+
raise WalletBackendError("purpose is required.")
|
|
6382
|
+
if mode == "intent_preview" and (
|
|
6383
|
+
not isinstance(valid_for_seconds, int)
|
|
6384
|
+
or valid_for_seconds <= 0
|
|
6385
|
+
or valid_for_seconds > 300
|
|
6386
|
+
):
|
|
6387
|
+
raise WalletBackendError("valid_for_seconds must be an integer between 1 and 300.")
|
|
6388
|
+
|
|
6389
|
+
action_label_map = {
|
|
6390
|
+
"kamino_earn_deposit": "Kamino Earn deposit",
|
|
6391
|
+
"kamino_earn_withdraw": "Kamino Earn withdraw",
|
|
6392
|
+
}
|
|
6393
|
+
intent_action_label_map = {
|
|
6394
|
+
"kamino_earn_deposit": "Kamino Earn deposit intent",
|
|
6395
|
+
"kamino_earn_withdraw": "Kamino Earn withdraw intent",
|
|
6396
|
+
}
|
|
6397
|
+
preview_method_map = {
|
|
6398
|
+
"kamino_earn_deposit": self.backend.preview_kamino_earn_deposit,
|
|
6399
|
+
"kamino_earn_withdraw": self.backend.preview_kamino_earn_withdraw,
|
|
6400
|
+
}
|
|
6401
|
+
intent_preview_method_map = {
|
|
6402
|
+
"kamino_earn_deposit": self.backend.preview_kamino_earn_deposit_intent,
|
|
6403
|
+
"kamino_earn_withdraw": self.backend.preview_kamino_earn_withdraw_intent,
|
|
6404
|
+
}
|
|
6405
|
+
execute_method_map = {
|
|
6406
|
+
"kamino_earn_deposit": self.backend.execute_kamino_earn_deposit,
|
|
6407
|
+
"kamino_earn_withdraw": self.backend.execute_kamino_earn_withdraw,
|
|
6408
|
+
}
|
|
6409
|
+
action_label = action_label_map[tool_name]
|
|
6410
|
+
intent_action_label = intent_action_label_map[tool_name]
|
|
6411
|
+
preview_method = preview_method_map[tool_name]
|
|
6412
|
+
intent_preview_method = intent_preview_method_map[tool_name]
|
|
6413
|
+
execute_method = execute_method_map[tool_name]
|
|
6414
|
+
|
|
6415
|
+
if mode == "intent_preview":
|
|
6416
|
+
intent_preview = await intent_preview_method(
|
|
6417
|
+
kvault=kvault.strip(),
|
|
6418
|
+
amount_ui=amount_ui.strip(),
|
|
6419
|
+
valid_for_seconds=valid_for_seconds,
|
|
6420
|
+
)
|
|
6421
|
+
return AgentToolResult(
|
|
6422
|
+
tool=tool_name,
|
|
6423
|
+
ok=True,
|
|
6424
|
+
data=self._annotate_sensitive_payload(
|
|
6425
|
+
intent_preview,
|
|
6426
|
+
action_label=intent_action_label,
|
|
6427
|
+
mode="preview",
|
|
6428
|
+
),
|
|
6429
|
+
)
|
|
6430
|
+
|
|
6431
|
+
if mode == "intent_execute":
|
|
6432
|
+
approval_payload = inspect_approval_token(
|
|
6433
|
+
approval_token,
|
|
6434
|
+
tool_name=tool_name,
|
|
6435
|
+
network=str(getattr(self.backend, "network", "unknown")),
|
|
6436
|
+
require_mainnet_confirmation=self._is_mainnet_for_backend(self.backend),
|
|
6437
|
+
)
|
|
6438
|
+
approval_summary = approval_payload.get("binding", {}).get("summary")
|
|
6439
|
+
if not isinstance(approval_summary, dict):
|
|
6440
|
+
raise WalletBackendError(
|
|
6441
|
+
"approval_token does not match the requested operation. Generate a new intent preview and approval before execute."
|
|
6442
|
+
)
|
|
6443
|
+
expected_summary = {
|
|
6444
|
+
"operation": intent_action_label,
|
|
6445
|
+
"network": str(getattr(self.backend, "network", "unknown")),
|
|
6446
|
+
"kvault": kvault.strip(),
|
|
6447
|
+
"amount_ui": amount_ui.strip(),
|
|
6448
|
+
}
|
|
6449
|
+
for key, expected_value in expected_summary.items():
|
|
6450
|
+
if approval_summary.get(key) != expected_value:
|
|
6451
|
+
raise WalletBackendError(
|
|
6452
|
+
f"approval_token does not match the requested {action_label} intent. Generate a fresh intent preview and approval before execute."
|
|
6453
|
+
)
|
|
6454
|
+
if approval_summary.get("recipient_policy") != "owner-only":
|
|
6455
|
+
raise WalletBackendError("approved Kamino intent recipient policy is invalid.")
|
|
6456
|
+
if approval_summary.get("spend_policy") != "exact-amount":
|
|
6457
|
+
raise WalletBackendError("approved Kamino intent spend policy is invalid.")
|
|
6458
|
+
current_owner = await self.backend.get_address()
|
|
6459
|
+
approved_owner = approval_summary.get("owner")
|
|
6460
|
+
if approved_owner and current_owner and str(approved_owner) != str(current_owner):
|
|
6461
|
+
raise WalletBackendError(
|
|
6462
|
+
"approval_token does not match the active wallet owner. Generate a fresh intent preview and approval before execute."
|
|
6463
|
+
)
|
|
6464
|
+
valid_until = approval_summary.get("valid_until_epoch_seconds")
|
|
6465
|
+
if valid_until is not None and int(time.time()) > int(valid_until):
|
|
6466
|
+
raise WalletBackendError(
|
|
6467
|
+
"Approved Kamino intent has expired. Create a fresh intent preview."
|
|
6468
|
+
)
|
|
6469
|
+
approval_summary_copy = dict(approval_summary)
|
|
6470
|
+
self._require_execute_approval(
|
|
6471
|
+
approval_token=approval_token,
|
|
6472
|
+
tool_name=tool_name,
|
|
6473
|
+
summary=approval_summary_copy,
|
|
6474
|
+
action_label=intent_action_label,
|
|
6475
|
+
)
|
|
6476
|
+
result = await execute_method(
|
|
6477
|
+
kvault=kvault.strip(),
|
|
6478
|
+
amount_ui=amount_ui.strip(),
|
|
6479
|
+
approved_preview=None,
|
|
6480
|
+
)
|
|
6481
|
+
return AgentToolResult(
|
|
6482
|
+
tool=tool_name,
|
|
6483
|
+
ok=True,
|
|
6484
|
+
data=self._annotate_sensitive_payload(
|
|
6485
|
+
result,
|
|
6486
|
+
action_label=action_label,
|
|
6487
|
+
mode="execute",
|
|
6488
|
+
),
|
|
6489
|
+
)
|
|
6490
|
+
|
|
6491
|
+
if mode == "preview":
|
|
6492
|
+
preview = await preview_method(
|
|
6493
|
+
kvault=kvault.strip(),
|
|
6494
|
+
amount_ui=amount_ui.strip(),
|
|
6495
|
+
)
|
|
6496
|
+
return AgentToolResult(
|
|
6497
|
+
tool=tool_name,
|
|
6498
|
+
ok=True,
|
|
6499
|
+
data=self._annotate_sensitive_payload(
|
|
6500
|
+
preview,
|
|
6501
|
+
action_label=action_label,
|
|
6502
|
+
mode="preview",
|
|
6503
|
+
),
|
|
6504
|
+
)
|
|
6505
|
+
|
|
6506
|
+
if mode == "prepare":
|
|
6507
|
+
self._require_prepare_intent(user_intent)
|
|
6508
|
+
preview = await preview_method(
|
|
6509
|
+
kvault=kvault.strip(),
|
|
6510
|
+
amount_ui=amount_ui.strip(),
|
|
6511
|
+
)
|
|
6512
|
+
return AgentToolResult(
|
|
6513
|
+
tool=tool_name,
|
|
6514
|
+
ok=True,
|
|
6515
|
+
data=self._annotate_sensitive_payload(
|
|
6516
|
+
self._build_prepare_plan(
|
|
6517
|
+
preview_payload=preview,
|
|
6518
|
+
action_label=action_label,
|
|
6519
|
+
),
|
|
6520
|
+
action_label=action_label,
|
|
6521
|
+
mode="prepare",
|
|
6522
|
+
),
|
|
6523
|
+
)
|
|
6524
|
+
|
|
6525
|
+
approved_preview = args.get("_approved_preview")
|
|
6526
|
+
execute_preview = None
|
|
6527
|
+
approval_payload = inspect_approval_token(
|
|
6528
|
+
approval_token,
|
|
6529
|
+
tool_name=tool_name,
|
|
6530
|
+
network=str(getattr(self.backend, "network", "unknown")),
|
|
6531
|
+
require_mainnet_confirmation=self._is_mainnet_for_backend(self.backend),
|
|
6532
|
+
)
|
|
6533
|
+
approval_summary = approval_payload.get("binding", {}).get("summary")
|
|
6534
|
+
if not isinstance(approval_summary, dict):
|
|
6535
|
+
raise WalletBackendError(
|
|
6536
|
+
"approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
|
|
6537
|
+
)
|
|
6538
|
+
approval_summary_copy = dict(approval_summary)
|
|
6539
|
+
if isinstance(approval_summary_copy.get("_preview_digest"), str):
|
|
6540
|
+
if not isinstance(approved_preview, dict):
|
|
6541
|
+
raise WalletBackendError(
|
|
6542
|
+
f"Approved {action_label} preview payload is required for execute mode. Generate a new preview and approval before execute."
|
|
6543
|
+
)
|
|
6544
|
+
if preview_payload_digest(approved_preview) != approval_summary_copy["_preview_digest"]:
|
|
6545
|
+
raise WalletBackendError(
|
|
6546
|
+
"approved preview payload does not match the approval token. Generate a new preview and approval before execute."
|
|
6547
|
+
)
|
|
6548
|
+
execute_preview = dict(approved_preview)
|
|
6549
|
+
else:
|
|
6550
|
+
execute_preview = await preview_method(
|
|
6551
|
+
kvault=kvault.strip(),
|
|
6552
|
+
amount_ui=amount_ui.strip(),
|
|
6553
|
+
)
|
|
6554
|
+
self._require_execute_approval(
|
|
6555
|
+
approval_token=approval_token,
|
|
6556
|
+
tool_name=tool_name,
|
|
6557
|
+
summary=approval_summary_copy,
|
|
6558
|
+
action_label=action_label,
|
|
6559
|
+
)
|
|
6560
|
+
result = await execute_method(
|
|
6561
|
+
kvault=kvault.strip(),
|
|
6562
|
+
amount_ui=amount_ui.strip(),
|
|
6563
|
+
approved_preview=execute_preview,
|
|
6564
|
+
)
|
|
6565
|
+
return AgentToolResult(
|
|
6566
|
+
tool=tool_name,
|
|
6567
|
+
ok=True,
|
|
6568
|
+
data=self._annotate_sensitive_payload(
|
|
6569
|
+
result,
|
|
6570
|
+
action_label=action_label,
|
|
6571
|
+
mode="execute",
|
|
6572
|
+
),
|
|
6573
|
+
)
|
|
6574
|
+
|
|
6098
6575
|
if tool_name in {
|
|
6099
6576
|
"kamino_lend_deposit",
|
|
6100
6577
|
"kamino_lend_withdraw",
|