✦CreditGate
AppDocsTransparency
Launch App →
⌕

GUIDE

00Overview01Architecture↗02Deployment03Security04Testing05FDC verification06Submission
Built for verification
Last reviewed · 14 Aug 2026
Docs/Architecturev1.0 · Coston2

01 / System design

Architecture

How CreditGate combines Flare primitives into a collateralized credit state machine.

LAST UPDATED
14 AUG 2026

CreditGate — Architecture

System Overview

CODE
┌─────────────┐     1. Deposit FXRP      ┌──────────────────┐
│   Borrower   │ ──────────────────────→ │  CreditGateVault  │
│  (EVM wallet) │                         │   (Coston2)       │
└──────┬───────┘     2. Register XRPL     │                  │
       │            r-address hash         │  State Machine:  │
       │            ─────────────────→   │  IDLE→DEPOSITED  │
       │                                    │  →PENDING→      │
       │  3. Request Eligibility            │  ELIGIBLE→       │
       │            ─────────────────→   │  FUNDED→CLOSED   │
       │                                    └────────┬─────────┘
       │                                             │
       │  4. FCC evaluates (off-chain)              │ 5. Draw USDT0
       │                                             ▼
       │  ┌──────────────────┐                ┌──────────────┐
       │  │  FCC Go Handler   │                │  FTSOv2      │
       │  │  (TEE simulated)  │                │  XRP/USD     │
       │  │                  │                │  price feed  │
       │  │  POST /action    │                └──────────────┘
       │  │  → EIP-191 sig   │
       │  └────────┬─────────┘
       │           │ 6. Attestation (v,r,s + limit + expiry)
       │           ▼
       │  ┌──────────────────┐
       │  │  Vault verifies   │
       │  │  ecrecover == TEE │
       │  │  authority        │
       │  └──────────────────┘
       │
       │  7. Repay on XRPL (send drops + 32-byte memo)
       │            ───────────────────────→ XRPL Testnet
       │
       │  8. FDC verifies XRPL payment
       │            ←─────────────────────── FDC Verifier
       │                        │
       │                        ▼
       │  9. submitRepaymentProof(loanId, proof)
       │            ─────────────────→ Vault checks:
       │                                • verifyXRPPayment(proof) == true
       │                                • receivedAmount >= requiredRepaymentDrops
       │                                • sourceAddressHash == loan snapshot
       │                                • receivingAddressHash == protocol settlement hash
       │                                • firstMemoData == loan.expectedCommitment
       │                                • proofConsumed[proofHash] == false
       │                        │
       │                        ▼
       │                         Collateral released → CLOSED

Liquidation Flow & New Risk Features ( auction / LTV / auto-trigger )

The system overview above traces the happy path (deposit → eligibility → draw → repay → close). This diagram captures the risk path: what happens when a loan becomes undercollateralized, how the new automated trigger and per-collateral LTV features plug into it, and the state machine the loan walks. Cross-references at the end point to the verifying test suites.

CODE
                                ┌──────────────────────┐
   (loan in FUNDED state)        │   Loan.state = FUNDED │  ←─── drawLoan sets this when
   ─────────────────────────────→│   collateral locked   │       FCC attestation + collateral ratio pass
                                └──────────┬───────────┘
                                           │
                ┌──────────────────────────┼──────────────────────────┐
                │                          │                          │
                ▼ (manual path)            ▼ (automated path)         ▼ (time path)
   ┌─────────────────────────┐  ┌────────────────────────┐  ┌────────────────────────┐
   │ startLiquidationAuction │  │ checkAndTriggerLiquid- │  │  loan.deadline expires │
   │ (anyone, HF < 1.0)      │  │  ation (single) /      │  │  repayDeadline passed  │
   │                         │  │  batchCheckLiquidation │  │                        │
   └────────────┬────────────┘  │  (uint256[] arr)       │  └───────────┬────────────┘
                │               └───────────┬────────────┘             │
                │                           │                          │
                │       ┌───────────────────┘                          │
                │       │  (reads live FTSOv2 XRP/USD, recomputes      │
                │       │   getHealthFactor(loanId). No-op if loan      │
                │       │   not FUNDED, price zero, or HF == 1.0.)      │
                │       ▼                                                │
                └─────────────────────► startLiquidationAuction ◄──────┘
                                         │
                                         ▼
                          ┌─────────────────────────────────┐
                          │  Loan.state = LIQUIDATION_AUCTION│
                          │  (Dutch auction, linear-decay)   │
                          │                                  │
                          │  bidOnLiquidation(loanId, bid)   │  ←── bidders as price decays
                          │  price = ceiling → floor         │
                          └────────────────┬────────────────┘
                                           │
                                           ▼
   ┌─────────────────────────┐  ┌─────────────────┐
   │   finalizeAuction()      │  │ (no bids:         │
   │   winner pays lender,    │  │  bad-debt path)   │
   │   surplus → borrower     │  └────────┬─────────┘
   └────────────┬────────────┘            │
                │                         ▼
                ▼            ┌──────────────────────────────┐
   ┌──────────────────┐    │  recoverDefaultedCollateral  │
   │ Loan.state=CLOSED │    │   lender seizes collateral,  │
   │  (lastState)      │    │   auction forfeited           │
   └──────────────────┘    │   → Loan.state = DEFAULTED   │
                          │     ⇒ then IDLE              │
                          └──────────────────────────────┘


──── Per-collateral LTV configuration path (parallel, owner-only) ────

       ┌──────────────────────┐
       │   vault owner        │
       │   (msg.sender==owner)│
       └──────────┬───────────┘
                  │ 1. registerCollateral(token, ltvBps, decimals)
                  │    e.g. onboard WFLR (18-dec, 50% LTV)
                  │ 2. updateLTV(token, newBps)
                  │    (only tightens ≤ ctor default;
                  ▼     emits CollateralRegistered / LTVUpdated)
   ┌───────────────────────────┐
   │ collateralLTV[token]      │
   │ collateralDecimals[token] │   ← mappings
   └──────────────┬────────────┘
                  │
                  │ (read at draw time)
                  ▼
   ┌───────────────────────────────────────────────────────────────┐
   │ getMaxLoanAmount(loanId):                                     │
   │   collateralValueUsd18 = collateral × price (FTSOv2) × 1e18   │
   │   maxLoan = min(collateralRatioBps, collateralLTV[token])      │
   │            × collateralValueUsd18 / 10000                     │
   │ drawLoan(loanId, loanAmount): reverts if loanAmount > maxLoan │
   └───────────────────────────────────────────────────────────────┘


──── FTSO-threshold trigger flow (the "auto" leg, expanded) ────

   ┌──────────────┐   live XRP/USD price    ┌────────────────────────────┐
   │   FTSOv2     │ ─────────────────────→  │ checkAndTriggerLiquidation │
   │  getFeed     │  (with staleness check  │  (external, payable)       │
   │  ByIdInWei   │   ftsoStalenessLimit)   │                            │
   └──────────────┘                         └──────────────┬─────────────┘
                                                            │
                              health = collateral × price / (principal + interest)
                                                            │
         ┌──────────────────────────────────────────────────┼─────────────────────────┐
         │                                                │                          │
         ▼                                                ▼                          ▼
   health ≥ 1.0                                       health < 1.0                price == 0
   → no-op (return)                                   → startLiquidationAuction    → no-op (return)
                                                       │
                                                       ▼
                                  Loan.state = FUNDED → LIQUIDATION_AUCTION

   batchCheckLiquidation(loanIds[]) iterates the same path per loan in one
   transaction, triggering only those whose recomputed HF < 1.0.

Verifying test suites (run with forge test --match-contract CreditGateVaultTriggerTest / CreditGateVaultLTVTest / CreditGateVaultAuctionTest):

FeatureTest suiteTestsKey cases
Auto triggertest/CreditGateVault.trigger.t.sol9checkAndTriggerLiquidation (fires when undercollateralized; no-op when healthy / price zero / not funded / exactly at threshold), batchCheckLiquidation (empty no-op, all-healthy empty, only-unhealthy triggered), triggeredAuctionIsFullyFunctional (auto-started auction is bid-able + finalizable)
Per-collateral LTVtest/CreditGateVault.ltv.t.sol11registerCollateral (owner-only, invalid LTV/zero-address reverts, CollateralRegistered emit), updateLTV (owner-only, unknown-collateral revert, LTVUpdated emit), getLTV default, getMaxLoanAmount (LTV-bound vs. ratio-bound, zero collateral), drawLoan respects tightened LTV cap
Dutch auctiontest/CreditGateVault.auction.t.sol7startLiquidationAuction (reverts if not FUNDED; succeeds on expired loan), bidOnLiquidation, finalizeAuction (with/without bids), price-decay math, debt-floor and replacement-bid guards, surplus refund

The test surface is 21 suites / 212 Foundry tests, all passing in the reproducible Foundry suite (trigger/LTV/auction/liquidity/settlement regressions included).

EIP-191 Eligibility Attestation Payload

The cross-language compatibility between the Go FCC handler and the Solidity vault hinges on byte-identical payload construction. Both sides must produce the exact same keccak256 hash.

Payload Construction (Solidity + Go produce identical bytes)

CODE
Domain Separator:
  keccak256("CREDITGATE_ELIGIBILITY_V1")     ← 32 bytes

Payload Hash:
  keccak256(abi.encode(
      DOMAIN,                    // bytes32
      borrower,                  // address  → 20 bytes, left-padded to 32
      limit,                     // uint256  → the credit limit (18-decimals USDT0)
      expiry,                    // uint64   → block.timestamp deadline
      nonce,                     // uint32   → borrowerNonce at request time
      revocationVersion          // uint8    → borrowerRevocationVersion
  ))

EIP-191 Signed Message:
  keccak256(abi.encodePacked(
      "\x19Ethereum Signed Message:\n32",    // EIP-191 prefix
      payloadHash                            // 32 bytes
  ))

Signature:
  (v, r, s) = vm.sign(teePrivateKey, ethSignedHash)
  → v ∈ {27, 28}
  → s ≤ secp256k1n / 2     (M1 fix: malleability check)

Cross-Language Proof

test/CreditGateVault.tee-compat.t.sol proves this: it hardcodes a real signature produced by the Go FCC handler (via POST /action to localhost:8080), feeds it to the Solidity vault's submitEligibility, and confirms the loan transitions to ELIGIBLE. A tampered limit (100e6 → 101e6) correctly reverts with InvalidEligibilitySigner.

FDC Repayment Proof Verification

CODE
XRPL Payment (off-chain):
  borrower sends `requiredRepaymentDrops` XRP from the registered source address
  to the protocol XRPL settlement address
  with `expectedCommitment` (32 bytes) as MemoData

FDC Attestation:
  1. Submit request: FdcHub.requestAttestation{value: fee}(abi.encode(request))
     - attestationType: bytes32("XRPPayment")
     - sourceId: bytes32("testXRP") for testnet
     - requestBody: { transactionId, proofOwner }
  2. Wait for voting round finalization (~180s on Coston2)
  3. Retrieve proof from DA layer
  4. Verify: FdcVerification.verifyXRPPayment(proof) → bool

Vault Checks (submitRepaymentProof):
  • verifyXRPPayment(proof) == true
  • resp.status == 1 (success)
  • resp.receivedAmount >= loan.requiredRepaymentDrops
  • resp.hasMemoData == true
  • resp.firstMemoData.length == 32
  • bytes32(resp.firstMemoData) == loan.expectedCommitment
  • resp.sourceAddressHash == loan.borrowerSourceAddressHash    ← L5 snapshot
  • resp.receivingAddressHash == repaymentReceiverHash           ← protocol destination
  • proofConsumed[keccak256(abi.encode(proof))] == false         ← anti-replay

Flare Primitives (load-bearing)

PrimitiveContract (Coston2)Role
FAssets (FXRP)0x0b6A3645c240605887a5532109323A3E12273dc7Collateral ERC-20 (6 decimals)
FTSOv2getFeedByIdInWei(XRP_USD_FEED_ID)XRP/USD price → 150% collateral ratio
FDCFdcVerification: 0x906507E0B64bcD494Db73bd0459d1C667e14B933XRPPayment proof verification
FCCGo handler (fcc/credit-extension/extension/main.go)Private credit eligibility → EIP-191 attestation
FdcRequestFeeConfigurations0x191a1282Ac700edE65c5B0AaF313BAcC3eA7fC7eRequest fee for FDC attestation

FCC Credit Evaluation Model (Gap #4)

The Go FCC handler (fcc/credit-extension/extension/handler/handler.go) implements a real credit evaluation pipeline — not just a signature over a raw number:

CODE
POST /action  (JSON body from TEE proxy)
  {
    "opCommand": "EVALUATE",
    "borrower": "0xDE62..."
    "collateralAmount": "100000000",   // FXRP 6dp
    "requestedLoan": "100000000",     // USDT0 6dp
    "expiry": "1893456000",
    "nonce": "0",
    "revocationVersion": "0"
  }

Response (JSON):
  {
    "eligible": true,
    "limit": "100000000",
    "reason": "",
    "attestation": {
      "borrower": "0xDE62...",
      "limit": "100000000",
      "expiry": "1893456000",
      "nonce": "0",
      "revocationVersion": "0",
      "v": 27,
      "r": "d8174e...",
      "s": "200618..."
    }
  }

The returned "limit" is derived from the live collateral mirror and the vault's on-chain borrower reputation. It can be lower than "requestedLoan"; the TEE never signs above the collateral-backed ceiling.

Evaluation pipeline (handler.go:133-185)

  1. Input validation — non-zero borrower, non-negative collateral/loan, parseable expiry/nonce
  2. Revocation check — if eligibilityRevoked[borrower] is true, deny with BORROWER_REVOKED
  3. Collateral sufficiency — mirrors vault's drawLoan math: collateral * xrpUsdPrice / 1e18 * 10000 >= requested * collateralRatioBps. If insufficient, deny with INSUFFICIENT_COLLATERAL
  4. Limit derivation — limit = min(requested, borrowerLimit) where borrowerLimit is a per-borrower cap held privately in the TEE
  5. Reputation adjustment — getBorrowerReputation(borrower) is read from the configured vault and the same history score policy as the Python TEE tightens the limit. The handler refuses to sign when the read fails
  6. EIP-191 signing — keccak256(abi.encode(DOMAIN, borrower, limit, expiry, nonce, revocationVersion)) with the \x19Ethereum Signed Message:\n32 prefix
  7. The signature (v, r, s) is returned to the borrower, who submits it to CreditGateVault.submitEligibility

Production vs simulated

In production (per the FCC Private Key Extension pattern):

  • The signing key is generated inside the TEE and never leaves the enclave
  • Credit data (credit score, income, debt-to-income) is fetched by the TEE from private data sources
  • The limits map is populated from the TEE's confidential computation, not hardcoded

In the hackathon demo (SIMULATED_TEE mode):

  • The signing key is loaded from CREDITGATE_SIGNING_KEY env var (matches TEE_AUTHORITY in the vault)
  • The XRP/USD price and collateral ratio are configurable via env vars
  • The limits map is an optional private operator cap; the default credit input is on-chain reputation
  • The credit input is implemented in live/default scope via on-chain borrower reputation, exposed for inspection at GET /credit-score/:address; deterministic address scoring is available only with explicit CREDIT_BUREAU_MODE=mock for offline tests

Credit Evaluation Model

Judge sim gap #5 (8.2/10): "The FCC credit-evaluation substance is a placeholder. The limit derivation floor is an env-configured per-borrower map." This section specifies what the TEE privately computes today, what it would ingest in production, and why the confidentiality boundary is the entire FCC value proposition — turning "TEE signs whatever the borrower requests" into "TEE computes a limit from confidential inputs we specified."

What the TEE privately computes (hackathon scope)

Inside the enclave, the evaluate function (handler.go:192-246) runs a real credit-decision pipeline before any signature is produced. Every step is private to the TEE; only the boolean outcome and a signed attestation leave the enclave.

#ComputationFailure codePurpose
1Borrower address validation — borrower decodes to a non-zero EVM addressINVALID_BORROWERBlocks griefing where an attacker submits an empty address to harvest a signed-but-unusable attestation.
2Revocation / anti-replay — revoked[strings.ToLower(borrower)] is checked; the attestation also binds revocationVersion and nonceBORROWER_REVOKEDA defaulted or KYC-revoked account can never obtain a fresh signed attestation, regardless of collateral. The vault independently re-checks the nonce, so a stale attestation from a prior revocation epoch is rejected on-chain.
3Input sanity — collateralAmount, requestedLoan, expiry, nonce parse as non-negative base-10 big integers; collateral and loan must be strictly positiveINVALID_COLLATERAL / INVALID_LOAN / INVALID_EXPIRY / INVALID_NONCEDefense in depth: malformed instructions never reach the signing key.
4Collateral-sufficiency mirror — collateralUsd = collateral(6dp) * xrpUsd18dp / 1e18 * 10000; require collateralUsd >= requested * collateralRatioBps (default 15000 bps = 150%)INSUFFICIENT_COLLATERALThe TEE re-derives the exact math the vault's drawLoan enforces, so a signed attestation can never approve something the vault would revert. With defaults at XRP=$2.50 and 150% ratio, 1,000 USDT0 needs ≥ 600 FXRP.
5Limit derivation — limit = min(requested, borrowerLimit) where borrowerLimit is a per-account cap held privately in the TEE's limits map(denial if requested exceeds cap)The signed attestation never exceeds either the request or the confidential account cap. If no cap is set for the account, the full requested amount is approved, so the vault's limit check is always satisfiable.
5bReputation adjustment — getBorrowerReputation(borrower) is read from the configured vault and converted to the production history score; the legacy score/850 factor tightens the limitCREDIT_BUREAU_UNAVAILABLECompletion/default/repayment history affects the signed limit. The factor is capped at 1.0, so it cannot inflate the collateral-backed ceiling.
6EIP-191 attestation signing — keccak256(abi.encode(DOMAIN, borrower, limit, expiry, nonce, revocationVersion)) prefixed and signed with the TEE authority key; v lifted to {27,28}(signing error → no attestation)Only the limit the TEE derived (after the credit-bureau step) is signed. A compromised TEE can under-sign (harming the borrower, who just rejects the attestation) but cannot inflate limits past the collateral ratio, because the vault re-derives coverage independently on-chain.

The net effect: the on-chain observer sees (borrower, limit, expiry, nonce, v, r, s). They see that a credit decision was made and what limit was approved, but never why — the inputs to the decision (borrower eligibility set, per-account cap, revocation state) never leave the enclave.

What the production TEE would ingest (future, vs hackathon-stubbed)

The per-borrower limits map is an optional private operator cap. The default handler consumes live vault reputation before signing; external income/DTI data is deliberately not claimed until a confidential provider is integrated:

Private inputStatusSourceHow the limit function weights it
On-chain collateral value✅ shippedFTSOv2 XRP/USD feed (wired via XRP_USD_PRICE_18DP)Determines the hard ceiling: no approved limit can exceed what the posted collateral covers at the current FTSO price × collateralization ratio. This is the collateralCoverage factor.
On-chain credit history✅ implementedgetBorrowerReputation on the configured vaultCompletion/default/repayment ratio is read before signing and tightens the collateral-backed ceiling.
Debt-to-income ratio⛔ stubbed via limits mapVerified income data (payroll, bank statements) ingested via a confidential data providerA high DTI (>43%) scales the limit down to reflect repayment capacity, independent of collateral. This is a capacity modifier. (The mock bureau returns a DTI datum for transparency at GET /credit-score/:address, but evaluate() does not yet fold it into the limit.)
Historical repayment behavior✅ implementedFDC-verified closes and liquidations update vault reputation countersThe same counters feed the TEE history score without an external oracle.

A representative production limit function would then be:

CODE
borrowerLimit = collateralCoverage       // ceiling from FTSO-priced FXRP
             × creditScoreFactor         // [0.5 … 1.0] from bureau score
             × capacityModifier          // [0.7 … 1.0] from DTI
             × repaymentHistoryFactor    // [0.5 … 1.2] from FDC-proven history

approvedLimit = min(requested, borrowerLimit)

The implementation ships the collateralCoverage leg (live FTSO-priced), the min(requested, cap) floor, and a repayment-history leg derived from real vault reputation. DTI and payroll claims remain intentionally absent until a confidential provider is integrated. The signature contract stays stable as further confidential inputs are added.

Why this is private (the FCC value proposition)

The Flare Confidential Compute value proposition for CreditGate is confidential compute → public verifiability:

  • The borrower's credit score, debt-to-income ratio, income data, and repayment history never leave the TEE. They are not written to the vault, not emitted in events, not stored on-chain, not logged by the proxy.
  • Only the signed attestation is published on-chain: {borrower, limit, expiry, nonce, revocationVersion, v, r, s}.
  • An observer can verify the signature came from the registered TEE authority and can read the approved limit, but they cannot see what data informed the decision — only that a credit decision was made.
  • The vault independently re-checks the collateral-sufficiency math on-chain, so even a malicious or compromised TEE cannot inflate a limit past the collateral-ratio floor. The TEE's only private latitude is in tightening the limit below the collateral ceiling based on confidential inputs — it can never loosen below the on-chain floor.

This is the difference the FCC makes: a plain oracle or off-chain signer would have to either (a) publish the credit inputs so anyone could reproduce the decision — destroying borrower privacy — or (b) sign opaquely with no verifiable tie to specified inputs, leaving "TEE signs whatever the borrower requests" as a fair criticism. CreditGate's model specifies the inputs, keeps them confidential inside the TEE, and pairs the confidential computation with on-chain collateral re-verification — producing a credit limit that is privately derived yet publicly auditable.

Credit input and explicit offline mock

The Go handler defaults to CREDIT_BUREAU_MODE=onchain: it reads getBorrowerReputation(address) from the configured CreditGateVault and derives the same history score policy as the Python TEE (base score 60, completion reward, default penalty, and repayment-ratio reward). RPC failure returns CREDIT_BUREAU_UNAVAILABLE and never signs.

GET /credit-score/:address is a read-only diagnostic of that configured input. The deterministic address-derived score remains available only under CREDIT_BUREAU_MODE=mock for offline unit tests; it is not used by the live demo configuration.

Every live evaluation also includes loanId. The handler reads that exact vault slot and checks ELIGIBILITY_PENDING, borrower, collateral, nonce, and revocation version before signing. This prevents stale or cross-loan attestations from being produced.

FCC HTTP API

The Go FCC extension (fcc/credit-extension/extension/main.go) exposes the following HTTP routes on port ${PORT:-8080}. All are logged with structured method=… path=… status=… duration_ms=… lines. Only POST /action is invoked by the TEE proxy; the rest are read-only inspection / liveness endpoints for the frontend and judges.

MethodPathBodyPurpose
POST/actionJSON EvaluationInput (opCommand, loanId, borrower, collateralAmount, requestedLoan, expiry, nonce, revocationVersion, optional xrplAddress)TEE-proxy entry point. EVALUATE → credit-decision pipeline + EIP-191 signed EvaluationResult; REGISTER_XRPL → records the borrower's XRPL r-address.
GET/credit-score/:address—Configured private credit input — on-chain reputation in default mode; deterministic mock only when CREDIT_BUREAU_MODE=mock.
GET/eligibility/:address—Replays the most recently cached EvaluationResult for a borrower without re-running a signed evaluation. found:false if the address has never been evaluated.
GET/state—Handler state: signing authority address, config (collateral ratio, XRP/USD price), mode.
GET/health—Liveness probe: {"status":"ok","handler":"creditgate-fcc"}.
GET/info—Legacy proxy health check (kept for fce-extension-scaffold compat).

ContractRegistry — Dynamic Flare Lookup (Future-Proofing)

Flare publishes an on-chain ContractRegistry (Coston2: 0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019) that maps canonical protocol contract names — "FdcVerification", "FlareContractsV2" (alias "FtsoV2") — to their live addresses. Flare governance can upgrade these contracts over time (e.g. a new FdcVerification release with stronger attestation security, or an FtsoV2 with updated feed IDs). Without integration, the CreditGateVault would need a full redeploy to track each upgrade.

The vault therefore declares ftsoV2 and fdcVerification as mutable address state (NOT immutable) and exposes two owner-only re-resolve functions that query the registry by name and hot-swap the vault's references without any code change:

FunctionLooks upPurpose
updateFdcVerificationFromRegistry(address registry)"FdcVerification"Re-point the vault's FDC verifier to the canonical Flare release; reverts if the registry returns address(0). Emits FdcVerificationUpdated(newFdc).
updateFtsoV2FromRegistry(address registry)"FlareContractsV2" then "FtsoV2" (fallback)Re-point the vault's price-feed source. Emits FtsoV2Updated(newFtso).

Why this future-proofs the vault

  1. No redeploy on Flare upgrades. When Flare rotates FtsoV2 or FdcVerification, the protocol owner calls one transaction (per primitive) on the vault with the registry address. The vault re-reads the canonical contract name from the registry, updates its storage slot, and emits an event — the rest of the protocol continues to read the same vault storage slot.
  2. Per-chain portability. Hard-coded addresses work only on the chain they were deployed on. The registry lookup lets the same vault byte-code be redeployed on Flare mainnet (or future testnets) and re-resolved by the local operator without source edits, since each chain's ContractRegistry returns the addresses canonical-for-that-chain.
  3. Provable integration. IContractRegistry is a minimal interface mirroring Flare's published getContractAddressByName(string) selector, so the integration is verifiable against Flare's on-chain registry — a judge can call the same getContractAddressByName("FdcVerification") against 0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019 on Coston2 and confirm the vault uses the same canonical address.
  4. Audit-friendly. ftsoV2 / fdcVerification are intentionally NOT immutable precisely because the registry path needs to overwrite them post-deploy; the mutability is documented at the storage declaration and the only writers are the constructor and the two owner-only update…FromRegistry functions.

Canonical addresses (verified live via ContractRegistry 2026-08-05)

PrimitiveContractRegistry nameCoston2 address
FdcVerification"FdcVerification"0x906507E0B64bcD494Db73bd0459d1C667e14B933
FdcHub"FdcHub"0x3d18a078cBd3a4C2FfeFab1Ff927AfD9dBA87Ce7
FdcRequestFeeConfigurations"FdcRequestFeeConfigurations"0x191a1282Ac700edE65c5B0AaF313BAcC3eA7fC7e
ContractRegistry itself—0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019

The ContractRegistry integration is surfaced here as a live, owner-controlled address-resolution path.

Security Fixes Applied (Audit-Verified)

IDSeverityFix
M1MEDIUMSignature s/v bounds check + recovered≠0 in submitEligibility
M2MEDIUMIncrement borrowerNonce in revokeEligibility (rotate outstanding attestations)
L1LOWRe-check eligibilityExpiry in drawLoan (was only checked at submission)
L4LOWrecoverDefaultedCollateral — owner can recover seized FXRP
L5LOWSnapshot borrowerSourceAddressHash at draw time (prevent re-binding attack)
← PreviousOverviewNext →Deployment

ON THIS PAGE

System OverviewLiquidation Flow & New Risk Features ( auction / LTV / auto-trigger )EIP-191 Eligibility Attestation PayloadFDC Repayment Proof VerificationFlare Primitives (load-bearing)FCC Credit Evaluation Model (Gap #4)Credit Evaluation ModelContractRegistry — Dynamic Flare Lookup (Future-Proofing)Security Fixes Applied (Audit-Verified)