@agentlayer.tech/wallet 0.1.93 → 0.1.94

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.
@@ -29,6 +29,7 @@ const AUTONOMOUS_DEFI_TOOLS = new Set([
29
29
  "manage_evm_lido_withdrawal",
30
30
  "manage_evm_morpho_market_position",
31
31
  "manage_evm_morpho_vault_position",
32
+ "manage_evm_uniswap_liquidity",
32
33
  ]);
33
34
  const approvalPreviewCache = new Map();
34
35
  const WALLET_TOOL_ONLY_GUIDANCE =
@@ -1977,6 +1978,23 @@ const evmToolDefinitions = [
1977
1978
  additionalProperties: false,
1978
1979
  },
1979
1980
  },
1981
+ {
1982
+ name: "search_uniswap_pairs",
1983
+ description: "Search read-only onchain Uniswap-tradeable pairs by name, ticker, or ERC-20 address. Verify any returned token address independently before quoting or trading.",
1984
+ parameters: {
1985
+ type: "object",
1986
+ properties: {
1987
+ query: { type: "string" },
1988
+ token_address: { type: "string" },
1989
+ chain: { type: "string" },
1990
+ dex_id: { type: "string" },
1991
+ all_chains: { type: "boolean" },
1992
+ limit: { type: "integer" },
1993
+ network: { type: "string", enum: EVM_CORE_NETWORKS },
1994
+ },
1995
+ additionalProperties: false,
1996
+ },
1997
+ },
1980
1998
  {
1981
1999
  name: "swap_evm_uniswap_tokens",
1982
2000
  description: "Preview, prepare, or execute a supported Uniswap path on ethereum, base, or robinhood: CLASSIC, UniswapX orders, or canonical ETH↔WETH wrap/unwrap. ERC-20 paths may use Permit2 EIP-712 or an UniswapX order signature. Preview or prepare first. After the user explicitly confirms the shown summary in chat, call execute; the OpenClaw plugin handles the internal execution authorization automatically.",
@@ -1997,6 +2015,29 @@ const evmToolDefinitions = [
1997
2015
  additionalProperties: false,
1998
2016
  },
1999
2017
  },
2018
+ {
2019
+ name: "manage_evm_uniswap_liquidity",
2020
+ description: "Preview, prepare, or execute a Uniswap V3/V4 liquidity action on ethereum, base, or robinhood. Supported actions are create, increase, decrease, and claim_fees. The official Liquidity API builds the transaction again immediately before signing, so confirmation is scoped to the LP intent rather than fragile ticks or calldata.",
2021
+ optional: true,
2022
+ parameters: {
2023
+ type: "object",
2024
+ properties: {
2025
+ action: { type: "string", enum: ["create", "increase", "decrease", "claim_fees"] },
2026
+ protocol: { type: "string", enum: ["V3", "V4"] },
2027
+ request: {
2028
+ type: "object",
2029
+ description: "Official Uniswap Liquidity API fields. walletAddress, chainId, protocol, simulateTransaction, and permit/signature fields are controlled by the wallet.",
2030
+ additionalProperties: true,
2031
+ },
2032
+ mode: { type: "string", enum: ["preview", "prepare", "execute"] },
2033
+ purpose: { type: "string" },
2034
+ user_intent: { type: "boolean" },
2035
+ network: { type: "string", enum: EVM_CORE_NETWORKS },
2036
+ },
2037
+ required: ["action", "protocol", "request", "mode", "purpose"],
2038
+ additionalProperties: false,
2039
+ },
2040
+ },
2000
2041
  {
2001
2042
  name: "swap_evm_lifi_cross_chain_tokens",
2002
2043
  description: "Preview, prepare, or execute an EVM-origin cross-chain swap through LI.FI. This currently supports ethereum/base as the source network and ethereum/base/solana as the destination chain. Preview or prepare first. After the user explicitly confirms the shown summary in chat, call execute; the OpenClaw plugin handles the internal execution authorization automatically.",
@@ -29,6 +29,7 @@ const AUTONOMOUS_DEFI_TOOLS = new Set([
29
29
  "manage_evm_lido_withdrawal",
30
30
  "manage_evm_morpho_market_position",
31
31
  "manage_evm_morpho_vault_position",
32
+ "manage_evm_uniswap_liquidity",
32
33
  ]);
33
34
  const approvalPreviewCache = new Map();
34
35
  const WALLET_TOOL_ONLY_GUIDANCE =
@@ -1977,6 +1978,23 @@ const evmToolDefinitions = [
1977
1978
  additionalProperties: false,
1978
1979
  },
1979
1980
  },
1981
+ {
1982
+ name: "search_uniswap_pairs",
1983
+ description: "Search read-only onchain Uniswap-tradeable pairs by name, ticker, or ERC-20 address. Verify any returned token address independently before quoting or trading.",
1984
+ parameters: {
1985
+ type: "object",
1986
+ properties: {
1987
+ query: { type: "string" },
1988
+ token_address: { type: "string" },
1989
+ chain: { type: "string" },
1990
+ dex_id: { type: "string" },
1991
+ all_chains: { type: "boolean" },
1992
+ limit: { type: "integer" },
1993
+ network: { type: "string", enum: EVM_CORE_NETWORKS },
1994
+ },
1995
+ additionalProperties: false,
1996
+ },
1997
+ },
1980
1998
  {
1981
1999
  name: "swap_evm_uniswap_tokens",
1982
2000
  description: "Preview, prepare, or execute a supported Uniswap path on ethereum, base, or robinhood: CLASSIC, UniswapX orders, or canonical ETH↔WETH wrap/unwrap. ERC-20 paths may use Permit2 EIP-712 or an UniswapX order signature. Preview or prepare first. After the user explicitly confirms the shown summary in chat, call execute; the OpenClaw plugin handles the internal execution authorization automatically.",
@@ -1997,6 +2015,29 @@ const evmToolDefinitions = [
1997
2015
  additionalProperties: false,
1998
2016
  },
1999
2017
  },
2018
+ {
2019
+ name: "manage_evm_uniswap_liquidity",
2020
+ description: "Preview, prepare, or execute a Uniswap V3/V4 liquidity action on ethereum, base, or robinhood. Supported actions are create, increase, decrease, and claim_fees. The official Liquidity API builds the transaction again immediately before signing, so confirmation is scoped to the LP intent rather than fragile ticks or calldata.",
2021
+ optional: true,
2022
+ parameters: {
2023
+ type: "object",
2024
+ properties: {
2025
+ action: { type: "string", enum: ["create", "increase", "decrease", "claim_fees"] },
2026
+ protocol: { type: "string", enum: ["V3", "V4"] },
2027
+ request: {
2028
+ type: "object",
2029
+ description: "Official Uniswap Liquidity API fields. walletAddress, chainId, protocol, simulateTransaction, and permit/signature fields are controlled by the wallet.",
2030
+ additionalProperties: true,
2031
+ },
2032
+ mode: { type: "string", enum: ["preview", "prepare", "execute"] },
2033
+ purpose: { type: "string" },
2034
+ user_intent: { type: "boolean" },
2035
+ network: { type: "string", enum: EVM_CORE_NETWORKS },
2036
+ },
2037
+ required: ["action", "protocol", "request", "mode", "purpose"],
2038
+ additionalProperties: false,
2039
+ },
2040
+ },
2000
2041
  {
2001
2042
  name: "swap_evm_lifi_cross_chain_tokens",
2002
2043
  description: "Preview, prepare, or execute an EVM-origin cross-chain swap through LI.FI. This currently supports ethereum/base as the source network and ethereum/base/solana as the destination chain. Preview or prepare first. After the user explicitly confirms the shown summary in chat, call execute; the OpenClaw plugin handles the internal execution authorization automatically.",
@@ -2,7 +2,7 @@
2
2
  "id": "agent-wallet",
3
3
  "name": "Agent Wallet",
4
4
  "description": "Official OpenClaw plugin bridge for the agent-wallet backends, including Solana, local BTC, and local EVM.",
5
- "version": "0.1.93",
5
+ "version": "0.1.94",
6
6
  "contracts": {
7
7
  "tools": [
8
8
  "agentlayer_autonomous_approve",
@@ -30,6 +30,7 @@
30
30
  "get_evm_token_balance",
31
31
  "get_evm_token_metadata",
32
32
  "get_evm_transaction_receipt",
33
+ "get_uniswap_swap_quote",
33
34
  "get_flash_trade_markets",
34
35
  "get_flash_trade_positions",
35
36
  "get_kamino_earn_positions",
@@ -61,11 +62,13 @@
61
62
  "manage_evm_morpho_vault_position",
62
63
  "manage_evm_lido_position",
63
64
  "manage_evm_lido_withdrawal",
65
+ "manage_evm_uniswap_liquidity",
64
66
  "search_uniswap_pairs",
65
67
  "set_evm_network",
66
68
  "set_wallet_backend",
67
69
  "sign_wallet_message",
68
70
  "swap_evm_lifi_cross_chain_tokens",
71
+ "swap_evm_uniswap_tokens",
69
72
  "swap_evm_tokens",
70
73
  "swap_solana_lifi_cross_chain_tokens",
71
74
  "swap_solana_tokens",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentlayertech/agent-wallet-plugin",
3
- "version": "0.1.93",
3
+ "version": "0.1.94",
4
4
  "description": "OpenClaw plugin bridge for the AgentLayer wallet runtime.",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN ../../../LICENSE",
package/CHANGELOG.md CHANGED
@@ -2,6 +2,24 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## v0.1.94 - 2026-08-13
6
+
7
+ - **Added Uniswap V3/V4 liquidity provisioning for Ethereum, Base, and
8
+ Robinhood Chain.** The EVM wallet now previews, prepares, and executes
9
+ create, increase, decrease, and fee-claim actions through Uniswap's
10
+ Liquidity API. Final LP calldata is refreshed and simulated immediately
11
+ before signing; executable transactions are accepted only for pinned
12
+ PositionManager deployments and matching wallet/network fields.
13
+ - **Added bounded LP approval handling and intent-safe confirmations.** Token
14
+ approvals are restricted to LP-request assets and approved spenders, while
15
+ confirmations bind the action, network, protocol, pool/position, and assets
16
+ rather than fast-expiring ticks or calldata. Single-sided concentrated
17
+ positions with a zero amount on one ERC-20 side no longer fail approval
18
+ discovery.
19
+ - **Exposed LP actions through the provider gateway, OpenClaw, and Codex.**
20
+ The provider gateway forwards the existing shared Uniswap API key to the
21
+ Liquidity API; OpenClaw's pair-search contract was restored and registered.
22
+
5
23
  ## v0.1.93 - 2026-08-10
6
24
 
7
25
  - **Added `/cards` (Claude Code) and `cards` (Codex) for Laso Finance card
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.93
1
+ 0.1.94
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Keep in sync with package.json, pyproject.toml, and the npm installer version.
4
4
  # scripts/check_release_version.mjs enforces this on release.
5
- __version__ = "0.1.93"
5
+ __version__ = "0.1.94"
6
6
 
7
7
  __all__ = [
8
8
  "config",
@@ -31,6 +31,7 @@ DEFI_TOOLS = frozenset(
31
31
  "manage_evm_lido_withdrawal",
32
32
  "manage_evm_morpho_market_position",
33
33
  "manage_evm_morpho_vault_position",
34
+ "manage_evm_uniswap_liquidity",
34
35
  }
35
36
  )
36
37
  DEFI_TOOLS_ISSUER = "autonomous-permission:defi-tools"
@@ -751,6 +751,28 @@ class OpenClawWalletAdapter:
751
751
  "quote_fingerprint": provided_fingerprint,
752
752
  }
753
753
 
754
+ if asset_type == "evm-uniswap-liquidity":
755
+ request = payload.get("request") if isinstance(payload.get("request"), dict) else {}
756
+ existing_pool = request.get("existingPool") if isinstance(request.get("existingPool"), dict) else {}
757
+ return {
758
+ "operation": action_label,
759
+ "network": str(payload.get("network") or getattr(self.backend, "network", "unknown")),
760
+ "from_address": payload.get("from_address"),
761
+ "protocol": payload.get("protocol"),
762
+ "liquidity_action": payload.get("liquidity_action"),
763
+ "liquidity_protocol": payload.get("liquidity_protocol"),
764
+ # Intent binds to the action and caller-provided LP scope, not a
765
+ # per-block transaction hash, adjusted ticks, or calculated amount.
766
+ "token0_address": request.get("token0Address") or existing_pool.get("token0Address"),
767
+ "token1_address": request.get("token1Address") or existing_pool.get("token1Address"),
768
+ "pool_reference": existing_pool.get("poolReference"),
769
+ "position_token_id": request.get("nftTokenId") or request.get("tokenId") or payload.get("position_token_id"),
770
+ "independent_token": request.get("independentToken"),
771
+ "liquidity_percentage_to_decrease": request.get("liquidityPercentageToDecrease"),
772
+ "slippage_tolerance": request.get("slippageTolerance"),
773
+ "position_manager": payload.get("position_manager"),
774
+ }
775
+
754
776
  if asset_type == "evm-morpho-vault":
755
777
  return {
756
778
  "operation": action_label,
@@ -1722,6 +1744,35 @@ class OpenClawWalletAdapter:
1722
1744
  risk_level="low",
1723
1745
  ),
1724
1746
  )
1747
+ tools.insert(
1748
+ 13,
1749
+ AgentToolSpec(
1750
+ name="manage_evm_uniswap_liquidity",
1751
+ description=(
1752
+ "Preview, prepare, or execute a Uniswap V3/V4 liquidity action on ethereum, base, or robinhood. "
1753
+ "Supported actions are create, increase, decrease, and claim_fees. The request is passed to the "
1754
+ "official Uniswap Liquidity API; execute refreshes the transaction immediately before signing."
1755
+ ),
1756
+ input_schema={
1757
+ "type": "object",
1758
+ "properties": {
1759
+ "action": {"type": "string", "enum": ["create", "increase", "decrease", "claim_fees"]},
1760
+ "protocol": {"type": "string", "enum": ["V3", "V4"]},
1761
+ "request": {"type": "object", "description": "Uniswap LP API fields excluding walletAddress, chainId, protocol, and simulateTransaction; those are set from the active wallet.", "additionalProperties": True},
1762
+ "mode": {"type": "string", "enum": ["preview", "prepare", "execute"]},
1763
+ "purpose": {"type": "string"},
1764
+ "user_intent": {"type": "boolean"},
1765
+ "approval_token": {"type": "string"},
1766
+ "network": {"type": "string", "enum": ["ethereum", "base", "robinhood"]},
1767
+ },
1768
+ "required": ["action", "protocol", "request", "mode", "purpose"],
1769
+ "additionalProperties": False,
1770
+ },
1771
+ read_only=False,
1772
+ requires_explicit_user_intent=True,
1773
+ risk_level="high",
1774
+ ),
1775
+ )
1725
1776
  tools.insert(
1726
1777
  14,
1727
1778
  AgentToolSpec(
@@ -5757,6 +5808,91 @@ class OpenClawWalletAdapter:
5757
5808
  ),
5758
5809
  )
5759
5810
 
5811
+ if tool_name == "manage_evm_uniswap_liquidity":
5812
+ action = args.get("action")
5813
+ protocol = args.get("protocol")
5814
+ request = args.get("request")
5815
+ mode = args.get("mode")
5816
+ purpose = args.get("purpose")
5817
+ user_intent = args.get("user_intent", False)
5818
+ approval_token = args.get("approval_token")
5819
+ if action not in {"create", "increase", "decrease", "claim_fees"}:
5820
+ raise WalletBackendError("action must be create, increase, decrease, or claim_fees.")
5821
+ if protocol not in {"V3", "V4"}:
5822
+ raise WalletBackendError("protocol must be V3 or V4.")
5823
+ if not isinstance(request, dict):
5824
+ raise WalletBackendError("request must be an object.")
5825
+ if mode not in {"preview", "prepare", "execute"}:
5826
+ raise WalletBackendError("mode must be 'preview', 'prepare' or 'execute'.")
5827
+ if not isinstance(purpose, str) or not purpose.strip():
5828
+ raise WalletBackendError("purpose is required.")
5829
+ # The caller must not override values that are derived from the
5830
+ # active wallet/network in the WDK layer.
5831
+ forbidden = {"walletAddress", "chainId", "protocol", "simulateTransaction", "signature", "batchPermitData", "v4BatchPermitData", "v3NftPermitData"}
5832
+ overlap = forbidden.intersection(request)
5833
+ if overlap:
5834
+ raise WalletBackendError(f"request must not set wallet-controlled fields: {', '.join(sorted(overlap))}.")
5835
+ preview_kwargs = {"action": action, "protocol": protocol, "request": dict(request)}
5836
+ if mode == "preview":
5837
+ preview = await active_backend.preview_uniswap_liquidity(**preview_kwargs)
5838
+ return AgentToolResult(tool=tool_name, ok=True, data=self._annotate_sensitive_payload(preview, action_label="Uniswap liquidity", mode="preview"))
5839
+ if mode == "prepare":
5840
+ self._require_prepare_intent(user_intent)
5841
+ preview = await active_backend.preview_uniswap_liquidity(**preview_kwargs)
5842
+ return AgentToolResult(tool=tool_name, ok=True, data=self._annotate_sensitive_payload(self._build_prepare_plan(preview_payload=preview, action_label="Uniswap liquidity"), action_label="Uniswap liquidity", mode="prepare"))
5843
+ if isinstance(approval_token, str) and approval_token.strip():
5844
+ approval_payload = inspect_approval_token(
5845
+ approval_token,
5846
+ tool_name=tool_name,
5847
+ network=str(getattr(active_backend, "network", "unknown")),
5848
+ require_mainnet_confirmation=self._is_mainnet_for_backend(active_backend),
5849
+ )
5850
+ approval_summary = approval_payload.get("binding", {}).get("summary")
5851
+ if not isinstance(approval_summary, dict):
5852
+ raise WalletBackendError("approval_token does not match the requested LP operation. Generate a new approval after prepare.")
5853
+ approval_summary_copy = dict(approval_summary)
5854
+ else:
5855
+ fresh_preview = await active_backend.preview_uniswap_liquidity(**preview_kwargs)
5856
+ approval_summary_copy = self._build_confirmation_summary(action_label="Uniswap liquidity", payload=fresh_preview)
5857
+ expected = {
5858
+ "operation": "Uniswap liquidity",
5859
+ "network": str(getattr(active_backend, "network", "unknown")),
5860
+ "liquidity_action": action,
5861
+ "liquidity_protocol": protocol,
5862
+ }
5863
+ for key, value in expected.items():
5864
+ if approval_summary_copy.get(key) != value:
5865
+ raise WalletBackendError("approval_token does not match the requested LP operation. Generate a new approval after prepare.")
5866
+ # An LP intent deliberately does not bind API-calculated ticks,
5867
+ # price, calldata, or quote amounts: those are regenerated just
5868
+ # before broadcast. It must still be scoped to the selected
5869
+ # assets/pool/position so an approval cannot be reused for a
5870
+ # different liquidity position.
5871
+ requested_scope = self._build_confirmation_summary(
5872
+ action_label="Uniswap liquidity",
5873
+ payload={
5874
+ "asset_type": "evm-uniswap-liquidity",
5875
+ "network": str(getattr(active_backend, "network", "unknown")),
5876
+ "protocol": "uniswap",
5877
+ "liquidity_action": action,
5878
+ "liquidity_protocol": protocol,
5879
+ "request": preview_kwargs["request"],
5880
+ },
5881
+ )
5882
+ for key in ("token0_address", "token1_address", "pool_reference", "position_token_id", "independent_token"):
5883
+ requested_value = requested_scope.get(key)
5884
+ if requested_value is not None and approval_summary_copy.get(key) != requested_value:
5885
+ raise WalletBackendError("approval_token does not match the requested LP assets or position. Generate a new approval after prepare.")
5886
+ self._require_execute_approval(
5887
+ approval_token=approval_token,
5888
+ tool_name=tool_name,
5889
+ summary=approval_summary_copy,
5890
+ action_label="Uniswap liquidity",
5891
+ backend=active_backend,
5892
+ )
5893
+ result = await active_backend.send_uniswap_liquidity(**preview_kwargs)
5894
+ return AgentToolResult(tool=tool_name, ok=True, data=self._annotate_sensitive_payload(result, action_label="Uniswap liquidity", mode="execute"))
5895
+
5760
5896
  if tool_name == "issue_wallet_approval":
5761
5897
  from agent_wallet.approval import issue_approval_token
5762
5898
 
@@ -40,6 +40,8 @@ LONG_RUNNING_POST_PATHS = {
40
40
  "/v1/evm/swap/send",
41
41
  "/v1/evm/uniswap/swap/quote",
42
42
  "/v1/evm/uniswap/swap/send",
43
+ "/v1/evm/uniswap/liquidity/quote",
44
+ "/v1/evm/uniswap/liquidity/send",
43
45
  "/v1/evm/lifi/quote",
44
46
  "/v1/evm/lifi/send",
45
47
  "/v1/evm/transfer/send",
@@ -349,6 +349,24 @@ class AgentWalletBackend(ABC):
349
349
  ) -> dict[str, Any]:
350
350
  raise WalletBackendError(f"{self.name} does not support Uniswap swaps.")
351
351
 
352
+ async def preview_uniswap_liquidity(
353
+ self,
354
+ *,
355
+ action: str,
356
+ protocol: str,
357
+ request: dict[str, Any],
358
+ ) -> dict[str, Any]:
359
+ raise WalletBackendError(f"{self.name} does not support Uniswap liquidity previews.")
360
+
361
+ async def send_uniswap_liquidity(
362
+ self,
363
+ *,
364
+ action: str,
365
+ protocol: str,
366
+ request: dict[str, Any],
367
+ ) -> dict[str, Any]:
368
+ raise WalletBackendError(f"{self.name} does not support Uniswap liquidity operations.")
369
+
352
370
  async def preview_evm_native_transfer(
353
371
  self,
354
372
  *,
@@ -2028,6 +2028,103 @@ class WdkEvmLocalWalletBackend(AgentWalletBackend):
2028
2028
  "source": str(data.get("source") or "wdk-evm-wallet"),
2029
2029
  }
2030
2030
 
2031
+ def _normalize_uniswap_liquidity_payload(
2032
+ self,
2033
+ data: dict[str, Any],
2034
+ *,
2035
+ action: str,
2036
+ protocol: str,
2037
+ ) -> dict[str, Any]:
2038
+ transaction = dict(data.get("transaction") or {})
2039
+ result = dict(data.get("result") or {})
2040
+ request = dict(data.get("request") or {})
2041
+ return {
2042
+ "chain": self.chain,
2043
+ "network": self.network,
2044
+ "asset_type": "evm-uniswap-liquidity",
2045
+ "protocol": str(data.get("protocol") or "uniswap"),
2046
+ "liquidity_action": str(data.get("liquidityAction") or action),
2047
+ "liquidity_protocol": str(data.get("liquidityProtocol") or protocol).upper(),
2048
+ "from_address": str(data.get("address") or "").strip() or None,
2049
+ "request": request,
2050
+ "request_id": str(data.get("requestId") or "").strip() or None,
2051
+ "token0": data.get("token0"),
2052
+ "token1": data.get("token1"),
2053
+ "tick_lower": data.get("tickLower"),
2054
+ "tick_upper": data.get("tickUpper"),
2055
+ "adjusted_min_price": data.get("adjustedMinPrice"),
2056
+ "adjusted_max_price": data.get("adjustedMaxPrice"),
2057
+ "position_token_id": str(data.get("positionTokenId") or "").strip() or None,
2058
+ "position_manager": str(data.get("positionManager") or "").strip() or None,
2059
+ "gas_fee": str(data.get("gasFee")) if data.get("gasFee") is not None else None,
2060
+ "approvals": list(data.get("approvals") or []),
2061
+ "approval_results": list(data.get("approvalResults") or []),
2062
+ "simulation": _normalize_swap_simulation(data.get("simulation")),
2063
+ "transaction": {
2064
+ "to": str(transaction.get("to") or "").strip() or None,
2065
+ "value": str(transaction.get("value") or "0"),
2066
+ "data_hash": str(transaction.get("dataHash") or "").strip() or None,
2067
+ },
2068
+ "hash": result.get("hash"),
2069
+ "result": result,
2070
+ "chain_id": int(data.get("chainId") or 0),
2071
+ "broadcasted": bool(result.get("hash")),
2072
+ "confirmed": bool(data.get("confirmed")),
2073
+ "source": str(data.get("source") or "wdk-evm-wallet"),
2074
+ }
2075
+
2076
+ async def preview_uniswap_liquidity(
2077
+ self,
2078
+ *,
2079
+ action: str,
2080
+ protocol: str,
2081
+ request: dict[str, Any],
2082
+ ) -> dict[str, Any]:
2083
+ if not isinstance(request, dict):
2084
+ raise WalletBackendError("Uniswap liquidity request must be an object.")
2085
+ data = await self.client.post(
2086
+ "/v1/evm/uniswap/liquidity/quote",
2087
+ {
2088
+ "walletId": self.wallet_id,
2089
+ "address": await self.get_address(),
2090
+ "accountIndex": self.account_index,
2091
+ "network": self.network,
2092
+ "action": action,
2093
+ "protocol": protocol,
2094
+ "request": request,
2095
+ },
2096
+ )
2097
+ normalized = self._normalize_uniswap_liquidity_payload(data, action=action, protocol=protocol)
2098
+ normalized["from_address"] = await self.get_address()
2099
+ normalized["execution_supported"] = not self.sign_only
2100
+ return normalized
2101
+
2102
+ async def send_uniswap_liquidity(
2103
+ self,
2104
+ *,
2105
+ action: str,
2106
+ protocol: str,
2107
+ request: dict[str, Any],
2108
+ ) -> dict[str, Any]:
2109
+ if self.sign_only:
2110
+ raise WalletBackendError("wdk_evm_local is configured as sign_only.")
2111
+ if not isinstance(request, dict):
2112
+ raise WalletBackendError("Uniswap liquidity request must be an object.")
2113
+ data = await self.client.post(
2114
+ "/v1/evm/uniswap/liquidity/send",
2115
+ {
2116
+ "walletId": self.wallet_id,
2117
+ "accountIndex": self.account_index,
2118
+ "network": self.network,
2119
+ "action": action,
2120
+ "protocol": protocol,
2121
+ "request": request,
2122
+ },
2123
+ )
2124
+ normalized = self._normalize_uniswap_liquidity_payload(data, action=action, protocol=protocol)
2125
+ normalized["from_address"] = await self.get_address()
2126
+ return normalized
2127
+
2031
2128
  async def preview_evm_lifi_cross_chain_swap(
2032
2129
  self,
2033
2130
  *,
@@ -2,7 +2,7 @@
2
2
  "id": "agent-wallet",
3
3
  "name": "Agent Wallet",
4
4
  "description": "Plugin-friendly wallet backend for OpenClaw agents with safe wallet tools and runtime instructions across Solana, local BTC, and local EVM.",
5
- "version": "0.1.93",
5
+ "version": "0.1.94",
6
6
  "skills": ["skills/wallet-operator"],
7
7
  "configSchema": {
8
8
  "type": "object",
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "openclaw-agent-wallet"
7
- version = "0.1.93"
7
+ version = "0.1.94"
8
8
  description = "Plugin-friendly wallet backend for OpenClaw agents"
9
9
  requires-python = ">=3.10"
10
10
  dependencies = [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "agent-wallet",
3
3
  "displayName": "Agent Wallet",
4
- "version": "0.1.93",
4
+ "version": "0.1.94",
5
5
  "description": "Claude Code bridge for the existing AgentLayer wallet runtime. Connects to Solana, Bitcoin, and EVM wallets without creating a new one.",
6
6
  "author": {
7
7
  "name": "AgentLayer"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-wallet",
3
- "version": "0.1.93",
3
+ "version": "0.1.94",
4
4
  "description": "Codex plugin bridge for the AgentLayer wallet runtime.",
5
5
  "author": {
6
6
  "name": "AgentLayer"
@@ -69,6 +69,7 @@ AUTONOMOUS_DEFI_TOOLS = {
69
69
  "manage_evm_lido_withdrawal",
70
70
  "manage_evm_morpho_market_position",
71
71
  "manage_evm_morpho_vault_position",
72
+ "manage_evm_uniswap_liquidity",
72
73
  }
73
74
  APPROVAL_PREVIEW_TOOL_ALIASES = {
74
75
  "x402_pay_request": "x402_preview_request",
@@ -1,5 +1,5 @@
1
1
  name: agent-wallet
2
- version: 0.1.93
2
+ version: 0.1.94
3
3
  description: Thin Hermes Agent bridge to the existing AgentLayer/OpenClaw wallet backend
4
4
  provides_tools:
5
5
  - agent_wallet_tools
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentlayer.tech/wallet",
3
- "version": "0.1.93",
3
+ "version": "0.1.94",
4
4
  "description": "Universal AgentLayer wallet installer for OpenClaw, Codex, Claude Code, and Hermes.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wdk-btc-wallet",
3
- "version": "0.1.93",
3
+ "version": "0.1.94",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "Separate BTC-only wallet service built on Tether WDK.",
@@ -26,6 +26,9 @@ MORPHO_API_BASE_URL=https://api.morpho.org/graphql
26
26
  # directly (https://trade-api.gateway.uniswap.org/v1) for legacy/offline direct mode.
27
27
  UNISWAP_API_KEY=
28
28
  # UNISWAP_TRADING_API_BASE_URL= # defaults to PROVIDER_GATEWAY_URL/v1/evm/uniswap
29
+ # LP actions use the Liquidity API. Gateway mode is the default; set this only
30
+ # for direct API access (for example https://liquidity.api.uniswap.org).
31
+ # UNISWAP_LIQUIDITY_API_BASE_URL= # defaults to PROVIDER_GATEWAY_URL/v1/evm/uniswap/lp
29
32
  UNISWAP_ROUTER_VERSION=2.0
30
33
  # Optional per-network override; each value must exist in the wallet's reviewed
31
34
  # execution profile and match the provider-gateway's per-chain configuration.
@@ -187,6 +187,7 @@ Environment variables:
187
187
  - `MORPHO_API_BASE_URL`
188
188
  - `UNISWAP_API_KEY`
189
189
  - `UNISWAP_TRADING_API_BASE_URL`
190
+ - `UNISWAP_LIQUIDITY_API_BASE_URL`
190
191
  - `UNISWAP_ROUTER_VERSION`
191
192
  - `UNISWAP_ROUTER_VERSION_BY_NETWORK`
192
193
  - `UNISWAP_DEFAULT_SLIPPAGE_BPS`
@@ -214,6 +215,11 @@ Swap providers:
214
215
  - `UNISWAP_API_KEY` is required for the Uniswap routes; it identifies the
215
216
  integrator (this service), not an end user — swaps are scoped per request by the
216
217
  active wallet address, so a single key never mixes users
218
+ - LP actions (`create`, `increase`, `decrease`, `claim_fees`) are exposed at
219
+ `/v1/evm/uniswap/liquidity/*` and use Uniswap's Liquidity API. The runtime
220
+ accepts transactions only for pinned V3/V4 PositionManager deployments on
221
+ Ethereum, Base, and Robinhood, refreshes the final LP transaction just before
222
+ signing, and executes only API-returned bounded approvals.
217
223
 
218
224
  Gateway mode:
219
225
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wdk-evm-wallet",
3
- "version": "0.1.93",
3
+ "version": "0.1.94",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "Separate EVM wallet service built on Tether WDK.",
@@ -359,6 +359,13 @@ export function loadConfig(env = process.env) {
359
359
  : "https://trade-api.gateway.uniswap.org/v1");
360
360
  const uniswapViaGateway =
361
361
  Boolean(gatewayBaseTrimmed) && uniswapTradingApiBaseUrl.startsWith(gatewayBaseTrimmed);
362
+ const uniswapLiquidityApiBaseUrl =
363
+ String(env.UNISWAP_LIQUIDITY_API_BASE_URL ?? "").trim() ||
364
+ (gatewayBaseTrimmed
365
+ ? `${gatewayBaseTrimmed}/v1/evm/uniswap/lp`
366
+ : "https://liquidity.api.uniswap.org");
367
+ const uniswapLiquidityViaGateway =
368
+ Boolean(gatewayBaseTrimmed) && uniswapLiquidityApiBaseUrl.startsWith(gatewayBaseTrimmed);
362
369
 
363
370
  return {
364
371
  host,
@@ -392,6 +399,8 @@ export function loadConfig(env = process.env) {
392
399
  lidoReferralAddress: String(env.LIDO_REFERRAL_ADDRESS ?? "").trim(),
393
400
  uniswapTradingApiBaseUrl,
394
401
  uniswapViaGateway,
402
+ uniswapLiquidityApiBaseUrl,
403
+ uniswapLiquidityViaGateway,
395
404
  providerGatewayToken,
396
405
  uniswapApiKey: String(env.UNISWAP_API_KEY ?? "").trim(),
397
406
  uniswapRouterVersion: String(env.UNISWAP_ROUTER_VERSION ?? "").trim() || "2.0",
@@ -640,6 +640,18 @@ async function handleRequest(request, response) {
640
640
  return sendJson(response, 200, { ok: true, data });
641
641
  }
642
642
 
643
+ if (method === "POST" && url.pathname === "/v1/evm/uniswap/liquidity/quote") {
644
+ const body = await withResolvedNetwork(await withResolvedSeedOrAddress(await readJsonBody(request)));
645
+ const data = await service.quoteUniswapLiquidity(body);
646
+ return sendJson(response, 200, { ok: true, data });
647
+ }
648
+
649
+ if (method === "POST" && url.pathname === "/v1/evm/uniswap/liquidity/send") {
650
+ const body = await withResolvedNetwork(await withResolvedSeed(await readJsonBody(request)));
651
+ const data = await service.sendUniswapLiquidity(body);
652
+ return sendJson(response, 200, { ok: true, data });
653
+ }
654
+
643
655
  if (method === "POST" && url.pathname === "/v1/evm/transfer/quote") {
644
656
  const body = await withResolvedNetwork(await withResolvedSeed(await readJsonBody(request)));
645
657
  const data = await service.quoteNativeTransfer(body);
@@ -23,6 +23,28 @@ const DEFAULT_LIFI_SLIPPAGE = 0.005;
23
23
  const ALWAYS_DENIED_LIFI_BRIDGES = ["mayan"];
24
24
  const PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
25
25
  const UNISWAP_SUPPORTED_CHAIN_IDS = { ethereum: 1, base: 8453, robinhood: 4663 };
26
+ const UNISWAP_LIQUIDITY_ACTIONS = new Set(["create", "increase", "decrease", "claim_fees"]);
27
+ const UNISWAP_LIQUIDITY_PROTOCOLS = new Set(["V3", "V4"]);
28
+ // LP API transactions are accepted only for these official per-chain position
29
+ // managers. Keeping the list here makes the local signer a narrow protocol
30
+ // integration rather than a generic calldata relay.
31
+ const UNISWAP_LIQUIDITY_POSITION_MANAGERS = {
32
+ ethereum: {
33
+ V3: "0xc36442b4a4522e871399cd717abdd847ab11fe88",
34
+ V4: "0xbd216513d74c8cf14cf4747e6aaa6420ff64ee9e",
35
+ },
36
+ base: {
37
+ V3: "0x03a520b32c04bf3beef7beb72e919cf822ed34f1",
38
+ V4: "0x7c5f5a4bbd8fd63184577525326123b519429bdc",
39
+ },
40
+ robinhood: {
41
+ V3: "0x73991a25c818bf1f1128deaab1492d45638de0d3",
42
+ V4: "0x58daec3116aae6d93017baaea7749052e8a04fa7",
43
+ },
44
+ };
45
+ const ERC20_APPROVE_INTERFACE = new Interface([
46
+ "function approve(address spender,uint256 amount) returns (bool)",
47
+ ]);
26
48
  // Every executable path is declared here rather than inferred from a Trading
27
49
  // API response. A new chain/router version therefore requires an explicit,
28
50
  // reviewed allow-list entry before it can receive a signed transaction.
@@ -925,6 +947,31 @@ function assertUniswapSupportedNetwork(network) {
925
947
  return chainId;
926
948
  }
927
949
 
950
+ function normalizeUniswapLiquidityAction(value) {
951
+ const action = String(value || "").trim().toLowerCase();
952
+ if (!UNISWAP_LIQUIDITY_ACTIONS.has(action)) {
953
+ throw new Error("Uniswap liquidity action must be create, increase, decrease, or claim_fees.");
954
+ }
955
+ return action;
956
+ }
957
+
958
+ function normalizeUniswapLiquidityProtocol(value) {
959
+ const protocol = String(value || "").trim().toUpperCase();
960
+ if (!UNISWAP_LIQUIDITY_PROTOCOLS.has(protocol)) {
961
+ throw new Error("Uniswap liquidity protocol must be V3 or V4.");
962
+ }
963
+ return protocol;
964
+ }
965
+
966
+ function getUniswapLiquidityPositionManager(network, protocol) {
967
+ assertUniswapSupportedNetwork(network);
968
+ const manager = UNISWAP_LIQUIDITY_POSITION_MANAGERS[network]?.[protocol];
969
+ if (!manager) {
970
+ throw new Error(`Uniswap ${protocol} liquidity is not configured for ${network}.`);
971
+ }
972
+ return manager;
973
+ }
974
+
928
975
  function getUniswapNetworkExecutionProfile(network) {
929
976
  const profile = UNISWAP_EXECUTION_PROFILES[network];
930
977
  if (!profile) {
@@ -5743,6 +5790,304 @@ export class WdkEvmWalletService {
5743
5790
  return payload;
5744
5791
  }
5745
5792
 
5793
+ async #uniswapLiquidityApiRequest(action, body) {
5794
+ const normalizedAction = String(action || "").trim().toLowerCase();
5795
+ const headers = { "Content-Type": "application/json", Accept: "application/json" };
5796
+ if (this.config.uniswapLiquidityViaGateway) {
5797
+ const token = String(this.config.providerGatewayToken || "").trim();
5798
+ if (token) {
5799
+ headers.Authorization = `Bearer ${token}`;
5800
+ }
5801
+ } else {
5802
+ if (!this.config.uniswapApiKey) {
5803
+ throw createTaggedError(
5804
+ "UNISWAP_API_KEY is not configured. Set it, or route Uniswap through the provider gateway, to use liquidity operations.",
5805
+ "uniswap_api_key_missing",
5806
+ { provider: "uniswap" }
5807
+ );
5808
+ }
5809
+ headers["x-api-key"] = this.config.uniswapApiKey;
5810
+ }
5811
+ const base = String(this.config.uniswapLiquidityApiBaseUrl).replace(/\/+$/, "");
5812
+ const suffix = this.config.uniswapLiquidityViaGateway ? `/${normalizedAction}` : `/lp/${normalizedAction}`;
5813
+ let response;
5814
+ try {
5815
+ response = await fetch(`${base}${suffix}`, { method: "POST", headers, body: JSON.stringify(body) });
5816
+ } catch (error) {
5817
+ const message = error instanceof Error ? error.message : String(error);
5818
+ throw createTaggedError(`Uniswap liquidity API unavailable: ${message}`, "network_unavailable", {
5819
+ provider: "uniswap",
5820
+ action: normalizedAction,
5821
+ });
5822
+ }
5823
+ let payload;
5824
+ try {
5825
+ payload = await response.json();
5826
+ } catch {
5827
+ payload = null;
5828
+ }
5829
+ if (!response.ok || !payload || typeof payload !== "object") {
5830
+ throw createTaggedError(
5831
+ String(payload?.message || payload?.error || `Uniswap liquidity ${normalizedAction} failed with HTTP ${response.status}.`),
5832
+ "network_unavailable",
5833
+ { provider: "uniswap", action: normalizedAction, httpStatus: response.status }
5834
+ );
5835
+ }
5836
+ return payload;
5837
+ }
5838
+
5839
+ #buildUniswapLiquidityRequest({ action, protocol, address, request }) {
5840
+ const body = { ...(request && typeof request === "object" ? request : {}) };
5841
+ // Caller cannot select identity, chain or simulator behavior. These are set
5842
+ // locally to make every preview/send reproducible through the active wallet.
5843
+ body.walletAddress = address;
5844
+ body.protocol = protocol;
5845
+ body.simulateTransaction = true;
5846
+ delete body.signature;
5847
+ delete body.batchPermitData;
5848
+ delete body.v4BatchPermitData;
5849
+ delete body.v3NftPermitData;
5850
+ if (action === "claim_fees") {
5851
+ delete body.independentToken;
5852
+ }
5853
+ return body;
5854
+ }
5855
+
5856
+ #extractUniswapLiquidityTransaction(payload, action) {
5857
+ const field = action === "create" ? "create" : action === "increase" ? "increase" : action === "decrease" ? "decrease" : "claim";
5858
+ const tx = payload?.[field] || payload?.transaction;
5859
+ if (!tx || typeof tx !== "object") {
5860
+ throw createTaggedError("Uniswap liquidity API returned no executable transaction.", "network_unavailable", {
5861
+ provider: "uniswap",
5862
+ action,
5863
+ });
5864
+ }
5865
+ return tx;
5866
+ }
5867
+
5868
+ async #getUniswapLiquidityApprovals({ runtimeConfig, protocol, action, address, payload }) {
5869
+ if (action !== "create" && action !== "increase") {
5870
+ return [];
5871
+ }
5872
+ const lpTokens = [payload?.token0, payload?.token1]
5873
+ .filter((token) => token && typeof token === "object")
5874
+ // The LP API represents native ETH with the zero-address sentinel. It
5875
+ // cannot have an ERC-20 allowance, so exclude it from check_approval.
5876
+ .filter((token) => !isZeroAddress(String(token.tokenAddress || "")))
5877
+ .map((token) => {
5878
+ const amount = BigInt(assertNonNegativeBigIntString(token.amount, "lp token amount"));
5879
+ return {
5880
+ tokenAddress: normalizeAddress(String(token.tokenAddress || ""), "lp token address"),
5881
+ amount,
5882
+ };
5883
+ })
5884
+ // Concentrated positions can legitimately be single-sided while the
5885
+ // current price lies outside their selected range. A zero contribution
5886
+ // has no ERC-20 allowance requirement and must not block the LP action.
5887
+ .filter((token) => token.amount > 0n)
5888
+ .map((token) => ({ ...token, amount: token.amount.toString() }));
5889
+ if (!lpTokens.length) {
5890
+ return [];
5891
+ }
5892
+ const expectedTokens = new Set(
5893
+ lpTokens.map((token) => String(token.tokenAddress).toLowerCase())
5894
+ );
5895
+ const approvalPayload = await this.#uniswapLiquidityApiRequest("check_approval", {
5896
+ walletAddress: address,
5897
+ chainId: runtimeConfig.chainId,
5898
+ protocol,
5899
+ lpTokens,
5900
+ action: action.toUpperCase(),
5901
+ // The initial version uses ordinary, bounded approval transactions. This
5902
+ // keeps the send path observable and avoids requiring an extra signature
5903
+ // protocol before LP workflows have seen live use.
5904
+ generatePermitAsTransaction: true,
5905
+ });
5906
+ const transactions = Array.isArray(approvalPayload.transactions) ? approvalPayload.transactions : [];
5907
+ return transactions.map((item, index) => {
5908
+ const raw = item?.transaction;
5909
+ if (!raw || typeof raw !== "object") {
5910
+ throw createTaggedError("Uniswap liquidity approval response is malformed.", "uniswap_liquidity_invalid_approval", { index });
5911
+ }
5912
+ const to = normalizeAddress(String(raw.to || ""), "liquidity approval.to");
5913
+ if (!expectedTokens.has(to.toLowerCase())) {
5914
+ throw createTaggedError("Uniswap liquidity approval is for a token outside this LP request.", "uniswap_liquidity_unexpected_token", { to });
5915
+ }
5916
+ const data = assertNonEmptyString(String(raw.data || ""), "liquidity approval.data");
5917
+ if (parseHexOrDecimalBigInt(raw.value || "0", "liquidity approval.value") !== 0n) {
5918
+ throw createTaggedError("Uniswap liquidity approval must not transfer native value.", "uniswap_liquidity_invalid_approval", { to });
5919
+ }
5920
+ if (raw.chainId !== undefined && Number(raw.chainId) !== runtimeConfig.chainId) {
5921
+ throw createTaggedError("Uniswap liquidity approval has the wrong chain id.", "uniswap_liquidity_chain_mismatch");
5922
+ }
5923
+ if (raw.from && normalizeAddress(String(raw.from), "liquidity approval.from").toLowerCase() !== address.toLowerCase()) {
5924
+ throw createTaggedError("Uniswap liquidity approval sender does not match the active wallet.", "uniswap_liquidity_sender_mismatch");
5925
+ }
5926
+ let decoded;
5927
+ try {
5928
+ decoded = ERC20_APPROVE_INTERFACE.parseTransaction({ data });
5929
+ } catch {
5930
+ decoded = null;
5931
+ }
5932
+ if (!decoded || decoded.name !== "approve") {
5933
+ throw createTaggedError("Uniswap liquidity approval is not a bounded ERC-20 approve call.", "uniswap_liquidity_invalid_approval", { to });
5934
+ }
5935
+ const spender = normalizeAddress(String(decoded.args[0]), "liquidity approval spender").toLowerCase();
5936
+ const amount = BigInt(decoded.args[1]);
5937
+ const positionManager = getUniswapLiquidityPositionManager(runtimeConfig.network, protocol);
5938
+ if (spender !== positionManager && spender !== PERMIT2_ADDRESS.toLowerCase()) {
5939
+ throw createTaggedError("Uniswap liquidity approval has an unexpected spender.", "uniswap_liquidity_unexpected_spender", { spender, positionManager });
5940
+ }
5941
+ if (amount <= 0n || amount >= (2n ** 255n)) {
5942
+ throw createTaggedError("Uniswap liquidity approval must use a bounded amount.", "uniswap_liquidity_unbounded_approval", { spender });
5943
+ }
5944
+ return {
5945
+ tx: { to, data, value: 0n },
5946
+ token: to,
5947
+ spender,
5948
+ amount,
5949
+ action: String(item?.action || action).toUpperCase(),
5950
+ };
5951
+ });
5952
+ }
5953
+
5954
+ #validateUniswapLiquidityTransaction({ runtimeConfig, protocol, address, transaction }) {
5955
+ const to = normalizeAddress(String(transaction.to || ""), "liquidity transaction.to");
5956
+ const expectedManager = getUniswapLiquidityPositionManager(runtimeConfig.network, protocol);
5957
+ if (to.toLowerCase() !== expectedManager) {
5958
+ throw createTaggedError("Uniswap liquidity API returned an unexpected PositionManager.", "uniswap_unexpected_position_manager", {
5959
+ expected: expectedManager,
5960
+ actual: to.toLowerCase(),
5961
+ network: runtimeConfig.network,
5962
+ protocol,
5963
+ });
5964
+ }
5965
+ if (transaction.chainId !== undefined && Number(transaction.chainId) !== runtimeConfig.chainId) {
5966
+ throw createTaggedError("Uniswap liquidity transaction has the wrong chain id.", "uniswap_liquidity_chain_mismatch", {
5967
+ expected: runtimeConfig.chainId,
5968
+ actual: transaction.chainId,
5969
+ });
5970
+ }
5971
+ if (transaction.from && normalizeAddress(String(transaction.from), "liquidity transaction.from").toLowerCase() !== address.toLowerCase()) {
5972
+ throw createTaggedError("Uniswap liquidity transaction sender does not match the active wallet.", "uniswap_liquidity_sender_mismatch");
5973
+ }
5974
+ const data = assertNonEmptyString(String(transaction.data || ""), "liquidity transaction.data");
5975
+ if (!/^0x[0-9a-fA-F]+$/.test(data) || data.length < 10) {
5976
+ throw createTaggedError("Uniswap liquidity transaction calldata is invalid.", "uniswap_liquidity_invalid_calldata");
5977
+ }
5978
+ return { to, data, value: parseHexOrDecimalBigInt(transaction.value || "0", "liquidity transaction.value") };
5979
+ }
5980
+
5981
+ #formatUniswapLiquidityResponse({ runtimeConfig, accountIndex, address, action, protocol, request, payload, transaction, approvals = [], simulation = null }) {
5982
+ const tx = transaction ? {
5983
+ to: transaction.to,
5984
+ value: transaction.value.toString(),
5985
+ dataHash: sha256Hex(transaction.data),
5986
+ } : null;
5987
+ return {
5988
+ network: runtimeConfig.network,
5989
+ chainId: runtimeConfig.chainId,
5990
+ accountIndex,
5991
+ address,
5992
+ protocol: "uniswap",
5993
+ liquidityAction: action,
5994
+ liquidityProtocol: protocol,
5995
+ request,
5996
+ requestId: String(payload?.requestId || "").trim() || null,
5997
+ token0: payload?.token0 || null,
5998
+ token1: payload?.token1 || null,
5999
+ tickLower: payload?.tickLower ?? null,
6000
+ tickUpper: payload?.tickUpper ?? null,
6001
+ adjustedMinPrice: payload?.adjustedMinPrice ?? null,
6002
+ adjustedMaxPrice: payload?.adjustedMaxPrice ?? null,
6003
+ gasFee: payload?.gasFee ?? null,
6004
+ positionTokenId: String(request?.nftTokenId || request?.tokenId || "").trim() || null,
6005
+ positionManager: getUniswapLiquidityPositionManager(runtimeConfig.network, protocol),
6006
+ approvals: approvals.map((approval) => ({
6007
+ token: approval.token,
6008
+ spender: approval.spender,
6009
+ amount: approval.amount.toString(),
6010
+ action: approval.action,
6011
+ })),
6012
+ transaction: tx,
6013
+ simulation,
6014
+ source: "uniswap-liquidity-api",
6015
+ };
6016
+ }
6017
+
6018
+ async quoteUniswapLiquidity({ seedPhrase, address, action, protocol, request, accountIndex = 0, network }) {
6019
+ return this.#withReadableAccount({ seedPhrase, address, accountIndex, network }, async (account, runtimeConfig) => {
6020
+ assertUniswapSupportedNetwork(runtimeConfig.network);
6021
+ const normalizedAction = normalizeUniswapLiquidityAction(action);
6022
+ const normalizedProtocol = normalizeUniswapLiquidityProtocol(protocol);
6023
+ const walletAddress = await account.getAddress();
6024
+ const body = this.#buildUniswapLiquidityRequest({ action: normalizedAction, protocol: normalizedProtocol, address: walletAddress, request });
6025
+ body.chainId = runtimeConfig.chainId;
6026
+ const payload = await this.#uniswapLiquidityApiRequest(normalizedAction, body);
6027
+ const transaction = this.#validateUniswapLiquidityTransaction({
6028
+ runtimeConfig,
6029
+ protocol: normalizedProtocol,
6030
+ address: walletAddress,
6031
+ transaction: this.#extractUniswapLiquidityTransaction(payload, normalizedAction),
6032
+ });
6033
+ const approvals = await this.#getUniswapLiquidityApprovals({ runtimeConfig, protocol: normalizedProtocol, action: normalizedAction, address: walletAddress, payload });
6034
+ const simulation = await this.#simulatePreparedTransaction({ runtimeConfig, from: walletAddress, tx: transaction, operationLabel: "Uniswap liquidity" });
6035
+ return this.#formatUniswapLiquidityResponse({ runtimeConfig, accountIndex, address: walletAddress, action: normalizedAction, protocol: normalizedProtocol, request: body, payload, transaction, approvals, simulation });
6036
+ });
6037
+ }
6038
+
6039
+ async sendUniswapLiquidity({ seedPhrase, action, protocol, request, accountIndex = 0, network }) {
6040
+ return this.#withAccount({ seedPhrase, accountIndex, network }, async (account, runtimeConfig) => {
6041
+ assertUniswapSupportedNetwork(runtimeConfig.network);
6042
+ const normalizedAction = normalizeUniswapLiquidityAction(action);
6043
+ const normalizedProtocol = normalizeUniswapLiquidityProtocol(protocol);
6044
+ const address = await account.getAddress();
6045
+ // Rebuild immediately before signing: price/ticks and calldata can change
6046
+ // while an intent approval is being reviewed.
6047
+ const body = this.#buildUniswapLiquidityRequest({ action: normalizedAction, protocol: normalizedProtocol, address, request });
6048
+ body.chainId = runtimeConfig.chainId;
6049
+ let payload = await this.#uniswapLiquidityApiRequest(normalizedAction, body);
6050
+ let transaction = this.#validateUniswapLiquidityTransaction({
6051
+ runtimeConfig,
6052
+ protocol: normalizedProtocol,
6053
+ address,
6054
+ transaction: this.#extractUniswapLiquidityTransaction(payload, normalizedAction),
6055
+ });
6056
+ const approvals = await this.#getUniswapLiquidityApprovals({ runtimeConfig, protocol: normalizedProtocol, action: normalizedAction, address, payload });
6057
+ const approvalResults = [];
6058
+ for (const approval of approvals) {
6059
+ const approvalSimulation = await this.#simulatePreparedTransaction({ runtimeConfig, from: address, tx: approval.tx, operationLabel: "Uniswap liquidity approval" });
6060
+ this.#assertSimulationSucceeded(approvalSimulation);
6061
+ const approvalResult = await this.#sendBufferedDefiTransaction({ account, runtimeConfig, from: address, tx: approval.tx, operationLabel: "Uniswap liquidity approval" });
6062
+ await this.#waitForTransactionReceipt(runtimeConfig, approvalResult.hash, { operationLabel: "Uniswap liquidity approval", failureCode: "uniswap_liquidity_approval_reverted", timeoutCode: "uniswap_liquidity_approval_timeout" });
6063
+ approvalResults.push({ token: approval.token, spender: approval.spender, amount: approval.amount.toString(), hash: approvalResult.hash });
6064
+ }
6065
+ if (approvals.length) {
6066
+ payload = await this.#uniswapLiquidityApiRequest(normalizedAction, body);
6067
+ transaction = this.#validateUniswapLiquidityTransaction({
6068
+ runtimeConfig,
6069
+ protocol: normalizedProtocol,
6070
+ address,
6071
+ transaction: this.#extractUniswapLiquidityTransaction(payload, normalizedAction),
6072
+ });
6073
+ }
6074
+ const simulation = await this.#simulatePreparedTransaction({ runtimeConfig, from: address, tx: transaction, operationLabel: "Uniswap liquidity" });
6075
+ this.#assertSimulationSucceeded(simulation);
6076
+ const result = await this.#sendBufferedDefiTransaction({ account, runtimeConfig, from: address, tx: transaction, operationLabel: "Uniswap liquidity" });
6077
+ await this.#waitForTransactionReceipt(runtimeConfig, result.hash, {
6078
+ operationLabel: "Uniswap liquidity",
6079
+ failureCode: "uniswap_liquidity_reverted",
6080
+ timeoutCode: "uniswap_liquidity_confirmation_timeout",
6081
+ });
6082
+ return {
6083
+ ...this.#formatUniswapLiquidityResponse({ runtimeConfig, accountIndex, address, action: normalizedAction, protocol: normalizedProtocol, request: body, payload, transaction, approvals, simulation }),
6084
+ result,
6085
+ approvalResults,
6086
+ confirmed: true,
6087
+ };
6088
+ });
6089
+ }
6090
+
5746
6091
  async #fetchUniswapQuote({ runtimeConfig, routerProfile, address, swapRequest }) {
5747
6092
  const chainId = UNISWAP_SUPPORTED_CHAIN_IDS[runtimeConfig.network];
5748
6093
  const quoteRequest = {