<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Raghuvansh</title>
    <description>The latest articles on DEV Community by Raghuvansh (@ra9huvansh).</description>
    <link>https://dev.to/ra9huvansh</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3853700%2F75076fbe-5ad3-4152-9990-92adde6d057b.jpg</url>
      <title>DEV Community: Raghuvansh</title>
      <link>https://dev.to/ra9huvansh</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ra9huvansh"/>
    <language>en</language>
    <item>
      <title>I Audited My Own DeFi Protocol — Here's Every Bug I Found (And How I Fixed Them)</title>
      <dc:creator>Raghuvansh</dc:creator>
      <pubDate>Fri, 10 Apr 2026 10:18:50 +0000</pubDate>
      <link>https://dev.to/ra9huvansh/i-audited-my-own-defi-protocol-heres-every-bug-i-found-and-how-i-fixed-them-1dk7</link>
      <guid>https://dev.to/ra9huvansh/i-audited-my-own-defi-protocol-heres-every-bug-i-found-and-how-i-fixed-them-1dk7</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"The scariest thing about writing smart contracts isn't that the code is complex. It's that the code looks fine — right until the moment it isn't."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Let me set the scene.&lt;/p&gt;

&lt;p&gt;It's late. You've been writing Solidity for weeks. Your stablecoin protocol is deployed on testnet. The frontend works. Transactions go through. Users can deposit collateral, mint tokens, earn yield. Everything looks beautiful.&lt;/p&gt;

&lt;p&gt;And then a quiet voice in the back of your head whispers: &lt;em&gt;"But have you actually checked?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That voice is your best friend. I listened to it. And what it led me to find, buried inside code I had written myself, code I had stared at for hours, genuinely surprised me.&lt;/p&gt;

&lt;p&gt;This is the story of how I audited &lt;strong&gt;Merix Holdings&lt;/strong&gt;, my own decentralized stablecoin protocol, using two of the most powerful static analysis tools in the Solidity ecosystem: &lt;strong&gt;Slither&lt;/strong&gt; and &lt;strong&gt;Aderyn&lt;/strong&gt;. I found bugs ranging from a high-severity reentrancy hole to a math mistake that could have liquidated innocent users too early. I'll show you every single one, in plain language, with real code.&lt;/p&gt;

&lt;p&gt;Buckle up.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wait, What Even Is Merix?
&lt;/h2&gt;

&lt;p&gt;Before we get into the bugs, let me give you thirty seconds of context.&lt;/p&gt;

&lt;p&gt;Merix is a &lt;strong&gt;decentralized, overcollateralized stablecoin protocol&lt;/strong&gt; built on Ethereum (currently deployed on the Sepolia testnet). Here's the whole thing in three bullet points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You deposit &lt;strong&gt;WETH or WBTC&lt;/strong&gt; as collateral into the protocol.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The protocol lets you mint &lt;strong&gt;DSC&lt;/strong&gt; (a USD-pegged ERC-20 stablecoin) against that collateral.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The system enforces a &lt;strong&gt;200% collateralization ratio&lt;/strong&gt; at all times. If your position drops below that, anyone can liquidate you and earn a 10% bonus.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On top of that core engine, I built two extra pieces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A &lt;strong&gt;YieldAggregator&lt;/strong&gt;: an ERC4626-style vault where users deposit DSC and earn simulated yield (think: strategy-based APY).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A &lt;strong&gt;RedemptionContract&lt;/strong&gt;: a clever mechanism that converts your realized yield profits directly into WETH collateral, improving your health factor without you having to do a separate deposit.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Five smart contracts. 513 lines of production code. One internal security review.&lt;/p&gt;

&lt;p&gt;Here's everything that went wrong.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Audit Setup: What Tools Did I Use?
&lt;/h2&gt;

&lt;p&gt;I ran &lt;strong&gt;three layers&lt;/strong&gt; of analysis:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Manual Line-by-Line Review
&lt;/h3&gt;

&lt;p&gt;I went through every contract function by function, asking myself: &lt;em&gt;"How would I steal from this?"&lt;/em&gt; I checked for reentrancy, bad math, access control gaps, oracle manipulation, and logic errors against the protocol spec.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Slither (Trail of Bits)
&lt;/h3&gt;

&lt;p&gt;Slither is a static analysis framework with over 100 detectors. It reads your Solidity AST and finds patterns that match known vulnerability classes: unchecked return values, reentrancy, divide-before-multiply, and more.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;slither &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--filter-paths&lt;/span&gt; &lt;span class="s2"&gt;"test|lib|script"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Aderyn (Cyfrin)
&lt;/h3&gt;

&lt;p&gt;Aderyn is a newer Rust-based static analyzer from the team at Cyfrin, the same people behind Codehawks and security research in the DeFi space. It has a cleaner output format and catches a slightly different set of patterns than Slither.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aderyn &lt;span class="nt"&gt;--output&lt;/span&gt; aderyn-report.md &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Fuzz + Invariant Testing (Foundry)
&lt;/h3&gt;

&lt;p&gt;This wasn't just static analysis. I wrote &lt;strong&gt;135 tests&lt;/strong&gt; including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;32 stateless fuzz test functions (1,000 runs each locally)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A full invariant campaign with 256 sequences × 100 calls deep&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;20 invariant properties that had to hold across all of them&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now let's get to the good part.&lt;/p&gt;




&lt;h2&gt;
  
  
  Finding #1: The Reentrancy Bug I Almost Missed
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Severity: HIGH | Status: Fixed&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's a fun psychological trick: when you write a function and add &lt;code&gt;nonReentrant&lt;/code&gt; to it, your brain says &lt;em&gt;"okay, that's safe"&lt;/em&gt; and moves on. That's exactly what happened to me.&lt;/p&gt;

&lt;p&gt;The function &lt;code&gt;depositToStrategy&lt;/code&gt; in &lt;code&gt;YieldAggregator.sol&lt;/code&gt; had &lt;code&gt;nonReentrant&lt;/code&gt; on it. But the lock only works if it's active &lt;em&gt;before&lt;/em&gt; the external call happens. My original code did this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// THE VULNERABLE VERSION
function depositToStrategy(uint256 strategyId, uint256 amount) external nonReentrant {
    _harvestAll();

    uint256 sharesToMint = ...;

    // First, update some state...
    totalShares += sharesToMint;
    totalAssets += amount;
    userShares[msg.sender] += sharesToMint;
    userPrincipal[msg.sender] += amount;
    userStrategyDeposited[msg.sender][strategyId] += amount;

    // Then make the external call...
    dsc.transferFrom(msg.sender, address(this), amount);

    // Then update the REST of the state -- AFTER the external call
    strategies[strategyId].totalDeposited += amount;  // &amp;lt;-- THIS IS THE PROBLEM
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Did you catch it?&lt;/p&gt;

&lt;p&gt;&lt;code&gt;strategies[strategyId].totalDeposited&lt;/code&gt; was being updated &lt;strong&gt;after&lt;/strong&gt; the external &lt;code&gt;transferFrom&lt;/code&gt; call. This is a violation of the &lt;strong&gt;Checks-Effects-Interactions (CEI)&lt;/strong&gt; pattern, one of the oldest rules in Solidity security.&lt;/p&gt;

&lt;p&gt;Here's why this matters. During the &lt;code&gt;transferFrom&lt;/code&gt; callback window, if an attacker could somehow re-enter the contract (say, via a malicious token or a cross-function call), they would read &lt;code&gt;strategies[strategyId].totalDeposited&lt;/code&gt; as &lt;strong&gt;zero&lt;/strong&gt;, because it hasn't been updated yet. The yield harvest math would then return zero yield for that strategy, distorting share prices for &lt;strong&gt;every depositor in the vault&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Both &lt;strong&gt;Aderyn&lt;/strong&gt; (H-1) and &lt;strong&gt;Slither&lt;/strong&gt; (&lt;code&gt;reentrancy-no-eth&lt;/code&gt;) independently flagged this. The fix was simple: move all state updates above the external call.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// THE FIXED VERSION
// All state changes happen BEFORE the external call
strategies[strategyId].totalDeposited += amount;

// Now the external call is last -- nothing can read stale state
dsc.safeTransferFrom(msg.sender, address(this), amount);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lesson learned: &lt;code&gt;nonReentrant&lt;/code&gt; is not a magic wand. CEI is still the law.&lt;/p&gt;




&lt;h2&gt;
  
  
  Finding #2: The Silent Money Thief — Unsafe ERC20
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Severity: HIGH | Status: Fixed&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This one is sneaky because the code looks completely reasonable.&lt;/p&gt;

&lt;p&gt;Across three contracts (&lt;code&gt;DSCEngine&lt;/code&gt;, &lt;code&gt;YieldAggregator&lt;/code&gt;, and &lt;code&gt;RedemptionContract&lt;/code&gt;), I was using raw ERC-20 calls like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Looks fine, right?
bool success = IERC20(tokenCollateralAddress).transferFrom(msg.sender, address(this), amountCollateral);
if (!success) { revert DSCEngine__TransferFailed(); }

// Or even worse -- no check at all:
dsc.transferFrom(msg.sender, address(this), amount);
dsc.transfer(msg.sender, dscAmount);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the dirty secret of the ERC-20 standard: &lt;strong&gt;not all tokens follow it correctly.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Some tokens (USDT being the most famous example) don't return a boolean from &lt;code&gt;transfer&lt;/code&gt;. Some return &lt;code&gt;false&lt;/code&gt; on failure instead of reverting. If you call &lt;code&gt;.transferFrom()&lt;/code&gt; on one of these tokens without using SafeERC20, the call &lt;strong&gt;silently does nothing&lt;/strong&gt;, but your contract happily records the deposit as if it succeeded.&lt;/p&gt;

&lt;p&gt;Imagine a user deposits 1,000 WETH as collateral. The transfer silently fails. But the contract says they have 1,000 WETH deposited. They mint DSC against it. They've essentially printed money from nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;All three contracts&lt;/strong&gt; had this across 11 different call sites. Slither's &lt;code&gt;unchecked-transfer&lt;/code&gt; detector and Aderyn's L-12 both lit up like a Christmas tree.&lt;/p&gt;

&lt;p&gt;The fix: replace every raw transfer with OpenZeppelin's &lt;code&gt;SafeERC20&lt;/code&gt;. And for approvals, use &lt;code&gt;forceApprove&lt;/code&gt; instead of &lt;code&gt;approve&lt;/code&gt; to handle the USDT-style approval reset pattern.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// The right way
using SafeERC20 for IERC20;

IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
IERC20(token).safeTransfer(to, amount);
IERC20(token).forceApprove(spender, amount);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Finding #3: The Math Bug That Could Liquidate You Early
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Severity: MEDIUM | Status: Fixed&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This one is my personal favourite because it's so subtle that you can stare at the formula and think it's correct. And mathematically, it is. But &lt;strong&gt;in Solidity&lt;/strong&gt;, integer division truncates, and the order of operations matters enormously.&lt;/p&gt;

&lt;p&gt;The health factor formula in &lt;code&gt;_calculateHealthFactor&lt;/code&gt; originally looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// THE BUGGY VERSION
uint256 collateralAdjustedForThreshold =
    (collateralValueInUsd * LIQUIDATION_THRESHOLD) / LIQUIDATION_PRECISION;

return (collateralAdjustedForThreshold * PRECISION) / totalDscMinted;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See the issue? It divides &lt;strong&gt;first&lt;/strong&gt;, then multiplies. In real math, this is fine. In Solidity, the first division truncates the result to an integer, destroying precision before the second multiplication can recover it.&lt;/p&gt;

&lt;p&gt;Here's a concrete example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;collateralValueInUsd&lt;/code&gt; = $1,500 (in wei scale: 1500e18)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;LIQUIDATION_THRESHOLD&lt;/code&gt; = 50&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;LIQUIDATION_PRECISION&lt;/code&gt; = 100&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Step 1: &lt;code&gt;(1500e18 * 50) / 100&lt;/code&gt; = &lt;code&gt;750e18&lt;/code&gt; ✓ (this happens to be exact here)&lt;/p&gt;

&lt;p&gt;But with smaller numbers near the boundary, the truncation rounds down aggressively. A user with a true health factor of &lt;strong&gt;1.01&lt;/strong&gt; might get computed as &lt;strong&gt;0.99&lt;/strong&gt;, and suddenly they're liquidatable when they shouldn't be.&lt;/p&gt;

&lt;p&gt;A user losing their 10% liquidation bonus collateral for no reason is not a rounding error. It's a financial loss caused by a math ordering mistake.&lt;/p&gt;

&lt;p&gt;The fix is one line: combine all the multiplications first, then do a single division at the end.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// THE FIXED VERSION
return (collateralValueInUsd * LIQUIDATION_THRESHOLD * PRECISION)
    / (LIQUIDATION_PRECISION * totalDscMinted);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Maximum multiplication first, single division last. Precision preserved.&lt;/p&gt;




&lt;h2&gt;
  
  
  Finding #4: The "Invisible Window" Reentrancy
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Severity: MEDIUM | Status: Fixed&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Remember how I said &lt;code&gt;nonReentrant&lt;/code&gt; isn't magic? Here's another proof.&lt;/p&gt;

&lt;p&gt;The function &lt;code&gt;redeemCollateralForDsc&lt;/code&gt; lets users burn their DSC and get their collateral back in one transaction. The original code did this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// THE VULNERABLE VERSION
function redeemCollateralForDsc(address token, uint256 collateral, uint256 dscToBurn) external {
    burnDsc(dscToBurn);        // &amp;lt;-- no nonReentrant, lock NOT held
    redeemCollateral(token, collateral); // &amp;lt;-- lock acquired HERE
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem: &lt;code&gt;burnDsc&lt;/code&gt; is a &lt;code&gt;public&lt;/code&gt; function with no &lt;code&gt;nonReentrant&lt;/code&gt; modifier. Inside &lt;code&gt;burnDsc&lt;/code&gt;, there's a call to &lt;code&gt;DSC.transferFrom&lt;/code&gt;, an external call. During that external call, the reentrancy lock is &lt;strong&gt;not held&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;An attacker with a DSC token that has transfer hooks (or a future upgraded DSC token) could:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Call &lt;code&gt;redeemCollateralForDsc(weth, largeAmount, smallDSC)&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;During the &lt;code&gt;DSC.transferFrom&lt;/code&gt; inside &lt;code&gt;burnDsc&lt;/code&gt;, re-enter &lt;code&gt;redeemCollateral&lt;/code&gt; directly&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;At this point, &lt;code&gt;s_DSCMinted&lt;/code&gt; is already decremented (debt looks paid off), but &lt;code&gt;s_collateralDeposited&lt;/code&gt; is not yet decremented&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Health factor check passes; attacker withdraws collateral for free&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;They've essentially redeemed collateral twice for the cost of one burn&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The fix: stop calling public functions from within each other. Use internal functions under a single &lt;code&gt;nonReentrant&lt;/code&gt; guard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// THE FIXED VERSION
function redeemCollateralForDsc(
    address tokenCollateralAddress,
    uint256 amountCollateral,
    uint256 amountDscToBurn
) external nonReentrant moreThanZero(amountCollateral) moreThanZero(amountDscToBurn) {
    _burnDsc(amountDscToBurn, msg.sender, msg.sender);    // internal
    _redeemCollateral(tokenCollateralAddress, amountCollateral, msg.sender, msg.sender); // internal
    _revertIfHealthFactorIsBroken(msg.sender);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One lock. One atomic operation. No windows.&lt;/p&gt;




&lt;h2&gt;
  
  
  The "Small" Things That Add Up
&lt;/h2&gt;

&lt;p&gt;Not every finding is a catastrophic money-printer exploit. Here's a rapid-fire tour of the lower-severity stuff, because in security, the small things are how you tell if a codebase is mature.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;nonReentrant&lt;/code&gt; Was in the Wrong Position
&lt;/h3&gt;

&lt;p&gt;Six functions had &lt;code&gt;nonReentrant&lt;/code&gt; listed &lt;em&gt;after&lt;/em&gt; other modifiers like &lt;code&gt;moreThanZero&lt;/code&gt; and &lt;code&gt;isAllowedToken&lt;/code&gt;. The rule: &lt;code&gt;nonReentrant&lt;/code&gt; &lt;strong&gt;must be first.&lt;/strong&gt; If any modifier before it makes an external call, reentrancy protection doesn't kick in in time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// WRONG ORDER
function depositCollateral(...) public moreThanZero(amount) isAllowedToken(token) nonReentrant { }

// CORRECT ORDER
function depositCollateral(...) public nonReentrant moreThanZero(amount) isAllowedToken(token) { }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Setting Address to Zero Could Brick the Protocol
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;setRedemptionContract&lt;/code&gt; in both &lt;code&gt;DSCEngine&lt;/code&gt; and &lt;code&gt;YieldAggregator&lt;/code&gt; accepted &lt;code&gt;address(0)&lt;/code&gt; without reverting. If the owner accidentally called it with a zero address, the &lt;code&gt;onlyRedemptionContract&lt;/code&gt; modifier would permanently block all redemption flows. The protocol would be bricked with no upgrade path.&lt;/p&gt;

&lt;p&gt;Fix: one line.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (rc == address(0)) revert DSCEngine__ZeroAddress();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Missing Events Everywhere
&lt;/h3&gt;

&lt;p&gt;Six critical state-changing functions, including &lt;code&gt;mintDsc&lt;/code&gt;, &lt;code&gt;burnDsc&lt;/code&gt;, &lt;code&gt;setRedemptionContract&lt;/code&gt;, and &lt;code&gt;deductRealizedProfit&lt;/code&gt;, emitted no events. This means off-chain monitoring, indexers like The Graph, and analytics dashboards are completely blind to what's happening. You can't build a frontend alert system on silence.&lt;/p&gt;

&lt;p&gt;Fixed by adding dedicated events: &lt;code&gt;DscMinted&lt;/code&gt;, &lt;code&gt;DscBurned&lt;/code&gt;, &lt;code&gt;DscBurnedExternal&lt;/code&gt;, &lt;code&gt;RedemptionContractSet&lt;/code&gt;, &lt;code&gt;ProfitDeducted&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Gas Waste Inside Loops
&lt;/h3&gt;

&lt;p&gt;Three loops were reading &lt;code&gt;.length&lt;/code&gt; from a storage array on every single iteration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for (uint256 i = 0; i &amp;lt; s_collateralTokens.length; i++) { ... }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every &lt;code&gt;.length&lt;/code&gt; call on a storage array costs an &lt;code&gt;SLOAD&lt;/code&gt; (2,100 gas cold, 100 gas warm). If you have 10 collateral tokens, that's 10 extra storage reads per function call. Cache it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uint256 len = s_collateralTokens.length;
for (uint256 i = 0; i &amp;lt; len; i++) { ... }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Magic Numbers in the Math
&lt;/h3&gt;

&lt;p&gt;The yield formula had &lt;code&gt;10_000&lt;/code&gt; (basis points divisor) scattered across multiple places as a raw literal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yieldAmount = (s.totalDeposited * s.apyBps * elapsed) / (365 days * 10_000);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you need to change it later, you have to find every instance. Replace with a constant:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uint256 private constant BPS_DIVISOR = 10_000;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What the Tools Found That I Had to Ignore (False Positives Are Real)
&lt;/h2&gt;

&lt;p&gt;Security tools are not perfect. They flag patterns, and some of those patterns are fine by design.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strict equality in&lt;/strong&gt; &lt;code&gt;_harvestStrategy&lt;/code&gt;&lt;strong&gt;:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (s.totalDeposited == 0 || elapsed == 0) return 0;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Slither flagged this as "dangerous strict equality." But here, both values are internal; no attacker can force &lt;code&gt;totalDeposited&lt;/code&gt; to be exactly zero during an attack in a way that changes the outcome. The check is intentional and correct. Acknowledged and documented.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Divide-before-multiply in share math (M-03):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uint256 sharesToBurn = (dscAmount * totalShares) / totalAssets;
uint256 principalReduction = (sharesToBurn * userPrincipal[msg.sender]) / userShares[msg.sender];
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is inherent to ERC4626-style accounting. The precision loss is bounded at 1 wei per operation and doesn't accumulate across users in an exploitable way. Acknowledged.&lt;/p&gt;

&lt;p&gt;Knowing &lt;em&gt;when not to fix something&lt;/em&gt; is just as important as knowing what to fix.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Test Suite: Because Static Analysis Is Only Half the Story
&lt;/h2&gt;

&lt;p&gt;After fixing all the high and medium issues, I ran a full test campaign:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Total tests&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;135&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fuzz test functions&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;32&lt;/strong&gt; (1,000 runs each)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Invariant sequences&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;256&lt;/strong&gt; (100 calls deep)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Invariants verified&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;20&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tests passing&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;135/135&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Selected invariants that had to hold no matter what:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The protocol must always be overcollateralized: total collateral value at or above total DSC minted × 2&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;No user's health factor can drop below 1.0 unless they are being liquidated in the same transaction&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Total DSC supply must match ghost accounting across all mint/burn operations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The YieldAggregator's &lt;code&gt;totalAssets&lt;/code&gt; can never decrease during a deposit-only sequence&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The RedemptionContract's WETH reserve is always bounded by cumulative redemptions&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Final coverage:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Contract&lt;/th&gt;
&lt;th&gt;Line Coverage&lt;/th&gt;
&lt;th&gt;Function Coverage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DSCEngine.sol&lt;/td&gt;
&lt;td&gt;93.80%&lt;/td&gt;
&lt;td&gt;91.67%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OracleLib.sol&lt;/td&gt;
&lt;td&gt;100.00%&lt;/td&gt;
&lt;td&gt;100.00%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RedemptionContract.sol&lt;/td&gt;
&lt;td&gt;100.00%&lt;/td&gt;
&lt;td&gt;100.00%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DecentralizedStableCoin.sol&lt;/td&gt;
&lt;td&gt;85.71%&lt;/td&gt;
&lt;td&gt;100.00%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;YieldAggregator.sol&lt;/td&gt;
&lt;td&gt;85.19%&lt;/td&gt;
&lt;td&gt;93.33%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  What I'm Still Watching (Known Risks)
&lt;/h2&gt;

&lt;p&gt;Being honest in a security review means talking about what you &lt;em&gt;didn't&lt;/em&gt; fully solve.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Oracle single point of failure.&lt;/strong&gt; The protocol uses Chainlink with a 3-hour staleness timeout. If Chainlink goes dark for 3+ hours, the protocol freezes. A secondary oracle (Uniswap TWAP) or a graceful circuit-breaker would add resilience.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Single-owner centralization.&lt;/strong&gt; Both &lt;code&gt;DSCEngine&lt;/code&gt; and &lt;code&gt;YieldAggregator&lt;/code&gt; are controlled by a single EOA. That's fine for testnet. On mainnet, that needs to be a Gnosis Safe multisig.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WETH reserve depletion in RedemptionContract.&lt;/strong&gt; If everyone redeems at once, the WETH reserve drains with no rate limit. A per-epoch cap would protect against this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No upgrade path.&lt;/strong&gt; The contracts are not upgradeable. Any future fix means a full redeployment and user migration. That's a deliberate design choice, but it needs a documented migration procedure.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Slither and Aderyn Feel Like to Actually Use
&lt;/h2&gt;

&lt;p&gt;One honest observation for anyone picking up these tools for the first time:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Slither&lt;/strong&gt; is the veteran. It's been around since 2018, has 100+ detectors, and integrates deeply with the Solidity AST. It's noisy, and you will get false positives. But the signal it gives you on real bugs (H-01, H-02, M-01, M-02) is worth the noise filtering.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Aderyn&lt;/strong&gt; is the newcomer with better UX. Its report is cleaner, the Markdown output is beautiful for sharing with your team, and it caught the same H-1 reentrancy independently which gave me double confidence. It's also faster to run.&lt;/p&gt;

&lt;p&gt;Run both. They don't perfectly overlap. The union of their findings is more complete than either alone.&lt;/p&gt;

&lt;p&gt;And neither of them replaces manual review. The reentrancy window in &lt;code&gt;redeemCollateralForDsc&lt;/code&gt; (M-02)? The tools caught the surface-level pattern. But understanding &lt;em&gt;why&lt;/em&gt; it was dangerous, the specific attack sequence, the economic impact, required thinking like an attacker.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Final Scorecard
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ID&lt;/th&gt;
&lt;th&gt;Issue&lt;/th&gt;
&lt;th&gt;Severity&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;H-01&lt;/td&gt;
&lt;td&gt;Reentrancy in &lt;code&gt;depositToStrategy&lt;/code&gt; (CEI violation)&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;H-02&lt;/td&gt;
&lt;td&gt;Unsafe ERC20 operations across all contracts&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M-01&lt;/td&gt;
&lt;td&gt;Divide-before-multiply in health factor formula&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M-02&lt;/td&gt;
&lt;td&gt;Reentrancy window in &lt;code&gt;redeemCollateralForDsc&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M-03&lt;/td&gt;
&lt;td&gt;Divide-before-multiply in share math&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Acknowledged&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L-01&lt;/td&gt;
&lt;td&gt;Missing zero-address check on &lt;code&gt;setRedemptionContract&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L-02&lt;/td&gt;
&lt;td&gt;Missing events on critical state changes&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L-03&lt;/td&gt;
&lt;td&gt;Storage array length not cached in loops&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L-04&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;nonReentrant&lt;/code&gt; not first modifier&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L-05&lt;/td&gt;
&lt;td&gt;Strict equality in &lt;code&gt;_harvestStrategy&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Acknowledged&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I-01&lt;/td&gt;
&lt;td&gt;Unused oracle return values&lt;/td&gt;
&lt;td&gt;Info&lt;/td&gt;
&lt;td&gt;Acknowledged&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I-02&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;OracleLib&lt;/code&gt; function visibility&lt;/td&gt;
&lt;td&gt;Info&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I-03&lt;/td&gt;
&lt;td&gt;Magic literal &lt;code&gt;10_000&lt;/code&gt; in yield math&lt;/td&gt;
&lt;td&gt;Info&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I-04&lt;/td&gt;
&lt;td&gt;Uninitialized local variable &lt;code&gt;newYield&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Info&lt;/td&gt;
&lt;td&gt;Acknowledged&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;No critical findings. Two high findings, both fixed. The protocol lives.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Real Lesson
&lt;/h2&gt;

&lt;p&gt;Here's what this whole exercise taught me, and I want you to sit with this.&lt;/p&gt;

&lt;p&gt;I wrote every line of this codebase. I knew every function. I had stared at &lt;code&gt;depositToStrategy&lt;/code&gt; probably fifty times. And I still had a high-severity CEI violation sitting right there, hidden by the false confidence of a &lt;code&gt;nonReentrant&lt;/code&gt; modifier.&lt;/p&gt;

&lt;p&gt;Security isn't about being smart. It's about being &lt;em&gt;systematic&lt;/em&gt;. About running the tools even when you think you don't need to. About treating your own code with the same suspicion you'd treat a stranger's.&lt;/p&gt;

&lt;p&gt;The tools (Slither, Aderyn, fuzz tests, invariants) aren't there to replace your brain. They're there to give your brain a second pair of eyes that never gets tired, never gets overconfident, and never skips a line because it "probably looks fine."&lt;/p&gt;

&lt;p&gt;Run the tools. Read the output. Fix what you can. Document what you acknowledge.&lt;/p&gt;

&lt;p&gt;And before you ship anything real on mainnet, get a professional audit. I'm building toward that. This internal review is step one, not step last.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;The full Merix Holdings codebase is open source:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/Ra9huvansh/merix-holdings" rel="noopener noreferrer"&gt;Ra9huvansh/merix-holdings&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The security review, Slither report, and Aderyn report are all in the repository. Clone it, run the tools yourself, see if you find something I missed. That's the whole point of open source security.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clone and set up&lt;/span&gt;
git clone https://github.com/Ra9huvansh/merix-holdings
&lt;span class="nb"&gt;cd &lt;/span&gt;merix-holdings
forge &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="c"&gt;# Run the test suite&lt;/span&gt;
forge &lt;span class="nb"&gt;test&lt;/span&gt;

&lt;span class="c"&gt;# Run Slither&lt;/span&gt;
slither &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--filter-paths&lt;/span&gt; &lt;span class="s2"&gt;"test|lib|script"&lt;/span&gt;

&lt;span class="c"&gt;# Run Aderyn&lt;/span&gt;
aderyn &lt;span class="nt"&gt;--output&lt;/span&gt; aderyn-report.md &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you find something, open an issue. Seriously.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with Foundry. Audited with Slither + Aderyn. Tested with 135 tests, 20 invariants, and a healthy dose of paranoia.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Raghuvansh Rastogi, Merix Holdings&lt;/em&gt;&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>web3</category>
      <category>solidity</category>
      <category>stablecoin</category>
    </item>
    <item>
      <title>How I Built a Capital Markets Trade Lifecycle System That Mirrors Real Banking Infrastructure</title>
      <dc:creator>Raghuvansh</dc:creator>
      <pubDate>Tue, 31 Mar 2026 15:01:14 +0000</pubDate>
      <link>https://dev.to/ra9huvansh/how-i-built-a-capital-markets-trade-lifecycle-system-that-mirrors-real-banking-infrastructure-898</link>
      <guid>https://dev.to/ra9huvansh/how-i-built-a-capital-markets-trade-lifecycle-system-that-mirrors-real-banking-infrastructure-898</guid>
      <description>&lt;p&gt;&lt;em&gt;What happens between a trader clicking "Buy" and the asset actually changing hands? The answer is six microservices, four Kafka topics, a compliance rules engine, and two business days of settlement scheduling.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;When most developers build a "finance project," they build a stock price tracker or a portfolio dashboard. Something that fetches data from an API and renders it on a chart.&lt;/p&gt;

&lt;p&gt;That is not what happens inside a bank.&lt;/p&gt;

&lt;p&gt;Inside a bank, a trade is not a number on a screen. It is a legally binding commitment that passes through compliance review, market execution, bilateral confirmation, T+2 settlement scheduling, and regulatory reporting, in that order, every single time, across systems built by different teams that talk to each other exclusively through message queues.&lt;/p&gt;

&lt;p&gt;I wanted to build something that actually modeled this. Not a simplified version. The real pipeline, with real failure modes, real latency concerns, and real regulatory structure. The result is Valoris Systems, a distributed trade lifecycle simulator built with Java 21, Spring Boot 3, Apache Kafka, PostgreSQL, Redis, and React.&lt;/p&gt;

&lt;p&gt;This post explains every architectural decision and why it mirrors what production trading systems actually do.&lt;/p&gt;




&lt;h2&gt;
  
  
  What a Trade Lifecycle Actually Is
&lt;/h2&gt;

&lt;p&gt;Before writing a single line of code, I spent time understanding the business domain. This matters because the architecture follows the business, not the other way around.&lt;/p&gt;

&lt;p&gt;When a trader at a bank or fund submits an order, it does not execute immediately. It passes through five mandatory stages:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Pre-Trade Compliance.&lt;/strong&gt; Before anything happens, the system checks: Is this counterparty on the approved list? Does this trade exceed the trader's notional risk limit? Is this instrument in the allowed universe? All three must pass. One failure kills the trade before it reaches the market.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Execution.&lt;/strong&gt; The trade hits the market. An execution price and timestamp get locked in. The venue is recorded (DFM, NASDAQ Dubai, DIFC dark pool in Valoris's case).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Confirmation.&lt;/strong&gt; Both sides of the trade, buyer and seller, must independently confirm they agreed to the same price and quantity. Mismatches happen. In Valoris, 5% of confirmations introduce a random price mismatch of plus or minus 2%, which sits in a &lt;code&gt;MISMATCHED&lt;/code&gt; state until resolved. This is realistic: in real markets, confirmation breaks happen and require manual intervention.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Settlement.&lt;/strong&gt; The actual exchange of asset and cash. This does not happen immediately after execution. It happens T+2, meaning two business days later, skipping weekends. A scheduler runs every 60 seconds, checks which trades have reached their settlement date, updates net positions per counterparty/instrument pair, and marks them settled.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Regulatory Reporting.&lt;/strong&gt; Every settled trade must be reported to the regulator (DFSA in Dubai, FCA in the UK, SEC in the US) within a defined window after execution. The report includes LEI identifiers, ISIN, notional value, execution price, venue, and counterparty details. In MiFID II terms this is called a transaction report. In Valoris, the reporting service generates a structured report in a format that mirrors real EMIR/MiFID II fields.&lt;/p&gt;

&lt;p&gt;Each of these stages is a separate microservice in Valoris. They do not call each other over HTTP. They communicate exclusively through Apache Kafka event streams.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Event-Driven Over REST-to-REST
&lt;/h2&gt;

&lt;p&gt;This is the most important architectural decision in the entire project and it needs a direct explanation.&lt;/p&gt;

&lt;p&gt;The naive way to build this system is as a chain of REST calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FIX Gateway -&amp;gt; HTTP POST -&amp;gt; Compliance Service -&amp;gt; HTTP POST -&amp;gt; Execution Service -&amp;gt; ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works in development. It fails in production for several reasons.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Temporal coupling.&lt;/strong&gt; If the execution service is down when compliance publishes a result, the trade is lost. With Kafka, compliance publishes to &lt;code&gt;trades.validated&lt;/code&gt;, and the execution service consumes that topic whenever it is ready. The topic persists messages. Nothing gets lost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backpressure handling.&lt;/strong&gt; In a real trading system, trade volume is not uniform. There are bursts at market open, major announcements, and high-volatility periods where thousands of trades hit the system simultaneously. Kafka acts as a buffer. Each downstream service processes at its own rate. The compliance service does not care whether execution is running fast or slow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audit trail by default.&lt;/strong&gt; Every Kafka topic in Valoris is a permanent, ordered log of events. If you want to know exactly what happened to trade &lt;code&gt;f47ac10b&lt;/code&gt; and when, you replay the events. This is not a nice-to-have in financial systems. It is a regulatory requirement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Independent deployability.&lt;/strong&gt; Each service can be updated, restarted, or scaled independently without any other service needing to know. The compliance service does not import any code from the execution service. They share nothing except the event schema.&lt;/p&gt;

&lt;p&gt;The Kafka topics in Valoris map directly to the business stages:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Topic&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;trades.incoming&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A trade has been submitted and needs compliance review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;trades.validated&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Compliance passed, route to execution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;trades.rejected&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Compliance failed, route to reporting for rejection record&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;trades.executed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Execution complete, needs counterparty confirmation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;trades.confirmed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Both sides confirmed, ready for settlement scheduling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;trades.settled&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Settlement complete, generate regulatory report&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Notice that &lt;code&gt;trades.rejected&lt;/code&gt; goes directly to the reporting service. Rejected trades still need to be recorded. Regulators care about failed trades too.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Compliance Service: Why Redis Matters Here
&lt;/h2&gt;

&lt;p&gt;The compliance service is the most latency-sensitive component in the pipeline. Every trade must pass through it before execution. In real markets, pre-trade compliance checks need to complete in sub-millisecond time, not because the user is waiting (they are not, this is async), but because compliance rule evaluation is on the critical path for market access.&lt;/p&gt;

&lt;p&gt;In Valoris, compliance rules are seeded into PostgreSQL on startup, then loaded into Redis at service initialization. The Redis cache holds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The approved counterparty list (Redis Set under &lt;code&gt;compliance:counterparty:approved&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Notional risk limits per trader (Redis strings under &lt;code&gt;compliance:risk_limit:{submitterId}&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The allowed instruments universe (Redis Set under &lt;code&gt;compliance:instrument:allowed&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a &lt;code&gt;TradeIncomingEvent&lt;/code&gt; arrives on the Kafka consumer, the three compliance checks hit Redis exclusively. PostgreSQL never gets queried during the hot path. This matters because Redis operations complete in microseconds. A PostgreSQL query across a network involves disk I/O and connection overhead that you cannot afford to put on every single trade.&lt;/p&gt;

&lt;p&gt;The three checks run in sequence with short-circuit logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;ComplianceCheckResponse&lt;/span&gt; &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TradeCheckRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Running compliance check for trade {}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTradeId&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="nc"&gt;LocalDateTime&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LocalDateTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Check 1: counterparty approval&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;rulesCacheService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isCounterpartyApproved&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCounterpartyId&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTradeId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"COUNTERPARTY"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"Counterparty "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCounterpartyId&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" is not on the approved list"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Check 2: notional risk limit&lt;/span&gt;
    &lt;span class="nc"&gt;Optional&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;BigDecimal&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rulesCacheService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRiskLimit&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSubmittedBy&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTradeId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"RISK_LIMIT"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"No risk limit configured for submitter "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSubmittedBy&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getNotionalValue&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;compareTo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTradeId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"RISK_LIMIT"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"Notional "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getNotionalValue&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" exceeds limit of "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
            &lt;span class="s"&gt;" for submitter "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSubmittedBy&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Check 3: instrument eligibility&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;rulesCacheService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isInstrumentAllowed&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInstrument&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTradeId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"INSTRUMENT"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"Instrument "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInstrument&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" is not in the allowed instruments universe"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;persistResult&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTradeId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"VALIDATED"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"PASS"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Trade {} passed all compliance checks"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTradeId&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ComplianceCheckResponse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTradeId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First failure short-circuits. No point running instrument eligibility if the counterparty is already blocked.&lt;/p&gt;

&lt;p&gt;The compliance service also exposes REST endpoints for rule management: adding/removing counterparties, updating notional limits, adding instruments. These write to PostgreSQL and update the relevant Redis keys. The dashboard's compliance panel calls these endpoints directly.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Execution Service: Simulating Real Market Pricing
&lt;/h2&gt;

&lt;p&gt;The execution service receives &lt;code&gt;TradeValidatedEvent&lt;/code&gt; messages and is responsible for pricing the trade.&lt;/p&gt;

&lt;p&gt;Valoris supports real ISINs with seeded base prices. For each instrument, the service applies a plus or minus 0.5% random spread to simulate market movement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;BigDecimal&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;BASE_PRICES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"US0378331005"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BigDecimal&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"189.50"&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;// Apple&lt;/span&gt;
    &lt;span class="s"&gt;"US5949181045"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BigDecimal&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"415.20"&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;// Microsoft&lt;/span&gt;
    &lt;span class="s"&gt;"US02079K3059"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BigDecimal&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"175.80"&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;// Alphabet&lt;/span&gt;
    &lt;span class="s"&gt;"US4592001014"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BigDecimal&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"188.90"&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;// IBM&lt;/span&gt;
    &lt;span class="s"&gt;"US912828ZL9"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BigDecimal&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"99.85"&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;   &lt;span class="c1"&gt;// US Treasury 2Y&lt;/span&gt;
    &lt;span class="s"&gt;"US9128284Y00"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BigDecimal&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"98.60"&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;   &lt;span class="c1"&gt;// US Treasury 5Y&lt;/span&gt;
    &lt;span class="s"&gt;"XS2314659447"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BigDecimal&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"100.25"&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;// Emirates NBD bond&lt;/span&gt;
    &lt;span class="s"&gt;"AEA007601011"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BigDecimal&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"8.42"&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;    &lt;span class="c1"&gt;// Emaar Properties (AED)&lt;/span&gt;
    &lt;span class="s"&gt;"AEA000301011"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BigDecimal&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"14.76"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;// First Abu Dhabi Bank (AED)&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;BigDecimal&lt;/span&gt; &lt;span class="nf"&gt;getPrice&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;isin&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;BigDecimal&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;BASE_PRICES&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getOrDefault&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isin&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BigDecimal&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"100.00"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;spreadFactor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ThreadLocalRandom&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;current&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;nextDouble&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;multiply&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BigDecimal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;valueOf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spreadFactor&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setScale&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;RoundingMode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;HALF_UP&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The execution service assigns a venue to each trade drawn from a realistic set for Gulf markets: &lt;code&gt;DIFC-DARK-POOL&lt;/code&gt;, &lt;code&gt;DFM&lt;/code&gt; (Dubai Financial Market), and &lt;code&gt;NASDAQ-DUBAI&lt;/code&gt;. The venue field is mandatory in MiFID II transaction reports, so this domain detail matters.&lt;/p&gt;

&lt;p&gt;The resulting &lt;code&gt;TradeExecutedEvent&lt;/code&gt; includes executionPrice, executionTimestamp, and venue. These three fields are immutable from this point forward. Downstream services reference them but cannot modify them. This is the principle of event immutability: past events are facts, not suggestions.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Confirmation Service: Modeling the Failure Path
&lt;/h2&gt;

&lt;p&gt;Most toy projects ignore failure modes. The confirmation service exists specifically to model one.&lt;/p&gt;

&lt;p&gt;In real markets, bilateral confirmation is a process where both sides of a trade independently report what they believe they agreed to. Mismatches happen when one side reports a slightly different price than the other due to rounding, latency, or fat-finger errors.Valoris simulates the counterparty-side confirmation response with a&lt;br&gt;
probabilistic mismatch.&lt;/p&gt;

&lt;p&gt;In Valoris, the confirmation service applies a 5% random mismatch rate on the execution price, introducing a plus or minus 2% variance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="no"&gt;MISMATCH_PROBABILITY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="no"&gt;MISMATCH_DEVIATION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.02&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;TradeConfirmedEvent&lt;/span&gt; &lt;span class="nf"&gt;confirm&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TradeExecutedEvent&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="n"&gt;isMismatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ThreadLocalRandom&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;current&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;nextDouble&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;MISMATCH_PROBABILITY&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nc"&gt;BigDecimal&lt;/span&gt; &lt;span class="n"&gt;confirmedPrice&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;mismatchReason&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isMismatch&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;deviation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ThreadLocalRandom&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;current&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;nextBoolean&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="no"&gt;MISMATCH_DEVIATION&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;confirmedPrice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getExecutionPrice&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;multiply&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BigDecimal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;valueOf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deviation&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setScale&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;RoundingMode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;HALF_UP&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"MISMATCHED"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;mismatchReason&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"Counterparty confirmed price %s differs from execution price %s"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;confirmedPrice&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getExecutionPrice&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;confirmedPrice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getExecutionPrice&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"CONFIRMED"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;mismatchReason&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// ... persist and publish&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A mismatched trade enters &lt;code&gt;MISMATCHED&lt;/code&gt; status and is exposed via a REST endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET :8084/api/confirmations/mismatched
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dashboard displays these trades with a warning state. A trader or operations staff can view the mismatch detail and resolve it manually. Only confirmed trades (status &lt;code&gt;CONFIRMED&lt;/code&gt;) advance to settlement. Mismatched trades do not.&lt;/p&gt;

&lt;p&gt;This failure path handling is what separates an engineering project from a demo. Real systems break in predictable ways. The architecture must handle it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Settlement Service: T+2 and Position Netting
&lt;/h2&gt;

&lt;p&gt;Settlement is where the asset and cash actually change hands. T+2 means two business days after execution, not two calendar days. Weekends are skipped.&lt;/p&gt;

&lt;p&gt;The settlement service implements this with a Spring &lt;code&gt;@Scheduled&lt;/code&gt; task that runs every 60 seconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Scheduled&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fixedDelay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60000&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@Transactional&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;settleMaturedTrades&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;LocalDate&lt;/span&gt; &lt;span class="n"&gt;today&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LocalDate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Settlement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;due&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;settlementRepository&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findBySettlementStatusAndSettlementDateLessThanEqual&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PENDING"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;due&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Settlement scheduler: {} trade(s) due for settlement on or before {}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;due&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Settlement&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;due&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setSettlementStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SETTLED"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;settlementRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;producer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;publish&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TradeSettledEvent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTradeId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInstrument&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSide&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getQuantity&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCounterpartyId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCurrency&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getNotionalValue&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSubmittedBy&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getExecutionPrice&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getExecutionVenue&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSettlementDate&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"SETTLED"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;LocalDateTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;));&lt;/span&gt;

        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Trade {} settled on {}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTradeId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Position queries are exposed via REST:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET :8085/api/positions/{counterpartyId}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This endpoint returns the current net position across all instruments for a given counterparty, matching the data structure a real prime broker would show a fund client.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Reporting Service: Mirroring Regulatory Requirements
&lt;/h2&gt;

&lt;p&gt;The reporting service is the final stage of the pipeline. It consumes both &lt;code&gt;trades.settled&lt;/code&gt; and &lt;code&gt;trades.rejected&lt;/code&gt;, since both settled and rejected trades require records in real regulatory frameworks.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;TradeReport&lt;/code&gt; model mirrors the fields required by EMIR and MiFID II Transaction Reporting. Here is what &lt;code&gt;GET /api/reports/{tradeId}&lt;/code&gt; returns for a settled trade:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tradeId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"f47ac10b-58cc-4372-a567-0e02b2c3d479"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"instrument"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"US0378331005"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"side"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"BUY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"counterpartyId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CP-001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"notionalValue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;94875.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"submittedBy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"trader.dubai"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"executionPrice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;189.750000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"executionVenue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NASDAQ-DUBAI"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"settlementDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-04-02"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"settlementStatus"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SETTLED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"settledAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-04-02T00:01:03.112Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reportGeneratedAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-04-02T00:01:03.215Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The LEI (Legal Entity Identifier) is the ISO standard identifier for firms participating in financial markets. Every regulated entity has one. Valoris's data model is structured to accommodate this field in a production extension without structural changes.&lt;/p&gt;

&lt;p&gt;The reporting service also powers analytics. Endpoints expose volume aggregated by instrument, by counterparty, and by venue. The dashboard's analytics section visualizes these using Recharts: bar charts for volume by ISIN, pie charts for distribution by counterparty, area charts for settlement activity over time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Database Isolation: One Schema Per Service
&lt;/h2&gt;

&lt;p&gt;Each service owns its own PostgreSQL database. The compliance service has no access to the execution service's database. The settlement service has no access to the confirmation service's database. This is strict.&lt;/p&gt;

&lt;p&gt;This is not just a microservices best practice. It is a business requirement in regulated financial infrastructure. When a regulator audits the compliance team's systems, they should be auditing only the compliance service's database. Cross-service database joins are a regulatory and operational risk.&lt;/p&gt;

&lt;p&gt;In practice this means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No foreign keys across service boundaries&lt;/li&gt;
&lt;li&gt;No shared ORM models&lt;/li&gt;
&lt;li&gt;No cross-database queries in the application layer&lt;/li&gt;
&lt;li&gt;Event schemas (the Kafka event DTOs) are the only shared contract&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the execution service needs compliance data, it gets it from the &lt;code&gt;TradeValidatedEvent&lt;/code&gt; payload that was published when compliance passed. It does not query the compliance database.&lt;/p&gt;




&lt;h2&gt;
  
  
  The React Dashboard: Live Pipeline Visibility
&lt;/h2&gt;

&lt;p&gt;The dashboard is built with React 18 and Vite, served in production by nginx. It has three sections.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trade Pipeline View.&lt;/strong&gt; A live table of all trades with columns for TradeID, Instrument, Side, Notional, Current Stage, and Status. Color coding: green for progressing trades, red for rejected or failed, yellow for pending states like &lt;code&gt;MISMATCHED&lt;/code&gt; or &lt;code&gt;PENDING_SETTLEMENT&lt;/code&gt;. The table auto-refreshes by polling the FIX gateway's trade list endpoint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trade Detail View.&lt;/strong&gt; Click any trade to open a side panel showing the full event timeline from submission through reporting. Each stage shows its timestamp and the full JSON payload, expandable inline. This is how an operations team would investigate a problem trade in a real system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analytics.&lt;/strong&gt; Recharts visualizations driven by the reporting service's analytics endpoints. Volume by instrument, volume by counterparty, settlement summary.&lt;/p&gt;




&lt;h2&gt;
  
  
  Running the Entire System
&lt;/h2&gt;

&lt;p&gt;The entire stack (PostgreSQL, Redis, Zookeeper, Kafka, six Spring Boot services, and the React frontend) starts with one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All 11 containers start in the correct order with health checks. The dashboard is available at &lt;code&gt;http://localhost:5173&lt;/code&gt;. Submit a trade via the dashboard or directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8081/api/trades &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "instrument": "US0378331005",
    "side": "BUY",
    "quantity": 500,
    "counterpartyId": "CP-001",
    "currency": "USD",
    "notionalValue": 94750.00,
    "submittedBy": "trader.dubai"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Watch it progress through the pipeline in real time on the dashboard.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This System Demonstrates
&lt;/h2&gt;

&lt;p&gt;The firms that care most about this kind of project (ION Group, Murex, Finastra, Broadridge, FIS, and the in-house technology teams at major banks) are all hiring engineers who understand the business domain, not just the technology.&lt;/p&gt;

&lt;p&gt;Anyone can build REST microservices. Not many graduate-level developers can explain why T+2 settlement exists (it is a historical artifact from paper certificate delivery that modern systems are slowly moving to T+1), what an LEI is and why it is mandatory in regulatory reports, why compliance rules need to live in Redis rather than being fetched from PostgreSQL on every check, or what happens operationally when a bilateral confirmation mismatch occurs.&lt;/p&gt;

&lt;p&gt;Valoris is not a demo. It is a working model of infrastructure that processes trillions of dollars of trades every day across global financial markets.&lt;/p&gt;

&lt;p&gt;The code is at &lt;a href="https://github.com/Ra9huvansh/Valoris-Systems" rel="noopener noreferrer"&gt;github.com/Ra9huvansh/Valoris-Systems&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Raghuvansh is a pre-final year Computer Science student at JIIT Noida targeting capital markets infrastructure and blockchain engineering roles in Dubai, Hong Kong, and Shanghai.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>kafka</category>
      <category>systemdesign</category>
      <category>distributedsystems</category>
    </item>
  </channel>
</rss>
