@megatao/sdk 1.1.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.
Files changed (228) hide show
  1. package/.env.example +37 -0
  2. package/CHANGELOG.md +19 -0
  3. package/README.md +199 -0
  4. package/bin/alf +4 -0
  5. package/cli/README.md +198 -0
  6. package/cli/TEST_MANUAL.md +577 -0
  7. package/cli/commands/account.ts +545 -0
  8. package/cli/commands/funding.ts +481 -0
  9. package/cli/commands/liquidation.ts +523 -0
  10. package/cli/commands/market.ts +590 -0
  11. package/cli/commands/orders.ts +395 -0
  12. package/cli/commands/position.ts +1085 -0
  13. package/cli/commands/shared/positionUtils.ts +239 -0
  14. package/cli/commands/trading.ts +483 -0
  15. package/cli/commands/utils.ts +281 -0
  16. package/cli/commands/vault.ts +522 -0
  17. package/cli/index.ts +169 -0
  18. package/cli/interactive.ts +530 -0
  19. package/cli/utils/client.ts +457 -0
  20. package/cli/utils/config.ts +226 -0
  21. package/cli/utils/display.ts +258 -0
  22. package/cli/utils/index.ts +10 -0
  23. package/cli/utils/prompts.ts +364 -0
  24. package/config.example.json +23 -0
  25. package/dist/AlphaFuturesClient.d.ts +36 -0
  26. package/dist/AlphaFuturesClient.d.ts.map +1 -0
  27. package/dist/AlphaFuturesClient.js +116 -0
  28. package/dist/AlphaFuturesClient.js.map +1 -0
  29. package/dist/abi/Alpha.json +5987 -0
  30. package/dist/abi/abis.d.ts +319 -0
  31. package/dist/abi/abis.d.ts.map +1 -0
  32. package/dist/abi/abis.js +128 -0
  33. package/dist/abi/abis.js.map +1 -0
  34. package/dist/abi/index.d.ts +11 -0
  35. package/dist/abi/index.d.ts.map +1 -0
  36. package/dist/abi/index.js +15 -0
  37. package/dist/abi/index.js.map +1 -0
  38. package/dist/config/contracts.config.d.ts +70 -0
  39. package/dist/config/contracts.config.d.ts.map +1 -0
  40. package/dist/config/contracts.config.js +137 -0
  41. package/dist/config/contracts.config.js.map +1 -0
  42. package/dist/config/environments/alpha.config.d.ts +17 -0
  43. package/dist/config/environments/alpha.config.d.ts.map +1 -0
  44. package/dist/config/environments/alpha.config.js +140 -0
  45. package/dist/config/environments/alpha.config.js.map +1 -0
  46. package/dist/config/environments/beta.config.d.ts +16 -0
  47. package/dist/config/environments/beta.config.d.ts.map +1 -0
  48. package/dist/config/environments/beta.config.js +131 -0
  49. package/dist/config/environments/beta.config.js.map +1 -0
  50. package/dist/config/environments/dev.config.d.ts +13 -0
  51. package/dist/config/environments/dev.config.d.ts.map +1 -0
  52. package/dist/config/environments/dev.config.js +123 -0
  53. package/dist/config/environments/dev.config.js.map +1 -0
  54. package/dist/config/environments/index.d.ts +48 -0
  55. package/dist/config/environments/index.d.ts.map +1 -0
  56. package/dist/config/environments/index.js +81 -0
  57. package/dist/config/environments/index.js.map +1 -0
  58. package/dist/config/environments/localhost.config.d.ts +16 -0
  59. package/dist/config/environments/localhost.config.d.ts.map +1 -0
  60. package/dist/config/environments/localhost.config.js +152 -0
  61. package/dist/config/environments/localhost.config.js.map +1 -0
  62. package/dist/config/environments/prod.config.d.ts +20 -0
  63. package/dist/config/environments/prod.config.d.ts.map +1 -0
  64. package/dist/config/environments/prod.config.js +143 -0
  65. package/dist/config/environments/prod.config.js.map +1 -0
  66. package/dist/config/index.d.ts +7 -0
  67. package/dist/config/index.d.ts.map +1 -0
  68. package/dist/config/index.js +41 -0
  69. package/dist/config/index.js.map +1 -0
  70. package/dist/constants/assets.d.ts +76 -0
  71. package/dist/constants/assets.d.ts.map +1 -0
  72. package/dist/constants/assets.js +277 -0
  73. package/dist/constants/assets.js.map +1 -0
  74. package/dist/constants/contracts.d.ts +41 -0
  75. package/dist/constants/contracts.d.ts.map +1 -0
  76. package/dist/constants/contracts.js +57 -0
  77. package/dist/constants/contracts.js.map +1 -0
  78. package/dist/constants/index.d.ts +36 -0
  79. package/dist/constants/index.d.ts.map +1 -0
  80. package/dist/constants/index.js +75 -0
  81. package/dist/constants/index.js.map +1 -0
  82. package/dist/constants/networks.d.ts +32 -0
  83. package/dist/constants/networks.d.ts.map +1 -0
  84. package/dist/constants/networks.js +174 -0
  85. package/dist/constants/networks.js.map +1 -0
  86. package/dist/contracts/index.d.ts +5 -0
  87. package/dist/contracts/index.d.ts.map +1 -0
  88. package/dist/contracts/index.js +21 -0
  89. package/dist/contracts/index.js.map +1 -0
  90. package/dist/contracts/viem/AlphaViem.d.ts +518 -0
  91. package/dist/contracts/viem/AlphaViem.d.ts.map +1 -0
  92. package/dist/contracts/viem/AlphaViem.js +1287 -0
  93. package/dist/contracts/viem/AlphaViem.js.map +1 -0
  94. package/dist/contracts/viem/PriceOracleViem.d.ts +71 -0
  95. package/dist/contracts/viem/PriceOracleViem.d.ts.map +1 -0
  96. package/dist/contracts/viem/PriceOracleViem.js +212 -0
  97. package/dist/contracts/viem/PriceOracleViem.js.map +1 -0
  98. package/dist/contracts/viem/index.d.ts +9 -0
  99. package/dist/contracts/viem/index.d.ts.map +1 -0
  100. package/dist/contracts/viem/index.js +17 -0
  101. package/dist/contracts/viem/index.js.map +1 -0
  102. package/dist/errors/index.d.ts +44 -0
  103. package/dist/errors/index.d.ts.map +1 -0
  104. package/dist/errors/index.js +83 -0
  105. package/dist/errors/index.js.map +1 -0
  106. package/dist/index.d.ts +19 -0
  107. package/dist/index.d.ts.map +1 -0
  108. package/dist/index.js +60 -0
  109. package/dist/index.js.map +1 -0
  110. package/dist/types/alpha.d.ts +299 -0
  111. package/dist/types/alpha.d.ts.map +1 -0
  112. package/dist/types/alpha.js +6 -0
  113. package/dist/types/alpha.js.map +1 -0
  114. package/dist/types/client.d.ts +24 -0
  115. package/dist/types/client.d.ts.map +1 -0
  116. package/dist/types/client.js +13 -0
  117. package/dist/types/client.js.map +1 -0
  118. package/dist/types/contracts.d.ts +48 -0
  119. package/dist/types/contracts.d.ts.map +1 -0
  120. package/dist/types/contracts.js +6 -0
  121. package/dist/types/contracts.js.map +1 -0
  122. package/dist/types/funding.d.ts +27 -0
  123. package/dist/types/funding.d.ts.map +1 -0
  124. package/dist/types/funding.js +6 -0
  125. package/dist/types/funding.js.map +1 -0
  126. package/dist/types/index.d.ts +92 -0
  127. package/dist/types/index.d.ts.map +1 -0
  128. package/dist/types/index.js +47 -0
  129. package/dist/types/index.js.map +1 -0
  130. package/dist/types/liquidation.d.ts +20 -0
  131. package/dist/types/liquidation.d.ts.map +1 -0
  132. package/dist/types/liquidation.js +6 -0
  133. package/dist/types/liquidation.js.map +1 -0
  134. package/dist/types/margin.d.ts +29 -0
  135. package/dist/types/margin.d.ts.map +1 -0
  136. package/dist/types/margin.js +6 -0
  137. package/dist/types/margin.js.map +1 -0
  138. package/dist/types/oracle.d.ts +21 -0
  139. package/dist/types/oracle.d.ts.map +1 -0
  140. package/dist/types/oracle.js +6 -0
  141. package/dist/types/oracle.js.map +1 -0
  142. package/dist/types/positions.d.ts +43 -0
  143. package/dist/types/positions.d.ts.map +1 -0
  144. package/dist/types/positions.js +13 -0
  145. package/dist/types/positions.js.map +1 -0
  146. package/dist/utils/calculations.d.ts +84 -0
  147. package/dist/utils/calculations.d.ts.map +1 -0
  148. package/dist/utils/calculations.js +155 -0
  149. package/dist/utils/calculations.js.map +1 -0
  150. package/dist/utils/errors.d.ts +24 -0
  151. package/dist/utils/errors.d.ts.map +1 -0
  152. package/dist/utils/errors.js +129 -0
  153. package/dist/utils/errors.js.map +1 -0
  154. package/dist/utils/events.d.ts +40 -0
  155. package/dist/utils/events.d.ts.map +1 -0
  156. package/dist/utils/events.js +73 -0
  157. package/dist/utils/events.js.map +1 -0
  158. package/dist/utils/format.d.ts +40 -0
  159. package/dist/utils/format.d.ts.map +1 -0
  160. package/dist/utils/format.js +86 -0
  161. package/dist/utils/format.js.map +1 -0
  162. package/dist/utils/index.d.ts +10 -0
  163. package/dist/utils/index.d.ts.map +1 -0
  164. package/dist/utils/index.js +26 -0
  165. package/dist/utils/index.js.map +1 -0
  166. package/dist/utils/network.d.ts +52 -0
  167. package/dist/utils/network.d.ts.map +1 -0
  168. package/dist/utils/network.js +192 -0
  169. package/dist/utils/network.js.map +1 -0
  170. package/dist/utils/positionCalculations.d.ts +145 -0
  171. package/dist/utils/positionCalculations.d.ts.map +1 -0
  172. package/dist/utils/positionCalculations.js +278 -0
  173. package/dist/utils/positionCalculations.js.map +1 -0
  174. package/dist/utils/validation.d.ts +28 -0
  175. package/dist/utils/validation.d.ts.map +1 -0
  176. package/dist/utils/validation.js +68 -0
  177. package/dist/utils/validation.js.map +1 -0
  178. package/docs/README.md +40 -0
  179. package/docs/api/API.md +831 -0
  180. package/docs/guides/GETTING_STARTED.md +316 -0
  181. package/docs/guides/TRADING_GUIDE.md +677 -0
  182. package/docs/integration/INTEGRATION_GUIDE.md +1679 -0
  183. package/docs/integration/VIEM_INTEGRATION.md +294 -0
  184. package/docs/reference/CLI_QUICK_REFERENCE.md +197 -0
  185. package/docs/reference/TROUBLESHOOTING.md +922 -0
  186. package/package.json +113 -0
  187. package/src/AlphaFuturesClient.ts +158 -0
  188. package/src/abi/.gitkeep +1 -0
  189. package/src/abi/Alpha.json +5987 -0
  190. package/src/abi/README.md +99 -0
  191. package/src/abi/abis.ts +131 -0
  192. package/src/abi/index.ts +13 -0
  193. package/src/config/contracts.config.ts +186 -0
  194. package/src/config/environments/alpha.config.ts +139 -0
  195. package/src/config/environments/beta.config.ts +130 -0
  196. package/src/config/environments/dev.config.ts +122 -0
  197. package/src/config/environments/index.ts +87 -0
  198. package/src/config/environments/localhost.config.ts +153 -0
  199. package/src/config/environments/prod.config.ts +142 -0
  200. package/src/config/index.ts +29 -0
  201. package/src/constants/assets.ts +299 -0
  202. package/src/constants/contracts.ts +64 -0
  203. package/src/constants/index.ts +69 -0
  204. package/src/constants/networks.ts +182 -0
  205. package/src/contracts/index.ts +5 -0
  206. package/src/contracts/viem/AlphaViem.ts +1615 -0
  207. package/src/contracts/viem/PriceOracleViem.ts +272 -0
  208. package/src/contracts/viem/index.ts +11 -0
  209. package/src/errors/index.ts +87 -0
  210. package/src/index.ts +59 -0
  211. package/src/types/VIEM_TYPES_README.md +70 -0
  212. package/src/types/alpha.ts +358 -0
  213. package/src/types/client.ts +27 -0
  214. package/src/types/contracts.ts +74 -0
  215. package/src/types/funding.ts +31 -0
  216. package/src/types/index.ts +108 -0
  217. package/src/types/liquidation.ts +23 -0
  218. package/src/types/margin.ts +34 -0
  219. package/src/types/oracle.ts +24 -0
  220. package/src/types/positions.ts +48 -0
  221. package/src/utils/calculations.ts +175 -0
  222. package/src/utils/errors.ts +147 -0
  223. package/src/utils/events.ts +98 -0
  224. package/src/utils/format.ts +84 -0
  225. package/src/utils/index.ts +10 -0
  226. package/src/utils/network.ts +212 -0
  227. package/src/utils/positionCalculations.ts +317 -0
  228. package/src/utils/validation.ts +76 -0
@@ -0,0 +1,523 @@
1
+ /**
2
+ * Liquidation Commands - Monitor and execute liquidations
3
+ */
4
+
5
+ import { Command } from 'commander';
6
+ // No ethers import needed
7
+ import chalk from 'chalk';
8
+ import ora from 'ora';
9
+ import Table from 'cli-table3';
10
+ import { AlphaFuturesClient } from '../../src';
11
+ import { formatUSD, formatTAO, formatPercentage } from '../../src/utils';
12
+ import {
13
+ getClient,
14
+ handleError,
15
+ getRecentEvents,
16
+ getEventsInChunks,
17
+ getEventsOptimized,
18
+ } from '../utils/client';
19
+ import { confirmAction } from '../utils/prompts';
20
+
21
+ export function liquidationCommands(program: Command) {
22
+ const liquidation = program
23
+ .command('liquidation')
24
+ .alias('liq')
25
+ .description('Monitor and execute liquidations');
26
+
27
+ // Check liquidatable positions
28
+ liquidation
29
+ .command('check')
30
+ .description('Check for liquidatable positions')
31
+ .option('-a, --asset <asset>', 'Filter by asset')
32
+ .option('-l, --limit <number>', 'Maximum positions to check', '50')
33
+ .option('-t, --threshold <percent>', 'Warning threshold (%)', '30')
34
+ .action(async (options) => {
35
+ const spinner = ora('Scanning for liquidatable positions...').start();
36
+
37
+ try {
38
+ const client = await getClient(program.opts());
39
+ const alpha = client.getAlpha();
40
+ const threshold = parseFloat(options.threshold);
41
+
42
+ // Get recent position opened events to find positions to check
43
+ spinner.text = 'Loading positions...';
44
+
45
+ const liquidatablePositions: any[] = [];
46
+ const atRiskPositions: any[] = [];
47
+
48
+ // Get recent position events to scan (last 10k blocks to avoid timeout)
49
+ const publicClient = client.getPublicClient();
50
+ const { ABIs } = await import('../../dist/abi/abis.js');
51
+ const positionEvents = await getRecentEvents(publicClient, {
52
+ address: alpha.getContractAddress(),
53
+ abi: ABIs.Alpha,
54
+ eventName: 'PositionOpened',
55
+ });
56
+ const limit = parseInt(options.limit);
57
+ const recentEvents = positionEvents.slice(-limit);
58
+
59
+ spinner.text = 'Checking liquidation status...';
60
+
61
+ for (const event of recentEvents) {
62
+ try {
63
+ const positionId = event.args?.positionId;
64
+ if (!positionId) continue;
65
+
66
+ // Check if position is still active
67
+ const position = await alpha.getPosition(positionId);
68
+ if (position.notionalValue === 0n) continue; // Position closed
69
+
70
+ // Check liquidation status
71
+ const liquidationStatus = await alpha.getLiquidationStatus(positionId);
72
+ const marginRatioBps = liquidationStatus.marginRatio;
73
+ const thresholdBps = BigInt(threshold * 100); // Convert % to basis points
74
+
75
+ if (liquidationStatus.isLiquidatable) {
76
+ liquidatablePositions.push({
77
+ id: positionId,
78
+ trader: event.args?.trader,
79
+ market: event.args?.market,
80
+ size: position.notionalValue,
81
+ margin: position.margin,
82
+ marginRatio: marginRatioBps,
83
+ isLong: position.isLong,
84
+ });
85
+ } else if (marginRatioBps < thresholdBps) {
86
+ atRiskPositions.push({
87
+ id: positionId,
88
+ trader: event.args?.trader,
89
+ market: event.args?.market,
90
+ size: position.notionalValue,
91
+ marginRatio: marginRatioBps,
92
+ isLong: position.isLong,
93
+ });
94
+ }
95
+ } catch (error) {
96
+ // Skip positions that can't be checked (might be closed)
97
+ continue;
98
+ }
99
+ }
100
+
101
+ spinner.stop();
102
+
103
+ if (liquidatablePositions.length === 0 && atRiskPositions.length === 0) {
104
+ console.log(chalk.green('\nāœ“ No positions at risk of liquidation'));
105
+ return;
106
+ }
107
+
108
+ if (liquidatablePositions.length > 0) {
109
+ console.log(
110
+ chalk.red.bold(`\nāš ļø ${liquidatablePositions.length} Liquidatable Positions:`),
111
+ );
112
+
113
+ const table = new Table({
114
+ head: ['Position ID', 'Trader', 'Asset', 'Size', 'Margin Ratio', 'Reward'],
115
+ colWidths: [12, 20, 10, 15, 15, 15],
116
+ });
117
+
118
+ for (const pos of liquidatablePositions) {
119
+ const reward = (pos.margin * 5n) / 100n; // 5% liquidation bonus
120
+ table.push([
121
+ '#' + pos.id,
122
+ pos.trader.slice(0, 10) + '...',
123
+ pos.asset,
124
+ formatUSD(pos.size),
125
+ chalk.red(formatPercentage(pos.marginRatio)),
126
+ chalk.green(formatTAO(reward)),
127
+ ]);
128
+ }
129
+
130
+ console.log(table.toString());
131
+ }
132
+
133
+ if (atRiskPositions.length > 0) {
134
+ console.log(
135
+ chalk.yellow.bold(
136
+ `\n⚔ ${atRiskPositions.length} Positions At Risk (< ${threshold}% margin):`,
137
+ ),
138
+ );
139
+
140
+ const table = new Table({
141
+ head: ['Position ID', 'Asset', 'Direction', 'Margin Ratio', 'Distance to Liq'],
142
+ colWidths: [12, 10, 12, 15, 18],
143
+ });
144
+
145
+ for (const pos of atRiskPositions) {
146
+ const distance = pos.marginRatio - 2000; // 20% maintenance margin
147
+ table.push([
148
+ '#' + pos.id,
149
+ pos.asset,
150
+ pos.isLong ? chalk.green('LONG') : chalk.red('SHORT'),
151
+ chalk.yellow(formatPercentage(pos.marginRatio)),
152
+ chalk.orange(formatPercentage(distance)),
153
+ ]);
154
+ }
155
+
156
+ console.log(table.toString());
157
+ }
158
+
159
+ console.log(
160
+ chalk.gray(
161
+ '\nšŸ’” Tip: Use "alpha-futures liquidation execute <id>" to liquidate a position',
162
+ ),
163
+ );
164
+ } catch (error) {
165
+ spinner.fail('Failed to check liquidations');
166
+ handleError(error, program.opts());
167
+ }
168
+ });
169
+
170
+ // Execute liquidation
171
+ liquidation
172
+ .command('execute <positionId>')
173
+ .description('Execute a liquidation')
174
+ .option('-y, --yes', 'Skip confirmation')
175
+ .action(async (positionId, options) => {
176
+ const spinner = ora('Checking position...').start();
177
+
178
+ try {
179
+ const client = await getClient(program.opts());
180
+
181
+ // Check if position is liquidatable
182
+ const alpha = client.getAlpha();
183
+ const canLiquidate = await alpha.isLiquidatable(positionId);
184
+
185
+ if (!canLiquidate) {
186
+ spinner.fail('Position is not liquidatable');
187
+ return;
188
+ }
189
+
190
+ // Get position details
191
+ const position = await alpha.getPosition(positionId);
192
+ const liquidationStatus = await alpha.getLiquidationStatus(positionId);
193
+
194
+ spinner.stop();
195
+
196
+ // Calculate liquidation reward
197
+ const liquidationBonus = (position.margin * 5n) / 100n; // 5% of margin
198
+
199
+ console.log(chalk.bold('\nšŸ”Ø Liquidation Details:'));
200
+ console.log(` Position: #${positionId}`);
201
+ console.log(` Direction: ${position.isLong ? chalk.green('LONG') : chalk.red('SHORT')}`);
202
+ console.log(` Size: ${formatUSD(position.notionalValue)}`);
203
+ console.log(` Margin: ${formatTAO(position.margin)} TAO`);
204
+ console.log(` Entry Price: ${formatUSD(position.entryPrice)}`);
205
+ console.log(` Liquidation Price: ${formatUSD(liquidationStatus.liquidationPrice)}`);
206
+ console.log(` Margin Ratio: ${formatPercentage(liquidationStatus.marginRatio)}`);
207
+ console.log(
208
+ chalk.bold(` Liquidation Reward: ${chalk.green(formatTAO(liquidationBonus) + ' TAO')}`),
209
+ );
210
+
211
+ // Confirm liquidation
212
+ if (!options.yes) {
213
+ const confirmed = await confirmAction('Execute liquidation?');
214
+ if (!confirmed) {
215
+ console.log(chalk.yellow('Liquidation cancelled'));
216
+ return;
217
+ }
218
+ }
219
+
220
+ spinner.start('Executing liquidation...');
221
+ const hash = await alpha.liquidatePosition(positionId, { waitForConfirmation: false });
222
+
223
+ spinner.text = 'Waiting for confirmation...';
224
+ const receipt = await alpha.waitForTransaction(hash);
225
+
226
+ spinner.succeed('Liquidation executed successfully!');
227
+ console.log(chalk.gray(` Transaction: ${receipt.transactionHash}`));
228
+ console.log(chalk.gray(` Gas used: ${receipt.gasUsed.toString()}`));
229
+
230
+ // Parse liquidation event from receipt
231
+ try {
232
+ const liquidationEvent = receipt.logs.find((log: any) => {
233
+ try {
234
+ // Check if this log is from our contract and matches PositionLiquidated event
235
+ return (
236
+ log.address.toLowerCase() === alpha.getContractAddress().toLowerCase() &&
237
+ log.topics.length >= 3
238
+ ); // PositionLiquidated has 2 indexed parameters + event signature
239
+ } catch {
240
+ return false;
241
+ }
242
+ });
243
+
244
+ if (liquidationEvent) {
245
+ // The keeperReward is in the log data, we can extract it
246
+ // For now, just show that liquidation was successful
247
+ console.log(
248
+ chalk.green(
249
+ `\nāœ“ Position liquidated successfully! Check your balance for the liquidation reward.`,
250
+ ),
251
+ );
252
+ }
253
+ } catch (error) {
254
+ console.log(
255
+ chalk.green(
256
+ `\nāœ“ Position liquidated successfully! Check your balance for the liquidation reward.`,
257
+ ),
258
+ );
259
+ }
260
+ } catch (error) {
261
+ spinner.fail('Liquidation failed');
262
+ handleError(error, program.opts());
263
+ }
264
+ });
265
+
266
+ // Monitor liquidations
267
+ liquidation
268
+ .command('monitor')
269
+ .description('Monitor positions for liquidation opportunities')
270
+ .option('-i, --interval <seconds>', 'Check interval in seconds', '30')
271
+ .option('-n, --notify', 'Enable desktop notifications')
272
+ .option('-a, --auto', 'Auto-execute liquidations (requires confirmation)')
273
+ .action(async (options) => {
274
+ console.log(chalk.cyan('šŸ” Starting liquidation monitor...'));
275
+ console.log(chalk.gray(`Checking every ${options.interval} seconds\n`));
276
+
277
+ const client = await getClient(program.opts());
278
+ let monitoring = true;
279
+
280
+ // Auto-execution warning
281
+ if (options.auto) {
282
+ console.log(chalk.yellow.bold('āš ļø AUTO-EXECUTION MODE ENABLED'));
283
+ console.log(chalk.yellow('Liquidatable positions will be executed automatically.'));
284
+ const confirmed = await confirmAction('Enable auto-execution?');
285
+ if (!confirmed) {
286
+ options.auto = false;
287
+ console.log(chalk.gray('Auto-execution disabled'));
288
+ }
289
+ }
290
+
291
+ const checkLiquidations = async () => {
292
+ if (!monitoring) return;
293
+
294
+ const spinner = ora('Scanning positions...').start();
295
+
296
+ try {
297
+ // Scan for liquidatable positions using same logic as check command
298
+ const alpha = client.getAlpha();
299
+ const publicClient = client.getPublicClient();
300
+ const { ABIs } = await import('../../dist/abi/abis.js');
301
+ const positionEvents = await getRecentEvents(publicClient, {
302
+ address: alpha.getContractAddress(),
303
+ abi: ABIs.Alpha,
304
+ eventName: 'PositionOpened',
305
+ });
306
+ const recentEvents = positionEvents.slice(-20); // Check last 20 positions
307
+
308
+ let foundLiquidatable = false;
309
+
310
+ for (const event of recentEvents) {
311
+ try {
312
+ const positionId = event.args?.positionId;
313
+ if (!positionId) continue;
314
+
315
+ const position = await alpha.getPosition(positionId);
316
+ if (position.notionalValue === 0n) continue;
317
+
318
+ const isLiquidatable = await alpha.isLiquidatable(positionId);
319
+ if (isLiquidatable) {
320
+ foundLiquidatable = true;
321
+ console.log(chalk.red.bold(`\n🚨 Liquidation opportunity found!`));
322
+ console.log(`Position ID: ${positionId}`);
323
+ console.log(`Size: ${formatUSD(position.notionalValue)}`);
324
+
325
+ if (options.notify) {
326
+ console.log(chalk.blue('šŸ“¬ Desktop notification sent'));
327
+ }
328
+
329
+ if (options.auto) {
330
+ console.log(chalk.yellow('šŸ¤– Auto-executing liquidation...'));
331
+ try {
332
+ const hash = await alpha.liquidatePosition(positionId);
333
+ console.log(chalk.green(`āœ“ Liquidation executed: ${hash}`));
334
+ } catch (error) {
335
+ console.log(chalk.red(`āŒ Auto-liquidation failed: ${error.message}`));
336
+ }
337
+ }
338
+ break;
339
+ }
340
+ } catch (error) {
341
+ continue;
342
+ }
343
+ }
344
+
345
+ spinner.stop();
346
+ const found = foundLiquidatable;
347
+
348
+ if (found) {
349
+ console.log(chalk.red.bold('\n🚨 Liquidation opportunity found!'));
350
+ // Display position details
351
+
352
+ if (options.notify) {
353
+ // Desktop notification (requires additional package)
354
+ console.log(chalk.blue('šŸ“¬ Desktop notification sent'));
355
+ }
356
+
357
+ if (options.auto) {
358
+ console.log(chalk.yellow('šŸ¤– Auto-executing liquidation...'));
359
+ // Execute liquidation
360
+ }
361
+ } else {
362
+ // Clear line and show status
363
+ process.stdout.write(
364
+ '\r' +
365
+ chalk.gray(
366
+ `Last check: ${new Date().toLocaleTimeString()} - No liquidations found`,
367
+ ),
368
+ );
369
+ }
370
+ } catch (error) {
371
+ spinner.fail('Monitor check failed');
372
+ console.error(chalk.red(error.message));
373
+ }
374
+ };
375
+
376
+ // Initial check
377
+ await checkLiquidations();
378
+
379
+ // Set up interval
380
+ const intervalMs = parseInt(options.interval) * 1000;
381
+ const intervalId = setInterval(checkLiquidations, intervalMs);
382
+
383
+ // Handle exit
384
+ process.on('SIGINT', () => {
385
+ monitoring = false;
386
+ clearInterval(intervalId);
387
+ console.log(chalk.yellow('\n\nMonitoring stopped'));
388
+ process.exit(0);
389
+ });
390
+
391
+ console.log(chalk.gray('\nPress Ctrl+C to stop monitoring'));
392
+ });
393
+
394
+ // Liquidation history
395
+ liquidation
396
+ .command('history')
397
+ .description('View liquidation history')
398
+ .option('-l, --limit <number>', 'Number of liquidations to show', '20')
399
+ .option('-u, --user <address>', 'Filter by liquidator address')
400
+ .option('-t, --trader <address>', 'Filter by trader address')
401
+ .action(async (options) => {
402
+ const spinner = ora('Loading liquidation history...').start();
403
+
404
+ try {
405
+ const client = await getClient(program.opts());
406
+
407
+ // Query liquidation events from Alpha contract with chunking to avoid timeouts
408
+ const alpha = client.getAlpha();
409
+ const { ABIs } = await import('../../dist/abi/abis.js');
410
+ const publicClient = client.getPublicClient();
411
+
412
+ const events = await getEventsOptimized(
413
+ publicClient,
414
+ {
415
+ address: alpha.getContractAddress(),
416
+ abi: ABIs.Alpha,
417
+ eventName: 'PositionLiquidated',
418
+ },
419
+ 'earliest',
420
+ 'latest',
421
+ parseInt(options.limit) * 2,
422
+ ); // Get more than needed
423
+
424
+ spinner.stop();
425
+
426
+ if (events.length === 0) {
427
+ console.log(chalk.yellow('\nNo liquidation history found'));
428
+ return;
429
+ }
430
+
431
+ // Filter and limit events
432
+ let filteredEvents = events;
433
+ if (options.user) {
434
+ filteredEvents = events.filter((e: any) => e.args?.liquidator === options.user);
435
+ }
436
+ if (options.trader) {
437
+ // Note: PositionLiquidated event doesn't have trader field, would need to cross-reference with position data
438
+ console.log(
439
+ chalk.yellow('Note: Filtering by trader not available in liquidation events'),
440
+ );
441
+ }
442
+
443
+ const limitedEvents = filteredEvents.slice(-parseInt(options.limit)).reverse();
444
+
445
+ console.log(chalk.bold(`\nšŸ“œ Liquidation History (Last ${limitedEvents.length}):`));
446
+
447
+ const table = new Table({
448
+ head: ['Time', 'Position', 'Size', 'Reward', 'Liquidator'],
449
+ colWidths: [20, 20, 15, 12, 20],
450
+ });
451
+
452
+ for (const event of limitedEvents) {
453
+ const block = await alpha.getPublicClient().getBlock({
454
+ blockNumber: event.blockNumber,
455
+ includeTransactions: false,
456
+ });
457
+ const timestamp = new Date(Number(block.timestamp) * 1000).toLocaleString();
458
+
459
+ table.push([
460
+ timestamp,
461
+ '#' + (event as any).args?.positionId,
462
+ formatUSD((event as any).args?.liquidatedSize || 0n),
463
+ formatTAO((event as any).args?.keeperReward || 0n),
464
+ ((event as any).args?.liquidator as string)?.slice(0, 10) + '...',
465
+ ]);
466
+ }
467
+
468
+ console.log(table.toString());
469
+
470
+ // Calculate stats
471
+ const totalRewards = limitedEvents.reduce(
472
+ (sum, e) => sum + ((e as any).args?.keeperReward || 0n),
473
+ 0n,
474
+ );
475
+
476
+ console.log(chalk.bold('\nšŸ“Š Statistics:'));
477
+ console.log(` Total Liquidations: ${limitedEvents.length}`);
478
+ console.log(` Total Rewards Earned: ${formatTAO(totalRewards)} TAO`);
479
+ console.log(
480
+ ` Average Reward: ${formatTAO(totalRewards / BigInt(limitedEvents.length || 1))} TAO`,
481
+ );
482
+ } catch (error) {
483
+ spinner.fail('Failed to load history');
484
+ handleError(error, program.opts());
485
+ }
486
+ });
487
+
488
+ // Liquidation stats
489
+ liquidation
490
+ .command('stats')
491
+ .description('View liquidation statistics')
492
+ .option('-p, --period <period>', 'Time period: 24h, 7d, 30d, all', '7d')
493
+ .action(async (options) => {
494
+ const spinner = ora('Loading liquidation statistics...').start();
495
+
496
+ try {
497
+ const client = await getClient(program.opts());
498
+
499
+ spinner.stop();
500
+
501
+ console.log(chalk.bold('\nšŸ“Š Liquidation Statistics:'));
502
+ console.log(chalk.gray(`Period: ${options.period}`));
503
+
504
+ // TODO: Implement proper time filtering
505
+ console.log(chalk.bold('\nšŸ”Ø Liquidation Metrics:'));
506
+ console.log(` Total Liquidations: ${chalk.gray('Coming soon')}`);
507
+ console.log(` Total Value Liquidated: ${chalk.gray('Coming soon')}`);
508
+ console.log(` Average Position Size: ${chalk.gray('Coming soon')}`);
509
+ console.log(` Total Rewards Paid: ${chalk.gray('Coming soon')}`);
510
+
511
+ console.log(chalk.bold('\nšŸ“ˆ Liquidation Trends:'));
512
+ console.log(` Daily Average: ${chalk.gray('Coming soon')}`);
513
+ console.log(` Peak Hour: ${chalk.gray('Coming soon')}`);
514
+ console.log(` Most Liquidated Asset: ${chalk.gray('Coming soon')}`);
515
+
516
+ console.log(chalk.bold('\nšŸ‘„ Top Liquidators:'));
517
+ console.log(` Coming soon - will show leaderboard`);
518
+ } catch (error) {
519
+ spinner.fail('Failed to load statistics');
520
+ handleError(error, program.opts());
521
+ }
522
+ });
523
+ }