@@ -19,26 +19,36 @@ contract Exchange is ReentrancyGuard, Ownable {
1919
2020 Registry public immutable registry;
2121 IERC20 public immutable usdcToken;
22+ IERC20 public immutable xsgdToken;
2223 IERC4626 public immutable vault;
2324
2425 uint256 public fee = 100 ; // 100 basis points fee (1%)
2526 address public feeCollector;
2627
28+ uint256 public lastUpdateTime;
29+ uint256 public lastPricePerShare;
30+
2731 event Transfer (address indexed from , address indexed to , uint256 amount , string uen );
2832 event FeeUpdated (uint256 newFee );
2933 event FeeCollectorUpdated (address newFeeCollector );
3034 event FeesWithdrawn (address indexed to , uint256 amount );
3135 event VaultDeposit (address indexed merchant , uint256 assets , uint256 shares );
3236 event VaultWithdraw (address indexed merchant , uint256 assets , uint256 shares );
3337
34- constructor (address _registryAddress , address _usdcAddress , address _vaultAddress ) Ownable (msg .sender ) {
38+ constructor (address _registryAddress , address _usdcAddress , address _xsgdAddress , address _vaultAddress )
39+ Ownable (msg .sender )
40+ {
3541 require (_registryAddress != address (0 ), "Invalid registry address " );
3642 require (_usdcAddress != address (0 ), "Invalid USDC address " );
43+ require (_xsgdAddress != address (0 ), "Invalid xSGD address " );
3744 require (_vaultAddress != address (0 ), "Invalid vault address " );
3845 registry = Registry (_registryAddress);
3946 usdcToken = IERC20 (_usdcAddress);
47+ xsgdToken = IERC20 (_xsgdAddress);
4048 feeCollector = address (this );
4149 vault = IERC4626 (_vaultAddress);
50+ lastUpdateTime = block .timestamp ;
51+ lastPricePerShare = vault.convertToAssets (1e6 );
4252 }
4353
4454 /////////////////////////
@@ -50,7 +60,7 @@ contract Exchange is ReentrancyGuard, Ownable {
5060 * @param _uen Merchant's UEN.
5161 * @param _amount Amount of USDC to transfer to merchant.
5262 */
53- function transferToMerchant (string memory _uen , uint256 _amount ) external nonReentrant {
63+ function transferUsdcToMerchant (string memory _uen , uint256 _amount ) external nonReentrant {
5464 require (_amount > 0 , "Amount must be greater than zero " );
5565
5666 address merchantWalletAddress = registry.getMerchantByUEN (_uen).wallet_address;
@@ -67,6 +77,23 @@ contract Exchange is ReentrancyGuard, Ownable {
6777 emit Transfer (msg .sender , merchantWalletAddress, merchantAmount, _uen);
6878 }
6979
80+ function transferXsgdToMerchant (string memory _uen , uint256 _amount ) external nonReentrant {
81+ require (_amount > 0 , "Amount must be greater than zero " );
82+
83+ address merchantWalletAddress = registry.getMerchantByUEN (_uen).wallet_address;
84+ require (merchantWalletAddress != address (0 ), "Invalid merchant wallet address " );
85+
86+ uint256 feeAmount = (_amount * fee) / 10000 ;
87+ uint256 merchantAmount = _amount - feeAmount;
88+
89+ xsgdToken.safeTransferFrom (msg .sender , merchantWalletAddress, merchantAmount);
90+ if (feeAmount > 0 ) {
91+ xsgdToken.safeTransferFrom (msg .sender , feeCollector, feeAmount);
92+ }
93+
94+ emit Transfer (msg .sender , merchantWalletAddress, merchantAmount, _uen);
95+ }
96+
7097 /**
7198 * @notice Transfer USDC to vault.
7299 * @param _uen Merchant's UEN.
@@ -149,16 +176,119 @@ contract Exchange is ReentrancyGuard, Ownable {
149176 }
150177
151178 /**
152- * @notice Withdraw fees.
179+ * @notice Withdraw USDC fees.
153180 * @param _to Withdrawal address.
154181 * @param _amount Amount of USDC to withdraw.
155182 */
156- function withdrawFees (address _to , uint256 _amount ) external onlyOwner {
183+ function withdrawUsdcFees (address _to , uint256 _amount ) external onlyOwner {
157184 require (_to != address (0 ), "Invalid withdrawal address " );
158185 require (_amount > 0 , "Withdrawal amount must be greater than zero " );
159186 require (_amount <= usdcToken.balanceOf (address (this )), "Insufficient balance " );
160187
161188 usdcToken.safeTransfer (_to, _amount);
162189 emit FeesWithdrawn (_to, _amount);
163190 }
191+
192+ /**
193+ * @notice Withdraw XSGD fees.
194+ * @param _to Withdrawal address.
195+ * @param _amount Amount of XSGD to withdraw.
196+ */
197+ function withdrawXsgdFees (address _to , uint256 _amount ) external onlyOwner {
198+ require (_to != address (0 ), "Invalid withdrawal address " );
199+ require (_amount > 0 , "Withdrawal amount must be greater than zero " );
200+ require (_amount <= xsgdToken.balanceOf (address (this )), "Insufficient balance " );
201+
202+ xsgdToken.safeTransfer (_to, _amount);
203+ emit FeesWithdrawn (_to, _amount);
204+ }
205+
206+ ///////////////////
207+ // VAULT HELPERS //
208+ ///////////////////
209+
210+ /**
211+ * @notice Get current price per share
212+ * @return Current price of 1 share in terms of assets (USDC)
213+ */
214+ function getCurrentPricePerShare () public view returns (uint256 ) {
215+ return vault.convertToAssets (1e6 );
216+ }
217+
218+ /**
219+ * @notice Get vault metrics
220+ * @return totalAssets Total assets in vault
221+ * @return totalShares Total shares issued
222+ * @return pricePerShare Current price per share
223+ */
224+ function getVaultMetrics () public view returns (uint256 totalAssets , uint256 totalShares , uint256 pricePerShare ) {
225+ totalAssets = vault.totalAssets ();
226+ totalShares = vault.totalSupply ();
227+ pricePerShare = getCurrentPricePerShare ();
228+ }
229+
230+ /**
231+ * @notice Calculate APY between two price points
232+ * @param startPrice Starting price per share
233+ * @param endPrice Ending price per share
234+ * @param timeElapsedInSeconds Time elapsed between prices in seconds
235+ * @return apy Annual Percentage Yield in basis points (1% = 100)
236+ */
237+ function calculateAPY (uint256 startPrice , uint256 endPrice , uint256 timeElapsedInSeconds )
238+ public
239+ pure
240+ returns (uint256 apy )
241+ {
242+ require (timeElapsedInSeconds > 0 , "Time elapsed must be > 0 " );
243+ require (startPrice > 0 , "Start price must be > 0 " );
244+
245+ // Calculate yield for the period
246+ uint256 yield = ((endPrice - startPrice) * 1e6 ) / startPrice;
247+
248+ // Annualize it (multiply by seconds in year and divide by elapsed time)
249+ uint256 secondsInYear = 365 days ;
250+ apy = (yield * secondsInYear) / timeElapsedInSeconds;
251+
252+ return apy;
253+ }
254+
255+ /**
256+ * @notice Get current APY based on last update
257+ * @return Current APY in basis points (1% = 100)
258+ */
259+ function getCurrentAPY () external view returns (uint256 ) {
260+ uint256 currentPrice = getCurrentPricePerShare ();
261+ uint256 timeElapsed = block .timestamp - lastUpdateTime;
262+
263+ return calculateAPY (lastPricePerShare, currentPrice, timeElapsed);
264+ }
265+
266+ /**
267+ * @notice Update the stored price per share
268+ * @dev This can be called periodically to update the reference point for APY calculations
269+ */
270+ function updatePricePerShare () external {
271+ lastPricePerShare = getCurrentPricePerShare ();
272+ lastUpdateTime = block .timestamp ;
273+ }
274+
275+ /**
276+ * @notice Get detailed yield information
277+ * @return currentPrice Current price per share
278+ * @return lastPrice Last recorded price per share
279+ * @return timeSinceLastUpdate Seconds since last update
280+ * @return currentAPY Current APY in basis points
281+ */
282+ function getYieldInfo ()
283+ external
284+ view
285+ returns (uint256 currentPrice , uint256 lastPrice , uint256 timeSinceLastUpdate , uint256 currentAPY )
286+ {
287+ currentPrice = getCurrentPricePerShare ();
288+ lastPrice = lastPricePerShare;
289+ timeSinceLastUpdate = block .timestamp - lastUpdateTime;
290+ currentAPY = calculateAPY (lastPrice, currentPrice, timeSinceLastUpdate);
291+
292+ return (currentPrice, lastPrice, timeSinceLastUpdate, currentAPY);
293+ }
164294}
0 commit comments