|
6 | 6 |
|
7 | 7 | import chalk from 'chalk'; |
8 | 8 | import Table from 'cli-table3'; |
| 9 | +import type { CrossChainSwapsSimulateResultDto, CrossChainSwapsSimulateErrorDto } from './types.js'; |
| 10 | + |
| 11 | +// ─── Wei / BigInt formatting ────────────────────────────────────────────── |
| 12 | + |
| 13 | +/** Convert wei (10^18) string to USD decimal string */ |
| 14 | +export function formatWeiToUsd(weiStr: string): string { |
| 15 | + try { |
| 16 | + const wei = BigInt(weiStr); |
| 17 | + // Divide by 10^18 (wei to ether conversion) |
| 18 | + const dollars = Number(wei) / 1e18; |
| 19 | + return dollars.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 6 }); |
| 20 | + } catch { |
| 21 | + return weiStr; // fallback to original if parsing fails |
| 22 | + } |
| 23 | +} |
| 24 | + |
| 25 | +/** Convert BigInt amount string using token decimals */ |
| 26 | +export function formatTokenAmount(amountStr: string, decimals: number): string { |
| 27 | + try { |
| 28 | + const amount = BigInt(amountStr); |
| 29 | + const value = Number(amount) / Math.pow(10, decimals); |
| 30 | + return value.toLocaleString('en-US', { maximumFractionDigits: decimals }); |
| 31 | + } catch { |
| 32 | + return amountStr; // fallback to original if parsing fails |
| 33 | + } |
| 34 | +} |
| 35 | + |
| 36 | +/** Check if a string looks like a wei value (large BigInt string) */ |
| 37 | +function isWeiString(value: string): boolean { |
| 38 | + // Wei strings are typically large numbers (18+ digits) |
| 39 | + return /^\d{15,}$/.test(value); |
| 40 | +} |
9 | 41 |
|
10 | 42 | // ─── Raw JSON mode ─────────────────────────────────────────────────────── |
11 | 43 |
|
@@ -70,6 +102,12 @@ export function formatValue(value: unknown, key?: string): string { |
70 | 102 | if (typeof value === 'string') { |
71 | 103 | // Hex addresses — must check before numeric coercion (0x… is valid Number) |
72 | 104 | if (/^0x[0-9a-fA-F]{20,}$/.test(value)) return chalk.yellow(value); |
| 105 | + |
| 106 | + // Wei-format USD fee strings (key contains "FeeInUsd" and value is large BigInt string) |
| 107 | + if (key && /FeeInUsd$/i.test(key) && isWeiString(value)) { |
| 108 | + return `$${formatWeiToUsd(value)}`; |
| 109 | + } |
| 110 | + |
73 | 111 | // Numeric string that looks like a price / amount |
74 | 112 | const num = Number(value); |
75 | 113 | if (!isNaN(num) && value.trim() !== '') { |
@@ -486,6 +524,100 @@ export function printCryptoMetrics(data: Record<string, unknown>): void { |
486 | 524 | } |
487 | 525 | } |
488 | 526 |
|
| 527 | +// ─── Swap Simulation ───────────────────────────────────────────────────── |
| 528 | + |
| 529 | +/** Check if a simulation result is an error */ |
| 530 | +export function isSimulateError( |
| 531 | + item: CrossChainSwapsSimulateResultDto | CrossChainSwapsSimulateErrorDto, |
| 532 | +): item is CrossChainSwapsSimulateErrorDto { |
| 533 | + return 'error' in item; |
| 534 | +} |
| 535 | + |
| 536 | +/** Pretty-print swap simulation result with wei-to-USD conversion */ |
| 537 | +export function printSwapSimulation( |
| 538 | + result: CrossChainSwapsSimulateResultDto | CrossChainSwapsSimulateErrorDto, |
| 539 | +): void { |
| 540 | + if (_rawJson) { |
| 541 | + console.log(JSON.stringify(result, null, 2)); |
| 542 | + return; |
| 543 | + } |
| 544 | + |
| 545 | + // Handle error case |
| 546 | + if (isSimulateError(result)) { |
| 547 | + console.log(chalk.red.bold(` Error: ${result.error}`)); |
| 548 | + if (result.message) { |
| 549 | + console.log(chalk.dim(` Message: ${result.message}`)); |
| 550 | + } |
| 551 | + return; |
| 552 | + } |
| 553 | + |
| 554 | + // Convert wei fees to USD (fees are always in 10^18 format) |
| 555 | + const totalFeeUsd = formatWeiToUsd(result.totalFeeInUsd); |
| 556 | + const gasFeeUsd = formatWeiToUsd(result.gasFeeInUsd); |
| 557 | + const serviceFeeUsd = formatWeiToUsd(result.serviceFeeInUsd); |
| 558 | + const lpFeeUsd = formatWeiToUsd(result.lpFeeInUsd); |
| 559 | + |
| 560 | + // Sanity check: warn if total fee exceeds $1000 |
| 561 | + const totalFeeNum = parseFloat(totalFeeUsd.replace(/,/g, '')); |
| 562 | + const feeWarning = totalFeeNum > 1000 ? chalk.yellow(' ⚠️ (unusually high)') : ''; |
| 563 | + |
| 564 | + // Print header |
| 565 | + console.log(''); |
| 566 | + console.log(chalk.bold(' Simulation Result:')); |
| 567 | + |
| 568 | + // Print token changes (using token.decimals for amount conversion) |
| 569 | + if (result.increased.length > 0) { |
| 570 | + console.log(chalk.green.bold(' Tokens Received:')); |
| 571 | + for (const change of result.increased) { |
| 572 | + const token = change.token; |
| 573 | + const decimals = token.decimals ?? 18; |
| 574 | + const amountFormatted = formatTokenAmount(change.amount, decimals); |
| 575 | + const amountUsd = change.amountInUSD ? formatWeiToUsd(change.amountInUSD) : null; |
| 576 | + |
| 577 | + console.log(` ${chalk.bold(`$${token.symbol}`)} — ${token.name}`); |
| 578 | + console.log(` ${chalk.dim('Address')} : ${chalk.yellow(token.address)}`); |
| 579 | + console.log(` ${chalk.dim('Amount')} : ${amountFormatted}`); |
| 580 | + if (amountUsd) console.log(` ${chalk.dim('Value')} : $${amountUsd}`); |
| 581 | + } |
| 582 | + } |
| 583 | + |
| 584 | + if (result.decreased.length > 0) { |
| 585 | + console.log(chalk.red.bold(' Tokens Spent:')); |
| 586 | + for (const change of result.decreased) { |
| 587 | + const token = change.token; |
| 588 | + const decimals = token.decimals ?? 18; |
| 589 | + const amountFormatted = formatTokenAmount(change.amount, decimals); |
| 590 | + const amountUsd = change.amountInUSD ? formatWeiToUsd(change.amountInUSD) : null; |
| 591 | + |
| 592 | + console.log(` ${chalk.bold(`$${token.symbol}`)} — ${token.name}`); |
| 593 | + console.log(` ${chalk.dim('Address')} : ${chalk.yellow(token.address)}`); |
| 594 | + console.log(` ${chalk.dim('Amount')} : ${amountFormatted}`); |
| 595 | + if (amountUsd) console.log(` ${chalk.dim('Value')} : $${amountUsd}`); |
| 596 | + } |
| 597 | + } |
| 598 | + |
| 599 | + // Print fees |
| 600 | + console.log(''); |
| 601 | + console.log(chalk.bold(' Fees:')); |
| 602 | + console.log(` ${chalk.dim('Total Fee')} : $${totalFeeUsd}${feeWarning}`); |
| 603 | + console.log(` ${chalk.dim('Gas Fee')} : $${gasFeeUsd}`); |
| 604 | + console.log(` ${chalk.dim('Service Fee')} : $${serviceFeeUsd}`); |
| 605 | + console.log(` ${chalk.dim('LP Fee')} : $${lpFeeUsd}`); |
| 606 | + |
| 607 | + // Print other info |
| 608 | + console.log(''); |
| 609 | + console.log(` ${chalk.dim('Slippage')} : ${result.slippageBps} bps`); |
| 610 | + if (result.priceImpact !== null && result.priceImpact !== undefined) { |
| 611 | + // -1 means price impact could not be calculated or is negligible |
| 612 | + const impactStr = String(result.priceImpact).trim(); |
| 613 | + const impactNum = parseFloat(impactStr); |
| 614 | + const impactDisplay = impactNum === -1 || impactStr === '-1' |
| 615 | + ? chalk.dim('— (negligible)') |
| 616 | + : `${impactStr}%`; |
| 617 | + console.log(` ${chalk.dim('Price Impact')} : ${impactDisplay}`); |
| 618 | + } |
| 619 | +} |
| 620 | + |
489 | 621 | // ─── Helpers ───────────────────────────────────────────────────────────── |
490 | 622 |
|
491 | 623 | function truncate(str: string, len: number): string { |
|
0 commit comments