rust-fintech
Skill1 starsChanged 6 months agoMITView on GitHub
Rust Fintech and financial system design guidelines. Use when: - User works with money, financial transactions, ledger accounts, or currency - Handling high-precision calculations (e.g. rust_decimal) - Enforcing auditability, immutability, and event sourcing in ledger designs - Writing code requiring checked arithmetic to prevent overflow losses - Building payment processing, settlement, or reconciliation systems - Implementing FX conversion, interest calculations, or fee computation - Designing idempotent payment flows or webhook verification
In this repository24 other files it gives its agents.
- rust-api-designSkill
- rust-asyncSkill
- rust-axumSkill
- rust-cliSkill
- rust-cloud-nativeSkill
- rust-concurrencySkill
- rust-databaseSkill
- rust-dioxusSkill
- rust-ecosystemSkill
- rust-embeddedSkill
- rust-error-handlingSkill
- rust-masterySkill
- rust-memory-optimizationSkill
- rust-mlSkill
- rust-networkingSkill
- rust-observabilitySkill
- rust-ownershipSkill
- rust-design-patternsSkill
- rust-performanceSkill
- rust-securitySkill
- rust-testingSkill
- rust-type-systemSkill
- rust-unsafeSkill
- rust-wasmSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
14 rules. About 0 are common, 13 are distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈13
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 28
Never use floats (f32, f64) to represent monetary values due to rounding errors and precision loss.
- Distinctive: line 77
Ensure that entries balance exactly: the sum of debits must equal the sum of credits.
- Distinctive: line 127
Always use checked, wrapping, or saturating arithmetic methods rather than standard operators if there's any chance of input overflow.
- Distinctive: line 135
Prefer integer minor units for ledgers and settlement.
- Distinctive: line 147
Every posted transaction must be:
- Distinctive: line 172
Never use floats for money.
- Distinctive: line 175
Keep FX rates with source, timestamp, precision, and effective interval.
- Distinctive: line 304
Test duplicate idempotency keys.
- Distinctive: line 305
Test reversal entries sum to zero.
- Distinctive: line 306
Test currency mismatch errors.
- Distinctive: line 309
Test rounding at domain boundaries with known trap values.
- Distinctive: line 310
Test FX rate staleness detection.
- Distinctive: line 337
All arithmetic uses checked operations.
- Distinctive: line 338
Test suite covers currency mismatch, overflow, and rounding edge cases.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Quick Navigation
- references/decimals.md
- references/ledger.md
Rust Financial & FinTech Systems Guide
Best practices for building accurate, compliant, and deterministic financial systems in Rust.
Core Rules & Constraints
1. Zero Floating-Point Tolerance
Never use floats (f32, f64) to represent monetary values due to rounding errors and precision loss. Instead, use rust_decimal::Decimal.
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
// Bad: f64 is subject to rounding inaccuracies
let price: f64 = 19.99;
// Good: Exact fixed-point precision
let price: Decimal = dec!(19.99);2. Money Value Object
Encapsulate currency and amount into a single type-safe domain boundary. Prevent addition or arithmetic operations between mismatched currencies.
use rust_decimal::Decimal;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Currency {
USD,
EUR,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Money {
amount: Decimal,
currency: Currency,
}
impl Money {
pub fn new(amount: Decimal, currency: Currency) -> Self {
Self { amount, currency }
}
pub fn add(&self, other: &Money) -> Result<Money, &'static str> {
if self.currency != other.currency {
return Err("Currency mismatch");
}
let new_amount = self.amount.checked_add(other.amount)
.ok_or("Arithmetic overflow")?;
Ok(Money::new(new_amount, self.currency.clone()))
}
}3. Double-Entry Integrity
Ensure that entries balance exactly: the sum of debits must equal the sum of credits.
pub struct LedgerEntry {
pub account_id: String,
pub amount: Decimal, // Positive for debit, negative for credit
}
pub struct Transaction {
pub id: uuid::Uuid,
pub entries: Vec<LedgerEntry>,
}
impl Transaction {
pub fn validate(&self) -> Result<(), &'static str> {
let sum: Decimal = self.entries.iter().map(|e| e.amount).sum();
if sum.is_zero() {
Ok(())
} else {
Err("Transaction is unbalanced: total sum of entries must equal zero")
}
}
}4. Immutable Event Sourcing
Ledger entries must be append-only and immutable. Rather than modifying states in-place, record discrete event entries over time.
pub enum AccountEvent {
Deposited { amount: Decimal, timestamp: chrono::DateTime<chrono::Utc> },
Withdrawn { amount: Decimal, timestamp: chrono::DateTime<chrono::Utc> },
}
pub struct Account {
id: String,
balance: Decimal,
}
impl Account {
pub fn apply(&mut self, event: &AccountEvent) {
match event {
AccountEvent::Deposited { amount, .. } => self.balance += amount,
AccountEvent::Withdrawn { amount, .. } => self.balance -= amount,
}
}
}5. Safe Math
Always use checked, wrapping, or saturating arithmetic methods rather than standard operators if there's any chance of input overflow.
checked_add/checked_subsaturating_add/saturating_sub
Financial Correctness Rules
Money Representation
Prefer integer minor units for ledgers and settlement. Use decimal types for pricing, rates, tax, and calculations where scale must be preserved.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct AmountMinor(i64);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Currency { Usd, Eur, Gbp }Ledger Invariants
Every posted transaction must be:
- balanced per currency;
- immutable after posting;
- idempotent at external boundaries;
- traceable to actor/request/source;
- reversible through compensating entries, not mutation.
Idempotency Pattern
pub struct IdempotencyKey(String);
pub async fn post_payment(req: PaymentRequest, key: IdempotencyKey) -> Result<Receipt, Error> {
// 1. reserve key in transaction
// 2. if existing response, return it
// 3. validate and post ledger entries
// 4. persist response tied to key
// 5. commit
todo_domain_example()
}Decimal/Rounding Rules
- Never use floats for money.
- Round exactly once at the domain-approved boundary.
- Store the rounding policy with calculated results when audit matters.
- Keep FX rates with source, timestamp, precision, and effective interval.
FX Conversion Pattern
pub struct FxRate {
pub base: Currency,
pub quote: Currency,
pub rate: Decimal,
pub source: &'static str,
pub fetched_at: chrono::DateTime<chrono::Utc>,
}
impl FxRate {
pub fn convert(&self, amount: Decimal) -> Result<Decimal, &'static str> {
(amount * self.rate).round_dp(4).checked_sub(Decimal::ZERO)
.ok_or("FX conversion overflow")
}
}
pub struct FxProvider {
rates: HashMap<(Currency, Currency), FxRate>,
}
impl FxProvider {
pub fn convert(&self, from: Currency, to: Currency, amount: Decimal) -> Result<Money, FxError> {
if from == to {
return Ok(Money::new(amount, from));
}
let rate = self.rates.get(&(from, to))
.ok_or(FxError::RateNotFound { from, to })?;
if rate.fetched_at + chrono::Duration::minutes(5) < chrono::Utc::now() {
return Err(FxError::StaleRate);
}
let converted = amount * rate.rate;
Ok(Money::new(converted.round_dp(2), to))
}
}Fee Calculation
pub enum FeeStructure {
Flat { amount: Decimal },
Percentage { rate: Decimal, min: Option<Decimal>, max: Option<Decimal> },
Tiered { tiers: Vec<(Decimal, Decimal)> },
}
impl FeeStructure {
pub fn calculate(&self, amount: Decimal) -> Result<Decimal, FeeError> {
match self {
FeeStructure::Flat { amount: fee } => Ok(*fee),
FeeStructure::Percentage { rate, min, max } => {
let raw = (amount * rate).round_dp(2);
let bounded = match (min, max) {
(Some(lo), _) if raw < *lo => *lo,
(_, Some(hi)) if raw > *hi => *hi,
_ => raw,
};
Ok(bounded)
}
FeeStructure::Tiered { tiers } => {
for (threshold, rate) in tiers.iter().rev() {
if amount >= *threshold {
return Ok((amount * rate).round_dp(2));
}
}
Ok(Decimal::ZERO)
}
}
}
}Interest Calculation
pub fn compound_interest(
principal: Decimal,
annual_rate: Decimal,
periods: u32,
compounds_per_year: u32,
) -> Result<Decimal, &'static str> {
let rate_per_period = annual_rate / Decimal::from(compounds_per_year);
let total_periods = periods * compounds_per_year;
let mut balance = principal;
for _ in 0..total_periods {
let interest = (balance * rate_per_period).round_dp(2);
balance = balance.checked_add(interest)
.ok_or("Interest calculation overflow")?;
}
Ok(balance - principal) // total interest earned
}Webhook Signature Verification
use hmac::{Hmac, Mac};
use sha2::Sha256;
type HmacSha256 = Hmac<Sha256>;
pub fn verify_webhook_signature(
payload: &[u8],
signature: &str,
secret: &[u8],
) -> Result<(), WebhookError> {
let expected = hex::decode(signature)
.map_err(|_| WebhookError::InvalidSignature)?;
let mut mac = HmacSha256::new_from_slice(secret)
.map_err(|_| WebhookError::InvalidSecret)?;
mac.update(payload);
mac.verify_slice(&expected)
.map_err(|_| WebhookError::SignatureMismatch)
}Testing Strategy
- Property test ledger balance invariants.
- Test duplicate idempotency keys.
- Test reversal entries sum to zero.
- Test currency mismatch errors.
- Snapshot audit records for required fields.
- Integration test database constraints.
- Test rounding at domain boundaries with known trap values.
- Test FX rate staleness detection.
Anti-Patterns
- Updating account balances without immutable entries.
- Storing money as JSON floats.
- Retrying non-idempotent payment operations.
- Combining currencies in one integer total.
- Hiding rounding in display formatting.
- Using local time for settlement boundaries.
- Expiring idempotency keys too soon (before settlement completes).
- Trusting client-provided FX rates without validation.
Review Prompt
For fintech Rust, verify numeric representation, ledger immutability, idempotency, audit trail completeness, database constraints, and property tests for invariants.
Production Checklist
- Money never uses binary floating point.
- Ledger updates are append-only and transactionally balanced.
- Idempotency keys protect retries at every external boundary.
- Audit records include actor, request/source, timestamp, and correlation ID.
- Rounding policy is explicit and tested at domain boundaries.
- Reversals use compensating entries instead of mutation.
- FX rates have source, timestamp, and staleness checks.
- Webhook signatures are verified with timing-safe comparison.
- All arithmetic uses checked operations.
- Test suite covers currency mismatch, overflow, and rounding edge cases.
References
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

