Functional Requirements
- Multi-Holding Wallet Balance: Holds concurrent balance registers for USD, EUR, GBP, and JPY in a single customer entity.
- Exchange Conversion Quote Locks: Offers dynamic lock-in pricing quotes with TTL protection bounds before conversion execution.
- Precision Rounding Safeguards: Utilizes Banker's Rounding (Round-Half-To-Even) to prevent floating-point calculation drift.
- Cross-Currency Settlements: Debits sending wallets in sending assets, calculates conversion rates with fees, and credits targets in foreign denominations atomically.
Production reference implementations demonstrating currency holdings maps, Banker's rounding calculations, and cross-currency FX flows:
// ─── JAVA BLUEPRINT ──────────────────────────────────────────────────────────
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.*;
enum CurrencyCode {
USD, EUR, GBP, JPY
}
class CurrencyBalance {
private final String walletId;
private final Map<CurrencyCode, BigDecimal> balances = new ConcurrentHashMap<>();
public CurrencyBalance(String walletId) {
this.walletId = walletId;
for (CurrencyCode code : CurrencyCode.values()) {
balances.put(code, BigDecimal.ZERO.setScale(4, RoundingMode.HALF_EVEN));
}
}
public String getWalletId() { return walletId; }
public BigDecimal getBalance(CurrencyCode code) {
return balances.getOrDefault(code, BigDecimal.ZERO);
}
public void credit(CurrencyCode code, BigDecimal amount) {
balances.merge(code, amount.setScale(4, RoundingMode.HALF_EVEN), BigDecimal::add);
}
public void debit(CurrencyCode code, BigDecimal amount) {
BigDecimal current = getBalance(code);
if (current.compareTo(amount) < 0) {
throw new IllegalArgumentException("Insufficient funds in " + code);
}
balances.put(code, current.subtract(amount).setScale(4, RoundingMode.HALF_EVEN));
}
public Map<CurrencyCode, BigDecimal> getAllBalances() {
return Collections.unmodifiableMap(balances);
}
}
class FXQuote {
private final String quoteId;
private final CurrencyCode from;
private final CurrencyCode to;
private final BigDecimal rate;
private final BigDecimal fee;
private final long expiresAt;
public FXQuote(CurrencyCode from, CurrencyCode to, BigDecimal rate, BigDecimal fee, long ttlMs) {
this.quoteId = UUID.randomUUID().toString();
this.from = from;
this.to = to;
this.rate = rate;
this.fee = fee;
this.expiresAt = System.currentTimeMillis() + ttlMs;
}
public String getQuoteId() { return quoteId; }
public CurrencyCode getFrom() { return from; }
public CurrencyCode getTo() { return to; }
public BigDecimal getRate() { return rate; }
public BigDecimal getFee() { return fee; }
public boolean isExpired() { return System.currentTimeMillis() > expiresAt; }
}
class FXRateService {
private final Map<String, BigDecimal> exchangeRates = new ConcurrentHashMap<>();
private final double spreadMarkup = 0.015; // 1.5% transaction spread fee
public FXRateService() {
exchangeRates.put("USD_EUR", new BigDecimal("0.9250"));
exchangeRates.put("USD_GBP", new BigDecimal("0.7880"));
exchangeRates.put("USD_JPY", new BigDecimal("155.60"));
exchangeRates.put("EUR_USD", new BigDecimal("1.0810"));
exchangeRates.put("GBP_USD", new BigDecimal("1.2690"));
exchangeRates.put("JPY_USD", new BigDecimal("0.0064"));
}
public BigDecimal getMidMarketRate(CurrencyCode from, CurrencyCode to) {
if (from == to) return BigDecimal.ONE;
String pair = from.name() + "_" + to.name();
BigDecimal rate = exchangeRates.get(pair);
if (rate == null) {
BigDecimal fromUSD = exchangeRates.get(from.name() + "_USD");
BigDecimal toUSD = exchangeRates.get("USD_" + to.name());
if (fromUSD != null && toUSD != null) {
return fromUSD.multiply(toUSD).setScale(6, RoundingMode.HALF_EVEN);
}
throw new IllegalArgumentException("Unsupported currency pair: " + pair);
}
return rate;
}
public FXQuote generateQuote(CurrencyCode from, CurrencyCode to, long ttlMs) {
BigDecimal midRate = getMidMarketRate(from, to);
BigDecimal feeRate = new BigDecimal(String.valueOf(spreadMarkup));
return new FXQuote(from, to, midRate, feeRate, ttlMs);
}
}
class ConversionService {
public BigDecimal convert(BigDecimal sourceAmount, FXQuote quote) {
if (quote.isExpired()) {
throw new IllegalStateException("FX quote has expired");
}
BigDecimal grossTarget = sourceAmount.multiply(quote.getRate());
BigDecimal feeDeduction = grossTarget.multiply(quote.getFee());
BigDecimal netTarget = grossTarget.subtract(feeDeduction);
return netTarget.setScale(4, RoundingMode.HALF_EVEN);
}
}
class CrossCurrencyTransfer {
private final FXRateService fxRateService;
private final ConversionService conversionService;
public CrossCurrencyTransfer(FXRateService fxRateService, ConversionService conversionService) {
this.fxRateService = fxRateService;
this.conversionService = conversionService;
}
public synchronized boolean executeTransfer(CurrencyBalance sender, CurrencyBalance receiver,
CurrencyCode sendCurrency, CurrencyCode recvCurrency,
BigDecimal sendAmount) {
FXQuote quote = fxRateService.generateQuote(sendCurrency, recvCurrency, 10000);
BigDecimal recvAmount = conversionService.convert(sendAmount, quote);
try {
sender.debit(sendCurrency, sendAmount);
receiver.credit(recvCurrency, recvAmount);
logTransfer(sender.getWalletId(), receiver.getWalletId(), sendAmount, sendCurrency, recvAmount, recvCurrency, quote);
return true;
} catch (Exception e) {
return false;
}
}
private void logTransfer(String from, String to, BigDecimal sAmt, CurrencyCode sCur,
BigDecimal rAmt, CurrencyCode rCur, FXQuote quote) {
System.out.printf("FX TRANSFER: Sent %s %s -> Recv %s %s [Rate: %s, Fee spread: %s]%n",
sAmt, sCur, rAmt, rCur, quote.getRate(), quote.getFee());
}
}
public class Main {
public static void main(String[] args) {
System.out.println("=== MULTI-CURRENCY WALLET DEMO (JAVA) ===");
FXRateService fxRateService = new FXRateService();
ConversionService conversionService = new ConversionService();
CrossCurrencyTransfer transferService = new CrossCurrencyTransfer(fxRateService, conversionService);
CurrencyBalance alice = new CurrencyBalance("W_ALICE");
CurrencyBalance bob = new CurrencyBalance("W_BOB");
alice.credit(CurrencyCode.USD, new BigDecimal("500.0000"));
bob.credit(CurrencyCode.EUR, new BigDecimal("100.0000"));
System.out.println("Initial Alice USD: $" + alice.getBalance(CurrencyCode.USD));
System.out.println("Initial Bob EUR: €" + bob.getBalance(CurrencyCode.EUR));
System.out.println("\n--- Performing cross-currency transfer of 100 USD to EUR ---");
boolean success = transferService.executeTransfer(alice, bob, CurrencyCode.USD, CurrencyCode.EUR, new BigDecimal("100.0000"));
System.out.println("Transfer Success: " + success);
System.out.println("\nAlice Balances after transfer:");
alice.getAllBalances().forEach((k, v) -> {
if (v.compareTo(BigDecimal.ZERO) > 0) {
System.out.println(k + ": " + v);
}
});
System.out.println("\nBob Balances after transfer:");
bob.getAllBalances().forEach((k, v) -> {
if (v.compareTo(BigDecimal.ZERO) > 0) {
System.out.println(k + ": " + v);
}
});
}
}