wayfinder-paths 0.1.22__py3-none-any.whl → 0.1.24__py3-none-any.whl
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.
Potentially problematic release.
This version of wayfinder-paths might be problematic. Click here for more details.
- wayfinder_paths/__init__.py +0 -4
- wayfinder_paths/adapters/balance_adapter/README.md +0 -1
- wayfinder_paths/adapters/balance_adapter/adapter.py +313 -167
- wayfinder_paths/adapters/balance_adapter/manifest.yaml +8 -0
- wayfinder_paths/adapters/balance_adapter/test_adapter.py +41 -124
- wayfinder_paths/adapters/boros_adapter/__init__.py +17 -0
- wayfinder_paths/adapters/boros_adapter/adapter.py +1574 -0
- wayfinder_paths/adapters/boros_adapter/client.py +476 -0
- wayfinder_paths/adapters/boros_adapter/manifest.yaml +10 -0
- wayfinder_paths/adapters/boros_adapter/parsers.py +88 -0
- wayfinder_paths/adapters/boros_adapter/test_adapter.py +460 -0
- wayfinder_paths/adapters/boros_adapter/test_golden.py +156 -0
- wayfinder_paths/adapters/boros_adapter/types.py +70 -0
- wayfinder_paths/adapters/boros_adapter/utils.py +85 -0
- wayfinder_paths/adapters/brap_adapter/README.md +22 -75
- wayfinder_paths/adapters/brap_adapter/adapter.py +187 -576
- wayfinder_paths/adapters/brap_adapter/examples.json +21 -140
- wayfinder_paths/adapters/brap_adapter/manifest.yaml +9 -0
- wayfinder_paths/adapters/brap_adapter/test_adapter.py +6 -234
- wayfinder_paths/adapters/hyperlend_adapter/adapter.py +180 -92
- wayfinder_paths/adapters/hyperlend_adapter/manifest.yaml +9 -0
- wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +82 -14
- wayfinder_paths/adapters/hyperliquid_adapter/__init__.py +2 -9
- wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +586 -61
- wayfinder_paths/adapters/hyperliquid_adapter/executor.py +47 -68
- wayfinder_paths/adapters/hyperliquid_adapter/manifest.yaml +14 -0
- wayfinder_paths/adapters/hyperliquid_adapter/paired_filler.py +2 -3
- wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +17 -21
- wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +3 -6
- wayfinder_paths/adapters/hyperliquid_adapter/test_executor.py +4 -8
- wayfinder_paths/adapters/hyperliquid_adapter/test_utils.py +2 -2
- wayfinder_paths/adapters/ledger_adapter/README.md +4 -1
- wayfinder_paths/adapters/ledger_adapter/adapter.py +3 -3
- wayfinder_paths/adapters/ledger_adapter/manifest.yaml +7 -0
- wayfinder_paths/adapters/ledger_adapter/test_adapter.py +1 -2
- wayfinder_paths/adapters/moonwell_adapter/adapter.py +649 -547
- wayfinder_paths/adapters/moonwell_adapter/manifest.yaml +14 -0
- wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +160 -239
- wayfinder_paths/adapters/multicall_adapter/__init__.py +7 -0
- wayfinder_paths/adapters/multicall_adapter/adapter.py +166 -0
- wayfinder_paths/adapters/multicall_adapter/manifest.yaml +5 -0
- wayfinder_paths/adapters/multicall_adapter/test_adapter.py +97 -0
- wayfinder_paths/adapters/pendle_adapter/README.md +102 -0
- wayfinder_paths/adapters/pendle_adapter/__init__.py +7 -0
- wayfinder_paths/adapters/pendle_adapter/adapter.py +1992 -0
- wayfinder_paths/adapters/pendle_adapter/examples.json +11 -0
- wayfinder_paths/adapters/pendle_adapter/manifest.yaml +21 -0
- wayfinder_paths/adapters/pendle_adapter/test_adapter.py +666 -0
- wayfinder_paths/adapters/pool_adapter/manifest.yaml +6 -0
- wayfinder_paths/adapters/token_adapter/adapter.py +14 -0
- wayfinder_paths/adapters/token_adapter/examples.json +0 -4
- wayfinder_paths/adapters/token_adapter/manifest.yaml +7 -0
- wayfinder_paths/conftest.py +24 -17
- wayfinder_paths/core/__init__.py +0 -3
- wayfinder_paths/core/adapters/BaseAdapter.py +0 -25
- wayfinder_paths/core/adapters/models.py +17 -7
- wayfinder_paths/core/clients/BRAPClient.py +4 -1
- wayfinder_paths/core/clients/ClientManager.py +0 -7
- wayfinder_paths/core/clients/LedgerClient.py +196 -172
- wayfinder_paths/core/clients/TokenClient.py +47 -1
- wayfinder_paths/core/clients/WayfinderClient.py +1 -3
- wayfinder_paths/core/clients/__init__.py +0 -5
- wayfinder_paths/core/clients/protocols.py +21 -35
- wayfinder_paths/core/clients/test_ledger_client.py +448 -0
- wayfinder_paths/core/config.py +10 -162
- wayfinder_paths/core/constants/__init__.py +73 -2
- wayfinder_paths/core/constants/base.py +8 -17
- wayfinder_paths/core/constants/chains.py +36 -0
- wayfinder_paths/core/constants/contracts.py +52 -0
- wayfinder_paths/core/constants/erc20_abi.py +0 -1
- wayfinder_paths/core/constants/hyperlend_abi.py +0 -4
- wayfinder_paths/core/constants/hyperliquid.py +16 -0
- wayfinder_paths/core/constants/moonwell_abi.py +0 -15
- wayfinder_paths/core/constants/tokens.py +9 -0
- wayfinder_paths/core/engine/manifest.py +66 -0
- wayfinder_paths/core/strategies/Strategy.py +0 -71
- wayfinder_paths/core/strategies/__init__.py +10 -1
- wayfinder_paths/core/strategies/opa_loop.py +167 -0
- wayfinder_paths/core/utils/evm_helpers.py +5 -15
- wayfinder_paths/core/utils/test_transaction.py +289 -0
- wayfinder_paths/core/utils/tokens.py +28 -0
- wayfinder_paths/core/utils/transaction.py +57 -8
- wayfinder_paths/core/utils/web3.py +8 -3
- wayfinder_paths/mcp/__init__.py +5 -0
- wayfinder_paths/mcp/preview.py +185 -0
- wayfinder_paths/mcp/scripting.py +84 -0
- wayfinder_paths/mcp/server.py +52 -0
- wayfinder_paths/mcp/state/profile_store.py +195 -0
- wayfinder_paths/mcp/state/store.py +89 -0
- wayfinder_paths/mcp/test_scripting.py +267 -0
- wayfinder_paths/mcp/tools/__init__.py +0 -0
- wayfinder_paths/mcp/tools/balances.py +290 -0
- wayfinder_paths/mcp/tools/discovery.py +158 -0
- wayfinder_paths/mcp/tools/execute.py +770 -0
- wayfinder_paths/mcp/tools/hyperliquid.py +931 -0
- wayfinder_paths/mcp/tools/quotes.py +288 -0
- wayfinder_paths/mcp/tools/run_script.py +286 -0
- wayfinder_paths/mcp/tools/strategies.py +188 -0
- wayfinder_paths/mcp/tools/tokens.py +46 -0
- wayfinder_paths/mcp/tools/wallets.py +354 -0
- wayfinder_paths/mcp/utils.py +129 -0
- wayfinder_paths/policies/enso.py +1 -2
- wayfinder_paths/policies/hyper_evm.py +6 -3
- wayfinder_paths/policies/hyperlend.py +1 -2
- wayfinder_paths/policies/hyperliquid.py +1 -1
- wayfinder_paths/policies/lifi.py +18 -0
- wayfinder_paths/policies/moonwell.py +12 -7
- wayfinder_paths/policies/prjx.py +1 -3
- wayfinder_paths/policies/util.py +8 -2
- wayfinder_paths/run_strategy.py +97 -300
- wayfinder_paths/strategies/basis_trading_strategy/constants.py +3 -1
- wayfinder_paths/strategies/basis_trading_strategy/strategy.py +47 -133
- wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +24 -53
- wayfinder_paths/strategies/boros_hype_strategy/__init__.py +3 -0
- wayfinder_paths/strategies/boros_hype_strategy/boros_ops_mixin.py +450 -0
- wayfinder_paths/strategies/boros_hype_strategy/constants.py +255 -0
- wayfinder_paths/strategies/boros_hype_strategy/examples.json +37 -0
- wayfinder_paths/strategies/boros_hype_strategy/hyperevm_ops_mixin.py +114 -0
- wayfinder_paths/strategies/boros_hype_strategy/hyperliquid_ops_mixin.py +642 -0
- wayfinder_paths/strategies/boros_hype_strategy/manifest.yaml +36 -0
- wayfinder_paths/strategies/boros_hype_strategy/planner.py +460 -0
- wayfinder_paths/strategies/boros_hype_strategy/risk_ops_mixin.py +886 -0
- wayfinder_paths/strategies/boros_hype_strategy/snapshot_mixin.py +494 -0
- wayfinder_paths/strategies/boros_hype_strategy/strategy.py +1194 -0
- wayfinder_paths/strategies/boros_hype_strategy/test_planner_golden.py +374 -0
- wayfinder_paths/{templates/strategy → strategies/boros_hype_strategy}/test_strategy.py +99 -63
- wayfinder_paths/strategies/boros_hype_strategy/types.py +365 -0
- wayfinder_paths/strategies/boros_hype_strategy/withdraw_mixin.py +997 -0
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +15 -23
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +27 -62
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +84 -58
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +5 -15
- wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +69 -164
- wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +43 -76
- wayfinder_paths/tests/test_mcp_quote_swap.py +165 -0
- wayfinder_paths/tests/test_test_coverage.py +1 -4
- wayfinder_paths-0.1.24.dist-info/METADATA +378 -0
- wayfinder_paths-0.1.24.dist-info/RECORD +185 -0
- {wayfinder_paths-0.1.22.dist-info → wayfinder_paths-0.1.24.dist-info}/WHEEL +1 -1
- wayfinder_paths/core/clients/WalletClient.py +0 -41
- wayfinder_paths/core/engine/StrategyJob.py +0 -110
- wayfinder_paths/core/services/test_local_evm_txn.py +0 -145
- wayfinder_paths/scripts/create_strategy.py +0 -139
- wayfinder_paths/scripts/make_wallets.py +0 -142
- wayfinder_paths/templates/adapter/README.md +0 -150
- wayfinder_paths/templates/adapter/adapter.py +0 -16
- wayfinder_paths/templates/adapter/examples.json +0 -8
- wayfinder_paths/templates/adapter/test_adapter.py +0 -30
- wayfinder_paths/templates/strategy/README.md +0 -186
- wayfinder_paths/templates/strategy/examples.json +0 -11
- wayfinder_paths/templates/strategy/strategy.py +0 -35
- wayfinder_paths/tests/test_smoke_manifest.py +0 -63
- wayfinder_paths-0.1.22.dist-info/METADATA +0 -355
- wayfinder_paths-0.1.22.dist-info/RECORD +0 -129
- /wayfinder_paths/{scripts → mcp/state}/__init__.py +0 -0
- {wayfinder_paths-0.1.22.dist-info → wayfinder_paths-0.1.24.dist-info}/LICENSE +0 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
from decimal import ROUND_DOWN, Decimal, InvalidOperation, getcontext
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
import yaml
|
|
11
|
+
|
|
12
|
+
getcontext().prec = 78
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def ok(result: Any) -> dict[str, Any]:
|
|
16
|
+
return {"ok": True, "result": result}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def err(code: str, message: str, details: Any | None = None) -> dict[str, Any]:
|
|
20
|
+
return {
|
|
21
|
+
"ok": False,
|
|
22
|
+
"error": {"code": str(code), "message": str(message), "details": details},
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def repo_root() -> Path:
|
|
27
|
+
cur = Path(__file__).resolve()
|
|
28
|
+
for parent in [cur, *cur.parents]:
|
|
29
|
+
if (parent / "pyproject.toml").exists():
|
|
30
|
+
return parent
|
|
31
|
+
return Path.cwd()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def read_yaml(path: Path) -> dict[str, Any]:
|
|
35
|
+
data = yaml.safe_load(path.read_text())
|
|
36
|
+
return data if isinstance(data, dict) else {}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def read_text_excerpt(path: Path, *, max_chars: int = 1200) -> str | None:
|
|
40
|
+
try:
|
|
41
|
+
text = path.read_text(encoding="utf-8", errors="replace").strip()
|
|
42
|
+
except OSError:
|
|
43
|
+
return None
|
|
44
|
+
if not text:
|
|
45
|
+
return None
|
|
46
|
+
if len(text) <= max_chars:
|
|
47
|
+
return text
|
|
48
|
+
return text[: max_chars - 3] + "..."
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def load_json_file(path: Path) -> Any:
|
|
52
|
+
try:
|
|
53
|
+
return json.loads(path.read_text())
|
|
54
|
+
except Exception:
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def load_config_json() -> dict[str, Any]:
|
|
59
|
+
cfg_path = os.getenv("WAYFINDER_CONFIG_PATH", "config.json")
|
|
60
|
+
path = (
|
|
61
|
+
repo_root() / cfg_path if not Path(cfg_path).is_absolute() else Path(cfg_path)
|
|
62
|
+
)
|
|
63
|
+
parsed = load_json_file(path)
|
|
64
|
+
return parsed if isinstance(parsed, dict) else {}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def wallets_path() -> Path:
|
|
68
|
+
cfg = load_config_json()
|
|
69
|
+
system = cfg.get("system") if isinstance(cfg.get("system"), dict) else {}
|
|
70
|
+
candidate = (
|
|
71
|
+
(system or {}).get("wallets_path")
|
|
72
|
+
or os.getenv("WALLETS_PATH")
|
|
73
|
+
or "wallets.json"
|
|
74
|
+
)
|
|
75
|
+
p = Path(candidate)
|
|
76
|
+
if not p.is_absolute():
|
|
77
|
+
p = repo_root() / p
|
|
78
|
+
return p
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def load_wallets() -> list[dict[str, Any]]:
|
|
82
|
+
cfg = load_config_json()
|
|
83
|
+
if isinstance(cfg.get("wallets"), list):
|
|
84
|
+
return [w for w in cfg["wallets"] if isinstance(w, dict)]
|
|
85
|
+
|
|
86
|
+
p = wallets_path()
|
|
87
|
+
if p.exists():
|
|
88
|
+
parsed = load_json_file(p)
|
|
89
|
+
if isinstance(parsed, list):
|
|
90
|
+
return [w for w in parsed if isinstance(w, dict)]
|
|
91
|
+
if isinstance(parsed, dict) and isinstance(parsed.get("wallets"), list):
|
|
92
|
+
return [w for w in parsed["wallets"] if isinstance(w, dict)]
|
|
93
|
+
return []
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def find_wallet_by_label(label: str) -> dict[str, Any] | None:
|
|
97
|
+
want = str(label).strip()
|
|
98
|
+
if not want:
|
|
99
|
+
return None
|
|
100
|
+
for w in load_wallets():
|
|
101
|
+
if str(w.get("label", "")).strip() == want:
|
|
102
|
+
return w
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def normalize_address(addr: str | None) -> str | None:
|
|
107
|
+
if not addr:
|
|
108
|
+
return None
|
|
109
|
+
a = str(addr).strip()
|
|
110
|
+
return a if a else None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def parse_amount_to_raw(amount: str, decimals: int) -> int:
|
|
114
|
+
try:
|
|
115
|
+
d = Decimal(str(amount).strip())
|
|
116
|
+
except (InvalidOperation, ValueError) as exc:
|
|
117
|
+
raise ValueError(f"Invalid amount: {amount}") from exc
|
|
118
|
+
if d <= 0:
|
|
119
|
+
raise ValueError("Amount must be positive")
|
|
120
|
+
scale = Decimal(10) ** int(decimals)
|
|
121
|
+
raw = (d * scale).to_integral_value(rounding=ROUND_DOWN)
|
|
122
|
+
if raw <= 0:
|
|
123
|
+
raise ValueError("Amount is too small after decimal scaling")
|
|
124
|
+
return int(raw)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def sha256_json(obj: Any) -> str:
|
|
128
|
+
payload = json.dumps(obj, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
|
|
129
|
+
return "sha256:" + hashlib.sha256(payload.encode("utf-8")).hexdigest()
|
wayfinder_paths/policies/enso.py
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
from wayfinder_paths.core.constants.contracts import (
|
|
2
|
+
HYPERCORE_SENTINEL_ADDRESS,
|
|
3
|
+
HYPERCORE_SENTINEL_VALUE,
|
|
4
|
+
HYPEREVM_WHYPE,
|
|
5
|
+
)
|
|
1
6
|
from wayfinder_paths.policies.evm import native_transfer
|
|
2
7
|
from wayfinder_paths.policies.util import allow_functions
|
|
3
8
|
|
|
4
|
-
WHYPE_TOKEN =
|
|
5
|
-
HYPERCORE_SENTINEL_ADDRESS = "0x2222222222222222222222222222222222222222"
|
|
6
|
-
HYPERCORE_SENTINEL_VALUE = 100_000_000_000
|
|
9
|
+
WHYPE_TOKEN = HYPEREVM_WHYPE
|
|
7
10
|
|
|
8
11
|
|
|
9
12
|
def hypecore_sentinel_deposit():
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
+
from wayfinder_paths.core.constants.contracts import HYPERLEND_POOL
|
|
1
2
|
from wayfinder_paths.policies.util import allow_functions
|
|
2
3
|
|
|
3
|
-
HYPERLEND_POOL = "0x00A89d7a5A02160f20150EbEA7a2b5E4879A1A8b"
|
|
4
|
-
|
|
5
4
|
|
|
6
5
|
async def hyperlend_supply_and_withdraw():
|
|
7
6
|
return await allow_functions(
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from wayfinder_paths.policies.util import allow_functions
|
|
2
|
+
|
|
3
|
+
LIFI_ROUTERS: dict[int:str] = {999: "0x0a0758d937d1059c356D4714e57F5df0239bce1A"}
|
|
4
|
+
LIFI_GENERIC = "0x31a9b1835864706Af10103b31Ea2b79bdb995F5F"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
async def lifi_swap(chain_id):
|
|
8
|
+
# NOTE: we get the abi from the base contract as it is a generic abi used everywhere
|
|
9
|
+
# and not all chains have this ABI published
|
|
10
|
+
return await allow_functions(
|
|
11
|
+
policy_name="Allow LIFI Swap",
|
|
12
|
+
abi_chain_id=8453,
|
|
13
|
+
address=LIFI_ROUTERS[chain_id],
|
|
14
|
+
function_names=[
|
|
15
|
+
"swapTokensMultipleV3ERC20ToERC20",
|
|
16
|
+
],
|
|
17
|
+
abi_address_override=LIFI_GENERIC,
|
|
18
|
+
)
|
|
@@ -1,12 +1,17 @@
|
|
|
1
|
+
from wayfinder_paths.core.constants.contracts import (
|
|
2
|
+
BASE_WETH,
|
|
3
|
+
MOONWELL_COMPTROLLER,
|
|
4
|
+
MOONWELL_M_USDC,
|
|
5
|
+
MOONWELL_M_WETH,
|
|
6
|
+
MOONWELL_M_WSTETH,
|
|
7
|
+
)
|
|
1
8
|
from wayfinder_paths.policies.util import allow_functions
|
|
2
9
|
|
|
3
|
-
WETH =
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
COMPTROLLER = "0xfbb21d0380bee3312b33c4353c8936a0f13ef26c"
|
|
10
|
+
WETH = BASE_WETH
|
|
11
|
+
M_USDC = MOONWELL_M_USDC
|
|
12
|
+
M_WETH = MOONWELL_M_WETH
|
|
13
|
+
M_WSTETH = MOONWELL_M_WSTETH
|
|
14
|
+
COMPTROLLER = MOONWELL_COMPTROLLER
|
|
10
15
|
|
|
11
16
|
|
|
12
17
|
async def weth_deposit():
|
wayfinder_paths/policies/prjx.py
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
+
from wayfinder_paths.core.constants.contracts import PRJX_NPM, PRJX_ROUTER
|
|
1
2
|
from wayfinder_paths.policies.util import allow_functions
|
|
2
3
|
|
|
3
|
-
PRJX_ROUTER = "0x1ebdfc75ffe3ba3de61e7138a3e8706ac841af9b"
|
|
4
|
-
PRJX_NPM = "0xeAd19AE861c29bBb2101E834922B2FEee69B9091"
|
|
5
|
-
|
|
6
4
|
|
|
7
5
|
async def prjx_swap():
|
|
8
6
|
return await allow_functions(
|
wayfinder_paths/policies/util.py
CHANGED
|
@@ -2,7 +2,11 @@ from wayfinder_paths.core.utils.evm_helpers import get_abi_filtered
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
async def allow_functions(
|
|
5
|
-
policy_name: str,
|
|
5
|
+
policy_name: str,
|
|
6
|
+
abi_chain_id: int,
|
|
7
|
+
address: str,
|
|
8
|
+
function_names: list[str],
|
|
9
|
+
abi_address_override=None,
|
|
6
10
|
):
|
|
7
11
|
# Note: ChainID is just for fetching ABI, doesn't appear in the final policy. Doesn't bind a strict chain.
|
|
8
12
|
return {
|
|
@@ -19,7 +23,9 @@ async def allow_functions(
|
|
|
19
23
|
{
|
|
20
24
|
"field_source": "ethereum_calldata",
|
|
21
25
|
"field": "function_name",
|
|
22
|
-
"abi": await get_abi_filtered(
|
|
26
|
+
"abi": await get_abi_filtered(
|
|
27
|
+
abi_chain_id, abi_address_override or address, function_names
|
|
28
|
+
),
|
|
23
29
|
"operator": "in",
|
|
24
30
|
"value": function_names,
|
|
25
31
|
},
|