@indigoprotocol/openclaw-indigo 0.1.3 → 0.2.0

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/src/index.ts CHANGED
@@ -1,69 +1,614 @@
1
- /**
2
- * OpenClaw Indigo Plugin
3
- *
4
- * Indigo Protocol integration for the OpenClaw platform.
5
- * Provides CDP management, price alerts, and portfolio tracking
6
- * through Telegram, Discord, and Slack.
7
- */
8
-
9
- export interface OpenClawIndigoConfig {
10
- /** Indigo MCP server endpoint */
11
- mcpEndpoint: string;
12
- /** Enable Telegram bot integration */
13
- telegram?: boolean;
14
- /** Enable Discord bot integration */
15
- discord?: boolean;
16
- /** Enable Slack bot integration */
17
- slack?: boolean;
18
- /** Enable price alert notifications */
19
- priceAlerts?: boolean;
20
- /** Enable portfolio tracking notifications */
21
- portfolioTracking?: boolean;
1
+ import { Type } from "@sinclair/typebox";
2
+ import axios, { type AxiosInstance } from "axios";
3
+ import {
4
+ markdownAdapter,
5
+ formatPrice,
6
+ formatPriceList,
7
+ formatCDPList,
8
+ formatCDPHealth,
9
+ formatStabilityList,
10
+ formatStakingList,
11
+ formatAPRList,
12
+ formatTVL,
13
+ formatError,
14
+ type PriceData,
15
+ type CDPData,
16
+ type CDPHealthData,
17
+ type StabilityAccountData,
18
+ type StakingPositionData,
19
+ type APRData,
20
+ type TVLData,
21
+ type FormatAdapter,
22
+ } from "@indigoprotocol/shared";
23
+
24
+ const DEFAULT_BASE_URL = "https://analytics.indigoprotocol.io/api/v1";
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // Helpers
28
+ // ---------------------------------------------------------------------------
29
+
30
+ function text(value: string) {
31
+ return { content: [{ type: "text" as const, text: value }] };
32
+ }
33
+
34
+ function errText(adapter: FormatAdapter, message: string, code?: string) {
35
+ return text(formatError(adapter, { message, code }));
36
+ }
37
+
38
+ // ---------------------------------------------------------------------------
39
+ // Data mapping helpers — map indexer responses to shared types
40
+ // ---------------------------------------------------------------------------
41
+
42
+ function mapAssetToPrice(asset: Record<string, unknown>): PriceData {
43
+ return {
44
+ asset: String(asset.name ?? asset.asset ?? ""),
45
+ price: Number(asset.price ?? 0),
46
+ change24h: asset.change24h != null ? Number(asset.change24h) : undefined,
47
+ };
48
+ }
49
+
50
+ function mapLoanToCDP(loan: Record<string, unknown>): CDPData {
51
+ return {
52
+ id: String(loan.id ?? loan.nftId ?? ""),
53
+ owner: String(loan.owner ?? ""),
54
+ asset: String(loan.asset ?? loan.iasset ?? ""),
55
+ collateral: Number(loan.collateral ?? loan.adaAmount ?? 0),
56
+ minted: Number(loan.minted ?? loan.mintedAmount ?? 0),
57
+ ratio: Number(loan.ratio ?? loan.collateralRatio ?? 0),
58
+ minRatio: Number(loan.minRatio ?? loan.minimumRatio ?? 0),
59
+ };
22
60
  }
23
61
 
24
- export const PLUGIN_NAME = "openclaw-indigo";
62
+ function mapLoanToCDPHealth(
63
+ loan: Record<string, unknown>,
64
+ currentPrice: number
65
+ ): CDPHealthData {
66
+ const cdp = mapLoanToCDP(loan);
67
+ const ratio = cdp.ratio;
68
+ const healthStatus =
69
+ ratio > cdp.minRatio * 1.5
70
+ ? "healthy"
71
+ : ratio > cdp.minRatio
72
+ ? "warning"
73
+ : "danger";
74
+
75
+ const liquidationPrice =
76
+ cdp.minted > 0 ? (cdp.collateral * currentPrice) / (cdp.minted * (cdp.minRatio / 100)) : 0;
77
+
78
+ return { ...cdp, healthStatus, liquidationPrice, currentPrice };
79
+ }
25
80
 
26
- export const FEATURES = [
27
- "indigo-mcp-integration",
28
- "telegram-bot",
29
- "discord-bot",
30
- "slack-bot",
31
- "cdp-management",
32
- "price-alerts",
33
- "portfolio-tracking",
34
- ] as const;
81
+ function mapStabilityAccount(
82
+ account: Record<string, unknown>
83
+ ): StabilityAccountData {
84
+ return {
85
+ id: String(account.id ?? ""),
86
+ owner: String(account.owner ?? ""),
87
+ deposited: Number(account.deposited ?? account.amount ?? 0),
88
+ asset: String(account.asset ?? ""),
89
+ rewards: Number(account.rewards ?? 0),
90
+ };
91
+ }
35
92
 
36
- export type Feature = (typeof FEATURES)[number];
93
+ function mapStakingPosition(
94
+ pos: Record<string, unknown>
95
+ ): StakingPositionData {
96
+ return {
97
+ id: String(pos.id ?? ""),
98
+ owner: String(pos.owner ?? ""),
99
+ stakedAmount: Number(pos.stakedAmount ?? pos.amount ?? 0),
100
+ rewardsEarned: Number(pos.rewardsEarned ?? pos.rewards ?? 0),
101
+ asset: String(pos.asset ?? "INDY"),
102
+ startDate: String(pos.startDate ?? pos.createdAt ?? ""),
103
+ };
104
+ }
37
105
 
38
- export function createPlugin(config: OpenClawIndigoConfig) {
106
+ function mapAPR(apr: Record<string, unknown>): APRData {
39
107
  return {
40
- name: PLUGIN_NAME,
41
- config,
42
- features: FEATURES,
108
+ pool: String(apr.pool ?? apr.name ?? ""),
109
+ apr: Number(apr.apr ?? 0),
110
+ tvl: Number(apr.tvl ?? 0),
111
+ asset: String(apr.asset ?? ""),
43
112
  };
44
113
  }
45
114
 
46
- // Commands
47
- export {
48
- cdpCommands,
49
- priceCommands,
50
- stakingCommands,
51
- portfolioCommands,
52
- } from "./commands/index.js";
53
-
54
- // Alerts
55
- export {
56
- priceAlerts,
57
- cdpAlerts,
58
- stakingAlerts,
59
- type PriceAlertConfig,
60
- type CDPAlertConfig,
61
- type StakingAlertConfig,
62
- } from "./alerts/index.js";
63
-
64
- // Formatters
65
- export {
66
- formatCDPMessage,
67
- formatPriceMessage,
68
- formatPortfolioMessage,
69
- } from "./formatters/index.js";
115
+ // ---------------------------------------------------------------------------
116
+ // Plugin register function
117
+ // ---------------------------------------------------------------------------
118
+
119
+ export default function register(api: any) {
120
+ const config = api.pluginConfig ?? {};
121
+ const baseURL = (config.indexerUrl as string) || DEFAULT_BASE_URL;
122
+ const defaultWallet = (config.walletAddress as string) || "";
123
+
124
+ const http: AxiosInstance = axios.create({ baseURL, timeout: 15_000 });
125
+ const adapter = markdownAdapter;
126
+
127
+ // =========================================================================
128
+ // Agent Tools (16 read-only)
129
+ // =========================================================================
130
+
131
+ // 1. indigo_assets
132
+ api.registerTool({
133
+ name: "indigo_assets",
134
+ description: "List all Indigo iAssets with current prices",
135
+ parameters: Type.Object({}),
136
+ execute: async () => {
137
+ const { data } = await http.get("/assets/");
138
+ const prices: PriceData[] = (data as Record<string, unknown>[]).map(mapAssetToPrice);
139
+ return text(formatPriceList(adapter, prices));
140
+ },
141
+ });
142
+
143
+ // 2. indigo_asset_price
144
+ api.registerTool({
145
+ name: "indigo_asset_price",
146
+ description: "Get the price of a specific Indigo iAsset",
147
+ parameters: Type.Object({
148
+ asset: Type.String({ description: "iAsset name (e.g. iUSD, iBTC, iETH)" }),
149
+ }),
150
+ execute: async (params: { asset: string }) => {
151
+ const { data } = await http.get("/assets/");
152
+ const assets = data as Record<string, unknown>[];
153
+ const match = assets.find(
154
+ (a) =>
155
+ String(a.name ?? a.asset ?? "").toLowerCase() ===
156
+ params.asset.toLowerCase()
157
+ );
158
+ if (!match) return errText(adapter, `Asset "${params.asset}" not found`);
159
+ return text(formatPrice(adapter, mapAssetToPrice(match)));
160
+ },
161
+ });
162
+
163
+ // 3. indigo_ada_price
164
+ api.registerTool({
165
+ name: "indigo_ada_price",
166
+ description: "Get the current ADA price",
167
+ parameters: Type.Object({}),
168
+ execute: async () => {
169
+ const { data } = await http.post("/ada-price/");
170
+ return text(
171
+ formatPrice(adapter, {
172
+ asset: "ADA",
173
+ price: Number((data as Record<string, unknown>).price ?? 0),
174
+ })
175
+ );
176
+ },
177
+ });
178
+
179
+ // 4. indigo_indy_price
180
+ api.registerTool({
181
+ name: "indigo_indy_price",
182
+ description: "Get the current INDY token price",
183
+ parameters: Type.Object({}),
184
+ execute: async () => {
185
+ const { data } = await http.post("/indy-price/");
186
+ return text(
187
+ formatPrice(adapter, {
188
+ asset: "INDY",
189
+ price: Number((data as Record<string, unknown>).price ?? 0),
190
+ })
191
+ );
192
+ },
193
+ });
194
+
195
+ // 5. indigo_tvl
196
+ api.registerTool({
197
+ name: "indigo_tvl",
198
+ description: "Get the total value locked in Indigo Protocol",
199
+ parameters: Type.Object({}),
200
+ execute: async () => {
201
+ const { data } = await http.get("/analytics/tvl");
202
+ const tvl: TVLData = {
203
+ protocol: "Indigo Protocol",
204
+ tvl: Number((data as Record<string, unknown>).tvl ?? 0),
205
+ change24h:
206
+ (data as Record<string, unknown>).change24h != null
207
+ ? Number((data as Record<string, unknown>).change24h)
208
+ : undefined,
209
+ breakdown: (data as Record<string, unknown>).breakdown as
210
+ | Record<string, number>
211
+ | undefined,
212
+ };
213
+ return text(formatTVL(adapter, tvl));
214
+ },
215
+ });
216
+
217
+ // 6. indigo_protocol_stats
218
+ api.registerTool({
219
+ name: "indigo_protocol_stats",
220
+ description: "Get Indigo Protocol statistics (TVL, assets, CDPs)",
221
+ parameters: Type.Object({}),
222
+ execute: async () => {
223
+ const [tvlRes, assetsRes, loansRes] = await Promise.all([
224
+ http.get("/analytics/tvl"),
225
+ http.get("/assets/"),
226
+ http.get("/loans/"),
227
+ ]);
228
+
229
+ const tvl = Number((tvlRes.data as Record<string, unknown>).tvl ?? 0);
230
+ const assets = (assetsRes.data as Record<string, unknown>[]).length;
231
+ const cdps = (loansRes.data as Record<string, unknown>[]).length;
232
+
233
+ const lines = [
234
+ adapter.header("Indigo Protocol Stats"),
235
+ adapter.keyValue("TVL", `$${tvl.toLocaleString()}`),
236
+ adapter.keyValue("iAssets", String(assets)),
237
+ adapter.keyValue("Active CDPs", String(cdps)),
238
+ ];
239
+ return text(lines.join("\n"));
240
+ },
241
+ });
242
+
243
+ // 7. indigo_apr_rewards
244
+ api.registerTool({
245
+ name: "indigo_apr_rewards",
246
+ description: "Get current APR rewards for Indigo pools",
247
+ parameters: Type.Object({}),
248
+ execute: async () => {
249
+ const { data } = await http.get("/apr/");
250
+ const aprs: APRData[] = (data as Record<string, unknown>[]).map(mapAPR);
251
+ return text(formatAPRList(adapter, aprs));
252
+ },
253
+ });
254
+
255
+ // 8. indigo_dex_yields
256
+ api.registerTool({
257
+ name: "indigo_dex_yields",
258
+ description: "Get DEX yield farming opportunities for Indigo iAssets",
259
+ parameters: Type.Object({}),
260
+ execute: async () => {
261
+ const { data } = await http.get("/dex/yields");
262
+ return text(adapter.codeBlock(JSON.stringify(data, null, 2)));
263
+ },
264
+ });
265
+
266
+ // 9. indigo_cdps
267
+ api.registerTool({
268
+ name: "indigo_cdps",
269
+ description: "List CDPs, optionally filtered by owner address or asset",
270
+ parameters: Type.Object({
271
+ owner: Type.Optional(
272
+ Type.String({ description: "Owner wallet address to filter by" })
273
+ ),
274
+ asset: Type.Optional(
275
+ Type.String({ description: "iAsset name to filter by" })
276
+ ),
277
+ }),
278
+ execute: async (params: { owner?: string; asset?: string }) => {
279
+ const query: Record<string, string> = {};
280
+ if (params.owner) query.owner = params.owner;
281
+ if (params.asset) query.asset = params.asset;
282
+ const { data } = await http.get("/loans/", { params: query });
283
+ const cdps: CDPData[] = (data as Record<string, unknown>[]).map(mapLoanToCDP);
284
+ return text(formatCDPList(adapter, cdps));
285
+ },
286
+ });
287
+
288
+ // 10. indigo_cdp_health
289
+ api.registerTool({
290
+ name: "indigo_cdp_health",
291
+ description:
292
+ "Analyze health of CDPs for a given owner — shows collateral ratio, liquidation risk",
293
+ parameters: Type.Object({
294
+ owner: Type.String({ description: "Owner wallet address" }),
295
+ }),
296
+ execute: async (params: { owner: string }) => {
297
+ const [loansRes, assetsRes] = await Promise.all([
298
+ http.get("/loans/", { params: { owner: params.owner } }),
299
+ http.get("/assets/"),
300
+ ]);
301
+
302
+ const loans = loansRes.data as Record<string, unknown>[];
303
+ if (loans.length === 0)
304
+ return errText(adapter, "No CDPs found for this address");
305
+
306
+ const assets = assetsRes.data as Record<string, unknown>[];
307
+ const priceMap = new Map<string, number>();
308
+ for (const a of assets) {
309
+ priceMap.set(
310
+ String(a.name ?? a.asset ?? "").toLowerCase(),
311
+ Number(a.price ?? 0)
312
+ );
313
+ }
314
+
315
+ const healthItems: CDPHealthData[] = loans.map((loan) => {
316
+ const assetName = String(loan.asset ?? loan.iasset ?? "").toLowerCase();
317
+ const currentPrice = priceMap.get(assetName) ?? 0;
318
+ return mapLoanToCDPHealth(loan, currentPrice);
319
+ });
320
+
321
+ const lines = healthItems.map((h) => formatCDPHealth(adapter, h));
322
+ return text(lines.join(`\n${adapter.divider()}\n`));
323
+ },
324
+ });
325
+
326
+ // 11. indigo_stability_pools
327
+ api.registerTool({
328
+ name: "indigo_stability_pools",
329
+ description: "Get Indigo stability pool information",
330
+ parameters: Type.Object({}),
331
+ execute: async () => {
332
+ const { data } = await http.get("/stability-pools/");
333
+ const accounts: StabilityAccountData[] = (
334
+ data as Record<string, unknown>[]
335
+ ).map(mapStabilityAccount);
336
+ return text(formatStabilityList(adapter, accounts));
337
+ },
338
+ });
339
+
340
+ // 12. indigo_staking_info
341
+ api.registerTool({
342
+ name: "indigo_staking_info",
343
+ description: "Get INDY staking overview and statistics",
344
+ parameters: Type.Object({}),
345
+ execute: async () => {
346
+ const { data } = await http.get("/staking/");
347
+ return text(adapter.codeBlock(JSON.stringify(data, null, 2)));
348
+ },
349
+ });
350
+
351
+ // 13. indigo_staking_positions
352
+ api.registerTool({
353
+ name: "indigo_staking_positions",
354
+ description: "List INDY staking positions, optionally filtered by owner",
355
+ parameters: Type.Object({
356
+ owner: Type.Optional(
357
+ Type.String({ description: "Owner wallet address to filter by" })
358
+ ),
359
+ }),
360
+ execute: async (params: { owner?: string }) => {
361
+ const query: Record<string, string> = {};
362
+ if (params.owner) query.owner = params.owner;
363
+ const { data } = await http.get("/staking-positions/", {
364
+ params: query,
365
+ });
366
+ const positions: StakingPositionData[] = (
367
+ data as Record<string, unknown>[]
368
+ ).map(mapStakingPosition);
369
+ return text(formatStakingList(adapter, positions));
370
+ },
371
+ });
372
+
373
+ // 14. indigo_polls
374
+ api.registerTool({
375
+ name: "indigo_polls",
376
+ description: "Get current Indigo governance polls",
377
+ parameters: Type.Object({}),
378
+ execute: async () => {
379
+ const { data } = await http.get("/polls/");
380
+ return text(adapter.codeBlock(JSON.stringify(data, null, 2)));
381
+ },
382
+ });
383
+
384
+ // 15. indigo_wallet_balances
385
+ api.registerTool({
386
+ name: "indigo_wallet_balances",
387
+ description: "Get token balances for a Cardano wallet address",
388
+ parameters: Type.Object({
389
+ address: Type.String({ description: "Cardano wallet address" }),
390
+ }),
391
+ execute: async (params: { address: string }) => {
392
+ const { data } = await http.get("/blockfrost/balances", {
393
+ params: { address: params.address },
394
+ });
395
+ return text(adapter.codeBlock(JSON.stringify(data, null, 2)));
396
+ },
397
+ });
398
+
399
+ // 16. indigo_order_book
400
+ api.registerTool({
401
+ name: "indigo_order_book",
402
+ description: "Get the Indigo redemption order book",
403
+ parameters: Type.Object({}),
404
+ execute: async () => {
405
+ const { data } = await http.get("/order-book/");
406
+ return text(adapter.codeBlock(JSON.stringify(data, null, 2)));
407
+ },
408
+ });
409
+
410
+ // =========================================================================
411
+ // Auto-reply Commands (8)
412
+ // =========================================================================
413
+
414
+ // /price <asset>
415
+ api.registerCommand({
416
+ name: "price",
417
+ description: "Get the price of an iAsset (e.g. /price iUSD)",
418
+ parameters: Type.Object({
419
+ asset: Type.String({ description: "Asset name" }),
420
+ }),
421
+ execute: async (params: { asset: string }) => {
422
+ try {
423
+ const assetLower = params.asset.toLowerCase();
424
+
425
+ if (assetLower === "ada") {
426
+ const { data } = await http.post("/ada-price/");
427
+ return {
428
+ text: formatPrice(adapter, {
429
+ asset: "ADA",
430
+ price: Number((data as Record<string, unknown>).price ?? 0),
431
+ }),
432
+ };
433
+ }
434
+
435
+ if (assetLower === "indy") {
436
+ const { data } = await http.post("/indy-price/");
437
+ return {
438
+ text: formatPrice(adapter, {
439
+ asset: "INDY",
440
+ price: Number((data as Record<string, unknown>).price ?? 0),
441
+ }),
442
+ };
443
+ }
444
+
445
+ const { data } = await http.get("/assets/");
446
+ const assets = data as Record<string, unknown>[];
447
+ const match = assets.find(
448
+ (a) =>
449
+ String(a.name ?? a.asset ?? "").toLowerCase() === assetLower
450
+ );
451
+ if (!match)
452
+ return { text: formatError(adapter, { message: `Asset "${params.asset}" not found` }) };
453
+ return { text: formatPrice(adapter, mapAssetToPrice(match)) };
454
+ } catch (e: any) {
455
+ return { text: formatError(adapter, { message: e.message, code: "PRICE_ERROR" }) };
456
+ }
457
+ },
458
+ });
459
+
460
+ // /prices
461
+ api.registerCommand({
462
+ name: "prices",
463
+ description: "Get all iAsset prices",
464
+ execute: async () => {
465
+ try {
466
+ const { data } = await http.get("/assets/");
467
+ const prices: PriceData[] = (data as Record<string, unknown>[]).map(
468
+ mapAssetToPrice
469
+ );
470
+ return { text: formatPriceList(adapter, prices) };
471
+ } catch (e: any) {
472
+ return { text: formatError(adapter, { message: e.message, code: "PRICES_ERROR" }) };
473
+ }
474
+ },
475
+ });
476
+
477
+ // /tvl
478
+ api.registerCommand({
479
+ name: "tvl",
480
+ description: "Show Indigo Protocol total value locked",
481
+ execute: async () => {
482
+ try {
483
+ const { data } = await http.get("/analytics/tvl");
484
+ const tvl: TVLData = {
485
+ protocol: "Indigo Protocol",
486
+ tvl: Number((data as Record<string, unknown>).tvl ?? 0),
487
+ change24h:
488
+ (data as Record<string, unknown>).change24h != null
489
+ ? Number((data as Record<string, unknown>).change24h)
490
+ : undefined,
491
+ breakdown: (data as Record<string, unknown>).breakdown as
492
+ | Record<string, number>
493
+ | undefined,
494
+ };
495
+ return { text: formatTVL(adapter, tvl) };
496
+ } catch (e: any) {
497
+ return { text: formatError(adapter, { message: e.message, code: "TVL_ERROR" }) };
498
+ }
499
+ },
500
+ });
501
+
502
+ // /balance [addr]
503
+ api.registerCommand({
504
+ name: "balance",
505
+ description: "Show wallet token balances (defaults to configured wallet)",
506
+ parameters: Type.Object({
507
+ address: Type.Optional(
508
+ Type.String({ description: "Cardano wallet address" })
509
+ ),
510
+ }),
511
+ execute: async (params: { address?: string }) => {
512
+ try {
513
+ const addr = params.address || defaultWallet;
514
+ if (!addr)
515
+ return {
516
+ text: formatError(adapter, {
517
+ message: "No wallet address provided. Pass an address or set walletAddress in plugin config.",
518
+ }),
519
+ };
520
+ const { data } = await http.get("/blockfrost/balances", {
521
+ params: { address: addr },
522
+ });
523
+ return { text: adapter.codeBlock(JSON.stringify(data, null, 2)) };
524
+ } catch (e: any) {
525
+ return { text: formatError(adapter, { message: e.message, code: "BALANCE_ERROR" }) };
526
+ }
527
+ },
528
+ });
529
+
530
+ // /cdps [owner]
531
+ api.registerCommand({
532
+ name: "cdps",
533
+ description: "List CDPs (defaults to configured wallet)",
534
+ parameters: Type.Object({
535
+ owner: Type.Optional(
536
+ Type.String({ description: "Owner wallet address" })
537
+ ),
538
+ }),
539
+ execute: async (params: { owner?: string }) => {
540
+ try {
541
+ const owner = params.owner || defaultWallet;
542
+ const query: Record<string, string> = {};
543
+ if (owner) query.owner = owner;
544
+ const { data } = await http.get("/loans/", { params: query });
545
+ const cdps: CDPData[] = (data as Record<string, unknown>[]).map(
546
+ mapLoanToCDP
547
+ );
548
+ return { text: formatCDPList(adapter, cdps) };
549
+ } catch (e: any) {
550
+ return { text: formatError(adapter, { message: e.message, code: "CDPS_ERROR" }) };
551
+ }
552
+ },
553
+ });
554
+
555
+ // /staking
556
+ api.registerCommand({
557
+ name: "staking",
558
+ description: "Show INDY staking overview",
559
+ execute: async () => {
560
+ try {
561
+ const { data } = await http.get("/staking/");
562
+ return { text: adapter.codeBlock(JSON.stringify(data, null, 2)) };
563
+ } catch (e: any) {
564
+ return { text: formatError(adapter, { message: e.message, code: "STAKING_ERROR" }) };
565
+ }
566
+ },
567
+ });
568
+
569
+ // /pools
570
+ api.registerCommand({
571
+ name: "pools",
572
+ description: "Show stability pool information",
573
+ execute: async () => {
574
+ try {
575
+ const { data } = await http.get("/stability-pools/");
576
+ const accounts: StabilityAccountData[] = (
577
+ data as Record<string, unknown>[]
578
+ ).map(mapStabilityAccount);
579
+ return { text: formatStabilityList(adapter, accounts) };
580
+ } catch (e: any) {
581
+ return { text: formatError(adapter, { message: e.message, code: "POOLS_ERROR" }) };
582
+ }
583
+ },
584
+ });
585
+
586
+ // /polls
587
+ api.registerCommand({
588
+ name: "polls",
589
+ description: "Show current governance polls",
590
+ execute: async () => {
591
+ try {
592
+ const { data } = await http.get("/polls/");
593
+ return { text: adapter.codeBlock(JSON.stringify(data, null, 2)) };
594
+ } catch (e: any) {
595
+ return { text: formatError(adapter, { message: e.message, code: "POLLS_ERROR" }) };
596
+ }
597
+ },
598
+ });
599
+
600
+ // =========================================================================
601
+ // Service (placeholder)
602
+ // =========================================================================
603
+
604
+ api.registerService({
605
+ name: "indigo-protocol-monitor",
606
+ description: "Indigo Protocol monitoring service",
607
+ start: async () => {
608
+ console.log("[openclaw-indigo] monitor service started");
609
+ },
610
+ stop: async () => {
611
+ console.log("[openclaw-indigo] monitor service stopped");
612
+ },
613
+ });
614
+ }