Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,60 @@ miniDex/
└── README.md # This file
```

## 🤝 Contributing

We welcome contributions to the MiniDEX project! Here's how you can help:

### Ways to Contribute
- **Smart Contract Improvements**: Optimize gas efficiency, add new features, enhance security
- **Frontend Development**: Improve the UI/UX, add new features, optimize performance
- **AMM Research**: Research and implement better stable-swap curves or liquidity mechanisms
- **Testing**: Add comprehensive test coverage for contracts and frontend
- **Documentation**: Improve README, add tutorials, create developer guides
- **Integration**: Add support for more tokens or integrate with other protocols

### Development Workflow
1. Fork the repository
2. Create a feature branch: `git checkout -b feature/your-feature-name`
3. Make your changes and ensure tests pass
4. Run the test suite: `forge test && cd frontend && npm test`
5. Submit a pull request with a clear description

### Code Standards
- Follow existing code patterns and Solidity best practices
- Add tests for new functionality
- Update documentation for significant changes
- Ensure backward compatibility where possible
- Use descriptive commit messages

### Smart Contract Guidelines
- Use OpenZeppelin contracts for security
- Add comprehensive test coverage with Foundry
- Optimize for gas efficiency
- Include proper error handling and revert messages
- Document complex mathematical operations

### Frontend Guidelines
- Use TypeScript for type safety
- Follow React best practices
- Ensure mobile responsiveness
- Test on multiple wallet connections
- Handle error states gracefully

### Testing
- All smart contract functions should have unit tests
- Frontend should have integration tests
- Test on both testnet and mainnet
- Include edge cases and failure scenarios

### Security Considerations
- Never modify the AMM math without thorough testing
- Be cautious with external contract calls
- Validate all user inputs
- Consider economic incentives when making changes

---

## License

MIT License
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"frontend:build": "cd frontend && npm run build"
},
"keywords": ["dex", "stablecoin", "base", "ethereum", "amm"],
"author": "",
"author": "Adekunle Bamz",
"license": "MIT",
"type": "commonjs",
"dependencies": {
Expand Down
252 changes: 252 additions & 0 deletions src/StablePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,258 @@ contract StablePool is IStablePool {
RECEIVE ETH
//////////////////////////////////////////////////////////////*/

/*//////////////////////////////////////////////////////////////
BULK OPERATIONS
//////////////////////////////////////////////////////////////*/

/// @notice Struct for bulk swap parameters
struct BulkSwap {
address tokenIn;
uint256 amountIn;
uint256 minAmountOut;
}

/// @notice Struct for bulk liquidity addition parameters
struct BulkLiquidityAddition {
uint256 amountUSDC;
uint256 amountUSDT;
uint256 minLpTokens;
}

/// @notice Struct for bulk liquidity removal parameters
struct BulkLiquidityRemoval {
uint256 lpTokens;
uint256 minUSDC;
uint256 minUSDT;
}

/// @notice Event emitted when bulk swaps are executed
event BulkSwapsExecuted(
address indexed caller,
uint256 totalSwaps,
uint256 totalValueTransferred
);

/// @notice Event emitted when bulk liquidity is added
event BulkLiquidityAdded(
address indexed provider,
uint256 totalAdditions,
uint256 totalLpTokens
);

/// @notice Event emitted when bulk liquidity is removed
event BulkLiquidityRemoved(
address indexed provider,
uint256 totalRemovals,
uint256 totalLpBurned,
uint256 totalUSDC,
uint256 totalUSDT
);

/// @notice Execute multiple swaps in a single transaction
/// @param swaps Array of swap parameters (up to 10 swaps per transaction)
/// @return totalAmountOut Total output amount across all swaps
function bulkSwap(BulkSwap[] calldata swaps)
external
payable
returns (uint256 totalAmountOut)
{
uint256 totalSwaps = swaps.length;
require(totalSwaps > 0 && totalSwaps <= 10, "StablePool: invalid swap count");

uint256 totalFeesRequired = totalSwaps * SWAP_FEE;
require(msg.value == totalFeesRequired, "StablePool: incorrect total fee");

uint256 totalValueTransferred = 0;

for (uint256 i = 0; i < totalSwaps; i++) {
BulkSwap calldata swapData = swaps[i];
require(swapData.amountIn > 0, "StablePool: zero amount");

// Validate token and determine direction
bool isUsdcIn = swapData.tokenIn == address(usdc);
require(isUsdcIn || swapData.tokenIn == address(usdt), "StablePool: invalid token");

// Calculate output using stable-swap math
uint256 amountOut = _calculateSwapOutput(swapData.amountIn, isUsdcIn);
require(amountOut >= swapData.minAmountOut, "StablePool: slippage");

// Check sufficient output reserves
if (isUsdcIn) {
require(amountOut <= reserveUSDT, "StablePool: insufficient USDT");
} else {
require(amountOut <= reserveUSDC, "StablePool: insufficient USDC");
}

// Transfer input token in
IERC20 inputToken = isUsdcIn ? usdc : usdt;
_safeTransferFrom(inputToken, msg.sender, address(this), swapData.amountIn);

// Update reserves
if (isUsdcIn) {
reserveUSDC += swapData.amountIn;
reserveUSDT -= amountOut;
} else {
reserveUSDT += swapData.amountIn;
reserveUSDC -= amountOut;
}

// Transfer output token out
IERC20 outputToken = isUsdcIn ? usdt : usdc;
_safeTransfer(outputToken, msg.sender, amountOut);

// Accumulate totals
totalAmountOut += amountOut;
totalValueTransferred += swapData.amountIn;
}

// Send total fees to recipient
(bool success, ) = feeRecipient.call{value: totalFeesRequired}("");
require(success, "StablePool: fee transfer failed");

emit BulkSwapsExecuted(msg.sender, totalSwaps, totalValueTransferred);

return totalAmountOut;
}

/// @notice Execute bulk liquidity additions
/// @param additions Array of liquidity addition parameters (up to 5 additions)
/// @return totalLpTokens Total LP tokens minted across all additions
function bulkAddLiquidity(BulkLiquidityAddition[] calldata additions)
external
returns (uint256 totalLpTokens)
{
uint256 totalAdditions = additions.length;
require(totalAdditions > 0 && totalAdditions <= 5, "StablePool: invalid addition count");

for (uint256 i = 0; i < totalAdditions; i++) {
BulkLiquidityAddition calldata addition = additions[i];
require(addition.amountUSDC > 0 || addition.amountUSDT > 0, "StablePool: zero amounts");

uint256 totalSupply = lpToken.totalSupply();
uint256 lpTokensMinted;

if (totalSupply == 0) {
// First deposit: LP tokens = sum of normalized amounts
lpTokensMinted = (addition.amountUSDC * usdcMultiplier) + (addition.amountUSDT * usdtMultiplier);
require(lpTokensMinted > 0, "StablePool: insufficient initial liquidity");
} else {
// Subsequent deposits: proportional to existing liquidity
uint256 totalReserves = (reserveUSDC * usdcMultiplier) + (reserveUSDT * usdtMultiplier);
uint256 depositValue = (addition.amountUSDC * usdcMultiplier) + (addition.amountUSDT * usdtMultiplier);
lpTokensMinted = (depositValue * totalSupply) / totalReserves;
}

require(lpTokensMinted >= addition.minLpTokens, "StablePool: slippage");

// Transfer tokens in
if (addition.amountUSDC > 0) {
_safeTransferFrom(usdc, msg.sender, address(this), addition.amountUSDC);
reserveUSDC += addition.amountUSDC;
}
if (addition.amountUSDT > 0) {
_safeTransferFrom(usdt, msg.sender, address(this), addition.amountUSDT);
reserveUSDT += addition.amountUSDT;
}

// Mint LP tokens
lpToken.mint(msg.sender, lpTokensMinted);

totalLpTokens += lpTokensMinted;
}

emit BulkLiquidityAdded(msg.sender, totalAdditions, totalLpTokens);
}

/// @notice Execute bulk liquidity removals
/// @param removals Array of liquidity removal parameters (up to 5 removals)
/// @return totalUSDC Total USDC withdrawn across all removals
/// @return totalUSDT Total USDT withdrawn across all removals
function bulkRemoveLiquidity(BulkLiquidityRemoval[] calldata removals)
external
returns (uint256 totalUSDC, uint256 totalUSDT)
{
uint256 totalRemovals = removals.length;
require(totalRemovals > 0 && totalRemovals <= 5, "StablePool: invalid removal count");

uint256 totalLpBurned = 0;

for (uint256 i = 0; i < totalRemovals; i++) {
BulkLiquidityRemoval calldata removal = removals[i];
require(removal.lpTokens > 0, "StablePool: zero LP tokens");

uint256 totalSupply = lpToken.totalSupply();
require(totalSupply > 0, "StablePool: no liquidity");

// Calculate proportional amounts
uint256 amountUSDC = (removal.lpTokens * reserveUSDC) / totalSupply;
uint256 amountUSDT = (removal.lpTokens * reserveUSDT) / totalSupply;

require(amountUSDC >= removal.minUSDC, "StablePool: USDC slippage");
require(amountUSDT >= removal.minUSDT, "StablePool: USDT slippage");

// Burn LP tokens first (checks balance)
lpToken.burn(msg.sender, removal.lpTokens);

// Update reserves
reserveUSDC -= amountUSDC;
reserveUSDT -= amountUSDT;

// Transfer tokens out
if (amountUSDC > 0) {
_safeTransfer(usdc, msg.sender, amountUSDC);
}
if (amountUSDT > 0) {
_safeTransfer(usdt, msg.sender, amountUSDT);
}

// Accumulate totals
totalUSDC += amountUSDC;
totalUSDT += amountUSDT;
totalLpBurned += removal.lpTokens;
}

emit BulkLiquidityRemoved(msg.sender, totalRemovals, totalLpBurned, totalUSDC, totalUSDT);
}

/// @notice Get bulk operation limits
/// @return maxBulkSwaps Maximum swaps per bulk transaction
/// @return maxBulkLiquidity Maximum liquidity operations per bulk transaction
function getBulkLimits()
external
pure
returns (uint256 maxBulkSwaps, uint256 maxBulkLiquidity)
{
return (10, 5);
}

/// @notice Estimate gas for bulk operations
/// @param operationCount Number of operations
/// @param isSwap True if estimating swaps, false for liquidity operations
/// @return estimatedGas Approximate gas cost
function estimateBulkGas(uint256 operationCount, bool isSwap)
external
pure
returns (uint256 estimatedGas)
{
require(operationCount > 0, "StablePool: invalid count");

uint256 baseGas = 21000; // Base transaction gas
uint256 perOperationGas;

if (isSwap) {
// Swap operations
perOperationGas = operationCount <= 10 ? 85000 : 100000;
require(operationCount <= 10, "StablePool: too many swaps");
} else {
// Liquidity operations
perOperationGas = 130000;
require(operationCount <= 5, "StablePool: too many operations");
}

return baseGas + (perOperationGas * operationCount);
}

/// @notice Reject direct ETH transfers (fees must go through swap)
receive() external payable {
revert("StablePool: use swap()");
Expand Down