@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.
- package/.env.example +37 -0
- package/CHANGELOG.md +19 -0
- package/README.md +199 -0
- package/bin/alf +4 -0
- package/cli/README.md +198 -0
- package/cli/TEST_MANUAL.md +577 -0
- package/cli/commands/account.ts +545 -0
- package/cli/commands/funding.ts +481 -0
- package/cli/commands/liquidation.ts +523 -0
- package/cli/commands/market.ts +590 -0
- package/cli/commands/orders.ts +395 -0
- package/cli/commands/position.ts +1085 -0
- package/cli/commands/shared/positionUtils.ts +239 -0
- package/cli/commands/trading.ts +483 -0
- package/cli/commands/utils.ts +281 -0
- package/cli/commands/vault.ts +522 -0
- package/cli/index.ts +169 -0
- package/cli/interactive.ts +530 -0
- package/cli/utils/client.ts +457 -0
- package/cli/utils/config.ts +226 -0
- package/cli/utils/display.ts +258 -0
- package/cli/utils/index.ts +10 -0
- package/cli/utils/prompts.ts +364 -0
- package/config.example.json +23 -0
- package/dist/AlphaFuturesClient.d.ts +36 -0
- package/dist/AlphaFuturesClient.d.ts.map +1 -0
- package/dist/AlphaFuturesClient.js +116 -0
- package/dist/AlphaFuturesClient.js.map +1 -0
- package/dist/abi/Alpha.json +5987 -0
- package/dist/abi/abis.d.ts +319 -0
- package/dist/abi/abis.d.ts.map +1 -0
- package/dist/abi/abis.js +128 -0
- package/dist/abi/abis.js.map +1 -0
- package/dist/abi/index.d.ts +11 -0
- package/dist/abi/index.d.ts.map +1 -0
- package/dist/abi/index.js +15 -0
- package/dist/abi/index.js.map +1 -0
- package/dist/config/contracts.config.d.ts +70 -0
- package/dist/config/contracts.config.d.ts.map +1 -0
- package/dist/config/contracts.config.js +137 -0
- package/dist/config/contracts.config.js.map +1 -0
- package/dist/config/environments/alpha.config.d.ts +17 -0
- package/dist/config/environments/alpha.config.d.ts.map +1 -0
- package/dist/config/environments/alpha.config.js +140 -0
- package/dist/config/environments/alpha.config.js.map +1 -0
- package/dist/config/environments/beta.config.d.ts +16 -0
- package/dist/config/environments/beta.config.d.ts.map +1 -0
- package/dist/config/environments/beta.config.js +131 -0
- package/dist/config/environments/beta.config.js.map +1 -0
- package/dist/config/environments/dev.config.d.ts +13 -0
- package/dist/config/environments/dev.config.d.ts.map +1 -0
- package/dist/config/environments/dev.config.js +123 -0
- package/dist/config/environments/dev.config.js.map +1 -0
- package/dist/config/environments/index.d.ts +48 -0
- package/dist/config/environments/index.d.ts.map +1 -0
- package/dist/config/environments/index.js +81 -0
- package/dist/config/environments/index.js.map +1 -0
- package/dist/config/environments/localhost.config.d.ts +16 -0
- package/dist/config/environments/localhost.config.d.ts.map +1 -0
- package/dist/config/environments/localhost.config.js +152 -0
- package/dist/config/environments/localhost.config.js.map +1 -0
- package/dist/config/environments/prod.config.d.ts +20 -0
- package/dist/config/environments/prod.config.d.ts.map +1 -0
- package/dist/config/environments/prod.config.js +143 -0
- package/dist/config/environments/prod.config.js.map +1 -0
- package/dist/config/index.d.ts +7 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +41 -0
- package/dist/config/index.js.map +1 -0
- package/dist/constants/assets.d.ts +76 -0
- package/dist/constants/assets.d.ts.map +1 -0
- package/dist/constants/assets.js +277 -0
- package/dist/constants/assets.js.map +1 -0
- package/dist/constants/contracts.d.ts +41 -0
- package/dist/constants/contracts.d.ts.map +1 -0
- package/dist/constants/contracts.js +57 -0
- package/dist/constants/contracts.js.map +1 -0
- package/dist/constants/index.d.ts +36 -0
- package/dist/constants/index.d.ts.map +1 -0
- package/dist/constants/index.js +75 -0
- package/dist/constants/index.js.map +1 -0
- package/dist/constants/networks.d.ts +32 -0
- package/dist/constants/networks.d.ts.map +1 -0
- package/dist/constants/networks.js +174 -0
- package/dist/constants/networks.js.map +1 -0
- package/dist/contracts/index.d.ts +5 -0
- package/dist/contracts/index.d.ts.map +1 -0
- package/dist/contracts/index.js +21 -0
- package/dist/contracts/index.js.map +1 -0
- package/dist/contracts/viem/AlphaViem.d.ts +518 -0
- package/dist/contracts/viem/AlphaViem.d.ts.map +1 -0
- package/dist/contracts/viem/AlphaViem.js +1287 -0
- package/dist/contracts/viem/AlphaViem.js.map +1 -0
- package/dist/contracts/viem/PriceOracleViem.d.ts +71 -0
- package/dist/contracts/viem/PriceOracleViem.d.ts.map +1 -0
- package/dist/contracts/viem/PriceOracleViem.js +212 -0
- package/dist/contracts/viem/PriceOracleViem.js.map +1 -0
- package/dist/contracts/viem/index.d.ts +9 -0
- package/dist/contracts/viem/index.d.ts.map +1 -0
- package/dist/contracts/viem/index.js +17 -0
- package/dist/contracts/viem/index.js.map +1 -0
- package/dist/errors/index.d.ts +44 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +83 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +60 -0
- package/dist/index.js.map +1 -0
- package/dist/types/alpha.d.ts +299 -0
- package/dist/types/alpha.d.ts.map +1 -0
- package/dist/types/alpha.js +6 -0
- package/dist/types/alpha.js.map +1 -0
- package/dist/types/client.d.ts +24 -0
- package/dist/types/client.d.ts.map +1 -0
- package/dist/types/client.js +13 -0
- package/dist/types/client.js.map +1 -0
- package/dist/types/contracts.d.ts +48 -0
- package/dist/types/contracts.d.ts.map +1 -0
- package/dist/types/contracts.js +6 -0
- package/dist/types/contracts.js.map +1 -0
- package/dist/types/funding.d.ts +27 -0
- package/dist/types/funding.d.ts.map +1 -0
- package/dist/types/funding.js +6 -0
- package/dist/types/funding.js.map +1 -0
- package/dist/types/index.d.ts +92 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +47 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/liquidation.d.ts +20 -0
- package/dist/types/liquidation.d.ts.map +1 -0
- package/dist/types/liquidation.js +6 -0
- package/dist/types/liquidation.js.map +1 -0
- package/dist/types/margin.d.ts +29 -0
- package/dist/types/margin.d.ts.map +1 -0
- package/dist/types/margin.js +6 -0
- package/dist/types/margin.js.map +1 -0
- package/dist/types/oracle.d.ts +21 -0
- package/dist/types/oracle.d.ts.map +1 -0
- package/dist/types/oracle.js +6 -0
- package/dist/types/oracle.js.map +1 -0
- package/dist/types/positions.d.ts +43 -0
- package/dist/types/positions.d.ts.map +1 -0
- package/dist/types/positions.js +13 -0
- package/dist/types/positions.js.map +1 -0
- package/dist/utils/calculations.d.ts +84 -0
- package/dist/utils/calculations.d.ts.map +1 -0
- package/dist/utils/calculations.js +155 -0
- package/dist/utils/calculations.js.map +1 -0
- package/dist/utils/errors.d.ts +24 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +129 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/events.d.ts +40 -0
- package/dist/utils/events.d.ts.map +1 -0
- package/dist/utils/events.js +73 -0
- package/dist/utils/events.js.map +1 -0
- package/dist/utils/format.d.ts +40 -0
- package/dist/utils/format.d.ts.map +1 -0
- package/dist/utils/format.js +86 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/utils/index.d.ts +10 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +26 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/network.d.ts +52 -0
- package/dist/utils/network.d.ts.map +1 -0
- package/dist/utils/network.js +192 -0
- package/dist/utils/network.js.map +1 -0
- package/dist/utils/positionCalculations.d.ts +145 -0
- package/dist/utils/positionCalculations.d.ts.map +1 -0
- package/dist/utils/positionCalculations.js +278 -0
- package/dist/utils/positionCalculations.js.map +1 -0
- package/dist/utils/validation.d.ts +28 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +68 -0
- package/dist/utils/validation.js.map +1 -0
- package/docs/README.md +40 -0
- package/docs/api/API.md +831 -0
- package/docs/guides/GETTING_STARTED.md +316 -0
- package/docs/guides/TRADING_GUIDE.md +677 -0
- package/docs/integration/INTEGRATION_GUIDE.md +1679 -0
- package/docs/integration/VIEM_INTEGRATION.md +294 -0
- package/docs/reference/CLI_QUICK_REFERENCE.md +197 -0
- package/docs/reference/TROUBLESHOOTING.md +922 -0
- package/package.json +113 -0
- package/src/AlphaFuturesClient.ts +158 -0
- package/src/abi/.gitkeep +1 -0
- package/src/abi/Alpha.json +5987 -0
- package/src/abi/README.md +99 -0
- package/src/abi/abis.ts +131 -0
- package/src/abi/index.ts +13 -0
- package/src/config/contracts.config.ts +186 -0
- package/src/config/environments/alpha.config.ts +139 -0
- package/src/config/environments/beta.config.ts +130 -0
- package/src/config/environments/dev.config.ts +122 -0
- package/src/config/environments/index.ts +87 -0
- package/src/config/environments/localhost.config.ts +153 -0
- package/src/config/environments/prod.config.ts +142 -0
- package/src/config/index.ts +29 -0
- package/src/constants/assets.ts +299 -0
- package/src/constants/contracts.ts +64 -0
- package/src/constants/index.ts +69 -0
- package/src/constants/networks.ts +182 -0
- package/src/contracts/index.ts +5 -0
- package/src/contracts/viem/AlphaViem.ts +1615 -0
- package/src/contracts/viem/PriceOracleViem.ts +272 -0
- package/src/contracts/viem/index.ts +11 -0
- package/src/errors/index.ts +87 -0
- package/src/index.ts +59 -0
- package/src/types/VIEM_TYPES_README.md +70 -0
- package/src/types/alpha.ts +358 -0
- package/src/types/client.ts +27 -0
- package/src/types/contracts.ts +74 -0
- package/src/types/funding.ts +31 -0
- package/src/types/index.ts +108 -0
- package/src/types/liquidation.ts +23 -0
- package/src/types/margin.ts +34 -0
- package/src/types/oracle.ts +24 -0
- package/src/types/positions.ts +48 -0
- package/src/utils/calculations.ts +175 -0
- package/src/utils/errors.ts +147 -0
- package/src/utils/events.ts +98 -0
- package/src/utils/format.ts +84 -0
- package/src/utils/index.ts +10 -0
- package/src/utils/network.ts +212 -0
- package/src/utils/positionCalculations.ts +317 -0
- 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
|
+
}
|