@agentlayer.tech/wallet 0.1.94 → 0.1.95
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.openclaw/extensions/agent-wallet/dist/index.js +39 -1
- package/.openclaw/extensions/agent-wallet/index.ts +39 -1
- package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +3 -1
- package/.openclaw/extensions/agent-wallet/package.json +1 -1
- package/VERSION +1 -1
- package/agent-wallet/agent_wallet/__init__.py +1 -1
- package/agent-wallet/agent_wallet/openclaw_adapter.py +128 -2
- package/agent-wallet/agent_wallet/wallet_layer/base.py +19 -0
- package/agent-wallet/agent_wallet/wallet_layer/wdk_evm.py +60 -0
- package/agent-wallet/openclaw.plugin.json +1 -1
- package/agent-wallet/pyproject.toml +1 -1
- package/agent-wallet/scripts/install_agent_wallet.py +29 -1
- package/claude-code/plugins/agent-wallet/.claude-plugin/plugin.json +1 -1
- package/claude-code/plugins/agent-wallet/AGENTLAYER_AGENT_GUIDE.md +282 -0
- package/claude-code/plugins/agent-wallet/README.md +2 -0
- package/claude-code/plugins/agent-wallet/commands/guide.md +40 -0
- 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/server.js +12 -0
- package/wdk-evm-wallet/src/wdk_evm_wallet.js +124 -0
|
@@ -1995,6 +1995,44 @@ const evmToolDefinitions = [
|
|
|
1995
1995
|
additionalProperties: false,
|
|
1996
1996
|
},
|
|
1997
1997
|
},
|
|
1998
|
+
{
|
|
1999
|
+
name: "get_evm_uniswap_pools",
|
|
2000
|
+
description: "Find existing Uniswap V3/V4 pools through the official Pool Info API on ethereum, base, or robinhood. Returns the canonical poolReferenceIdentifier needed for an LP create request. Read-only: it does not approve, sign, or execute anything.",
|
|
2001
|
+
parameters: {
|
|
2002
|
+
type: "object",
|
|
2003
|
+
properties: {
|
|
2004
|
+
protocol: { type: "string", enum: ["V3", "V4"] },
|
|
2005
|
+
pool_parameters: {
|
|
2006
|
+
type: "object",
|
|
2007
|
+
description: "Official Pool Info API token-pair parameters, such as tokenAddressA, tokenAddressB and optional fee/tickSpacing/hooks.",
|
|
2008
|
+
additionalProperties: true,
|
|
2009
|
+
},
|
|
2010
|
+
pool_references: {
|
|
2011
|
+
type: "array",
|
|
2012
|
+
maxItems: 20,
|
|
2013
|
+
items: { type: "object", additionalProperties: true },
|
|
2014
|
+
},
|
|
2015
|
+
page_size: { type: "integer", minimum: 1, maximum: 20 },
|
|
2016
|
+
current_page: { type: "integer", minimum: 1 },
|
|
2017
|
+
network: { type: "string", enum: EVM_CORE_NETWORKS },
|
|
2018
|
+
},
|
|
2019
|
+
required: ["protocol"],
|
|
2020
|
+
additionalProperties: false,
|
|
2021
|
+
},
|
|
2022
|
+
},
|
|
2023
|
+
{
|
|
2024
|
+
name: "get_evm_uniswap_positions",
|
|
2025
|
+
description: "List the active wallet's Uniswap V3 LP NFTs on ethereum, base, or robinhood. Returns token ids, token pair, fee tier, ticks, liquidity, and owed fees. Read-only. V4 is excluded because its PositionManager is not enumerable without a verified indexed source.",
|
|
2026
|
+
parameters: {
|
|
2027
|
+
type: "object",
|
|
2028
|
+
properties: {
|
|
2029
|
+
protocol: { type: "string", enum: ["V3"] },
|
|
2030
|
+
limit: { type: "integer", minimum: 1, maximum: 100 },
|
|
2031
|
+
network: { type: "string", enum: EVM_CORE_NETWORKS },
|
|
2032
|
+
},
|
|
2033
|
+
additionalProperties: false,
|
|
2034
|
+
},
|
|
2035
|
+
},
|
|
1998
2036
|
{
|
|
1999
2037
|
name: "swap_evm_uniswap_tokens",
|
|
2000
2038
|
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.",
|
|
@@ -2017,7 +2055,7 @@ const evmToolDefinitions = [
|
|
|
2017
2055
|
},
|
|
2018
2056
|
{
|
|
2019
2057
|
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.",
|
|
2058
|
+
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. Create needs existingPool.poolReference and position actions need an NFT token id: use get_evm_uniswap_pools or the V3 get_evm_uniswap_positions scanner to obtain them, never guess. 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
2059
|
optional: true,
|
|
2022
2060
|
parameters: {
|
|
2023
2061
|
type: "object",
|
|
@@ -1995,6 +1995,44 @@ const evmToolDefinitions = [
|
|
|
1995
1995
|
additionalProperties: false,
|
|
1996
1996
|
},
|
|
1997
1997
|
},
|
|
1998
|
+
{
|
|
1999
|
+
name: "get_evm_uniswap_pools",
|
|
2000
|
+
description: "Find existing Uniswap V3/V4 pools through the official Pool Info API on ethereum, base, or robinhood. Returns the canonical poolReferenceIdentifier needed for an LP create request. Read-only: it does not approve, sign, or execute anything.",
|
|
2001
|
+
parameters: {
|
|
2002
|
+
type: "object",
|
|
2003
|
+
properties: {
|
|
2004
|
+
protocol: { type: "string", enum: ["V3", "V4"] },
|
|
2005
|
+
pool_parameters: {
|
|
2006
|
+
type: "object",
|
|
2007
|
+
description: "Official Pool Info API token-pair parameters, such as tokenAddressA, tokenAddressB and optional fee/tickSpacing/hooks.",
|
|
2008
|
+
additionalProperties: true,
|
|
2009
|
+
},
|
|
2010
|
+
pool_references: {
|
|
2011
|
+
type: "array",
|
|
2012
|
+
maxItems: 20,
|
|
2013
|
+
items: { type: "object", additionalProperties: true },
|
|
2014
|
+
},
|
|
2015
|
+
page_size: { type: "integer", minimum: 1, maximum: 20 },
|
|
2016
|
+
current_page: { type: "integer", minimum: 1 },
|
|
2017
|
+
network: { type: "string", enum: EVM_CORE_NETWORKS },
|
|
2018
|
+
},
|
|
2019
|
+
required: ["protocol"],
|
|
2020
|
+
additionalProperties: false,
|
|
2021
|
+
},
|
|
2022
|
+
},
|
|
2023
|
+
{
|
|
2024
|
+
name: "get_evm_uniswap_positions",
|
|
2025
|
+
description: "List the active wallet's Uniswap V3 LP NFTs on ethereum, base, or robinhood. Returns token ids, token pair, fee tier, ticks, liquidity, and owed fees. Read-only. V4 is excluded because its PositionManager is not enumerable without a verified indexed source.",
|
|
2026
|
+
parameters: {
|
|
2027
|
+
type: "object",
|
|
2028
|
+
properties: {
|
|
2029
|
+
protocol: { type: "string", enum: ["V3"] },
|
|
2030
|
+
limit: { type: "integer", minimum: 1, maximum: 100 },
|
|
2031
|
+
network: { type: "string", enum: EVM_CORE_NETWORKS },
|
|
2032
|
+
},
|
|
2033
|
+
additionalProperties: false,
|
|
2034
|
+
},
|
|
2035
|
+
},
|
|
1998
2036
|
{
|
|
1999
2037
|
name: "swap_evm_uniswap_tokens",
|
|
2000
2038
|
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.",
|
|
@@ -2017,7 +2055,7 @@ const evmToolDefinitions = [
|
|
|
2017
2055
|
},
|
|
2018
2056
|
{
|
|
2019
2057
|
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.",
|
|
2058
|
+
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. Create needs existingPool.poolReference and position actions need an NFT token id: use get_evm_uniswap_pools or the V3 get_evm_uniswap_positions scanner to obtain them, never guess. 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
2059
|
optional: true,
|
|
2022
2060
|
parameters: {
|
|
2023
2061
|
type: "object",
|
|
@@ -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.
|
|
5
|
+
"version": "0.1.95",
|
|
6
6
|
"contracts": {
|
|
7
7
|
"tools": [
|
|
8
8
|
"agentlayer_autonomous_approve",
|
|
@@ -64,6 +64,8 @@
|
|
|
64
64
|
"manage_evm_lido_withdrawal",
|
|
65
65
|
"manage_evm_uniswap_liquidity",
|
|
66
66
|
"search_uniswap_pairs",
|
|
67
|
+
"get_evm_uniswap_pools",
|
|
68
|
+
"get_evm_uniswap_positions",
|
|
67
69
|
"set_evm_network",
|
|
68
70
|
"set_wallet_backend",
|
|
69
71
|
"sign_wallet_message",
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.1.
|
|
1
|
+
0.1.95
|
|
@@ -1746,12 +1746,62 @@ class OpenClawWalletAdapter:
|
|
|
1746
1746
|
)
|
|
1747
1747
|
tools.insert(
|
|
1748
1748
|
13,
|
|
1749
|
+
AgentToolSpec(
|
|
1750
|
+
name="get_evm_uniswap_pools",
|
|
1751
|
+
description=(
|
|
1752
|
+
"Find existing Uniswap V3/V4 pool metadata on ethereum, base, or robinhood using the official "
|
|
1753
|
+
"Uniswap Pool Info API. Returns the canonical poolReferenceIdentifier required to create liquidity; "
|
|
1754
|
+
"this is read-only and does not approve, sign, or execute anything."
|
|
1755
|
+
),
|
|
1756
|
+
input_schema={
|
|
1757
|
+
"type": "object",
|
|
1758
|
+
"properties": {
|
|
1759
|
+
"protocol": {"type": "string", "enum": ["V3", "V4"]},
|
|
1760
|
+
"pool_parameters": {"type": "object", "description": "Official Pool Info API token-pair parameters, such as tokenAddressA, tokenAddressB and optional fee/tickSpacing/hooks.", "additionalProperties": True},
|
|
1761
|
+
"pool_references": {"type": "array", "maxItems": 20, "items": {"type": "object", "additionalProperties": True}, "description": "One to twenty official Pool Info API pool reference objects."},
|
|
1762
|
+
"page_size": {"type": "integer", "minimum": 1, "maximum": 20},
|
|
1763
|
+
"current_page": {"type": "integer", "minimum": 1},
|
|
1764
|
+
"network": {"type": "string", "enum": ["ethereum", "base", "robinhood"]},
|
|
1765
|
+
},
|
|
1766
|
+
"required": ["protocol"],
|
|
1767
|
+
"additionalProperties": False,
|
|
1768
|
+
},
|
|
1769
|
+
read_only=True,
|
|
1770
|
+
risk_level="low",
|
|
1771
|
+
),
|
|
1772
|
+
)
|
|
1773
|
+
tools.insert(
|
|
1774
|
+
14,
|
|
1775
|
+
AgentToolSpec(
|
|
1776
|
+
name="get_evm_uniswap_positions",
|
|
1777
|
+
description=(
|
|
1778
|
+
"List read-only Uniswap V3 LP position NFTs owned by the active wallet on ethereum, base, or robinhood. "
|
|
1779
|
+
"Returns NFT token ids, tokens, fee tier, tick range, liquidity, and currently owed fees. V4 discovery is "
|
|
1780
|
+
"not exposed until a verified indexed source is configured because V4 PositionManager is not enumerable."
|
|
1781
|
+
),
|
|
1782
|
+
input_schema={
|
|
1783
|
+
"type": "object",
|
|
1784
|
+
"properties": {
|
|
1785
|
+
"protocol": {"type": "string", "enum": ["V3"], "description": "V3 is the currently enumerable, on-chain-supported position scanner."},
|
|
1786
|
+
"limit": {"type": "integer", "minimum": 1, "maximum": 100},
|
|
1787
|
+
"network": {"type": "string", "enum": ["ethereum", "base", "robinhood"]},
|
|
1788
|
+
},
|
|
1789
|
+
"additionalProperties": False,
|
|
1790
|
+
},
|
|
1791
|
+
read_only=True,
|
|
1792
|
+
risk_level="low",
|
|
1793
|
+
),
|
|
1794
|
+
)
|
|
1795
|
+
tools.insert(
|
|
1796
|
+
15,
|
|
1749
1797
|
AgentToolSpec(
|
|
1750
1798
|
name="manage_evm_uniswap_liquidity",
|
|
1751
1799
|
description=(
|
|
1752
1800
|
"Preview, prepare, or execute a Uniswap V3/V4 liquidity action on ethereum, base, or robinhood. "
|
|
1753
1801
|
"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."
|
|
1802
|
+
"official Uniswap Liquidity API; execute refreshes the transaction immediately before signing. "
|
|
1803
|
+
"Create requires existingPool.poolReference; increase/decrease/claim_fees require a position NFT token id. "
|
|
1804
|
+
"Use get_evm_uniswap_pools or get_evm_uniswap_positions to obtain these identifiers; never guess them."
|
|
1755
1805
|
),
|
|
1756
1806
|
input_schema={
|
|
1757
1807
|
"type": "object",
|
|
@@ -5682,6 +5732,46 @@ class OpenClawWalletAdapter:
|
|
|
5682
5732
|
)
|
|
5683
5733
|
return AgentToolResult(tool=tool_name, ok=True, data=data)
|
|
5684
5734
|
|
|
5735
|
+
if tool_name == "get_evm_uniswap_pools":
|
|
5736
|
+
protocol = args.get("protocol")
|
|
5737
|
+
pool_parameters = args.get("pool_parameters")
|
|
5738
|
+
pool_references = args.get("pool_references")
|
|
5739
|
+
page_size = args.get("page_size", 20)
|
|
5740
|
+
current_page = args.get("current_page", 1)
|
|
5741
|
+
if protocol not in {"V3", "V4"}:
|
|
5742
|
+
raise WalletBackendError("protocol must be V3 or V4.")
|
|
5743
|
+
if (pool_parameters is None) == (pool_references is None):
|
|
5744
|
+
raise WalletBackendError("Provide exactly one of pool_parameters or pool_references.")
|
|
5745
|
+
if pool_parameters is not None and not isinstance(pool_parameters, dict):
|
|
5746
|
+
raise WalletBackendError("pool_parameters must be an object.")
|
|
5747
|
+
if pool_references is not None:
|
|
5748
|
+
if not isinstance(pool_references, list) or not 1 <= len(pool_references) <= 20:
|
|
5749
|
+
raise WalletBackendError("pool_references must contain between 1 and 20 objects.")
|
|
5750
|
+
if any(not isinstance(reference, dict) for reference in pool_references):
|
|
5751
|
+
raise WalletBackendError("pool_references must contain only objects.")
|
|
5752
|
+
if not isinstance(page_size, int) or not 1 <= page_size <= 20:
|
|
5753
|
+
raise WalletBackendError("page_size must be an integer between 1 and 20.")
|
|
5754
|
+
if not isinstance(current_page, int) or current_page < 1:
|
|
5755
|
+
raise WalletBackendError("current_page must be a positive integer.")
|
|
5756
|
+
data = await active_backend.get_uniswap_liquidity_pools(
|
|
5757
|
+
protocol=protocol,
|
|
5758
|
+
pool_parameters=pool_parameters,
|
|
5759
|
+
pool_references=pool_references,
|
|
5760
|
+
page_size=page_size,
|
|
5761
|
+
current_page=current_page,
|
|
5762
|
+
)
|
|
5763
|
+
return AgentToolResult(tool=tool_name, ok=True, data=data)
|
|
5764
|
+
|
|
5765
|
+
if tool_name == "get_evm_uniswap_positions":
|
|
5766
|
+
protocol = args.get("protocol", "V3")
|
|
5767
|
+
limit = args.get("limit", 20)
|
|
5768
|
+
if protocol != "V3":
|
|
5769
|
+
raise WalletBackendError("Only V3 position discovery is currently available; V4 PositionManager is not enumerable.")
|
|
5770
|
+
if not isinstance(limit, int) or not 1 <= limit <= 100:
|
|
5771
|
+
raise WalletBackendError("limit must be an integer between 1 and 100.")
|
|
5772
|
+
data = await active_backend.get_uniswap_liquidity_positions(protocol=protocol, limit=limit)
|
|
5773
|
+
return AgentToolResult(tool=tool_name, ok=True, data=data)
|
|
5774
|
+
|
|
5685
5775
|
if tool_name == "swap_evm_uniswap_tokens":
|
|
5686
5776
|
token_in = args.get("token_in")
|
|
5687
5777
|
token_out = args.get("token_out")
|
|
@@ -5826,13 +5916,49 @@ class OpenClawWalletAdapter:
|
|
|
5826
5916
|
raise WalletBackendError("mode must be 'preview', 'prepare' or 'execute'.")
|
|
5827
5917
|
if not isinstance(purpose, str) or not purpose.strip():
|
|
5828
5918
|
raise WalletBackendError("purpose is required.")
|
|
5919
|
+
normalized_request = dict(request)
|
|
5920
|
+
if action == "create":
|
|
5921
|
+
existing_pool = normalized_request.get("existingPool")
|
|
5922
|
+
if not isinstance(existing_pool, dict):
|
|
5923
|
+
raise WalletBackendError(
|
|
5924
|
+
"create requires existingPool with token0Address, token1Address, and poolReference. "
|
|
5925
|
+
"Use get_evm_uniswap_pools to obtain the exact existing pool; do not infer it from market search data."
|
|
5926
|
+
)
|
|
5927
|
+
missing_pool_fields = [
|
|
5928
|
+
field
|
|
5929
|
+
for field in ("token0Address", "token1Address", "poolReference")
|
|
5930
|
+
if not isinstance(existing_pool.get(field), str) or not existing_pool[field].strip()
|
|
5931
|
+
]
|
|
5932
|
+
if missing_pool_fields:
|
|
5933
|
+
raise WalletBackendError(
|
|
5934
|
+
"create existingPool is missing "
|
|
5935
|
+
f"{', '.join(missing_pool_fields)}. Ask the user for the exact existing Uniswap pool; do not infer it from market search data."
|
|
5936
|
+
)
|
|
5937
|
+
elif action in {"increase", "decrease"}:
|
|
5938
|
+
nft_token_id = normalized_request.get("nftTokenId")
|
|
5939
|
+
if not isinstance(nft_token_id, str) or not nft_token_id.strip().isdigit() or int(nft_token_id.strip()) <= 0:
|
|
5940
|
+
raise WalletBackendError(
|
|
5941
|
+
f"{action} requires a positive nftTokenId. Use get_evm_uniswap_positions for V3 or request a verified V4 token id; never infer it."
|
|
5942
|
+
)
|
|
5943
|
+
normalized_request["nftTokenId"] = nft_token_id.strip()
|
|
5944
|
+
else: # claim_fees
|
|
5945
|
+
token_id = normalized_request.get("tokenId", normalized_request.get("nftTokenId"))
|
|
5946
|
+
if not isinstance(token_id, str) or not token_id.strip().isdigit() or int(token_id.strip()) <= 0:
|
|
5947
|
+
raise WalletBackendError(
|
|
5948
|
+
"claim_fees requires the user's positive tokenId (the LP position NFT id). "
|
|
5949
|
+
"Use get_evm_uniswap_positions for V3 or request a verified V4 token id; never infer it."
|
|
5950
|
+
)
|
|
5951
|
+
# The Liquidity API names this field tokenId for fee claims;
|
|
5952
|
+
# accept nftTokenId as the user-facing alias used by other LP actions.
|
|
5953
|
+
normalized_request["tokenId"] = token_id.strip()
|
|
5954
|
+
normalized_request.pop("nftTokenId", None)
|
|
5829
5955
|
# The caller must not override values that are derived from the
|
|
5830
5956
|
# active wallet/network in the WDK layer.
|
|
5831
5957
|
forbidden = {"walletAddress", "chainId", "protocol", "simulateTransaction", "signature", "batchPermitData", "v4BatchPermitData", "v3NftPermitData"}
|
|
5832
5958
|
overlap = forbidden.intersection(request)
|
|
5833
5959
|
if overlap:
|
|
5834
5960
|
raise WalletBackendError(f"request must not set wallet-controlled fields: {', '.join(sorted(overlap))}.")
|
|
5835
|
-
preview_kwargs = {"action": action, "protocol": protocol, "request":
|
|
5961
|
+
preview_kwargs = {"action": action, "protocol": protocol, "request": normalized_request}
|
|
5836
5962
|
if mode == "preview":
|
|
5837
5963
|
preview = await active_backend.preview_uniswap_liquidity(**preview_kwargs)
|
|
5838
5964
|
return AgentToolResult(tool=tool_name, ok=True, data=self._annotate_sensitive_payload(preview, action_label="Uniswap liquidity", mode="preview"))
|
|
@@ -337,6 +337,25 @@ class AgentWalletBackend(ABC):
|
|
|
337
337
|
) -> dict[str, Any]:
|
|
338
338
|
raise WalletBackendError(f"{self.name} does not support Uniswap pair search.")
|
|
339
339
|
|
|
340
|
+
async def get_uniswap_liquidity_pools(
|
|
341
|
+
self,
|
|
342
|
+
*,
|
|
343
|
+
protocol: str,
|
|
344
|
+
pool_parameters: dict[str, Any] | None = None,
|
|
345
|
+
pool_references: list[dict[str, Any]] | None = None,
|
|
346
|
+
page_size: int = 20,
|
|
347
|
+
current_page: int = 1,
|
|
348
|
+
) -> dict[str, Any]:
|
|
349
|
+
raise WalletBackendError(f"{self.name} does not support Uniswap liquidity pool discovery.")
|
|
350
|
+
|
|
351
|
+
async def get_uniswap_liquidity_positions(
|
|
352
|
+
self,
|
|
353
|
+
*,
|
|
354
|
+
protocol: str = "V3",
|
|
355
|
+
limit: int = 20,
|
|
356
|
+
) -> dict[str, Any]:
|
|
357
|
+
raise WalletBackendError(f"{self.name} does not support Uniswap liquidity position discovery.")
|
|
358
|
+
|
|
340
359
|
async def send_uniswap_swap(
|
|
341
360
|
self,
|
|
342
361
|
*,
|
|
@@ -1957,6 +1957,66 @@ class WdkEvmLocalWalletBackend(AgentWalletBackend):
|
|
|
1957
1957
|
"source": "dexscreener",
|
|
1958
1958
|
}
|
|
1959
1959
|
|
|
1960
|
+
async def get_uniswap_liquidity_pools(
|
|
1961
|
+
self,
|
|
1962
|
+
*,
|
|
1963
|
+
protocol: str,
|
|
1964
|
+
pool_parameters: dict[str, Any] | None = None,
|
|
1965
|
+
pool_references: list[dict[str, Any]] | None = None,
|
|
1966
|
+
page_size: int = 20,
|
|
1967
|
+
current_page: int = 1,
|
|
1968
|
+
) -> dict[str, Any]:
|
|
1969
|
+
if protocol not in {"V3", "V4"}:
|
|
1970
|
+
raise WalletBackendError("protocol must be V3 or V4.")
|
|
1971
|
+
if (pool_parameters is None) == (pool_references is None):
|
|
1972
|
+
raise WalletBackendError("Provide exactly one of pool_parameters or pool_references.")
|
|
1973
|
+
if pool_parameters is not None and not isinstance(pool_parameters, dict):
|
|
1974
|
+
raise WalletBackendError("pool_parameters must be an object.")
|
|
1975
|
+
if pool_references is not None:
|
|
1976
|
+
if not isinstance(pool_references, list) or not 1 <= len(pool_references) <= 20:
|
|
1977
|
+
raise WalletBackendError("pool_references must contain between 1 and 20 objects.")
|
|
1978
|
+
if any(not isinstance(reference, dict) for reference in pool_references):
|
|
1979
|
+
raise WalletBackendError("pool_references must contain only objects.")
|
|
1980
|
+
if not isinstance(page_size, int) or not 1 <= page_size <= 20:
|
|
1981
|
+
raise WalletBackendError("page_size must be an integer between 1 and 20.")
|
|
1982
|
+
if not isinstance(current_page, int) or current_page < 1:
|
|
1983
|
+
raise WalletBackendError("current_page must be a positive integer.")
|
|
1984
|
+
body: dict[str, Any] = {
|
|
1985
|
+
"network": self.network,
|
|
1986
|
+
"protocol": protocol,
|
|
1987
|
+
"pageSize": page_size,
|
|
1988
|
+
"currentPage": current_page,
|
|
1989
|
+
}
|
|
1990
|
+
if pool_parameters is not None:
|
|
1991
|
+
body["poolParameters"] = pool_parameters
|
|
1992
|
+
else:
|
|
1993
|
+
body["poolReferences"] = pool_references
|
|
1994
|
+
data = await self.client.post("/v1/evm/uniswap/liquidity/pools", body)
|
|
1995
|
+
return dict(data)
|
|
1996
|
+
|
|
1997
|
+
async def get_uniswap_liquidity_positions(
|
|
1998
|
+
self,
|
|
1999
|
+
*,
|
|
2000
|
+
protocol: str = "V3",
|
|
2001
|
+
limit: int = 20,
|
|
2002
|
+
) -> dict[str, Any]:
|
|
2003
|
+
if protocol not in {"V3", "V4"}:
|
|
2004
|
+
raise WalletBackendError("protocol must be V3 or V4.")
|
|
2005
|
+
if not isinstance(limit, int) or not 1 <= limit <= 100:
|
|
2006
|
+
raise WalletBackendError("limit must be an integer between 1 and 100.")
|
|
2007
|
+
data = await self.client.post(
|
|
2008
|
+
"/v1/evm/uniswap/liquidity/positions",
|
|
2009
|
+
{
|
|
2010
|
+
"walletId": self.wallet_id,
|
|
2011
|
+
"address": await self.get_address(),
|
|
2012
|
+
"accountIndex": self.account_index,
|
|
2013
|
+
"network": self.network,
|
|
2014
|
+
"protocol": protocol,
|
|
2015
|
+
"limit": limit,
|
|
2016
|
+
},
|
|
2017
|
+
)
|
|
2018
|
+
return dict(data)
|
|
2019
|
+
|
|
1960
2020
|
async def send_uniswap_swap(
|
|
1961
2021
|
self,
|
|
1962
2022
|
*,
|
|
@@ -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.
|
|
5
|
+
"version": "0.1.95",
|
|
6
6
|
"skills": ["skills/wallet-operator"],
|
|
7
7
|
"configSchema": {
|
|
8
8
|
"type": "object",
|
|
@@ -513,6 +513,26 @@ def _pip_install_editable(python_bin: Path, package_root: Path) -> None:
|
|
|
513
513
|
)
|
|
514
514
|
|
|
515
515
|
|
|
516
|
+
def _python_runtime_is_healthy(python_bin: Path) -> bool:
|
|
517
|
+
"""Return whether a reusable venv has the installer dependencies available.
|
|
518
|
+
|
|
519
|
+
A shared runtime can be left half-created when an install is interrupted
|
|
520
|
+
between ``venv`` creation and the editable pip install. The interpreter
|
|
521
|
+
itself then exists, but the next update must repair it rather than treating
|
|
522
|
+
it as a valid cache entry.
|
|
523
|
+
"""
|
|
524
|
+
try:
|
|
525
|
+
result = subprocess.run(
|
|
526
|
+
[str(python_bin), "-c", "import pydantic, pydantic_settings"],
|
|
527
|
+
capture_output=True,
|
|
528
|
+
text=True,
|
|
529
|
+
check=False,
|
|
530
|
+
)
|
|
531
|
+
except OSError:
|
|
532
|
+
return False
|
|
533
|
+
return result.returncode == 0
|
|
534
|
+
|
|
535
|
+
|
|
516
536
|
def _ensure_python_runtime(
|
|
517
537
|
venv_path: Path,
|
|
518
538
|
package_root: Path,
|
|
@@ -529,9 +549,17 @@ def _ensure_python_runtime(
|
|
|
529
549
|
created = True
|
|
530
550
|
_bootstrap_venv_pip(python_bin)
|
|
531
551
|
_pip_install_editable(python_bin, package_root)
|
|
552
|
+
elif not _python_runtime_is_healthy(python_bin):
|
|
553
|
+
# A prior update may have been interrupted after creating the venv
|
|
554
|
+
# but before pip finished. Repair the cache in place; release links
|
|
555
|
+
# continue to point at one verified shared environment.
|
|
556
|
+
_bootstrap_venv_pip(python_bin)
|
|
557
|
+
_pip_install_editable(python_bin, package_root)
|
|
558
|
+
plan["action"] = "repair"
|
|
532
559
|
shared_wrapper = _ensure_python_wrapper(shared_venv_path)
|
|
533
560
|
_replace_with_directory_symlink(venv_path, shared_venv_path)
|
|
534
|
-
|
|
561
|
+
if created:
|
|
562
|
+
plan["action"] = "create"
|
|
535
563
|
plan["exists"] = True
|
|
536
564
|
return (
|
|
537
565
|
venv_path / shared_wrapper.relative_to(shared_venv_path),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-wallet",
|
|
3
3
|
"displayName": "Agent Wallet",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.95",
|
|
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"
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# AgentLayer Wallet — Agent Guide
|
|
2
|
+
|
|
3
|
+
> **Relationship to `skills/wallet-operator/SKILL.md`:** that file is the
|
|
4
|
+
> authoritative, terse routing skill actually loaded by hosts (provider map, param
|
|
5
|
+
> tables, approval-flow template) — keep changes to tool routing/params there, not
|
|
6
|
+
> here. This document is a narrative companion: written the way an agent (or a human
|
|
7
|
+
> reading over its shoulder) would want the product *explained*, not just routed —
|
|
8
|
+
> modeled on how the `catena` CLI ships a self-contained `catena guide` command
|
|
9
|
+
> alongside its terse `--help` output; the `/guide` command in this plugin serves
|
|
10
|
+
> that exact role. Tool names below are short; the full MCP name is
|
|
11
|
+
> `mcp__plugin_agent-wallet_agent-wallet__<name>`.
|
|
12
|
+
|
|
13
|
+
This server holds funds in the local AgentLayer wallet across Solana, EVM
|
|
14
|
+
(Ethereum, Base, Robinhood chain), and Bitcoin. It is not a generic
|
|
15
|
+
crypto-data tool — every write here moves real money unless stated as
|
|
16
|
+
mainnet-gated preview. Leverage markets (Flash Trade perps:
|
|
17
|
+
`flash_trade_open_position`, `flash_trade_close_position`,
|
|
18
|
+
`get_flash_trade_markets`, `get_flash_trade_positions`) are out of scope for
|
|
19
|
+
this guide — do not use them without separate instructions.
|
|
20
|
+
|
|
21
|
+
## What You Can Do
|
|
22
|
+
|
|
23
|
+
- **Solana**: transfers, swaps via Jupiter, native staking, Kamino (lending +
|
|
24
|
+
earn vaults + LP positions), token launches via Bags.
|
|
25
|
+
- **EVM** (Ethereum / Base / Robinhood): transfers, swaps (Velora or
|
|
26
|
+
Uniswap), and DeFi — Aave (lending), Lido (ETH staking), Morpho (markets +
|
|
27
|
+
vaults), and Uniswap concentrated liquidity positions (create / increase /
|
|
28
|
+
decrease / claim fees, V3 and V4 — existing V4 positions can't be
|
|
29
|
+
auto-discovered, only V3; for a V4 position the id has to come from the
|
|
30
|
+
user).
|
|
31
|
+
- **Cross-chain bridging** (LI.FI): Ethereum / Base / Solana to each other.
|
|
32
|
+
- **x402**: pay per-request HTTP 402 paywalls straight from the wallet —
|
|
33
|
+
preview the payment terms for free, then pay in one call.
|
|
34
|
+
|
|
35
|
+
See the sections below for exact tool names and parameters.
|
|
36
|
+
|
|
37
|
+
## Setup & Session State
|
|
38
|
+
|
|
39
|
+
1. `get_active_wallet_backend` — which backend (solana / evm / btc) is live
|
|
40
|
+
for this session, and whether it differs from the startup default.
|
|
41
|
+
2. `get_wallet_address` — the address for the active backend.
|
|
42
|
+
3. `get_wallet_capabilities` — chain, backend, and the safety limits in force.
|
|
43
|
+
4. `set_wallet_backend` (`backend`: solana / evm / ethereum / base /
|
|
44
|
+
robinhood / btc / bitcoin, optional `network`) — switch backend for this
|
|
45
|
+
session without touching config files.
|
|
46
|
+
5. For EVM specifically: `get_evm_network` shows the effective network and
|
|
47
|
+
which networks support swaps; `set_evm_network` (ethereum / base /
|
|
48
|
+
robinhood) changes it.
|
|
49
|
+
|
|
50
|
+
Balance reads (Solana): `get_wallet_balance` / `get_wallet_portfolio` are the
|
|
51
|
+
same enriched payload (native SOL + non-zero SPL accounts + USD pricing via
|
|
52
|
+
Jupiter) — `_portfolio` is just the more detailed name for the same call.
|
|
53
|
+
`get_wallet_overview` does the same lookup for an arbitrary backend/network/
|
|
54
|
+
address **without** switching the session's active wallet — use it to peek at
|
|
55
|
+
another chain or address in passing.
|
|
56
|
+
|
|
57
|
+
## Quick Commands (Claude Code slash commands)
|
|
58
|
+
|
|
59
|
+
The plugin also ships fixed-format slash commands for the most common
|
|
60
|
+
requests — faster and more predictable than a free-form tool call, but each
|
|
61
|
+
covers only its one exact use case:
|
|
62
|
+
|
|
63
|
+
| Command | What it does |
|
|
64
|
+
|---|---|
|
|
65
|
+
| `/wallet-setup` | Install or repair the local wallet backend runtime. |
|
|
66
|
+
| `/wallet-sol` | Print the Solana wallet portfolio. |
|
|
67
|
+
| `/wallet-evm` | Print the EVM wallet overview for the current/default network. |
|
|
68
|
+
| `/wallet-base` | Print the Base wallet overview, and switch the session's active backend to Base. |
|
|
69
|
+
| `/wallet-ethereum` | Print the Ethereum mainnet wallet overview. |
|
|
70
|
+
| `/cards` | Buy a Laso Finance prepaid card (US or international), paid via x402 from the connected wallet. |
|
|
71
|
+
| `/agentlayer-autonomous-approve` | Turn on the full autonomous permission group (see below), with an in-command confirmation step first. |
|
|
72
|
+
| `/agentlayer-autonomous-revoke` | Turn it back off. |
|
|
73
|
+
| `/guide` | Walk a new user through this document conversationally. |
|
|
74
|
+
|
|
75
|
+
Every command except `/wallet-setup` requires the user to type it themselves
|
|
76
|
+
— the agent cannot trigger them on its own.
|
|
77
|
+
|
|
78
|
+
## The preview → prepare → execute → approve Pattern
|
|
79
|
+
|
|
80
|
+
Nearly every write tool (transfers, swaps, staking, DeFi positions, BTC
|
|
81
|
+
sends, token launch) shares one lifecycle via a `mode` argument:
|
|
82
|
+
|
|
83
|
+
- `preview` — read-only summary of what the operation would do. No signing,
|
|
84
|
+
no broadcast. Always do this first.
|
|
85
|
+
- `prepare` — returns an execution plan (unsigned) for the same operation.
|
|
86
|
+
Requires `user_intent: true`. Used when the host needs to inspect the plan
|
|
87
|
+
before approving.
|
|
88
|
+
- `execute` — actually signs and broadcasts. Requires a host-issued approval
|
|
89
|
+
token bound to the exact previewed operation. In an interactive session the
|
|
90
|
+
host's own confirmation dialog supplies this. When it doesn't,
|
|
91
|
+
`issue_wallet_approval` is the explicit bridge step: call it with the
|
|
92
|
+
`tool_name` and the verbatim `confirmation_summary` from the prepare
|
|
93
|
+
response, plus `mainnet_confirmed: true` to acknowledge real funds are at
|
|
94
|
+
stake.
|
|
95
|
+
|
|
96
|
+
In Claude Code, don't call `issue_wallet_approval` or ask the user for a raw
|
|
97
|
+
`approval_token` yourself — the host's own confirmation dialog supplies it
|
|
98
|
+
once the user approves the call.
|
|
99
|
+
|
|
100
|
+
Never skip straight to `execute` on a mainnet operation the user has not
|
|
101
|
+
explicitly approved. Preview it, show the human what it does, then execute.
|
|
102
|
+
|
|
103
|
+
Always pass a short `purpose` string on write calls — it's the human-facing
|
|
104
|
+
audit label for what the transaction is for.
|
|
105
|
+
|
|
106
|
+
## Standing Authority: Autonomous Sessions
|
|
107
|
+
|
|
108
|
+
Two separate mechanisms remove the per-transaction approval step. Both grant
|
|
109
|
+
broad, real-money authority and must only be turned on when the user has
|
|
110
|
+
explicitly asked for it:
|
|
111
|
+
|
|
112
|
+
- **Scoped autonomous session** — `start_autonomous_session` (preview then
|
|
113
|
+
execute) opens a bounded session: `allowed_tools`, `allowed_networks`,
|
|
114
|
+
`allowed_recipients`, per-tx / hourly / daily spend caps, tx-rate cap,
|
|
115
|
+
operation count cap, and a session TTL. `allow_mainnet: true` is required to
|
|
116
|
+
let it touch real funds. `get_autonomous_session` reads current status
|
|
117
|
+
(active, limits, operations used, expiry); `stop_autonomous_session` always
|
|
118
|
+
works and hands control back to per-transaction approval.
|
|
119
|
+
- **Full high-trust permission group** — `agentlayer_autonomous_approve`
|
|
120
|
+
(scope `all`) is broader and unbounded by comparison: it covers *every*
|
|
121
|
+
wallet write tool (transfers, bridges, Solana swaps, staking, x402
|
|
122
|
+
payments, contract calls, EVM DeFi management) with no per-operation
|
|
123
|
+
allow-list. Requires `user_intent: true` and an explicit purpose.
|
|
124
|
+
`agentlayer_autonomous_status` reads it; `agentlayer_autonomous_revoke`
|
|
125
|
+
turns it off.
|
|
126
|
+
|
|
127
|
+
Prefer the scoped session over the full permission group whenever the task
|
|
128
|
+
has a defined boundary (a specific token, a specific recipient, a spend cap)
|
|
129
|
+
— it's the difference between "let the agent do this one job unattended" and
|
|
130
|
+
"let the agent spend freely until told to stop."
|
|
131
|
+
|
|
132
|
+
There is no narrower version of the full permission group — pass `scope:
|
|
133
|
+
"all"`; for a bounded grant, use the scoped session instead.
|
|
134
|
+
|
|
135
|
+
## Solana
|
|
136
|
+
|
|
137
|
+
- **Transfers**: `transfer_sol` (native), `transfer_spl_token` (by mint
|
|
138
|
+
address, optional `decimals` override).
|
|
139
|
+
- **Swaps**: `swap_solana_tokens` routes through Jupiter. Prefer
|
|
140
|
+
`mode: intent_preview` then `intent_execute` — it re-quotes fresh
|
|
141
|
+
immediately before sending and only executes within the previously
|
|
142
|
+
approved limits, which matters because Jupiter quotes expire fast. Legacy
|
|
143
|
+
`preview`/`prepare`/`execute` still works but is not preferred.
|
|
144
|
+
- **Prices**: `get_solana_token_prices` — Jupiter prices for a list of mints.
|
|
145
|
+
- **Housekeeping**: `close_empty_token_accounts` reclaims rent from
|
|
146
|
+
zero-balance SPL token accounts (preview lists them, execute closes up to
|
|
147
|
+
`limit`, default 8).
|
|
148
|
+
- **Native staking** (Solana Stake Program, not a DeFi protocol):
|
|
149
|
+
`stake_sol_native` (to a validator vote account), `get_solana_stake_account`
|
|
150
|
+
(activation status of one stake account), `get_solana_staking_validators`
|
|
151
|
+
(list validators by commission/activated stake), `deactivate_solana_stake`,
|
|
152
|
+
`withdraw_solana_stake`.
|
|
153
|
+
- **Kamino** (Solana's largest lend/earn/liquidity protocol):
|
|
154
|
+
- Discovery (read-only): `get_kamino_lend_markets` → main market first;
|
|
155
|
+
`get_kamino_lend_market_reserves` for per-token supply/borrow APY and
|
|
156
|
+
maxLtv; `get_kamino_vaults` for Earn-vault discovery
|
|
157
|
+
(`include_metrics: true` for APY/TVL, `token_mint` to filter).
|
|
158
|
+
- Lending writes: `kamino_lend_deposit`, `kamino_lend_borrow`,
|
|
159
|
+
`kamino_lend_repay`, `kamino_lend_withdraw` — all take `market`, `reserve`,
|
|
160
|
+
`amount_ui`; prefer `intent_preview` → `intent_execute` for the same
|
|
161
|
+
re-quote-before-send reason as swaps.
|
|
162
|
+
- Earn vault writes: `kamino_earn_deposit`, `kamino_earn_withdraw` (take
|
|
163
|
+
`kvault`, `amount_ui`).
|
|
164
|
+
- Position reads: `get_kamino_lend_user_obligations` (one market),
|
|
165
|
+
`get_kamino_open_positions` (all lending positions across markets),
|
|
166
|
+
`get_kamino_earn_positions`, `get_kamino_liquidity_positions`,
|
|
167
|
+
`get_kamino_lend_user_rewards`, and `get_kamino_portfolio` for the single
|
|
168
|
+
unified view across lending/earn/liquidity/staking.
|
|
169
|
+
- **Token launch**: `launch_bags_token` creates a token via Bags with a
|
|
170
|
+
fee-share config (`claimers` + `basis_points`, must sum to 10000) and an
|
|
171
|
+
optional `initial_buy_sol`. Same preview/prepare/execute lifecycle.
|
|
172
|
+
|
|
173
|
+
## EVM (Ethereum, Base, Robinhood chain)
|
|
174
|
+
|
|
175
|
+
- **Read utilities**: `get_evm_token_balance`, `get_evm_token_metadata`,
|
|
176
|
+
`get_evm_transaction_receipt` (by tx hash), `get_evm_fee_rates`.
|
|
177
|
+
- **Transfers**: `transfer_evm_native` (wei), `transfer_evm_token` (ERC-20,
|
|
178
|
+
raw base units + `token_address`).
|
|
179
|
+
- **Swaps — two independent routers, pick one**:
|
|
180
|
+
- Velora: `get_evm_swap_quote` (read-only) → `swap_evm_tokens`. Ethereum/
|
|
181
|
+
Base only.
|
|
182
|
+
- Uniswap: `get_uniswap_swap_quote` → `swap_evm_uniswap_tokens`. Covers
|
|
183
|
+
CLASSIC pools, UniswapX orders, and ETH↔WETH wrap/unwrap; supports
|
|
184
|
+
Ethereum, Base, and Robinhood chain; has a `slippage_bps` param (default
|
|
185
|
+
300 = 3%).
|
|
186
|
+
- `search_uniswap_pairs` finds a token's contract address by ticker/name
|
|
187
|
+
via DexScreener — **security note**: free-text ticker search can surface
|
|
188
|
+
impersonator tokens with fabricated liquidity/FDV, especially for tickers
|
|
189
|
+
claiming to represent a real-world stock/ETF. Verify the resolved
|
|
190
|
+
`token_address` independently (`get_evm_token_metadata`, or the chain's
|
|
191
|
+
official contract list) before quoting or swapping a real-world-asset
|
|
192
|
+
ticker — a successful quote does not itself prove legitimacy.
|
|
193
|
+
- **DeFi protocols** (read tools are free; every write follows preview →
|
|
194
|
+
execute):
|
|
195
|
+
- **Aave v3** (lending): `get_evm_aave_account` (health factor etc.),
|
|
196
|
+
`get_evm_aave_positions` (per-reserve supplied/borrowed),
|
|
197
|
+
`get_evm_aave_reserves` (market catalog) →
|
|
198
|
+
`manage_evm_aave_position` with `operation`: supply / withdraw / borrow /
|
|
199
|
+
repay.
|
|
200
|
+
- **Lido** (ETH liquid staking, Ethereum mainnet only):
|
|
201
|
+
`get_evm_lido_overview`, `get_evm_lido_positions` (stETH/wstETH),
|
|
202
|
+
`get_evm_lido_withdrawal_requests` →
|
|
203
|
+
`manage_evm_lido_position` (`stake_eth_for_wsteth` / `wrap_steth` /
|
|
204
|
+
`unwrap_wsteth`) and `manage_evm_lido_withdrawal`
|
|
205
|
+
(`request_withdrawal_steth` / `request_withdrawal_wsteth` /
|
|
206
|
+
`claim_withdrawal`, needs `request_id` to claim).
|
|
207
|
+
- **Morpho** (lending markets + curated vaults): `get_evm_morpho_markets` /
|
|
208
|
+
`get_evm_morpho_vaults` for discovery (filter by asset, sort by APY —
|
|
209
|
+
pair APY sorts with a `min_supply_usd`/`min_tvl_usd` floor to skip dust),
|
|
210
|
+
`get_evm_morpho_positions` for what the wallet currently holds →
|
|
211
|
+
`manage_evm_morpho_market_position` (supply_collateral / borrow / repay /
|
|
212
|
+
withdraw_collateral, isolated market by `market_id` or `market_preset`)
|
|
213
|
+
and `manage_evm_morpho_vault_position` (supply / withdraw, by
|
|
214
|
+
`vault_address` or `vault_preset`).
|
|
215
|
+
- **Uniswap Liquidity Provisioning** (concentrated LP positions, V3 and
|
|
216
|
+
V4): discovery first — `get_evm_uniswap_pools` finds an existing pool by
|
|
217
|
+
token pair and returns its `poolReferenceIdentifier`;
|
|
218
|
+
`get_evm_uniswap_positions` lists the wallet's V3 position NFTs (fee
|
|
219
|
+
tier, tick range, owed fees) — V4 position discovery isn't available
|
|
220
|
+
(V4's PositionManager isn't enumerable on-chain the way V3's is) →
|
|
221
|
+
`manage_evm_uniswap_liquidity` with `action`: create / increase /
|
|
222
|
+
decrease / claim_fees. `create` needs `existingPool.poolReference` (the
|
|
223
|
+
discovery tool's `poolReferenceIdentifier` value, under a different
|
|
224
|
+
field name); increase/decrease/claim_fees need the position's NFT token
|
|
225
|
+
id. Never guess either identifier — always discover it first, the tool
|
|
226
|
+
rejects the call outright if it's missing. This is a thin pass-through
|
|
227
|
+
to Uniswap's own official Liquidity API; deploying a brand-new pool is
|
|
228
|
+
out of scope.
|
|
229
|
+
|
|
230
|
+
## Cross-Chain Bridging (LI.FI)
|
|
231
|
+
|
|
232
|
+
- `get_lifi_supported_chains` — currently allowed chains for routing.
|
|
233
|
+
- `get_lifi_quote` — read-only quote between any two of Ethereum / Base /
|
|
234
|
+
Solana (bridge preferences via `allow_bridges` / `deny_bridges` /
|
|
235
|
+
`prefer_bridges`, slippage as a decimal fraction).
|
|
236
|
+
- `swap_evm_lifi_cross_chain_tokens` — execute EVM-origin (Ethereum/Base) →
|
|
237
|
+
Ethereum/Base/Solana.
|
|
238
|
+
- `swap_solana_lifi_cross_chain_tokens` — execute Solana-origin →
|
|
239
|
+
Ethereum/Base.
|
|
240
|
+
- `get_lifi_transfer_status` — poll a bridge transfer by source tx hash.
|
|
241
|
+
|
|
242
|
+
Same preview/prepare/execute + approval-token discipline as everything else.
|
|
243
|
+
Mayan routes are deliberately denied — see `skills/wallet-operator/SKILL.md`.
|
|
244
|
+
|
|
245
|
+
## Bitcoin
|
|
246
|
+
|
|
247
|
+
- `transfer_btc` — amount in `amount_sats`, optional `fee_rate` (sats/vB) or
|
|
248
|
+
`confirmation_target`.
|
|
249
|
+
- `get_btc_fee_rates`, `get_btc_max_spendable` (post-fee spendable estimate),
|
|
250
|
+
`get_btc_transfer_history` (filter by `direction`, paginate with
|
|
251
|
+
`limit`/`skip`).
|
|
252
|
+
|
|
253
|
+
## x402 — Paying HTTP 402 Endpoints
|
|
254
|
+
|
|
255
|
+
- `x402_search_services` — read-only discovery of paid services via CDP
|
|
256
|
+
Bazaar or Agentic Market (filter by `query`, `max_usd_price`, `network`).
|
|
257
|
+
- `x402_get_service_details` — resolve one service/resource URL into details.
|
|
258
|
+
- `x402_preview_request` — makes the *unpaid* request, reads the 402
|
|
259
|
+
challenge, and summarizes payment options. Does not pay.
|
|
260
|
+
- `x402_pay_request` — does the whole flow in one call: probes the endpoint,
|
|
261
|
+
validates it, signs the payment from the active wallet backend, and
|
|
262
|
+
returns the paid response. Requires `purpose`.
|
|
263
|
+
|
|
264
|
+
This is the same x402 v2 protocol the `catena` MCP/CLI speaks — a discrete
|
|
265
|
+
pay-per-request charge, not a subscription or usage meter — but here the
|
|
266
|
+
payer is this wallet directly rather than a governed bank rail, so there is
|
|
267
|
+
no separate approval-parking step: the preview → execute discipline above is
|
|
268
|
+
what stands between the agent and the payment.
|
|
269
|
+
|
|
270
|
+
## Explaining This to a Human
|
|
271
|
+
|
|
272
|
+
If asked to summarize this server in plain terms: it's a direct line to the
|
|
273
|
+
local AgentLayer wallet — Solana, an EVM chain (Ethereum/Base/Robinhood),
|
|
274
|
+
and Bitcoin — plus the major yield/lending/LP protocols on those chains
|
|
275
|
+
(Kamino, Aave, Lido, Morpho, Uniswap concentrated liquidity) and the ability
|
|
276
|
+
to pay per-request API/data paywalls (x402) straight from the wallet.
|
|
277
|
+
Everything that moves funds is gated by a preview step and an approval token
|
|
278
|
+
by default; "autonomous session" and "autonomous permission group" are the
|
|
279
|
+
two ways a human can explicitly grant the agent standing authority to skip
|
|
280
|
+
that per-transaction gate, bounded (the former) or broad (the latter) —
|
|
281
|
+
always confirm with the user before either one is enabled, and treat any
|
|
282
|
+
operation on mainnet as real, irreversible money movement.
|
|
@@ -66,6 +66,8 @@ no-op once the backend is healthy.
|
|
|
66
66
|
explicit in-command confirmation before enabling the standing permission.
|
|
67
67
|
- `/agentlayer-autonomous-revoke` — disable the combined autonomous permission
|
|
68
68
|
group for Base swaps and supported EVM DeFi tools.
|
|
69
|
+
- `/guide` — walk a new user through what this plugin can do, conversationally
|
|
70
|
+
(see `AGENTLAYER_AGENT_GUIDE.md` for the source material).
|
|
69
71
|
- `AGENT_WALLET_AUTO_BOOTSTRAP=0` — opt out of the auto-install: the
|
|
70
72
|
`SessionStart` hook then only reminds you to run `/wallet-setup` instead of
|
|
71
73
|
installing the backend itself.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Walk a new user through what the AgentLayer wallet plugin can do, conversationally.
|
|
3
|
+
allowed-tools: Bash(cat:*)
|
|
4
|
+
disable-model-invocation: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Give the user a plain-language walkthrough of this wallet plugin, the way
|
|
8
|
+
you'd onboard someone who has never used it before.
|
|
9
|
+
|
|
10
|
+
1. Read the source material:
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
cat "${CLAUDE_PLUGIN_ROOT}/AGENTLAYER_AGENT_GUIDE.md"
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
2. Do not paste or summarize the raw file section-by-section. Retell it as a
|
|
17
|
+
conversational walkthrough, addressed directly to the user ("you"), in
|
|
18
|
+
the language they've been using. Cover, roughly in this order:
|
|
19
|
+
|
|
20
|
+
- **What it is** — a direct line to their own local AgentLayer wallet
|
|
21
|
+
(Solana, an EVM chain, Bitcoin), not a generic crypto-data tool; every
|
|
22
|
+
write moves real money unless it's an explicit preview.
|
|
23
|
+
- **What they can do** — the "What You Can Do" list: Solana (transfers,
|
|
24
|
+
Jupiter swaps, staking, Kamino, Bags launches), EVM (transfers, swaps,
|
|
25
|
+
Aave/Lido/Morpho/Uniswap LP), cross-chain bridging, x402 payments.
|
|
26
|
+
- **How a session works** — the agent checks which backend/network is
|
|
27
|
+
active before doing anything, and can switch between them.
|
|
28
|
+
- **The quick commands** — list the slash commands from the "Quick
|
|
29
|
+
Commands" table by name and one-line purpose only; skip why each one
|
|
30
|
+
is or isn't model-invokable, that's implementation detail.
|
|
31
|
+
- **How money-moving operations work** — preview first, then execute
|
|
32
|
+
only after they confirm; keep this to the outcome (see it, approve it,
|
|
33
|
+
then it happens), not the internal approval-token mechanics.
|
|
34
|
+
- **Autonomous mode** — that it exists as an opt-in way to skip
|
|
35
|
+
per-operation confirmation, with a bounded (scoped) and an unbounded
|
|
36
|
+
(full) version, and that either requires them to explicitly ask for it.
|
|
37
|
+
|
|
38
|
+
3. Keep the whole thing skimmable — short paragraphs or a few bullets per
|
|
39
|
+
topic, no wall of text. Close by inviting them to ask about any specific
|
|
40
|
+
chain or protocol for the exact tool-level detail.
|
package/package.json
CHANGED
|
@@ -646,6 +646,18 @@ async function handleRequest(request, response) {
|
|
|
646
646
|
return sendJson(response, 200, { ok: true, data });
|
|
647
647
|
}
|
|
648
648
|
|
|
649
|
+
if (method === "POST" && url.pathname === "/v1/evm/uniswap/liquidity/pools") {
|
|
650
|
+
const body = await withResolvedNetwork(await readJsonBody(request));
|
|
651
|
+
const data = await service.getUniswapLiquidityPools(body);
|
|
652
|
+
return sendJson(response, 200, { ok: true, data });
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
if (method === "POST" && url.pathname === "/v1/evm/uniswap/liquidity/positions") {
|
|
656
|
+
const body = await withResolvedNetwork(await withResolvedSeedOrAddress(await readJsonBody(request)));
|
|
657
|
+
const data = await service.getUniswapLiquidityPositions(body);
|
|
658
|
+
return sendJson(response, 200, { ok: true, data });
|
|
659
|
+
}
|
|
660
|
+
|
|
649
661
|
if (method === "POST" && url.pathname === "/v1/evm/uniswap/liquidity/send") {
|
|
650
662
|
const body = await withResolvedNetwork(await withResolvedSeed(await readJsonBody(request)));
|
|
651
663
|
const data = await service.sendUniswapLiquidity(body);
|
|
@@ -45,6 +45,14 @@ const UNISWAP_LIQUIDITY_POSITION_MANAGERS = {
|
|
|
45
45
|
const ERC20_APPROVE_INTERFACE = new Interface([
|
|
46
46
|
"function approve(address spender,uint256 amount) returns (bool)",
|
|
47
47
|
]);
|
|
48
|
+
// V3 PositionManager is ERC-721 enumerable, so positions owned by the active
|
|
49
|
+
// wallet can be discovered and then read directly from the canonical contract.
|
|
50
|
+
// V4 deliberately is not enumerable; see getUniswapLiquidityPositions below.
|
|
51
|
+
const UNISWAP_V3_POSITION_MANAGER_INTERFACE = new Interface([
|
|
52
|
+
"function balanceOf(address owner) view returns (uint256)",
|
|
53
|
+
"function tokenOfOwnerByIndex(address owner,uint256 index) view returns (uint256)",
|
|
54
|
+
"function positions(uint256 tokenId) view returns (uint96 nonce,address operator,address token0,address token1,uint24 fee,int24 tickLower,int24 tickUpper,uint128 liquidity,uint256 feeGrowthInside0LastX128,uint256 feeGrowthInside1LastX128,uint128 tokensOwed0,uint128 tokensOwed1)",
|
|
55
|
+
]);
|
|
48
56
|
// Every executable path is declared here rather than inferred from a Trading
|
|
49
57
|
// API response. A new chain/router version therefore requires an explicit,
|
|
50
58
|
// reviewed allow-list entry before it can receive a signed transaction.
|
|
@@ -6015,6 +6023,122 @@ export class WdkEvmWalletService {
|
|
|
6015
6023
|
};
|
|
6016
6024
|
}
|
|
6017
6025
|
|
|
6026
|
+
async getUniswapLiquidityPools({ protocol, poolParameters, poolReferences, pageSize = 20, currentPage = 1, network }) {
|
|
6027
|
+
const runtimeConfig = this.#resolveRuntimeConfig(network);
|
|
6028
|
+
assertUniswapSupportedNetwork(runtimeConfig.network);
|
|
6029
|
+
const normalizedProtocol = normalizeUniswapLiquidityProtocol(protocol);
|
|
6030
|
+
const hasParameters = poolParameters !== undefined && poolParameters !== null;
|
|
6031
|
+
const hasReferences = poolReferences !== undefined && poolReferences !== null;
|
|
6032
|
+
if (hasParameters === hasReferences) {
|
|
6033
|
+
throw new Error("Provide exactly one of poolParameters or poolReferences.");
|
|
6034
|
+
}
|
|
6035
|
+
const boundedPageSize = Number(pageSize);
|
|
6036
|
+
const normalizedPageSize = Number.isInteger(boundedPageSize) && boundedPageSize > 0
|
|
6037
|
+
? Math.min(boundedPageSize, 20)
|
|
6038
|
+
: 20;
|
|
6039
|
+
const boundedCurrentPage = Number(currentPage);
|
|
6040
|
+
const normalizedCurrentPage = Number.isInteger(boundedCurrentPage) && boundedCurrentPage > 0
|
|
6041
|
+
? boundedCurrentPage
|
|
6042
|
+
: 1;
|
|
6043
|
+
const body = {
|
|
6044
|
+
protocol: normalizedProtocol,
|
|
6045
|
+
chainId: runtimeConfig.chainId,
|
|
6046
|
+
pageSize: normalizedPageSize,
|
|
6047
|
+
currentPage: normalizedCurrentPage,
|
|
6048
|
+
};
|
|
6049
|
+
if (hasParameters) {
|
|
6050
|
+
body.poolParameters = assertPlainObject(poolParameters, "poolParameters");
|
|
6051
|
+
} else {
|
|
6052
|
+
if (!Array.isArray(poolReferences) || poolReferences.length === 0 || poolReferences.length > 20) {
|
|
6053
|
+
throw new Error("poolReferences must be an array containing between 1 and 20 references.");
|
|
6054
|
+
}
|
|
6055
|
+
body.poolReferences = poolReferences.map((reference, index) => assertPlainObject(reference, `poolReferences[${index}]`));
|
|
6056
|
+
}
|
|
6057
|
+
const payload = await this.#uniswapLiquidityApiRequest("pool_info", body);
|
|
6058
|
+
return {
|
|
6059
|
+
network: runtimeConfig.network,
|
|
6060
|
+
chainId: runtimeConfig.chainId,
|
|
6061
|
+
protocol: normalizedProtocol,
|
|
6062
|
+
requestId: String(payload?.requestId || "").trim() || null,
|
|
6063
|
+
pools: Array.isArray(payload?.pools) ? payload.pools : [],
|
|
6064
|
+
pageSize: Number(payload?.pageSize ?? normalizedPageSize),
|
|
6065
|
+
currentPage: Number(payload?.currentPage ?? normalizedCurrentPage),
|
|
6066
|
+
source: "uniswap-liquidity-api",
|
|
6067
|
+
};
|
|
6068
|
+
}
|
|
6069
|
+
|
|
6070
|
+
async getUniswapLiquidityPositions({ seedPhrase, address, protocol = "V3", accountIndex = 0, network, limit = 20 }) {
|
|
6071
|
+
return this.#withReadableAccount({ seedPhrase, address, accountIndex, network }, async (account, runtimeConfig) => {
|
|
6072
|
+
assertUniswapSupportedNetwork(runtimeConfig.network);
|
|
6073
|
+
const normalizedProtocol = normalizeUniswapLiquidityProtocol(protocol);
|
|
6074
|
+
if (normalizedProtocol === "V4") {
|
|
6075
|
+
// Uniswap's V4 PositionManager is intentionally not ERC-721 enumerable.
|
|
6076
|
+
// Discovering token IDs requires a chain-specific indexed subgraph, then
|
|
6077
|
+
// on-chain verification. Do not pretend balanceOf/tokenOfOwnerByIndex is
|
|
6078
|
+
// available or ask an RPC provider to scan unbounded transfer history.
|
|
6079
|
+
throw createTaggedError(
|
|
6080
|
+
"Uniswap V4 position discovery requires a configured indexed subgraph; the V4 PositionManager is not ERC-721 enumerable.",
|
|
6081
|
+
"uniswap_v4_position_discovery_unavailable",
|
|
6082
|
+
{ network: runtimeConfig.network }
|
|
6083
|
+
);
|
|
6084
|
+
}
|
|
6085
|
+
const owner = await account.getAddress();
|
|
6086
|
+
const positionManager = getUniswapLiquidityPositionManager(runtimeConfig.network, "V3");
|
|
6087
|
+
const rawBalance = await callContract(
|
|
6088
|
+
runtimeConfig.providerUrl,
|
|
6089
|
+
positionManager,
|
|
6090
|
+
UNISWAP_V3_POSITION_MANAGER_INTERFACE,
|
|
6091
|
+
"balanceOf",
|
|
6092
|
+
[owner]
|
|
6093
|
+
);
|
|
6094
|
+
const balance = BigInt(rawBalance[0]);
|
|
6095
|
+
const requestedLimit = Number(limit);
|
|
6096
|
+
const boundedLimit = Number.isInteger(requestedLimit) && requestedLimit > 0 ? Math.min(requestedLimit, 100) : 20;
|
|
6097
|
+
const count = Number(balance > BigInt(boundedLimit) ? BigInt(boundedLimit) : balance);
|
|
6098
|
+
const positions = [];
|
|
6099
|
+
for (let index = 0; index < count; index += 1) {
|
|
6100
|
+
const tokenIdResult = await callContract(
|
|
6101
|
+
runtimeConfig.providerUrl,
|
|
6102
|
+
positionManager,
|
|
6103
|
+
UNISWAP_V3_POSITION_MANAGER_INTERFACE,
|
|
6104
|
+
"tokenOfOwnerByIndex",
|
|
6105
|
+
[owner, index]
|
|
6106
|
+
);
|
|
6107
|
+
const tokenId = BigInt(tokenIdResult[0]);
|
|
6108
|
+
const position = await callContract(
|
|
6109
|
+
runtimeConfig.providerUrl,
|
|
6110
|
+
positionManager,
|
|
6111
|
+
UNISWAP_V3_POSITION_MANAGER_INTERFACE,
|
|
6112
|
+
"positions",
|
|
6113
|
+
[tokenId]
|
|
6114
|
+
);
|
|
6115
|
+
positions.push({
|
|
6116
|
+
tokenId: tokenId.toString(),
|
|
6117
|
+
token0: String(position[2]),
|
|
6118
|
+
token1: String(position[3]),
|
|
6119
|
+
fee: Number(position[4]),
|
|
6120
|
+
tickLower: Number(position[5]),
|
|
6121
|
+
tickUpper: Number(position[6]),
|
|
6122
|
+
liquidity: BigInt(position[7]).toString(),
|
|
6123
|
+
tokensOwed0: BigInt(position[10]).toString(),
|
|
6124
|
+
tokensOwed1: BigInt(position[11]).toString(),
|
|
6125
|
+
});
|
|
6126
|
+
}
|
|
6127
|
+
return {
|
|
6128
|
+
network: runtimeConfig.network,
|
|
6129
|
+
chainId: runtimeConfig.chainId,
|
|
6130
|
+
protocol: "V3",
|
|
6131
|
+
owner,
|
|
6132
|
+
positionManager,
|
|
6133
|
+
totalCount: balance.toString(),
|
|
6134
|
+
returnedCount: positions.length,
|
|
6135
|
+
truncated: balance > BigInt(positions.length),
|
|
6136
|
+
positions,
|
|
6137
|
+
source: "uniswap-v3-position-manager",
|
|
6138
|
+
};
|
|
6139
|
+
});
|
|
6140
|
+
}
|
|
6141
|
+
|
|
6018
6142
|
async quoteUniswapLiquidity({ seedPhrase, address, action, protocol, request, accountIndex = 0, network }) {
|
|
6019
6143
|
return this.#withReadableAccount({ seedPhrase, address, accountIndex, network }, async (account, runtimeConfig) => {
|
|
6020
6144
|
assertUniswapSupportedNetwork(runtimeConfig.network);
|