finoptima 1.3.2 → 1.3.3
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/CLAUDE.md +108 -0
- package/index.js +3 -90
- package/lib/api-client.js +1 -12
- package/package.json +2 -3
- package/tools/index.js +181 -51
- package/tools/intent-tools.js +0 -58
- package/tools/performance-tools.js +0 -30
- package/tools/strategy-tools.js +0 -102
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# FINOPTIMA — Agent IA Finance & Optimisation
|
|
2
|
+
|
|
3
|
+
Tu es **FINOPTIMA**, un agent IA spécialisé en finance décentralisée sur la plateforme **EcoChain** (https://www.toutcreer.com/trading).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Qui tu es
|
|
8
|
+
|
|
9
|
+
- **Nom** : FINOPTIMA (Finance + Optimisation)
|
|
10
|
+
- **Role** : Conseiller financier IA pour la plateforme EcoChain
|
|
11
|
+
- **Ton** : Professionnel mais accessible, tu tutoies l'utilisateur, tu parles en français
|
|
12
|
+
- **Specialite** : Gestion de portfolio, liquidité AMM, trading, swaps, analyse de marché
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Connexion — OBLIGATOIRE avant tout
|
|
17
|
+
|
|
18
|
+
Tu ne peux rien faire sans te connecter d'abord. Voici le flow :
|
|
19
|
+
|
|
20
|
+
1. **Demande le numéro de téléphone** à l'utilisateur (format international : +33...)
|
|
21
|
+
2. **Appelle le tool `login`** avec ce numéro
|
|
22
|
+
3. **Dis à l'utilisateur** : "Approuve la connexion sur ton app EcoAuth"
|
|
23
|
+
4. Une fois connecté, tu as accès à tous les tools
|
|
24
|
+
|
|
25
|
+
Si l'utilisateur dit "connecte-toi", "va sur le site", "login", ou similaire → démarre ce flow.
|
|
26
|
+
|
|
27
|
+
**Exemple :**
|
|
28
|
+
```
|
|
29
|
+
Utilisateur: "Salut, va sur toutcreer.com"
|
|
30
|
+
Toi: "Pour me connecter à EcoChain, j'ai besoin de ton numéro de téléphone (format +33...). Tu recevras une notification sur ton app EcoAuth pour approuver."
|
|
31
|
+
Utilisateur: "+33768572653"
|
|
32
|
+
Toi: → appelle login(phone_number: "+33768572653")
|
|
33
|
+
→ "Notification envoyée ! Approuve sur ton app EcoAuth."
|
|
34
|
+
→ une fois approuvé : "Connecté en tant que [nom]. Comment puis-je t'aider ?"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Ce que tu peux faire
|
|
40
|
+
|
|
41
|
+
### Lecture (toujours disponible après login)
|
|
42
|
+
- `get_balances` / `get_balance` — Soldes wallets (ECO, BTC, ETH)
|
|
43
|
+
- `get_portfolio` — Vue complète : wallets + positions LP
|
|
44
|
+
- `get_prices` — Prix BTC/ETH en EUR (CoinGecko)
|
|
45
|
+
- `get_pools` / `get_pool_details` — Pools de liquidité AMM
|
|
46
|
+
- `get_lp_positions` — Positions de liquidité de l'utilisateur
|
|
47
|
+
- `get_orderbook` — Carnet d'ordres
|
|
48
|
+
- `get_ticker` — Stats 24h d'une paire
|
|
49
|
+
- `get_candles` — Données OHLCV pour analyse technique
|
|
50
|
+
- `get_trading_pairs` — Paires de trading actives
|
|
51
|
+
- `get_my_orders` — Ordres ouverts
|
|
52
|
+
- `get_trade_history` — Historique des trades
|
|
53
|
+
- `get_transaction_history` — Historique des transactions
|
|
54
|
+
|
|
55
|
+
### Trading
|
|
56
|
+
- `place_order` — Placer un ordre (limit/market, buy/sell)
|
|
57
|
+
- `cancel_order` — Annuler un ordre
|
|
58
|
+
|
|
59
|
+
### Swap AMM
|
|
60
|
+
- `get_swap_quote` — Simuler un swap (sans exécuter)
|
|
61
|
+
- `execute_swap` — Exécuter un swap (0.3% fee)
|
|
62
|
+
|
|
63
|
+
### Liquidité
|
|
64
|
+
- `add_liquidity` — Ajouter de la liquidité à un pool (mint LP tokens)
|
|
65
|
+
- `remove_liquidity` — Retirer de la liquidité (burn LP tokens)
|
|
66
|
+
|
|
67
|
+
### Transfert
|
|
68
|
+
- `send_funds` — Envoyer des fonds à un autre utilisateur
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Stratégies que tu proposes
|
|
73
|
+
|
|
74
|
+
### 1. Ajout de liquidité (prioritaire)
|
|
75
|
+
- Analyse le portfolio de l'utilisateur
|
|
76
|
+
- Calcule le ratio optimal pour le pool ECO/BTC
|
|
77
|
+
- Propose quelle portion investir en LP
|
|
78
|
+
- Explique les risques (impermanent loss) simplement
|
|
79
|
+
|
|
80
|
+
### 2. Optimisation de portfolio
|
|
81
|
+
- Bilan complet des actifs
|
|
82
|
+
- Répartition recommandée
|
|
83
|
+
- Opportunités de rendement via les pools
|
|
84
|
+
|
|
85
|
+
### 3. Trading
|
|
86
|
+
- Analyse du carnet d'ordres
|
|
87
|
+
- Tendances via les candles
|
|
88
|
+
- Suggestions d'ordres limit vs market
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Règles
|
|
93
|
+
|
|
94
|
+
- **TOUJOURS** te connecter avant de tenter toute action
|
|
95
|
+
- **TOUJOURS** expliquer ce que tu fais avant d'exécuter une action qui modifie les fonds (swap, trade, add/remove liquidity, send)
|
|
96
|
+
- **TOUJOURS** demander confirmation avant d'exécuter une action financière
|
|
97
|
+
- **JAMAIS** inventer des données — utilise les tools pour obtenir les vraies valeurs
|
|
98
|
+
- **JAMAIS** envoyer des fonds sans confirmation explicite de l'utilisateur
|
|
99
|
+
- Quand tu affiches des montants BTC, convertis aussi en EUR avec `get_prices`
|
|
100
|
+
- Arrondis les montants pour la lisibilité (2 décimales EUR, 8 décimales BTC)
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Premier message
|
|
105
|
+
|
|
106
|
+
Quand l'utilisateur démarre une conversation, présente-toi :
|
|
107
|
+
|
|
108
|
+
"Salut ! Je suis **FINOPTIMA**, ton assistant finance sur EcoChain. Pour commencer, j'ai besoin de me connecter à ton compte. Quel est ton numéro de téléphone ? (format +33...)"
|
package/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* EcoChain MCP Server (DEC-2026-043)
|
|
4
4
|
*
|
|
5
5
|
* Model Context Protocol server for AI agent access to EcoChain.
|
|
6
|
-
* Provides
|
|
6
|
+
* Provides 35 tools: 1 login + 34 platform tools.
|
|
7
7
|
*
|
|
8
8
|
* Authentication: EcoAuth login flow (no hardcoded credentials)
|
|
9
9
|
* 1. AI asks user for their phone number
|
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
*
|
|
14
14
|
* Environment variables:
|
|
15
15
|
* ECOCHAIN_API_URL — Optional. API base URL (default: https://api.toutcreer.com)
|
|
16
|
-
* ECOCHAIN_API_KEY — Optional. API key for agent auth (skips phone login)
|
|
17
16
|
*/
|
|
18
17
|
|
|
19
18
|
const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
|
|
@@ -31,14 +30,6 @@ const POLL_TIMEOUT_MS = 120000;
|
|
|
31
30
|
|
|
32
31
|
let authenticated = false;
|
|
33
32
|
|
|
34
|
-
// API key auth — skip phone login if ECOCHAIN_API_KEY is set (DEC-2026-050)
|
|
35
|
-
const API_KEY = process.env.ECOCHAIN_API_KEY;
|
|
36
|
-
if (API_KEY) {
|
|
37
|
-
apiClient.setApiKey(API_KEY);
|
|
38
|
-
authenticated = true;
|
|
39
|
-
console.error('[EcoChain MCP] Authenticated via API key (no phone login required)');
|
|
40
|
-
}
|
|
41
|
-
|
|
42
33
|
/**
|
|
43
34
|
* EcoAuth login flow
|
|
44
35
|
*/
|
|
@@ -106,11 +97,7 @@ const loginTool = {
|
|
|
106
97
|
},
|
|
107
98
|
handler: async ({ phone_number }) => {
|
|
108
99
|
if (authenticated) {
|
|
109
|
-
return JSON.stringify({
|
|
110
|
-
status: 'already_authenticated',
|
|
111
|
-
auth_method: API_KEY ? 'api_key' : 'ecoauth',
|
|
112
|
-
user_id: process.env.ECOCHAIN_USER_ID
|
|
113
|
-
});
|
|
100
|
+
return JSON.stringify({ status: 'already_authenticated', user_id: process.env.ECOCHAIN_USER_ID });
|
|
114
101
|
}
|
|
115
102
|
|
|
116
103
|
const result = await loginWithEcoAuth(phone_number);
|
|
@@ -130,79 +117,6 @@ const loginTool = {
|
|
|
130
117
|
// All tools: login first, then platform tools
|
|
131
118
|
const allToolsWithLogin = [loginTool, ...allTools];
|
|
132
119
|
|
|
133
|
-
// Server instructions — shown to the AI to explain how to use FINOPTIMA
|
|
134
|
-
const SERVER_INSTRUCTIONS = `FINOPTIMA — EcoChain AI Finance Agent (v1.3.0, ${allTools.length + 1} tools)
|
|
135
|
-
|
|
136
|
-
## Setup
|
|
137
|
-
If the user doesn't have FINOPTIMA installed, they can add it to their MCP config:
|
|
138
|
-
\`\`\`json
|
|
139
|
-
{
|
|
140
|
-
"mcpServers": {
|
|
141
|
-
"ecochain": {
|
|
142
|
-
"command": "npx",
|
|
143
|
-
"args": ["-y", "finoptima"],
|
|
144
|
-
"env": { "ECOCHAIN_API_URL": "https://api.toutcreer.com" }
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
\`\`\`
|
|
149
|
-
|
|
150
|
-
## Authentication
|
|
151
|
-
Two authentication methods are supported:
|
|
152
|
-
|
|
153
|
-
### Method 1: EcoAuth (interactive — for users)
|
|
154
|
-
1. Ask the user for their phone number (format: +33...)
|
|
155
|
-
2. Call the \`login\` tool — a push notification is sent to their EcoAuth mobile app
|
|
156
|
-
3. The user approves on their phone — JWT session is established
|
|
157
|
-
4. All other tools are now active
|
|
158
|
-
|
|
159
|
-
### Method 2: API Key (non-interactive — for agents)
|
|
160
|
-
Set \`ECOCHAIN_API_KEY\` in the MCP config env:
|
|
161
|
-
\`\`\`json
|
|
162
|
-
{
|
|
163
|
-
"env": {
|
|
164
|
-
"ECOCHAIN_API_URL": "https://api.toutcreer.com",
|
|
165
|
-
"ECOCHAIN_API_KEY": "eco_ak_..."
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
\`\`\`
|
|
169
|
-
No login call needed — all tools are immediately available.
|
|
170
|
-
API keys are scoped (read/trade/swap/liquidity/send/strategy) and rate-limited.
|
|
171
|
-
Requires a Pro or API subscription plan.
|
|
172
|
-
|
|
173
|
-
## Available Capabilities
|
|
174
|
-
- **Portfolio**: get_balances, get_portfolio, get_prices, get_lp_positions, get_transaction_history
|
|
175
|
-
- **Market**: get_pools, get_pool_details, get_orderbook, get_ticker, get_candles, get_trading_pairs
|
|
176
|
-
- **Trading**: place_order, cancel_order, get_my_orders, get_trade_history
|
|
177
|
-
- **Swap (AMM)**: get_swap_quote, execute_swap (0.3% fee, multi-hop routing supported)
|
|
178
|
-
- **Liquidity**: add_liquidity, remove_liquidity
|
|
179
|
-
- **Transfer**: send_funds
|
|
180
|
-
- **AI Strategies**: create_strategy, list_strategies, get_strategy, activate_strategy, pause_strategy
|
|
181
|
-
- **Marketplace**: get_templates, subscribe_template, get_leaderboard, follow_strategy, unfollow_strategy
|
|
182
|
-
- **Intents**: create_intent, list_intents
|
|
183
|
-
- **Performance**: get_strategy_performance
|
|
184
|
-
|
|
185
|
-
## Multi-Pool AMM
|
|
186
|
-
Multiple liquidity pools supported (ECO/BTC, ECO/ETH). Swaps between non-direct pairs (e.g., BTC↔ETH) are automatically routed via ECO as a hub token (2-hop swap).
|
|
187
|
-
|
|
188
|
-
## AI Trading Strategies
|
|
189
|
-
4 strategy types: LP Yield, DCA, BTC Hedge, Eco Pilot.
|
|
190
|
-
Pipeline: Create (draft) → Activate → Runner evaluates every 60s → Creates intents → Policy validation (8 rules) → Auto-approve if < 0.01 BTC, else manual approval → Execute.
|
|
191
|
-
Modes: "auto" (auto-approve small trades) or "watch" (all manual approval).
|
|
192
|
-
|
|
193
|
-
## Strategy Marketplace
|
|
194
|
-
- Browse public strategy templates (\`get_templates\`)
|
|
195
|
-
- Subscribe to a template to clone its config (\`subscribe_template\`)
|
|
196
|
-
- Follow a provider's live trades with copy trading (\`follow_strategy\`)
|
|
197
|
-
- View top-performing strategies (\`get_leaderboard\`)
|
|
198
|
-
|
|
199
|
-
## Rules
|
|
200
|
-
- Always call \`login\` first before any other tool (unless using API key auth)
|
|
201
|
-
- Always confirm financial actions (trade, swap, send) with the user before executing
|
|
202
|
-
- Use \`get_swap_quote\` before \`execute_swap\` to show expected outcome
|
|
203
|
-
- Platform: https://www.toutcreer.com/trading
|
|
204
|
-
`;
|
|
205
|
-
|
|
206
120
|
// Create MCP server
|
|
207
121
|
const server = new Server(
|
|
208
122
|
{
|
|
@@ -213,7 +127,6 @@ const server = new Server(
|
|
|
213
127
|
capabilities: {
|
|
214
128
|
tools: {},
|
|
215
129
|
},
|
|
216
|
-
instructions: SERVER_INSTRUCTIONS,
|
|
217
130
|
}
|
|
218
131
|
);
|
|
219
132
|
|
|
@@ -243,7 +156,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
243
156
|
// Block all tools except login if not authenticated
|
|
244
157
|
if (!authenticated && name !== 'login') {
|
|
245
158
|
return {
|
|
246
|
-
content: [{ type: 'text', text: 'Not authenticated.
|
|
159
|
+
content: [{ type: 'text', text: 'Not authenticated. Please ask the user for their phone number and call the `login` tool first.' }],
|
|
247
160
|
isError: true,
|
|
248
161
|
};
|
|
249
162
|
}
|
package/lib/api-client.js
CHANGED
|
@@ -12,7 +12,6 @@ class ApiClient {
|
|
|
12
12
|
constructor() {
|
|
13
13
|
this.apiUrl = process.env.ECOCHAIN_API_URL || 'https://api.toutcreer.com';
|
|
14
14
|
this.jwtToken = null; // Set after EcoAuth login
|
|
15
|
-
this.apiKey = null; // Set via ECOCHAIN_API_KEY env var (DEC-2026-050)
|
|
16
15
|
|
|
17
16
|
const parsed = new URL(this.apiUrl);
|
|
18
17
|
this.hostname = parsed.hostname;
|
|
@@ -28,13 +27,6 @@ class ApiClient {
|
|
|
28
27
|
this.jwtToken = token;
|
|
29
28
|
}
|
|
30
29
|
|
|
31
|
-
/**
|
|
32
|
-
* Set API key for agent authentication (DEC-2026-050)
|
|
33
|
-
*/
|
|
34
|
-
setApiKey(key) {
|
|
35
|
-
this.apiKey = key;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
30
|
/**
|
|
39
31
|
* Make an HTTP request to the backend API
|
|
40
32
|
*/
|
|
@@ -43,12 +35,9 @@ class ApiClient {
|
|
|
43
35
|
|
|
44
36
|
const headers = {
|
|
45
37
|
'Content-Type': 'application/json',
|
|
46
|
-
'X-Client-Type': 'mcp-agent',
|
|
47
38
|
};
|
|
48
39
|
|
|
49
|
-
if (this.
|
|
50
|
-
headers['X-API-Key'] = this.apiKey;
|
|
51
|
-
} else if (this.jwtToken) {
|
|
40
|
+
if (this.jwtToken) {
|
|
52
41
|
headers['Authorization'] = `Bearer ${this.jwtToken}`;
|
|
53
42
|
}
|
|
54
43
|
|
package/package.json
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "finoptima",
|
|
3
|
-
"version": "1.3.
|
|
4
|
-
"
|
|
5
|
-
"description": "FINOPTIMA — EcoChain AI Finance Agent via MCP. Multi-pool AMM (ECO/BTC, ECO/ETH), 4 AI strategies, strategy marketplace, copy trading, leaderboard. 35 tools. API key auth. Secured by EcoAuth 2FA.",
|
|
3
|
+
"version": "1.3.3",
|
|
4
|
+
"description": "FINOPTIMA — EcoChain AI Finance Agent via MCP. Portfolio optimization, AMM liquidity, trading, swaps. Secured by EcoAuth 2FA.",
|
|
6
5
|
"main": "index.js",
|
|
7
6
|
"bin": {
|
|
8
7
|
"finoptima": "index.js"
|
package/tools/index.js
CHANGED
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MCP Tools Registry (DEC-2026-043)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 34 tools organized by scope:
|
|
5
5
|
* - READ (14): Portfolio, balances, prices, pools, orderbook, etc.
|
|
6
6
|
* - TRADE (2): Place/cancel orders
|
|
7
7
|
* - SWAP (2): Quote/execute AMM swaps
|
|
8
8
|
* - LIQUIDITY (2): Add/remove liquidity
|
|
9
9
|
* - SEND (1): Internal transfers
|
|
10
|
+
* - STRATEGY (5): Create/list/get/activate/pause strategies
|
|
11
|
+
* - MARKETPLACE (5): Templates, leaderboard, follow/unfollow
|
|
12
|
+
* - INTENTS (2): Create/list intents
|
|
13
|
+
* - PERFORMANCE (1): Strategy performance metrics
|
|
10
14
|
*/
|
|
11
15
|
|
|
12
16
|
const apiClient = require('../lib/api-client');
|
|
13
17
|
const { withLogging } = require('../lib/logger');
|
|
14
18
|
|
|
15
|
-
// [DEC-2026-046] AI Trading Platform tools
|
|
16
|
-
const { strategyTools } = require('./strategy-tools');
|
|
17
|
-
const { intentTools } = require('./intent-tools');
|
|
18
|
-
const { performanceTools } = require('./performance-tools');
|
|
19
|
-
|
|
20
19
|
// Helper: get user ID from env (set at startup from API key lookup)
|
|
21
20
|
function uid() {
|
|
22
21
|
return process.env.ECOCHAIN_USER_ID || 'me';
|
|
@@ -354,7 +353,7 @@ const liquidityTools = [
|
|
|
354
353
|
const data = await apiClient.post('/amm/liquidity/remove', {
|
|
355
354
|
user_id: uid(),
|
|
356
355
|
pool_id,
|
|
357
|
-
lp_amount
|
|
356
|
+
lp_tokens: lp_amount
|
|
358
357
|
});
|
|
359
358
|
return JSON.stringify(data, null, 2);
|
|
360
359
|
})
|
|
@@ -380,12 +379,17 @@ const sendTools = [
|
|
|
380
379
|
required: ['to', 'chain', 'amount']
|
|
381
380
|
},
|
|
382
381
|
handler: withLogging('send_funds', async ({ to, chain, amount, note }) => {
|
|
382
|
+
const isPhone = /^\+\d+$/.test(to);
|
|
383
383
|
const body = {
|
|
384
384
|
from_uid: uid(),
|
|
385
|
-
to_identifier: to,
|
|
386
385
|
chain,
|
|
387
386
|
amount
|
|
388
387
|
};
|
|
388
|
+
if (isPhone) {
|
|
389
|
+
body.to_phone = to;
|
|
390
|
+
} else {
|
|
391
|
+
body.to_uid = to;
|
|
392
|
+
}
|
|
389
393
|
if (note) body.note = note;
|
|
390
394
|
const data = await apiClient.post('/transactions/send', body);
|
|
391
395
|
return JSON.stringify(data, null, 2);
|
|
@@ -394,42 +398,104 @@ const sendTools = [
|
|
|
394
398
|
];
|
|
395
399
|
|
|
396
400
|
// ============================================
|
|
397
|
-
//
|
|
401
|
+
// STRATEGY TOOLS (scope: strategy)
|
|
398
402
|
// ============================================
|
|
399
403
|
|
|
400
|
-
const
|
|
404
|
+
const strategyTools = [
|
|
401
405
|
{
|
|
402
|
-
name: '
|
|
403
|
-
description: '
|
|
406
|
+
name: 'list_strategies',
|
|
407
|
+
description: 'List all AI trading strategies for the user. Returns name, type, status, intent counts.',
|
|
408
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
409
|
+
handler: withLogging('list_strategies', async () => {
|
|
410
|
+
const data = await apiClient.get(`/strategies/${uid()}`);
|
|
411
|
+
return JSON.stringify(data, null, 2);
|
|
412
|
+
})
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
name: 'get_strategy',
|
|
416
|
+
description: 'Get detailed information about a specific strategy by ID.',
|
|
404
417
|
inputSchema: {
|
|
405
418
|
type: 'object',
|
|
406
419
|
properties: {
|
|
407
|
-
|
|
420
|
+
strategy_id: { type: 'string', description: 'The UUID of the strategy' }
|
|
408
421
|
},
|
|
409
|
-
required: []
|
|
422
|
+
required: ['strategy_id']
|
|
410
423
|
},
|
|
411
|
-
handler: withLogging('
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
return JSON.stringify(data);
|
|
424
|
+
handler: withLogging('get_strategy', async ({ strategy_id }) => {
|
|
425
|
+
const data = await apiClient.get(`/strategies/detail/${strategy_id}`);
|
|
426
|
+
return JSON.stringify(data, null, 2);
|
|
415
427
|
})
|
|
416
428
|
},
|
|
417
429
|
{
|
|
418
|
-
name: '
|
|
419
|
-
description: '
|
|
430
|
+
name: 'create_strategy',
|
|
431
|
+
description: 'Create a new AI trading strategy. Types: lp_yield, dca, btc_hedge, eco_pilot. Created in draft status. Modes: "auto" (auto-approve small trades) or "watch" (manual approval).',
|
|
420
432
|
inputSchema: {
|
|
421
433
|
type: 'object',
|
|
422
|
-
properties: {
|
|
423
|
-
|
|
434
|
+
properties: {
|
|
435
|
+
name: { type: 'string', description: 'Strategy name' },
|
|
436
|
+
type: { type: 'string', description: 'Strategy type', enum: ['lp_yield', 'dca', 'btc_hedge', 'eco_pilot'] },
|
|
437
|
+
mode: { type: 'string', description: 'Execution mode', enum: ['auto', 'watch'] },
|
|
438
|
+
params: { type: 'object', description: 'Strategy-specific parameters (e.g., drop_threshold, step, cooldown for btc_hedge)' }
|
|
439
|
+
},
|
|
440
|
+
required: ['name', 'type']
|
|
441
|
+
},
|
|
442
|
+
handler: withLogging('create_strategy', async ({ name, type, mode, params }) => {
|
|
443
|
+
const body = { user_id: uid(), name, type };
|
|
444
|
+
if (mode) body.mode = mode;
|
|
445
|
+
if (params) body.params = params;
|
|
446
|
+
const data = await apiClient.post('/strategies', body);
|
|
447
|
+
return JSON.stringify(data, null, 2);
|
|
448
|
+
})
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
name: 'activate_strategy',
|
|
452
|
+
description: 'Activate a strategy (draft or paused -> active). The runner will evaluate it every 60 seconds.',
|
|
453
|
+
inputSchema: {
|
|
454
|
+
type: 'object',
|
|
455
|
+
properties: {
|
|
456
|
+
strategy_id: { type: 'string', description: 'The UUID of the strategy to activate' }
|
|
457
|
+
},
|
|
458
|
+
required: ['strategy_id']
|
|
424
459
|
},
|
|
460
|
+
handler: withLogging('activate_strategy', async ({ strategy_id }) => {
|
|
461
|
+
const data = await apiClient.post(`/strategies/${strategy_id}/activate`, { user_id: uid() });
|
|
462
|
+
return JSON.stringify(data, null, 2);
|
|
463
|
+
})
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
name: 'pause_strategy',
|
|
467
|
+
description: 'Pause an active strategy. Pending intents are cancelled automatically.',
|
|
468
|
+
inputSchema: {
|
|
469
|
+
type: 'object',
|
|
470
|
+
properties: {
|
|
471
|
+
strategy_id: { type: 'string', description: 'The UUID of the strategy to pause' }
|
|
472
|
+
},
|
|
473
|
+
required: ['strategy_id']
|
|
474
|
+
},
|
|
475
|
+
handler: withLogging('pause_strategy', async ({ strategy_id }) => {
|
|
476
|
+
const data = await apiClient.post(`/strategies/${strategy_id}/pause`, { user_id: uid() });
|
|
477
|
+
return JSON.stringify(data, null, 2);
|
|
478
|
+
})
|
|
479
|
+
}
|
|
480
|
+
];
|
|
481
|
+
|
|
482
|
+
// ============================================
|
|
483
|
+
// MARKETPLACE TOOLS (scope: marketplace)
|
|
484
|
+
// ============================================
|
|
485
|
+
|
|
486
|
+
const marketplaceTools = [
|
|
487
|
+
{
|
|
488
|
+
name: 'get_templates',
|
|
489
|
+
description: 'Browse available strategy templates in the marketplace.',
|
|
490
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
425
491
|
handler: withLogging('get_templates', async () => {
|
|
426
492
|
const data = await apiClient.get('/strategies/templates');
|
|
427
|
-
return JSON.stringify(data);
|
|
493
|
+
return JSON.stringify(data, null, 2);
|
|
428
494
|
})
|
|
429
495
|
},
|
|
430
496
|
{
|
|
431
497
|
name: 'subscribe_template',
|
|
432
|
-
description: 'Subscribe to a strategy template
|
|
498
|
+
description: 'Subscribe to a strategy template to clone its configuration into a new personal strategy.',
|
|
433
499
|
inputSchema: {
|
|
434
500
|
type: 'object',
|
|
435
501
|
properties: {
|
|
@@ -438,33 +504,37 @@ const marketplaceTools = [
|
|
|
438
504
|
required: ['template_id']
|
|
439
505
|
},
|
|
440
506
|
handler: withLogging('subscribe_template', async ({ template_id }) => {
|
|
441
|
-
const data = await apiClient.post(
|
|
442
|
-
return JSON.stringify(data);
|
|
507
|
+
const data = await apiClient.post('/strategies/templates/subscribe', { user_id: uid(), template_id });
|
|
508
|
+
return JSON.stringify(data, null, 2);
|
|
509
|
+
})
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
name: 'get_leaderboard',
|
|
513
|
+
description: 'View the strategy leaderboard — top-performing public strategies ranked by returns.',
|
|
514
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
515
|
+
handler: withLogging('get_leaderboard', async () => {
|
|
516
|
+
const data = await apiClient.get('/strategies/leaderboard');
|
|
517
|
+
return JSON.stringify(data, null, 2);
|
|
443
518
|
})
|
|
444
519
|
},
|
|
445
520
|
{
|
|
446
521
|
name: 'follow_strategy',
|
|
447
|
-
description: '
|
|
522
|
+
description: 'Follow a public strategy for copy trading. Automatically replicates the provider\'s trades.',
|
|
448
523
|
inputSchema: {
|
|
449
524
|
type: 'object',
|
|
450
525
|
properties: {
|
|
451
|
-
strategy_id: { type: 'string', description: 'The UUID of the public strategy to follow' }
|
|
452
|
-
scale_factor: { type: 'number', description: 'Trade size multiplier (0.1-2.0, default 1.0)' },
|
|
453
|
-
max_trade_btc: { type: 'number', description: 'Maximum trade size in BTC (default 0.01)' }
|
|
526
|
+
strategy_id: { type: 'string', description: 'The UUID of the public strategy to follow' }
|
|
454
527
|
},
|
|
455
528
|
required: ['strategy_id']
|
|
456
529
|
},
|
|
457
|
-
handler: withLogging('follow_strategy', async ({ strategy_id
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
if (max_trade_btc !== undefined) body.max_trade_btc = max_trade_btc;
|
|
461
|
-
const data = await apiClient.post(`/strategies/${strategy_id}/follow`, body);
|
|
462
|
-
return JSON.stringify(data);
|
|
530
|
+
handler: withLogging('follow_strategy', async ({ strategy_id }) => {
|
|
531
|
+
const data = await apiClient.post(`/strategies/${strategy_id}/follow`, { user_id: uid() });
|
|
532
|
+
return JSON.stringify(data, null, 2);
|
|
463
533
|
})
|
|
464
534
|
},
|
|
465
535
|
{
|
|
466
536
|
name: 'unfollow_strategy',
|
|
467
|
-
description: 'Stop
|
|
537
|
+
description: 'Stop following a strategy and end copy trading.',
|
|
468
538
|
inputSchema: {
|
|
469
539
|
type: 'object',
|
|
470
540
|
properties: {
|
|
@@ -473,10 +543,80 @@ const marketplaceTools = [
|
|
|
473
543
|
required: ['strategy_id']
|
|
474
544
|
},
|
|
475
545
|
handler: withLogging('unfollow_strategy', async ({ strategy_id }) => {
|
|
476
|
-
const data = await apiClient.
|
|
477
|
-
return JSON.stringify(data);
|
|
546
|
+
const data = await apiClient.post(`/strategies/${strategy_id}/unfollow`, { user_id: uid() });
|
|
547
|
+
return JSON.stringify(data, null, 2);
|
|
548
|
+
})
|
|
549
|
+
}
|
|
550
|
+
];
|
|
551
|
+
|
|
552
|
+
// ============================================
|
|
553
|
+
// INTENT TOOLS (scope: strategy)
|
|
554
|
+
// ============================================
|
|
555
|
+
|
|
556
|
+
const intentTools = [
|
|
557
|
+
{
|
|
558
|
+
name: 'create_intent',
|
|
559
|
+
description: 'Create a trading intent for policy validation and execution. Goes through the policy engine (8 rules). Auto-approved if under threshold, otherwise requires manual approval. TTL: 10 minutes.',
|
|
560
|
+
inputSchema: {
|
|
561
|
+
type: 'object',
|
|
562
|
+
properties: {
|
|
563
|
+
strategy_id: { type: 'string', description: 'Strategy UUID (optional — omit for manual intents)' },
|
|
564
|
+
action: { type: 'string', description: 'Intent action type (e.g., execute_swap, vault_to_hot)' },
|
|
565
|
+
params: { type: 'object', description: 'Action parameters (e.g., token_in, token_out, amount)' },
|
|
566
|
+
reasoning: { type: 'string', description: 'Human-readable explanation of why this intent was created' }
|
|
567
|
+
},
|
|
568
|
+
required: ['action', 'params']
|
|
569
|
+
},
|
|
570
|
+
handler: withLogging('create_intent', async ({ strategy_id, action, params, reasoning }) => {
|
|
571
|
+
const body = { user_id: uid(), action, params };
|
|
572
|
+
if (strategy_id) body.strategy_id = strategy_id;
|
|
573
|
+
if (reasoning) body.reasoning = reasoning;
|
|
574
|
+
const data = await apiClient.post('/intents', body);
|
|
575
|
+
return JSON.stringify(data, null, 2);
|
|
478
576
|
})
|
|
479
577
|
},
|
|
578
|
+
{
|
|
579
|
+
name: 'list_intents',
|
|
580
|
+
description: 'List all intents for the user, showing the full pipeline: created -> policy_checked -> approved/rejected -> executed/expired.',
|
|
581
|
+
inputSchema: {
|
|
582
|
+
type: 'object',
|
|
583
|
+
properties: {
|
|
584
|
+
strategy_id: { type: 'string', description: 'Filter by strategy UUID (optional)' },
|
|
585
|
+
status: { type: 'string', description: 'Filter by status: pending, approved, rejected, executed, expired' }
|
|
586
|
+
},
|
|
587
|
+
required: []
|
|
588
|
+
},
|
|
589
|
+
handler: withLogging('list_intents', async ({ strategy_id, status }) => {
|
|
590
|
+
const params = new URLSearchParams();
|
|
591
|
+
if (strategy_id) params.set('strategy_id', strategy_id);
|
|
592
|
+
if (status) params.set('status', status);
|
|
593
|
+
const qs = params.toString();
|
|
594
|
+
const data = await apiClient.get(`/intents/${uid()}${qs ? '?' + qs : ''}`);
|
|
595
|
+
return JSON.stringify(data, null, 2);
|
|
596
|
+
})
|
|
597
|
+
}
|
|
598
|
+
];
|
|
599
|
+
|
|
600
|
+
// ============================================
|
|
601
|
+
// PERFORMANCE TOOLS (scope: strategy)
|
|
602
|
+
// ============================================
|
|
603
|
+
|
|
604
|
+
const performanceTools = [
|
|
605
|
+
{
|
|
606
|
+
name: 'get_strategy_performance',
|
|
607
|
+
description: 'Get performance metrics for a strategy: total return, max drawdown, daily snapshots with balances and prices.',
|
|
608
|
+
inputSchema: {
|
|
609
|
+
type: 'object',
|
|
610
|
+
properties: {
|
|
611
|
+
strategy_id: { type: 'string', description: 'The UUID of the strategy' }
|
|
612
|
+
},
|
|
613
|
+
required: ['strategy_id']
|
|
614
|
+
},
|
|
615
|
+
handler: withLogging('get_strategy_performance', async ({ strategy_id }) => {
|
|
616
|
+
const data = await apiClient.get(`/strategies/${strategy_id}/performance`);
|
|
617
|
+
return JSON.stringify(data, null, 2);
|
|
618
|
+
})
|
|
619
|
+
}
|
|
480
620
|
];
|
|
481
621
|
|
|
482
622
|
// Export all tools grouped by scope
|
|
@@ -487,18 +627,8 @@ module.exports = {
|
|
|
487
627
|
liquidityTools,
|
|
488
628
|
sendTools,
|
|
489
629
|
strategyTools,
|
|
630
|
+
marketplaceTools,
|
|
490
631
|
intentTools,
|
|
491
632
|
performanceTools,
|
|
492
|
-
marketplaceTools,
|
|
493
|
-
allTools: [
|
|
494
|
-
...readTools,
|
|
495
|
-
...tradeTools,
|
|
496
|
-
...swapTools,
|
|
497
|
-
...liquidityTools,
|
|
498
|
-
...sendTools,
|
|
499
|
-
...strategyTools,
|
|
500
|
-
...intentTools,
|
|
501
|
-
...performanceTools,
|
|
502
|
-
...marketplaceTools,
|
|
503
|
-
]
|
|
633
|
+
allTools: [...readTools, ...tradeTools, ...swapTools, ...liquidityTools, ...sendTools, ...strategyTools, ...marketplaceTools, ...intentTools, ...performanceTools]
|
|
504
634
|
};
|
package/tools/intent-tools.js
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MCP Intent Tools (DEC-2026-046)
|
|
3
|
-
* 2 tools for AI agent intent management (scope: trade)
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const apiClient = require('../lib/api-client');
|
|
7
|
-
const { withLogging } = require('../lib/logger');
|
|
8
|
-
|
|
9
|
-
const intentTools = [
|
|
10
|
-
{
|
|
11
|
-
name: 'create_intent',
|
|
12
|
-
description: 'Create a trading intent with policy validation. Returns canonical_display (human-readable JSON) and intent_hash (SHA256) for approval verification. Auto-approved if amount < 0.01 BTC, otherwise awaits manual approval (30min TTL).',
|
|
13
|
-
inputSchema: {
|
|
14
|
-
type: 'object',
|
|
15
|
-
properties: {
|
|
16
|
-
strategy_id: { type: 'string', description: 'Optional: link to a strategy' },
|
|
17
|
-
action: { type: 'string', description: 'Trading action', enum: ['execute_swap', 'add_liquidity', 'remove_liquidity', 'zap_liquidity', 'place_order', 'vault_to_hot'] },
|
|
18
|
-
params: {
|
|
19
|
-
type: 'object',
|
|
20
|
-
description: 'Action parameters. Swap: {token_in, token_out, amount_in, max_slippage_bps}. Liquidity: {pool_id, amount_a, amount_b}. Order: {pair_id, side, type, amount, price}.'
|
|
21
|
-
},
|
|
22
|
-
reasoning: { type: 'string', description: 'Why this trade (shown to user for approval)' }
|
|
23
|
-
},
|
|
24
|
-
required: ['action', 'params']
|
|
25
|
-
},
|
|
26
|
-
handler: withLogging('create_intent', async ({ strategy_id, action, params, reasoning }) => {
|
|
27
|
-
const body = { action, params };
|
|
28
|
-
if (strategy_id) body.strategy_id = strategy_id;
|
|
29
|
-
if (reasoning) body.reasoning = reasoning;
|
|
30
|
-
const data = await apiClient.post('/intents', body);
|
|
31
|
-
return JSON.stringify(data, null, 2);
|
|
32
|
-
})
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
name: 'list_intents',
|
|
36
|
-
description: 'List trading intents with optional filters. Shows status, canonical display, policy result, and execution result.',
|
|
37
|
-
inputSchema: {
|
|
38
|
-
type: 'object',
|
|
39
|
-
properties: {
|
|
40
|
-
status: { type: 'string', description: 'Filter by status', enum: ['pending', 'awaiting_approval', 'approved', 'rejected', 'executing', 'executed', 'failed', 'expired', 'canceled'] },
|
|
41
|
-
strategy_id: { type: 'string', description: 'Filter by strategy' },
|
|
42
|
-
limit: { type: 'number', description: 'Max results (default 50)' }
|
|
43
|
-
},
|
|
44
|
-
required: []
|
|
45
|
-
},
|
|
46
|
-
handler: withLogging('list_intents', async ({ status, strategy_id, limit }) => {
|
|
47
|
-
const params = new URLSearchParams();
|
|
48
|
-
if (status) params.set('status', status);
|
|
49
|
-
if (strategy_id) params.set('strategy_id', strategy_id);
|
|
50
|
-
if (limit) params.set('limit', limit.toString());
|
|
51
|
-
const qs = params.toString();
|
|
52
|
-
const data = await apiClient.get(`/intents${qs ? '?' + qs : ''}`);
|
|
53
|
-
return JSON.stringify(data, null, 2);
|
|
54
|
-
})
|
|
55
|
-
}
|
|
56
|
-
];
|
|
57
|
-
|
|
58
|
-
module.exports = { intentTools };
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MCP Performance Tools (DEC-2026-046)
|
|
3
|
-
* 1 tool for AI agent performance monitoring (scope: read)
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const apiClient = require('../lib/api-client');
|
|
7
|
-
const { withLogging } = require('../lib/logger');
|
|
8
|
-
|
|
9
|
-
const performanceTools = [
|
|
10
|
-
{
|
|
11
|
-
name: 'get_strategy_performance',
|
|
12
|
-
description: 'Get performance metrics for a trading strategy: total return, max drawdown, vs HODL benchmark, fees paid/earned, and PnL snapshots time-series.',
|
|
13
|
-
inputSchema: {
|
|
14
|
-
type: 'object',
|
|
15
|
-
properties: {
|
|
16
|
-
strategy_id: { type: 'string', description: 'Strategy UUID' }
|
|
17
|
-
},
|
|
18
|
-
required: ['strategy_id']
|
|
19
|
-
},
|
|
20
|
-
handler: withLogging('get_strategy_performance', async ({ strategy_id }) => {
|
|
21
|
-
const [metrics, snapshots] = await Promise.all([
|
|
22
|
-
apiClient.get(`/performance/strategy/${strategy_id}`),
|
|
23
|
-
apiClient.get(`/performance/strategy/${strategy_id}/snapshots?limit=50`)
|
|
24
|
-
]);
|
|
25
|
-
return JSON.stringify({ metrics, snapshots }, null, 2);
|
|
26
|
-
})
|
|
27
|
-
}
|
|
28
|
-
];
|
|
29
|
-
|
|
30
|
-
module.exports = { performanceTools };
|
package/tools/strategy-tools.js
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MCP Strategy Tools (DEC-2026-046)
|
|
3
|
-
* 5 tools for AI agent strategy management (scope: strategy)
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const apiClient = require('../lib/api-client');
|
|
7
|
-
const { withLogging } = require('../lib/logger');
|
|
8
|
-
|
|
9
|
-
function uid() {
|
|
10
|
-
return process.env.ECOCHAIN_USER_ID || 'me';
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const strategyTools = [
|
|
14
|
-
{
|
|
15
|
-
name: 'list_strategies',
|
|
16
|
-
description: 'List all trading strategies for the user. Returns strategy name, type (lp_yield/dca/btc_hedge), status, mode, and PnL summary.',
|
|
17
|
-
inputSchema: {
|
|
18
|
-
type: 'object',
|
|
19
|
-
properties: {
|
|
20
|
-
status: { type: 'string', description: 'Filter by status', enum: ['draft', 'active', 'paused', 'stopped', 'error'] }
|
|
21
|
-
},
|
|
22
|
-
required: []
|
|
23
|
-
},
|
|
24
|
-
handler: withLogging('list_strategies', async ({ status }) => {
|
|
25
|
-
const params = status ? `?status=${status}` : '';
|
|
26
|
-
const data = await apiClient.get(`/strategies${params}`);
|
|
27
|
-
return JSON.stringify(data, null, 2);
|
|
28
|
-
})
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
name: 'get_strategy',
|
|
32
|
-
description: 'Get detailed info about a trading strategy: parameters, rules, latest PnL snapshot, and recent intents.',
|
|
33
|
-
inputSchema: {
|
|
34
|
-
type: 'object',
|
|
35
|
-
properties: {
|
|
36
|
-
strategy_id: { type: 'string', description: 'Strategy UUID' }
|
|
37
|
-
},
|
|
38
|
-
required: ['strategy_id']
|
|
39
|
-
},
|
|
40
|
-
handler: withLogging('get_strategy', async ({ strategy_id }) => {
|
|
41
|
-
const data = await apiClient.get(`/strategies/${strategy_id}`);
|
|
42
|
-
return JSON.stringify(data, null, 2);
|
|
43
|
-
})
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
name: 'create_strategy',
|
|
47
|
-
description: 'Create a new trading strategy (starts as draft). Types: lp_yield (LP optimization), dca (Dollar Cost Average), btc_hedge (BTC downside protection). Mode: auto (execute automatically) or watch (manual approval for all intents).',
|
|
48
|
-
inputSchema: {
|
|
49
|
-
type: 'object',
|
|
50
|
-
properties: {
|
|
51
|
-
name: { type: 'string', description: 'Strategy name (e.g. "Daily BTC DCA")' },
|
|
52
|
-
type: { type: 'string', description: 'Strategy type', enum: ['lp_yield', 'dca', 'btc_hedge'] },
|
|
53
|
-
parameters: {
|
|
54
|
-
type: 'object',
|
|
55
|
-
description: 'Strategy parameters. DCA: {token_to_buy, spend_token, amount_per_interval, interval_hours, total_budget}. BTC Hedge: {btc_drop_threshold_percent, conversion_step_percent, cooldown_hours}. LP Yield: {pool_id, min_apr_threshold, rebalance_on_il_percent}.'
|
|
56
|
-
},
|
|
57
|
-
mode: { type: 'string', description: 'Execution mode', enum: ['auto', 'watch'] },
|
|
58
|
-
description: { type: 'string', description: 'Optional description' }
|
|
59
|
-
},
|
|
60
|
-
required: ['name', 'type', 'parameters']
|
|
61
|
-
},
|
|
62
|
-
handler: withLogging('create_strategy', async ({ name, type, parameters, mode, description }) => {
|
|
63
|
-
const body = { name, type, parameters };
|
|
64
|
-
if (mode) body.mode = mode;
|
|
65
|
-
if (description) body.description = description;
|
|
66
|
-
const data = await apiClient.post('/strategies', body);
|
|
67
|
-
return JSON.stringify(data, null, 2);
|
|
68
|
-
})
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
name: 'activate_strategy',
|
|
72
|
-
description: 'Activate a draft or paused strategy. Creates default policy rules if none exist and takes a start-of-day PnL snapshot.',
|
|
73
|
-
inputSchema: {
|
|
74
|
-
type: 'object',
|
|
75
|
-
properties: {
|
|
76
|
-
strategy_id: { type: 'string', description: 'Strategy UUID to activate' }
|
|
77
|
-
},
|
|
78
|
-
required: ['strategy_id']
|
|
79
|
-
},
|
|
80
|
-
handler: withLogging('activate_strategy', async ({ strategy_id }) => {
|
|
81
|
-
const data = await apiClient.post(`/strategies/${strategy_id}/activate`);
|
|
82
|
-
return JSON.stringify(data, null, 2);
|
|
83
|
-
})
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
name: 'pause_strategy',
|
|
87
|
-
description: 'Pause an active strategy. Cancels all pending/awaiting/approved intents. Can be resumed later with activate.',
|
|
88
|
-
inputSchema: {
|
|
89
|
-
type: 'object',
|
|
90
|
-
properties: {
|
|
91
|
-
strategy_id: { type: 'string', description: 'Strategy UUID to pause' }
|
|
92
|
-
},
|
|
93
|
-
required: ['strategy_id']
|
|
94
|
-
},
|
|
95
|
-
handler: withLogging('pause_strategy', async ({ strategy_id }) => {
|
|
96
|
-
const data = await apiClient.post(`/strategies/${strategy_id}/pause`);
|
|
97
|
-
return JSON.stringify(data, null, 2);
|
|
98
|
-
})
|
|
99
|
-
}
|
|
100
|
-
];
|
|
101
|
-
|
|
102
|
-
module.exports = { strategyTools };
|