@darksol/terminal 0.4.0-beta.2 → 0.4.1
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/package.json +1 -1
- package/src/cli.js +1 -1
- package/src/trading/swap.js +56 -4
- package/src/ui/banner.js +3 -2
package/package.json
CHANGED
package/src/cli.js
CHANGED
package/src/trading/swap.js
CHANGED
|
@@ -56,6 +56,18 @@ const SWAP_ROUTER_ABI = [
|
|
|
56
56
|
'function multicall(uint256 deadline, bytes[] data) external payable returns (bytes[])',
|
|
57
57
|
];
|
|
58
58
|
|
|
59
|
+
// Uniswap V3 Quoter V2 ABI (for getting expected output)
|
|
60
|
+
const QUOTER_ABI = [
|
|
61
|
+
'function quoteExactInputSingle((address tokenIn, address tokenOut, uint256 amountIn, uint24 fee, uint160 sqrtPriceLimitX96)) external returns (uint256 amountOut, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed, uint256 gasEstimate)',
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
// Quoter V2 addresses per chain
|
|
65
|
+
const QUOTERS = {
|
|
66
|
+
base: '0x3d4e44Eb1374240CE5F1B871ab261CD16335B76a',
|
|
67
|
+
ethereum: '0x61fFE014bA17989E743c5F6cB21bF9697530B21e',
|
|
68
|
+
arbitrum: '0x61fFE014bA17989E743c5F6cB21bF9697530B21e',
|
|
69
|
+
};
|
|
70
|
+
|
|
59
71
|
// Resolve token symbol to address
|
|
60
72
|
export function resolveToken(symbol, chain) {
|
|
61
73
|
const upper = symbol.toUpperCase();
|
|
@@ -177,7 +189,7 @@ export async function executeSwap(opts = {}) {
|
|
|
177
189
|
return;
|
|
178
190
|
}
|
|
179
191
|
|
|
180
|
-
|
|
192
|
+
let swapSpin = spinner('Executing swap...').start();
|
|
181
193
|
|
|
182
194
|
// Approve if needed (non-native)
|
|
183
195
|
if (!isNativeIn) {
|
|
@@ -191,15 +203,54 @@ export async function executeSwap(opts = {}) {
|
|
|
191
203
|
}
|
|
192
204
|
|
|
193
205
|
// Execute swap
|
|
194
|
-
swapSpin.text = '
|
|
206
|
+
swapSpin.text = 'Getting quote for slippage protection...';
|
|
195
207
|
const swapRouter = new ethers.Contract(router, SWAP_ROUTER_ABI, signer);
|
|
208
|
+
const actualTokenOut = tokenOutAddr === ethers.ZeroAddress ? TOKENS[chain]?.WETH : tokenOutAddr;
|
|
196
209
|
|
|
197
210
|
const deadline = Math.floor(Date.now() / 1000) + 300; // 5 min
|
|
198
|
-
const amountOutMin = 0; // TODO: get quote for proper slippage protection
|
|
199
211
|
|
|
212
|
+
// Get quote from Quoter V2 for proper slippage protection
|
|
213
|
+
let amountOutMin = 0n;
|
|
214
|
+
const quoterAddr = QUOTERS[chain];
|
|
215
|
+
if (quoterAddr) {
|
|
216
|
+
try {
|
|
217
|
+
const quoter = new ethers.Contract(quoterAddr, QUOTER_ABI, provider);
|
|
218
|
+
const quoteResult = await quoter.quoteExactInputSingle.staticCall({
|
|
219
|
+
tokenIn: actualTokenIn,
|
|
220
|
+
tokenOut: actualTokenOut,
|
|
221
|
+
amountIn,
|
|
222
|
+
fee: 3000,
|
|
223
|
+
sqrtPriceLimitX96: 0,
|
|
224
|
+
});
|
|
225
|
+
// staticCall returns [amountOut, sqrtPriceX96After, initializedTicksCrossed, gasEstimate]
|
|
226
|
+
const expectedOut = quoteResult[0];
|
|
227
|
+
// Apply slippage tolerance: minOut = expectedOut * (100 - slippage) / 100
|
|
228
|
+
amountOutMin = (expectedOut * BigInt(Math.floor((100 - maxSlippage) * 100))) / 10000n;
|
|
229
|
+
swapSpin.text = `Quote: ~${ethers.formatUnits(expectedOut, tokenOutInfo.decimals)} ${tokenOutInfo.symbol} (min: ${ethers.formatUnits(amountOutMin, tokenOutInfo.decimals)})`;
|
|
230
|
+
} catch (quoteErr) {
|
|
231
|
+
// If quote fails, warn but allow user to proceed with zero protection
|
|
232
|
+
swapSpin.warn('Could not get quote — no slippage protection');
|
|
233
|
+
warn(`Quote failed: ${quoteErr.message}`);
|
|
234
|
+
const { proceedAnyway } = await inquirer.prompt([{
|
|
235
|
+
type: 'confirm',
|
|
236
|
+
name: 'proceedAnyway',
|
|
237
|
+
message: theme.accent('Proceed WITHOUT slippage protection? (risky)'),
|
|
238
|
+
default: false,
|
|
239
|
+
}]);
|
|
240
|
+
if (!proceedAnyway) {
|
|
241
|
+
warn('Swap cancelled — no slippage protection available');
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
swapSpin = spinner('Sending swap transaction...').start();
|
|
245
|
+
}
|
|
246
|
+
} else {
|
|
247
|
+
warn(`No Quoter available for ${chain} — swap will have no slippage protection`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
swapSpin.text = 'Sending swap transaction...';
|
|
200
251
|
const swapParams = {
|
|
201
252
|
tokenIn: actualTokenIn,
|
|
202
|
-
tokenOut:
|
|
253
|
+
tokenOut: actualTokenOut,
|
|
203
254
|
fee: 3000, // 0.3% fee tier
|
|
204
255
|
recipient: address,
|
|
205
256
|
deadline,
|
|
@@ -222,6 +273,7 @@ export async function executeSwap(opts = {}) {
|
|
|
222
273
|
['TX Hash', receipt.hash],
|
|
223
274
|
['Block', receipt.blockNumber.toString()],
|
|
224
275
|
['Gas Used', receipt.gasUsed.toString()],
|
|
276
|
+
['Min Output', amountOutMin > 0n ? `${ethers.formatUnits(amountOutMin, tokenOutInfo.decimals)} ${tokenOutInfo.symbol}` : theme.error('None (unprotected)')],
|
|
225
277
|
['Status', receipt.status === 1 ? theme.success('Success') : theme.error('Failed')],
|
|
226
278
|
]);
|
|
227
279
|
console.log('');
|
package/src/ui/banner.js
CHANGED
|
@@ -26,7 +26,7 @@ export function showBanner(opts = {}) {
|
|
|
26
26
|
);
|
|
27
27
|
console.log(
|
|
28
28
|
theme.dim(' ║ ') +
|
|
29
|
-
theme.subtle(' v0.4.0
|
|
29
|
+
theme.subtle(' v0.4.0') +
|
|
30
30
|
theme.dim(' ') +
|
|
31
31
|
theme.gold('🌑') +
|
|
32
32
|
theme.dim(' ║')
|
|
@@ -44,7 +44,7 @@ export function showBanner(opts = {}) {
|
|
|
44
44
|
|
|
45
45
|
export function showMiniBanner() {
|
|
46
46
|
console.log('');
|
|
47
|
-
console.log(theme.gold.bold(' 🌑 DARKSOL TERMINAL') + theme.dim(' v0.4.0
|
|
47
|
+
console.log(theme.gold.bold(' 🌑 DARKSOL TERMINAL') + theme.dim(' v0.4.0'));
|
|
48
48
|
console.log(theme.dim(' ─────────────────────────────'));
|
|
49
49
|
console.log('');
|
|
50
50
|
}
|
|
@@ -71,3 +71,4 @@ export function showDivider() {
|
|
|
71
71
|
|
|
72
72
|
|
|
73
73
|
|
|
74
|
+
|