@agentlayer.tech/wallet 0.1.93 → 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.
Files changed (30) hide show
  1. package/.openclaw/extensions/agent-wallet/dist/index.js +79 -0
  2. package/.openclaw/extensions/agent-wallet/index.ts +79 -0
  3. package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +6 -1
  4. package/.openclaw/extensions/agent-wallet/package.json +1 -1
  5. package/CHANGELOG.md +18 -0
  6. package/VERSION +1 -1
  7. package/agent-wallet/agent_wallet/__init__.py +1 -1
  8. package/agent-wallet/agent_wallet/autonomous_permissions.py +1 -0
  9. package/agent-wallet/agent_wallet/openclaw_adapter.py +262 -0
  10. package/agent-wallet/agent_wallet/providers/wdk_evm_local.py +2 -0
  11. package/agent-wallet/agent_wallet/wallet_layer/base.py +37 -0
  12. package/agent-wallet/agent_wallet/wallet_layer/wdk_evm.py +157 -0
  13. package/agent-wallet/openclaw.plugin.json +1 -1
  14. package/agent-wallet/pyproject.toml +1 -1
  15. package/agent-wallet/scripts/install_agent_wallet.py +29 -1
  16. package/claude-code/plugins/agent-wallet/.claude-plugin/plugin.json +1 -1
  17. package/claude-code/plugins/agent-wallet/AGENTLAYER_AGENT_GUIDE.md +282 -0
  18. package/claude-code/plugins/agent-wallet/README.md +2 -0
  19. package/claude-code/plugins/agent-wallet/commands/guide.md +40 -0
  20. package/codex/plugins/agent-wallet/.codex-plugin/plugin.json +1 -1
  21. package/codex/plugins/agent-wallet/server.py +1 -0
  22. package/hermes/plugins/agent_wallet/plugin.yaml +1 -1
  23. package/package.json +1 -1
  24. package/wdk-btc-wallet/package.json +1 -1
  25. package/wdk-evm-wallet/.env.example +3 -0
  26. package/wdk-evm-wallet/README.md +6 -0
  27. package/wdk-evm-wallet/package.json +1 -1
  28. package/wdk-evm-wallet/src/config.js +9 -0
  29. package/wdk-evm-wallet/src/server.js +24 -0
  30. package/wdk-evm-wallet/src/wdk_evm_wallet.js +469 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wdk-evm-wallet",
3
- "version": "0.1.93",
3
+ "version": "0.1.95",
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,30 @@ 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/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
+
661
+ if (method === "POST" && url.pathname === "/v1/evm/uniswap/liquidity/send") {
662
+ const body = await withResolvedNetwork(await withResolvedSeed(await readJsonBody(request)));
663
+ const data = await service.sendUniswapLiquidity(body);
664
+ return sendJson(response, 200, { ok: true, data });
665
+ }
666
+
643
667
  if (method === "POST" && url.pathname === "/v1/evm/transfer/quote") {
644
668
  const body = await withResolvedNetwork(await withResolvedSeed(await readJsonBody(request)));
645
669
  const data = await service.quoteNativeTransfer(body);
@@ -23,6 +23,36 @@ 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
+ ]);
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
+ ]);
26
56
  // Every executable path is declared here rather than inferred from a Trading
27
57
  // API response. A new chain/router version therefore requires an explicit,
28
58
  // reviewed allow-list entry before it can receive a signed transaction.
@@ -925,6 +955,31 @@ function assertUniswapSupportedNetwork(network) {
925
955
  return chainId;
926
956
  }
927
957
 
958
+ function normalizeUniswapLiquidityAction(value) {
959
+ const action = String(value || "").trim().toLowerCase();
960
+ if (!UNISWAP_LIQUIDITY_ACTIONS.has(action)) {
961
+ throw new Error("Uniswap liquidity action must be create, increase, decrease, or claim_fees.");
962
+ }
963
+ return action;
964
+ }
965
+
966
+ function normalizeUniswapLiquidityProtocol(value) {
967
+ const protocol = String(value || "").trim().toUpperCase();
968
+ if (!UNISWAP_LIQUIDITY_PROTOCOLS.has(protocol)) {
969
+ throw new Error("Uniswap liquidity protocol must be V3 or V4.");
970
+ }
971
+ return protocol;
972
+ }
973
+
974
+ function getUniswapLiquidityPositionManager(network, protocol) {
975
+ assertUniswapSupportedNetwork(network);
976
+ const manager = UNISWAP_LIQUIDITY_POSITION_MANAGERS[network]?.[protocol];
977
+ if (!manager) {
978
+ throw new Error(`Uniswap ${protocol} liquidity is not configured for ${network}.`);
979
+ }
980
+ return manager;
981
+ }
982
+
928
983
  function getUniswapNetworkExecutionProfile(network) {
929
984
  const profile = UNISWAP_EXECUTION_PROFILES[network];
930
985
  if (!profile) {
@@ -5743,6 +5798,420 @@ export class WdkEvmWalletService {
5743
5798
  return payload;
5744
5799
  }
5745
5800
 
5801
+ async #uniswapLiquidityApiRequest(action, body) {
5802
+ const normalizedAction = String(action || "").trim().toLowerCase();
5803
+ const headers = { "Content-Type": "application/json", Accept: "application/json" };
5804
+ if (this.config.uniswapLiquidityViaGateway) {
5805
+ const token = String(this.config.providerGatewayToken || "").trim();
5806
+ if (token) {
5807
+ headers.Authorization = `Bearer ${token}`;
5808
+ }
5809
+ } else {
5810
+ if (!this.config.uniswapApiKey) {
5811
+ throw createTaggedError(
5812
+ "UNISWAP_API_KEY is not configured. Set it, or route Uniswap through the provider gateway, to use liquidity operations.",
5813
+ "uniswap_api_key_missing",
5814
+ { provider: "uniswap" }
5815
+ );
5816
+ }
5817
+ headers["x-api-key"] = this.config.uniswapApiKey;
5818
+ }
5819
+ const base = String(this.config.uniswapLiquidityApiBaseUrl).replace(/\/+$/, "");
5820
+ const suffix = this.config.uniswapLiquidityViaGateway ? `/${normalizedAction}` : `/lp/${normalizedAction}`;
5821
+ let response;
5822
+ try {
5823
+ response = await fetch(`${base}${suffix}`, { method: "POST", headers, body: JSON.stringify(body) });
5824
+ } catch (error) {
5825
+ const message = error instanceof Error ? error.message : String(error);
5826
+ throw createTaggedError(`Uniswap liquidity API unavailable: ${message}`, "network_unavailable", {
5827
+ provider: "uniswap",
5828
+ action: normalizedAction,
5829
+ });
5830
+ }
5831
+ let payload;
5832
+ try {
5833
+ payload = await response.json();
5834
+ } catch {
5835
+ payload = null;
5836
+ }
5837
+ if (!response.ok || !payload || typeof payload !== "object") {
5838
+ throw createTaggedError(
5839
+ String(payload?.message || payload?.error || `Uniswap liquidity ${normalizedAction} failed with HTTP ${response.status}.`),
5840
+ "network_unavailable",
5841
+ { provider: "uniswap", action: normalizedAction, httpStatus: response.status }
5842
+ );
5843
+ }
5844
+ return payload;
5845
+ }
5846
+
5847
+ #buildUniswapLiquidityRequest({ action, protocol, address, request }) {
5848
+ const body = { ...(request && typeof request === "object" ? request : {}) };
5849
+ // Caller cannot select identity, chain or simulator behavior. These are set
5850
+ // locally to make every preview/send reproducible through the active wallet.
5851
+ body.walletAddress = address;
5852
+ body.protocol = protocol;
5853
+ body.simulateTransaction = true;
5854
+ delete body.signature;
5855
+ delete body.batchPermitData;
5856
+ delete body.v4BatchPermitData;
5857
+ delete body.v3NftPermitData;
5858
+ if (action === "claim_fees") {
5859
+ delete body.independentToken;
5860
+ }
5861
+ return body;
5862
+ }
5863
+
5864
+ #extractUniswapLiquidityTransaction(payload, action) {
5865
+ const field = action === "create" ? "create" : action === "increase" ? "increase" : action === "decrease" ? "decrease" : "claim";
5866
+ const tx = payload?.[field] || payload?.transaction;
5867
+ if (!tx || typeof tx !== "object") {
5868
+ throw createTaggedError("Uniswap liquidity API returned no executable transaction.", "network_unavailable", {
5869
+ provider: "uniswap",
5870
+ action,
5871
+ });
5872
+ }
5873
+ return tx;
5874
+ }
5875
+
5876
+ async #getUniswapLiquidityApprovals({ runtimeConfig, protocol, action, address, payload }) {
5877
+ if (action !== "create" && action !== "increase") {
5878
+ return [];
5879
+ }
5880
+ const lpTokens = [payload?.token0, payload?.token1]
5881
+ .filter((token) => token && typeof token === "object")
5882
+ // The LP API represents native ETH with the zero-address sentinel. It
5883
+ // cannot have an ERC-20 allowance, so exclude it from check_approval.
5884
+ .filter((token) => !isZeroAddress(String(token.tokenAddress || "")))
5885
+ .map((token) => {
5886
+ const amount = BigInt(assertNonNegativeBigIntString(token.amount, "lp token amount"));
5887
+ return {
5888
+ tokenAddress: normalizeAddress(String(token.tokenAddress || ""), "lp token address"),
5889
+ amount,
5890
+ };
5891
+ })
5892
+ // Concentrated positions can legitimately be single-sided while the
5893
+ // current price lies outside their selected range. A zero contribution
5894
+ // has no ERC-20 allowance requirement and must not block the LP action.
5895
+ .filter((token) => token.amount > 0n)
5896
+ .map((token) => ({ ...token, amount: token.amount.toString() }));
5897
+ if (!lpTokens.length) {
5898
+ return [];
5899
+ }
5900
+ const expectedTokens = new Set(
5901
+ lpTokens.map((token) => String(token.tokenAddress).toLowerCase())
5902
+ );
5903
+ const approvalPayload = await this.#uniswapLiquidityApiRequest("check_approval", {
5904
+ walletAddress: address,
5905
+ chainId: runtimeConfig.chainId,
5906
+ protocol,
5907
+ lpTokens,
5908
+ action: action.toUpperCase(),
5909
+ // The initial version uses ordinary, bounded approval transactions. This
5910
+ // keeps the send path observable and avoids requiring an extra signature
5911
+ // protocol before LP workflows have seen live use.
5912
+ generatePermitAsTransaction: true,
5913
+ });
5914
+ const transactions = Array.isArray(approvalPayload.transactions) ? approvalPayload.transactions : [];
5915
+ return transactions.map((item, index) => {
5916
+ const raw = item?.transaction;
5917
+ if (!raw || typeof raw !== "object") {
5918
+ throw createTaggedError("Uniswap liquidity approval response is malformed.", "uniswap_liquidity_invalid_approval", { index });
5919
+ }
5920
+ const to = normalizeAddress(String(raw.to || ""), "liquidity approval.to");
5921
+ if (!expectedTokens.has(to.toLowerCase())) {
5922
+ throw createTaggedError("Uniswap liquidity approval is for a token outside this LP request.", "uniswap_liquidity_unexpected_token", { to });
5923
+ }
5924
+ const data = assertNonEmptyString(String(raw.data || ""), "liquidity approval.data");
5925
+ if (parseHexOrDecimalBigInt(raw.value || "0", "liquidity approval.value") !== 0n) {
5926
+ throw createTaggedError("Uniswap liquidity approval must not transfer native value.", "uniswap_liquidity_invalid_approval", { to });
5927
+ }
5928
+ if (raw.chainId !== undefined && Number(raw.chainId) !== runtimeConfig.chainId) {
5929
+ throw createTaggedError("Uniswap liquidity approval has the wrong chain id.", "uniswap_liquidity_chain_mismatch");
5930
+ }
5931
+ if (raw.from && normalizeAddress(String(raw.from), "liquidity approval.from").toLowerCase() !== address.toLowerCase()) {
5932
+ throw createTaggedError("Uniswap liquidity approval sender does not match the active wallet.", "uniswap_liquidity_sender_mismatch");
5933
+ }
5934
+ let decoded;
5935
+ try {
5936
+ decoded = ERC20_APPROVE_INTERFACE.parseTransaction({ data });
5937
+ } catch {
5938
+ decoded = null;
5939
+ }
5940
+ if (!decoded || decoded.name !== "approve") {
5941
+ throw createTaggedError("Uniswap liquidity approval is not a bounded ERC-20 approve call.", "uniswap_liquidity_invalid_approval", { to });
5942
+ }
5943
+ const spender = normalizeAddress(String(decoded.args[0]), "liquidity approval spender").toLowerCase();
5944
+ const amount = BigInt(decoded.args[1]);
5945
+ const positionManager = getUniswapLiquidityPositionManager(runtimeConfig.network, protocol);
5946
+ if (spender !== positionManager && spender !== PERMIT2_ADDRESS.toLowerCase()) {
5947
+ throw createTaggedError("Uniswap liquidity approval has an unexpected spender.", "uniswap_liquidity_unexpected_spender", { spender, positionManager });
5948
+ }
5949
+ if (amount <= 0n || amount >= (2n ** 255n)) {
5950
+ throw createTaggedError("Uniswap liquidity approval must use a bounded amount.", "uniswap_liquidity_unbounded_approval", { spender });
5951
+ }
5952
+ return {
5953
+ tx: { to, data, value: 0n },
5954
+ token: to,
5955
+ spender,
5956
+ amount,
5957
+ action: String(item?.action || action).toUpperCase(),
5958
+ };
5959
+ });
5960
+ }
5961
+
5962
+ #validateUniswapLiquidityTransaction({ runtimeConfig, protocol, address, transaction }) {
5963
+ const to = normalizeAddress(String(transaction.to || ""), "liquidity transaction.to");
5964
+ const expectedManager = getUniswapLiquidityPositionManager(runtimeConfig.network, protocol);
5965
+ if (to.toLowerCase() !== expectedManager) {
5966
+ throw createTaggedError("Uniswap liquidity API returned an unexpected PositionManager.", "uniswap_unexpected_position_manager", {
5967
+ expected: expectedManager,
5968
+ actual: to.toLowerCase(),
5969
+ network: runtimeConfig.network,
5970
+ protocol,
5971
+ });
5972
+ }
5973
+ if (transaction.chainId !== undefined && Number(transaction.chainId) !== runtimeConfig.chainId) {
5974
+ throw createTaggedError("Uniswap liquidity transaction has the wrong chain id.", "uniswap_liquidity_chain_mismatch", {
5975
+ expected: runtimeConfig.chainId,
5976
+ actual: transaction.chainId,
5977
+ });
5978
+ }
5979
+ if (transaction.from && normalizeAddress(String(transaction.from), "liquidity transaction.from").toLowerCase() !== address.toLowerCase()) {
5980
+ throw createTaggedError("Uniswap liquidity transaction sender does not match the active wallet.", "uniswap_liquidity_sender_mismatch");
5981
+ }
5982
+ const data = assertNonEmptyString(String(transaction.data || ""), "liquidity transaction.data");
5983
+ if (!/^0x[0-9a-fA-F]+$/.test(data) || data.length < 10) {
5984
+ throw createTaggedError("Uniswap liquidity transaction calldata is invalid.", "uniswap_liquidity_invalid_calldata");
5985
+ }
5986
+ return { to, data, value: parseHexOrDecimalBigInt(transaction.value || "0", "liquidity transaction.value") };
5987
+ }
5988
+
5989
+ #formatUniswapLiquidityResponse({ runtimeConfig, accountIndex, address, action, protocol, request, payload, transaction, approvals = [], simulation = null }) {
5990
+ const tx = transaction ? {
5991
+ to: transaction.to,
5992
+ value: transaction.value.toString(),
5993
+ dataHash: sha256Hex(transaction.data),
5994
+ } : null;
5995
+ return {
5996
+ network: runtimeConfig.network,
5997
+ chainId: runtimeConfig.chainId,
5998
+ accountIndex,
5999
+ address,
6000
+ protocol: "uniswap",
6001
+ liquidityAction: action,
6002
+ liquidityProtocol: protocol,
6003
+ request,
6004
+ requestId: String(payload?.requestId || "").trim() || null,
6005
+ token0: payload?.token0 || null,
6006
+ token1: payload?.token1 || null,
6007
+ tickLower: payload?.tickLower ?? null,
6008
+ tickUpper: payload?.tickUpper ?? null,
6009
+ adjustedMinPrice: payload?.adjustedMinPrice ?? null,
6010
+ adjustedMaxPrice: payload?.adjustedMaxPrice ?? null,
6011
+ gasFee: payload?.gasFee ?? null,
6012
+ positionTokenId: String(request?.nftTokenId || request?.tokenId || "").trim() || null,
6013
+ positionManager: getUniswapLiquidityPositionManager(runtimeConfig.network, protocol),
6014
+ approvals: approvals.map((approval) => ({
6015
+ token: approval.token,
6016
+ spender: approval.spender,
6017
+ amount: approval.amount.toString(),
6018
+ action: approval.action,
6019
+ })),
6020
+ transaction: tx,
6021
+ simulation,
6022
+ source: "uniswap-liquidity-api",
6023
+ };
6024
+ }
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
+
6142
+ async quoteUniswapLiquidity({ seedPhrase, address, action, protocol, request, accountIndex = 0, network }) {
6143
+ return this.#withReadableAccount({ seedPhrase, address, accountIndex, network }, async (account, runtimeConfig) => {
6144
+ assertUniswapSupportedNetwork(runtimeConfig.network);
6145
+ const normalizedAction = normalizeUniswapLiquidityAction(action);
6146
+ const normalizedProtocol = normalizeUniswapLiquidityProtocol(protocol);
6147
+ const walletAddress = await account.getAddress();
6148
+ const body = this.#buildUniswapLiquidityRequest({ action: normalizedAction, protocol: normalizedProtocol, address: walletAddress, request });
6149
+ body.chainId = runtimeConfig.chainId;
6150
+ const payload = await this.#uniswapLiquidityApiRequest(normalizedAction, body);
6151
+ const transaction = this.#validateUniswapLiquidityTransaction({
6152
+ runtimeConfig,
6153
+ protocol: normalizedProtocol,
6154
+ address: walletAddress,
6155
+ transaction: this.#extractUniswapLiquidityTransaction(payload, normalizedAction),
6156
+ });
6157
+ const approvals = await this.#getUniswapLiquidityApprovals({ runtimeConfig, protocol: normalizedProtocol, action: normalizedAction, address: walletAddress, payload });
6158
+ const simulation = await this.#simulatePreparedTransaction({ runtimeConfig, from: walletAddress, tx: transaction, operationLabel: "Uniswap liquidity" });
6159
+ return this.#formatUniswapLiquidityResponse({ runtimeConfig, accountIndex, address: walletAddress, action: normalizedAction, protocol: normalizedProtocol, request: body, payload, transaction, approvals, simulation });
6160
+ });
6161
+ }
6162
+
6163
+ async sendUniswapLiquidity({ seedPhrase, action, protocol, request, accountIndex = 0, network }) {
6164
+ return this.#withAccount({ seedPhrase, accountIndex, network }, async (account, runtimeConfig) => {
6165
+ assertUniswapSupportedNetwork(runtimeConfig.network);
6166
+ const normalizedAction = normalizeUniswapLiquidityAction(action);
6167
+ const normalizedProtocol = normalizeUniswapLiquidityProtocol(protocol);
6168
+ const address = await account.getAddress();
6169
+ // Rebuild immediately before signing: price/ticks and calldata can change
6170
+ // while an intent approval is being reviewed.
6171
+ const body = this.#buildUniswapLiquidityRequest({ action: normalizedAction, protocol: normalizedProtocol, address, request });
6172
+ body.chainId = runtimeConfig.chainId;
6173
+ let payload = await this.#uniswapLiquidityApiRequest(normalizedAction, body);
6174
+ let transaction = this.#validateUniswapLiquidityTransaction({
6175
+ runtimeConfig,
6176
+ protocol: normalizedProtocol,
6177
+ address,
6178
+ transaction: this.#extractUniswapLiquidityTransaction(payload, normalizedAction),
6179
+ });
6180
+ const approvals = await this.#getUniswapLiquidityApprovals({ runtimeConfig, protocol: normalizedProtocol, action: normalizedAction, address, payload });
6181
+ const approvalResults = [];
6182
+ for (const approval of approvals) {
6183
+ const approvalSimulation = await this.#simulatePreparedTransaction({ runtimeConfig, from: address, tx: approval.tx, operationLabel: "Uniswap liquidity approval" });
6184
+ this.#assertSimulationSucceeded(approvalSimulation);
6185
+ const approvalResult = await this.#sendBufferedDefiTransaction({ account, runtimeConfig, from: address, tx: approval.tx, operationLabel: "Uniswap liquidity approval" });
6186
+ await this.#waitForTransactionReceipt(runtimeConfig, approvalResult.hash, { operationLabel: "Uniswap liquidity approval", failureCode: "uniswap_liquidity_approval_reverted", timeoutCode: "uniswap_liquidity_approval_timeout" });
6187
+ approvalResults.push({ token: approval.token, spender: approval.spender, amount: approval.amount.toString(), hash: approvalResult.hash });
6188
+ }
6189
+ if (approvals.length) {
6190
+ payload = await this.#uniswapLiquidityApiRequest(normalizedAction, body);
6191
+ transaction = this.#validateUniswapLiquidityTransaction({
6192
+ runtimeConfig,
6193
+ protocol: normalizedProtocol,
6194
+ address,
6195
+ transaction: this.#extractUniswapLiquidityTransaction(payload, normalizedAction),
6196
+ });
6197
+ }
6198
+ const simulation = await this.#simulatePreparedTransaction({ runtimeConfig, from: address, tx: transaction, operationLabel: "Uniswap liquidity" });
6199
+ this.#assertSimulationSucceeded(simulation);
6200
+ const result = await this.#sendBufferedDefiTransaction({ account, runtimeConfig, from: address, tx: transaction, operationLabel: "Uniswap liquidity" });
6201
+ await this.#waitForTransactionReceipt(runtimeConfig, result.hash, {
6202
+ operationLabel: "Uniswap liquidity",
6203
+ failureCode: "uniswap_liquidity_reverted",
6204
+ timeoutCode: "uniswap_liquidity_confirmation_timeout",
6205
+ });
6206
+ return {
6207
+ ...this.#formatUniswapLiquidityResponse({ runtimeConfig, accountIndex, address, action: normalizedAction, protocol: normalizedProtocol, request: body, payload, transaction, approvals, simulation }),
6208
+ result,
6209
+ approvalResults,
6210
+ confirmed: true,
6211
+ };
6212
+ });
6213
+ }
6214
+
5746
6215
  async #fetchUniswapQuote({ runtimeConfig, routerProfile, address, swapRequest }) {
5747
6216
  const chainId = UNISWAP_SUPPORTED_CHAIN_IDS[runtimeConfig.network];
5748
6217
  const quoteRequest = {