Version 1.0 — June 2026
Contracts: creditra-credit (credit-line core), gateway-auction (default
liquidation auction)
Target chain: Stellar / Soroban
Audience: protocol engineers, security reviewers, grant evaluators
Creditra is a decentralized credit protocol that prices and sizes credit lines from continuously updated on-chain behavioral signals rather than from overcollateralized deposits. The protocol maintains per-borrower credit lines whose interest rate and credit limit evolve as a deterministic function of a borrower's risk score, utilization, market risk premium, and a configurable piecewise-linear rate formula. Default events are settled through a separate auction contract (English or Dutch mode) using a one-shot, replay-protected cross-contract handoff.
The protocol is implemented as two Soroban WebAssembly contracts totaling
~14.5 KLOC of Rust (contracts/credit/src/lib.rs alone is 5 449 lines), with
≥40 integration test files and current measured line coverage of 98.94 %
(COVERAGE_REPORT.md). The credit contract's release WASM is under a hard
50 KB CI budget and is built with opt-level = "z", full LTO, and stripped
symbols (Cargo.toml).
This document describes the formal credit-pricing model, the per-credit-line
state machine, the storage and event ABI, the cross-contract liquidation
handoff, the oracle circuit-breaker model, and the protocol's known limitations.
All claims are grounded in the actual source under
contracts/credit/src/*.rs and gateway-contract/contracts/auction_contract/src/*.rs
— every formula, constant, and event identifier in this paper is reproducible
from the named file and symbol.
Existing on-chain credit markets — Aave, Compound, MakerDAO, Liquity — require overcollateralization. A borrower wanting to draw 100 USDC must first deposit ≥ 130–150 USD of volatile collateral (ETH, LSTs, BTC). This produces two related failure modes:
- Eligibility collapse. A user who has 100 USDC of net wealth and wants to borrow 100 USDC against future income cannot. Empirically, the overcollateralized model excludes the median wallet on every chain.
- Capital sterility. Collateral cannot be used productively while locked — it earns at most a sub-base lending APY on the very pool it secures. The resulting LTV-to-utilization is an inefficient store of risk-bearing capital.
Anecdotally, this is why the long tail of on-chain credit ends up routed through unsecured social vouching (Goldfinch), credit delegation (Aave/Spark), or structured RWA wrappers. None of these scale to the median wallet on a public network.
Stellar/Soroban gives the protocol primitives Ethereum lacked when Aave was designed:
- Cheap, predictable host environment. Storage tiers (
Instance/Persistent/Temporary) with explicit TTL budgets remove the unbounded-state problem that has driven so much L1 lending into stateless oracle relays. - First-class authorization (
require_auth). Borrower consent is verified at the protocol level, not via ad-hocmsg.senderchecks. - Cross-contract events with structured topics. Off-chain indexers can reliably reconstruct credit-line history without RPC trace heuristics.
- Smaller surface area for upgrade. A single admin-gated
update_current_contract_wasmcall (contracts/credit/src/lib.rs:1330) replaces proxy patterns and storage-rewrite migrations.
Creditra is the protocol design these primitives make possible: per-borrower, algorithmically priced, without collateral as the eligibility predicate.
A credit line in Creditra is the tuple
CreditLineData {
borrower: Address,
credit_limit: i128, // signed for diff math; always >= 0
utilized_amount: i128,
interest_rate_bps: u32, // annualized rate in basis points
risk_score: u32, // 0..=100 (MAX_RISK_SCORE)
status: CreditStatus, // Active|Suspended|Defaulted|Closed|Restricted
last_rate_update_ts: u64,
accrued_interest: i128,
last_accrual_ts: u64,
suspension_ts: u64,
}
(contracts/credit/src/types.rs:173-200)
The two fields a credit-card issuer normally sets — credit_limit and
interest_rate_bps — are not chosen by the borrower. They are produced by a
deterministic protocol function over the borrower's measurable on-chain history
plus an off-chain risk score the admin authority writes (the data feed that
later becomes a decentralized scoring oracle; see §10).
We define:
where b = base_rate_bps, s = slope_bps_per_score, and
r_cap = MAX_INTEREST_RATE_BPS = 10_000 (= 100 % APR), all as configured by
set_rate_formula_config(...)
(contracts/credit/src/lib.rs:1159,
contracts/credit/src/risk.rs:77). When the formula is disabled, the admin
supplies interest_rate_bps directly and the same clamp to r_cap applies.
The credit limit is set at origination and adjusted by
update_risk_parameters(borrower, credit_limit, rate_bps, score)
(contracts/credit/src/risk.rs:207). If the new limit is below the borrower's
current utilized_amount, the status transitions to Restricted instead of
reverting — the borrower can still repay but cannot draw, and the line
auto-cures back to Active on sufficient repayment
(docs/credit.md, see also tests/restricted_status.rs).
Creditra does not require collateral as an eligibility predicate. Credit is
gated by behavioral signal, not by a deposit. The protocol nevertheless exposes
an optional MinCollateralRatioBps (default 15 000 = 150 %) which can be
enforced at draw time
(contracts/credit/src/lib.rs:261-424, step 13;
contracts/credit/src/collateral.rs:34-126). This is the dial that an operator
can turn between "pure unsecured credit" (set ratio to 0) and "fully
collateralized like Aave" (set ratio to 15 000+). The default
configuration ships at 15 000 bps so the contract is safe to deploy in a
conservative mode and progressively loosened as scoring quality improves.
This is the key innovation surface: the credit-extension function is a configurable mixture of behavioral signal and capital signal, parameterized at the protocol level, not hard-coded in the contract.
Interest accrues continuously in math, but the contract only realizes the
accrual on a mutating call (draw, repay, status change, rate change). The model
is documented in docs/interest-accrual.md and implemented in
contracts/credit/src/accrual.rs:87:
where utilized_amount, SECONDS_PER_YEAR = 31_557_600 (Julian year, see
contracts/credit/src/math_utils.rs:60). Floor rounding favors the borrower —
the bias accumulates against protocol revenue, never against user balance — and
is enforced via the Rounding::Floor enum in math_utils.rs:76. There is no
periodic settlement cron, no keeper, no liveness assumption.
The accrual fold has three branches:
-
Active line, current. Rate =
interest_rate_bps. -
Active line, delinquent (past
next_due_ts + grace). Effective rate =min(rate + penalty_surcharge_bps, MAX_INTEREST_RATE_BPS). EmitsPenaltyRateEnteredEventon first delinquent accrual andPenaltyRateExitedEventon cure (contracts/credit/src/accrual.rs, events atcontracts/credit/src/events.rs:278,296). -
Suspended line under a grace policy. Splits
$\Delta t$ intomin(Δt, grace_seconds)and the post-grace remainder. The in-grace portion is waived inFullWaivermode (GraceWaiverMode::FullWaiver = 0) or charged atreduced_rate_bpsinReducedRatemode (contracts/credit/src/types.rs:255-272).
The accrual is folded into utilized_amount (capitalized) so subsequent
interest is compounded at the per-mutation granularity — a borrower who never
touches the line accrues simple interest; a borrower who draws frequently
compounds at draw frequency. This makes the gas-per-byte cost of accrual
O(1) per transaction, not O(time) like a periodic cron.
The current contract takes risk_score: u32 ∈ [0, 100] as an admin-provided
scalar
(contracts/credit/src/risk.rs:27, MAX_RISK_SCORE = 100). The off-chain
scoring stack (out of scope for the on-chain contracts but shipped with the
protocol) computes this score from the signals enumerated below. The taxonomy
is included here so reviewers can see how the model composes; the on-chain
contract is signal-agnostic — it only sees the scalar and a cryptographic
attestation path.
| Signal class | Source | Weight class | Freshness window | Manipulation cost (intuitive) |
|---|---|---|---|---|
| Wallet age | Soroban ledger history of address | Low | static once observed | Free (Sybil-able) — capped weight |
| Repayment history (this protocol) | RepaymentEvent, InterestAccruedEvent, next_due_ts deltas |
High | rolling 180 d | Equal to outstanding utilized × time |
| Counter-party diversity | Distinct token contracts and addresses transacted with | Medium | rolling 90 d | Linear in volume to fake |
| Stablecoin throughput | Native-asset and SAC transfers via the Stellar token interface | Medium | rolling 30 d | Linear, observable |
| Liquidity provision | LP positions, time-weighted | Medium | rolling 60 d | High — capital lockup |
| Off-chain attestation | Soulbound credential / income attestation via set_credit_attestation (planned hook in docs/default-oracle.md) |
High | per-attestation expiry | Issuer trust |
| Default / delinquency penalty | DefaultLiquidationSettledEvent, ("credit","pen_enter") |
Negative-only, never zero | sticky 24 mo | Free to incur — never to undo |
| Auction recovery rate | recovered_amount / utilized_amount at settlement |
Medium | per-event | N/A (post-hoc) |
The on-chain scoring oracle (specified in docs/default-oracle.md) is the bridge
between this taxonomy and the contract: it submits a signed
(borrower, score, observed_at, expires_at, nonce) envelope, the contract
verifies signature + freshness + nonce-not-used, and the score is consumed by
update_risk_parameters. The default oracle module is staged in
docs/default-oracle.md — the contract today exposes set_oracle_config /
OracleConfig { max_deviation_bps, max_age_seconds } for the price circuit
breaker, which is the same primitive applied to a different feed.
The credit-line lifecycle is encoded by CreditStatus
(contracts/credit/src/types.rs:24-38):
stateDiagram-v2
[*] --> Draft : pre-init (no DataKey present)
Draft --> Active : open_credit_line (admin)
Active --> Restricted : update_risk_parameters lowers limit below utilized
Restricted --> Active : repay_credit until utilized <= new limit
Active --> Suspended : suspend_credit_line (admin) OR self_suspend_credit_line (borrower)
Suspended --> Active : reinstate_credit_line (admin) [target = Active]
Active --> Defaulted : default_credit_line (admin)
Restricted --> Defaulted : default_credit_line (admin)
Suspended --> Defaulted : default_credit_line (admin)
Defaulted --> Active : reinstate_credit_line (admin, post-cure)
Defaulted --> Restricted : reinstate_credit_line (admin, partial cure)
Defaulted --> Closed : settle_default_liquidation (utilized reaches 0)
Active --> Closed : close_credit_line (borrower if utilized == 0; admin always)
Suspended --> Closed : close_credit_line (admin)
Restricted --> Closed : close_credit_line (admin)
Closed --> [*] : terminal
(Source: contracts/credit/src/lifecycle.rs:147-666, docs/state-machine.md,
event publishers in contracts/credit/src/events.rs.)
Properties of the state machine:
-
Capability matrix (also in
docs/threat-model.md):State draw_creditrepay_creditupdate_risk_parametersclose_credit_lineActive yes yes yes borrower if utilized==0, admin alwaysRestricted no (fails OverLimit)yes yes admin Suspended no ( CreditLineSuspended)yes yes admin Defaulted no ( CreditLineDefaulted)yes admin only only via settlement Closed no no no idempotent (no-op) -
Accrual is applied before every transition.
apply_accrualis called at the head of every state-mutating entrypoint inlifecycle.rsandrisk.rs, so the line'sutilized_amountreflects realized interest before the new state is evaluated. -
Monotonic timestamps.
assert_ts_monotonic(contracts/credit/src/storage.rs:538) enforces thatsuspension_tsandlast_rate_update_tsonly move forward. Backdating reverts withContractError::TimestampRegression = 33. -
Settlement replay protection.
settle_default_liquidationuses a per-(borrower, settlement_id)persistent marker(symbol_short!("liq_seen"), borrower, settlement_id). Replay reverts withAlreadyInitialized(contracts/credit/src/lifecycle.rs:539-630).
Given RateFormulaConfig { base_rate_bps b, slope_bps_per_score s, min_rate_bps r_min, max_rate_bps r_max }
(contracts/credit/src/types.rs:241-250) and a borrower's risk_score k:
The implementation is compute_rate_from_score at
contracts/credit/src/risk.rs:77 using saturating arithmetic, so a
misconfigured b + 100·s cannot overflow u32 — it saturates and is then
clamped.
If a per-borrower floor exists (DataKey::RateFloorBps(Address),
contracts/credit/src/storage.rs:357), it is applied as a final lower bound:
Rate-change cadence is gated by RateChangeConfig { max_rate_change_bps, rate_change_min_interval }
(contracts/credit/src/types.rs:217-222,
Symbol("rate_cfg") instance key). A rate update reverts with
RateTooHigh if the absolute delta exceeds max_rate_change_bps and with
TimestampRegression if it falls inside rate_change_min_interval seconds of
the prior update.
update_risk_parameters accepts an admin-supplied credit_limit that is
validated against the protocol-wide bounds (MinCreditLimit, MaxCreditLimit)
(contracts/credit/src/lifecycle.rs:78-145) and against the per-borrower
exposure cap. The function does not algorithmically derive
credit_limit from risk_score today — it accepts the admin/oracle's value
and validates. The composition of an off-chain scoring function with the
on-chain update_risk_parameters call is the model:
where
Per-call accrual (contracts/credit/src/accrual.rs:87):
Capitalization rule:
(Note: t_last is only advanced when ΔI > 0 to avoid silently zeroing out
sub-tick accrual on chains with sub-second ledger close times. See
docs/interest-accrual-design.md and the test tests/monotonic_timestamps.rs.)
For suspended lines with a grace policy, the split is:
repay_credit allocates a repayment contracts/credit/src/lib.rs:437-556):
where protocol_fee_bps (capped at MAX_PROTOCOL_FEE_BPS = 1_000 =
10 % of interest, never principal — contracts/credit/src/lib.rs:63). The
fee is transfer_from(borrower, contract, fee); the reserve portion is
transfer_from(borrower, liquidity_source, a_reserve). The fee is then
withdrawable by the admin via withdraw_treasury(admin).
Creditra does not embed an auction in the credit contract. Default settlement is a two-contract handoff:
-
Default signal.
default_credit_line(borrower)is called by admin (today; an oracle-driven path is staged indocs/default-oracle.md). The credit line transitionsActive|Restricted|Suspended → Defaulted. An event("credit", "liq_req")is emitted with the outstanding amount (contracts/credit/src/events.rs:236). -
Off-chain auction orchestration. An off-chain orchestrator (or, in a later version, a permissionless keeper) observes the
liq_reqtopic, constructs an auction on thegateway-auctioncontract viainit_auction(auction_id, mode, start_time, end_time, min_bid, min_increment_bps, dutch_start_price, dutch_floor_price)(gateway-contract/contracts/auction_contract/src/lib.rs). -
Settlement. Once the auction closes, an admin call to the credit contract's
settle_default_liquidation(borrower, recovered_amount, settlement_id, oracle_price)(contracts/credit/src/lib.rs:953) cross-contract calls the auction'ssettle_default_liquidation(auction_id, credit_contract, borrower) -> i128and asserts the returned amount matches the suppliedrecovered_amount. If they diverge, the call reverts withInvalidAmount.
The cross-contract call is reentrancy-guarded on both sides
(storage::set_reentrancy_guard /
storage::clear_reentrancy_guard,
Symbol("reentrancy") in instance storage). Settlement is one-shot per
(borrower, settlement_id) on the credit side and per auction_id on the
auction side (AuctionKey::LiquidationSettled(Symbol)).
AuctionMode is set at auction init
(gateway-contract/contracts/auction_contract/src/types.rs):
-
English (ascending, open).
min_next_bid = max(highest_bid * (1 + min_inc_bps/10_000), highest_bid + 1)(gateway-contract/contracts/auction_contract/src/lib.rs, helpermin_next_bid). Previous bidder is atomically refunded under the reentrancy guard; the refund emitsBidRefundedEventon topic("BID_RFDN", auction_id). The auction closes when the admin invokesclose_auctionafternow >= end_time. -
Dutch (descending).
$p(t) = p_0 - (p_0 - p_f) \cdot t / T$ for$t \in [0, T]$ , clamped to$p_f$ . Implemented incompute_dutch_price(start, floor, elapsed, duration). The first bidamount ≥ p(t) ∧ amount ≥ min_bidimmediately closes the auction.
The auction module's docstring describes an anti-snipe extension where bids
within an extension window push out the close time. The constant
ANTI_SNIPE_WINDOW_SECS / ANTI_SNIPE_EXTEND_SECS is referenced in
PR #430's description (feature/auction-anti-snipe); after the
merge-with-overlapping-AUCTION_CLOSE_TIME_FIX.md reconciliation, the live
place_bid path hard-rejects bids when now >= end_time without extending
(see docs/SECURITY.md "Known gaps"). This is tracked as an open item in
docs/EXECUTION_QUALITY.md.
settle_default_liquidation (in lifecycle.rs) clips recovered_amount to
the borrower's outstanding utilized_amount, decrements both
utilized_amount and accrued_interest pro-rata to interest-vs-principal,
adjusts TotalUtilized, and — if utilized_amount reaches 0 — automatically
transitions the line to Closed and clears the repayment schedule. The event
DefaultLiquidationSettledEvent is emitted with the full settlement breakdown
(contracts/credit/src/events.rs:242, schema in docs/indexer-integration.md).
The protocol uses two oracle surfaces:
set_oracle_config(max_deviation_bps, max_age_seconds)
(contracts/credit/src/lib.rs:1055). On every settle_default_liquidation
call with an oracle config present:
- The supplied
oracle_pricemust be > 0 (elseOraclePriceInvalid = 36). now - last_price_ts <= max_age_seconds(elseOraclePriceStale = 37).- Deviation in bps from the last accepted price must be
<= max_deviation_bps(elseOraclePriceDeviation = 38). - The new price and timestamp are persisted atomically to
(OracleLastPrice, OracleLastPriceTs)in instance storage — there is no intermediate state where one is updated without the other (contracts/credit/src/storage.rs:561-593).
Deviation is computed by math_utils::compute_deviation_bps
(contracts/credit/src/math_utils.rs:306): returns None when
last_price <= 0; saturates output at u32::MAX so an absurd new price still
trips the breaker. Manipulation cost (informal): an attacker who controls a
fraction f of oracle reports must move the price by
max_deviation_bps / 10_000 · last_price within max_age_seconds and
pay the gas to push that update through the deviation gate on every block. The
breaker bounds the per-block price move by the configured threshold.
A signature-verified attestation envelope
(borrower, reason_code, observed_at, expires_at, nonce, chain_id, contract_id)
that, when valid, lets a default_credit_line call originate from an oracle
rather than the admin. Phase 1 (admin-assisted): the signal is persisted at
PendingDefaultSignal(borrower) and the admin then calls default_credit_line
within a freshness window. Phase 2 (permissionless): a
default_credit_line_with_signal entrypoint accepts the signal directly.
Storage layout for the planned module (in docs/default-oracle.md):
OracleSignerSet (instance), UsedSignalNonce(borrower, nonce) (persistent),
PendingDefaultSignal(borrower) (persistent). Nonce reuse reverts with
AlreadyInitialized.
| Property | Aave v3 | Compound v3 | MakerDAO Spark | Creditra |
|---|---|---|---|---|
| Eligibility predicate | Deposit ≥ LTV-cap × loan | Deposit ≥ LTV-cap × loan | Deposit ≥ LTV-cap × loan (Vault) | Behavioral score + optional collateral floor |
| Collateralization ratio (default) | 125–166 % | 130–150 % | 150 %+ | 0 % (default unsecured) / 150 % (default-on optional collateral) |
| Median user eligibility | Excludes wallets w/o LTV-capable assets | Same | Same | Includes wallets with on-chain behavioral history |
| Pricing input | Utilization curve | Utilization curve | Stability fee (governance) | Behavioral risk score + utilization + market premium |
| Settlement of bad debt | Keeper-driven liquidation w/ bonus | Keeper-driven | Vault auction | Cross-contract English / Dutch auction with replay-safe handoff |
| Settlement speed | Single-tx liquidation | Single-tx | Auction window | Auction window (configurable) — accounting is single-tx |
| Recovery rate assumption | LTV × liquidation bonus | LTV × liquidation bonus | Auction discount | Empirical from recovered_amount / utilized_amount |
| Interest model | Per-block accrual | Per-block accrual | Per-stability-fee | Lazy checkpoint, capitalized on mutation |
| Upgrade model | Proxy + governance | Governance migrations | Module replacement | Admin-gated atomic update_current_contract_wasm + version bump |
| Event schema for indexers | Solidity events | Solidity events | Solidity events | Stable Soroban topics, see docs/indexer-integration.md |
| WASM / bytecode size | N/A (Solidity, ≥ 30 KB per facet) | N/A | N/A | < 50 KB hard CI budget (creditra-credit.wasm) |
| Test coverage | varies | varies | varies | 98.94 % lines / 99.51 % regions |
A reviewer who skims the repo should look for:
- 5 449-line
lib.rswith one#[contractimpl]block (contracts/credit/src/lib.rs). 13 sub-modules (accrual, auth, borrow, collateral, config, events, freeze, lifecycle, math_utils, query, risk, storage, types). - 38 enumerated
ContractErrorvariants with stable discriminants (contracts/credit/src/types.rs:91-168). CI testtests/error_discriminants.rsreverts if any discriminant moves. - 30 enumerated
DataKeyvariants with explicit storage tier per variant (contracts/credit/src/storage.rs:31-98, documented indocs/storage-layout.md). - 25+ unique event topics under the
creditnamespace (contracts/credit/src/events.rs, cataloged indocs/indexer-integration.md). Event payload structs are#[contracttype]and the topic strings aresymbol_short!-compatible (≤ 9 chars). - TTL hygiene. Every persistent read goes through helpers that bump TTL
on the read path. Bump cadence: extend to ~6 months
(
LEDGER_BUMP_AMOUNT = 3_110_400) when remaining TTL drops below ~3 months (LEDGER_BUMP_THRESHOLD = 1_555_200). - CEI-disciplined draw path.
draw_creditwalks 23 ordered validation steps before any token transfer; the reentrancy guard wraps the transfer-then-persist tail. Seedocs/PROTOCOL_SPEC.mdfor the exhaustive ordering. - 40+ integration test files under
contracts/credit/tests/covering every entrypoint and every adversarial path enumerated indocs/SECURITY.md.
The protocol's PR cadence is visible in the merge history — recent merges
include PR #433 (admin-gated WASM upgrade), #432 (late-payment penalty), #431
(reentrancy guards on auction refund), #430 (anti-snipe extension), #425
(Dutch auction mode), #420 (per-borrower rate floor), #418
(replace production unwraps with explicit contract errors),
#415 (oracle deviation breaker), #408 (token-failure rollback tests).
That cadence is documented in docs/EXECUTION_QUALITY.md.
These are real and acknowledged.
-
The risk-score oracle is centralized in v1.
update_risk_parametersis admin-only today. The path to decentralization is the default-signal oracle indocs/default-oracle.md: a signer set, signature verification, nonce-replay protection, freshness window. A full move to a Schelling-point or stake-weighted scoring layer is out of scope for the first deployment. -
The behavioral signal vector is off-chain. The score is a scalar; the off-chain compute that produces it is part of the protocol's trust surface. This is mitigated by (a) the on-chain admin can be a multisig with diverse key custody, and (b)
MaxTotalExposureputs an absolute cap on protocol-wide losses from a compromised scoring oracle. -
Anti-snipe is documented but not live. Per §6.3 and PR #430's reconciliation note in the merge history, the live
place_bidrejects late bids rather than extending the close time. Reviewers should treat the anti-snipe behavior as a planned, not delivered, feature. -
The lazy-accrual model implies that a borrower whose line is never touched can accumulate unbounded interest in math before realization. This is bounded in contract storage (no interim writes) but a reviewer should note that the on-chain
accrued_interestfield is a function oflast_accrual_tsandnowuntil someone calls a mutating method.accrue_batch(borrowers)is provided as a keeper hook (contracts/credit/src/lib.rs:1133, capped atACCRUE_BATCH_MAX = 50) to let an off-chain worker advance accruals on a schedule without invoking borrower-state mutation. -
Auction settlement is admin-driven today. The cross-contract handoff from
default_credit_linetosettle_default_liquidationis intended to become permissionless once the off-chain orchestrator is replaced by a keeper-incentivized one. The atomic settlement primitive on-chain is already permissionless once the auction is closed; the trigger is what remains gated. -
Pre-existing build failures in the workspace are not introduced by this release. A baseline
cargo check --workspaceagainstmainat commit28bcf4freports 65 errors localized to known merge artifacts incontracts/credit/src/lifecycle.rs(duplicate function bodies) andcontracts/credit/src/risk.rs(duplicateuseblocks). These are tracked inIMPLEMENTATION_STATUS.mdand are the next milestone after the documentation pass.
This document cites the actual source repo at every numbered claim. For the reader who wants to verify, the load-bearing files are:
contracts/credit/src/lib.rs— all#[contractimpl]entrypointscontracts/credit/src/types.rs—ContractError,CreditStatus,CreditLineData, config structscontracts/credit/src/storage.rs—DataKey, TTL constants, reentrancy guard, oracle helperscontracts/credit/src/accrual.rs—apply_accrual, penalty + grace branchescontracts/credit/src/risk.rs—compute_rate_from_score,update_risk_parameterscontracts/credit/src/lifecycle.rs— state transitions,settle_default_liquidationcontracts/credit/src/math_utils.rs—mul_div,prorate_interest,compute_deviation_bps,Roundinggateway-contract/contracts/auction_contract/src/lib.rs— English & Dutch auctions, settlement handoffdocs/state-machine.md,docs/interest-accrual.md,docs/risk-based-rate-formula.md,docs/threat-model.md,docs/default-liquidation-auction-hook.md,docs/storage-layout.md,docs/contract-errors.md,docs/indexer-integration.md
The new long-form companion documents are:
docs/PROTOCOL_SPEC.md— per-module contract surface, invariants, error taxonomydocs/ARCHITECTURE.md— system & sequence diagramsdocs/RISK_PRICING.md— the algorithm in depth with a worked exampledocs/SECURITY.md— threat model, attacker capabilities, mitigationsdocs/EXECUTION_QUALITY.md— coverage, tests, CI, deployment, PR cadence
Creditra Contracts, v1.0. Released June 2026.