Originally published on Fone.
One unit priced at 1.005 currency units cannot be represented exactly in a currency with two decimal places. An investor portal needs an explicit rounding rule before it can quote the amount due or the redemption payment.
Aurum RWA's current subscription model rounds gross amounts up to the currency's smallest unit. Its redemption model rounds gross amounts down. Both round the proportional fee up, then add a fixed fee. Subscription fees increase the amount payable; redemption fees reduce the amount to settle. Those rules were checked in the local source on 29 October 2026.
This lab turns those arithmetic choices into a runnable Python calculator and a quote acceptance check. It uses synthetic prices and investor identifiers. You will calculate both directions, reject a changed quote, and test amounts larger than ordinary floating-point integers can reliably represent. Download the code, tests and README.
Run the two directions
The archive needs Python only. Verification used Python 3.13.3 with its standard library.
python3 quotes.py python3 -m unittest -v The demo uses 100 raw units with two token decimals, which represents one whole token. The price is the string 1.005, the currency has two decimals, the fixed fee is one minor unit, and the proportional fee is one basis point. The values are deliberately small enough to expose rounding.
| Operation | Gross minor units | Fee minor units | Net minor units |
|---|---|---|---|
| Subscription | 101 | 2 | 103 |
| Redemption | 100 | 2 | 98 |
The five-unit difference between subscription payable and redemption receivable follows from this synthetic fee schedule and the two gross rounding rules. It is not a return estimate. The example performs no purchase, redemption or bank transfer.
In a portal, these amounts deserve separate labels. An investor can check the quoted asset value, the service charge and the amount that changes hands. A single unlabeled total leaves operations staff reconstructing the calculation whenever somebody asks about a difference.
Keep decimal input as text
The calculator accepts the price as a plain decimal string. It rejects exponent notation, negative values and excess precision. That keeps parsing choices visible at the boundary rather than silently rounding an input before the calculation starts.
def scaled(text, scale): if not isinstance(text, str) or not re.fullmatch(r"(?:0|[1-9][0-9]*)(?:\.[0-9]+)?", text): raise ValueError("plain decimal string required") whole, _, fraction = text.partition(".") if len(fraction) > scale: raise ValueError("excess precision") return int(whole) * 10**scale + int(fraction.ljust(scale, "0") or "0") The parser creates a scaled integer using eighteen fractional places. Token quantity and currency scale remain separate. A quantity of 100 means something different for a token with two decimals than for one with six, even when the displayed price is identical.
Python integers let this teaching program retain arbitrarily large intermediate values. A deployed integration still needs storage limits and input bounds appropriate to its database and contracts. The Aurum source validates its resulting monetary strings through its own money parser. The downloadable example does not reproduce every product constraint or database column.
Make rounding direction a named choice
The calculator takes subscription or redemption explicitly. It computes the quotient and remainder of the gross amount, then increments the quotient only when a subscription has a remainder.
def quote(terms, kind, units, wallet): if kind not in {"subscription", "redemption"}: raise ValueError("unknown kind") if natural(units) == 0 or not isinstance(wallet, str) or not wallet: raise ValueError("positive units and wallet required") for value in (terms.token_decimals, terms.currency_scale, terms.fixed_minor, terms.fee_bps, terms.valid_from, terms.expires_at): natural(value) if terms.token_decimals > 18 or terms.currency_scale > 18 or terms.fee_bps > 10000: raise ValueError("unsupported scale or fee") if terms.expires_at <= terms.valid_from: raise ValueError("invalid validity interval") numerator = units * scaled(terms.price, 18) * 10**terms.currency_scale denominator = 10**(terms.token_decimals + 18) gross, remainder = divmod(numerator, denominator) if kind == "subscription" and remainder: gross += 1 fee = terms.fixed_minor + (gross * terms.fee_bps + 9999) // 10000 net = gross + fee if kind == "subscription" else gross - fee if gross <= 0 or net <= 0: raise ValueError("nonpositive amount") snapshot = {"terms": asdict(terms), "kind": kind, "units": str(units), "wallet": wallet, "gross_minor": str(gross), "fee_minor": str(fee), "net_minor": str(net)} encoded = json.dumps(snapshot, sort_keys=True, separators=(",", ":")).encode() return {**snapshot, "hash": sha256(encoded).hexdigest()} The proportional fee uses integer ceiling division by 10,000. A nonzero fractional fee therefore rounds to at least one minor unit. That is why one basis point on a gross amount of 101 produces one minor unit before the fixed fee is added.
For redemption, the program rejects a fee that consumes the entire gross amount. Returning zero or a negative payout would leave the next stage with an invalid settlement obligation. The product's redemption model likewise checks that gross exceeds the fee.
The returned snapshot stores gross, fee and net as strings. A later JavaScript client can display or parse them deliberately without receiving a large JSON number that may already have lost precision. The model also carries token decimals, currency scale and the original pricing inputs inside its terms record.
Recompute from authoritative terms
A quote hash identifies a particular snapshot. Anyone who can edit a snapshot can also calculate a new unkeyed hash. The hash in this exercise therefore serves as a stable fingerprint; acceptance relies on recomputing the full quote from terms supplied by the server-side caller.
def accept(snapshot, authoritative_terms, kind, units, wallet, now): natural(now) if not authoritative_terms.valid_from <= now < authoritative_terms.expires_at: raise ValueError("quote outside validity window") expected = quote(authoritative_terms, kind, units, wallet) if snapshot != expected: raise ValueError("quote changed; review again") return expected["net_minor"] The check binds the accepted result to direction, wallet and quantity. It also compares every term in the current version, including the price, fees and document version. A stale browser cannot turn an old snapshot into a current offer by changing only the displayed total.
Time comes from an explicit now argument so tests can exercise the boundary precisely. The valid interval includes its start and excludes its end. A quote expiring at 200 is accepted at 199 and rejected at 200. An application should supply that time from its trusted service, rather than accept the customer's browser clock.
The product's subscription snapshot expires at the earlier of quote validity and offering end. The lab takes one effective expiry as an input, so an adapter must compute that effective time before creating Terms. Its SHA-256 format is original teaching code and is not compatible with Aurum's wire format or typed authorizations.
Verify money with an independent calculation
Twelve named tests passed after extracting the ZIP into a fresh temporary directory. The arithmetic grid checks 300 quantities in both directions against Python's exact Fraction type. That gives the integer implementation a separate mathematical reference for gross rounding and fees.
A large-value case uses 2**60 + 1 raw units with a price of one and zero fees. The expected result retains that final one. Other tests reject altered totals, changed prices and a different wallet. The expiry test checks both sides of the validity interval, while the fee test rejects a redemption whose net would be nonpositive.
Try changing the fee rounding expression to ordinary truncating division. The small subscription case should fail because its fee drops from two minor units to one. Try rounding redemption gross up and the paired fixture should fail as well. These mutations make the acceptance criteria observable instead of relying on a reviewer recognizing the formula.
Use the calculator in an implementation review
The earlier RWA redemption article follows the workflow from locked units to settlement evidence. This exercise concentrates on the numeric obligation that enters that workflow. Quote correctness and payment completion need separate checks.
For a team evaluating Aurum RWA, bring one proposed asset denomination, currency and fee schedule to the walkthrough. Ask the team to show the smallest permitted subscription, the smallest viable redemption and a quote that expires while the investor is reviewing it. Compare the displayed numbers with an independently calculated fixture before connecting payment or identity services.
Aurum supplies software for the investor and operations workflow. Asset rights and participation conditions belong in the project's actual documents. This calculator verifies a narrow set of software rules and provides no evidence about asset value, regulatory eligibility or completed payment. Its useful output is a set of exact amounts and failing cases that your integration team can carry into acceptance testing.
Prepared with AI assistance. The source wording and code are preserved.


