Skip to content

Architecture ​

This page is a high-level map of Fuel Credit. It explains how the deployed pieces fit together and links to their implementations.

Version boundary: V9 remains the active legacy testnet facility with immediate owner transfer and all-or-nothing default collateral. V10 is implemented but not deployed: it uses two-step ownership and floor-rounded pro-rata maturity settlement. New V10 originations stay disabled until a verified V10 contract and isolated indexer are configured; V9 positions remain contract-scoped and actionable. Mainnet is unchanged.

Code is the source of truth. This document describes the intended wiring but does not override contract code, deployment manifests, Worker configuration, migrations, tests, or verified onchain state.

System map ​

text
Fuel Wallet / Fuelet / Burner
            │
            ▼
     fuel.credit app
   Cloudflare Pages + React
            │
            ├── wallet transactions ────────────────┐
            │                                       │
            └── HTTPS                               ▼
                 testnet.api.fuel.credit/v1     Fuel testnet
                            │                   GraphQL RPC
                    API gateway Worker              │
                      ┌─────┴─────┐                 │
                      ▼           ▼                 │
                Oracle Worker  Indexer Worker ◄─────┘
                      │           │
              price sources       ├── D1 loan projection
              + Fuel time         ├── Queue + dead-letter queue
                                  ├── sync coordinator
                                  └── five-minute cron

The app is implemented in app/src/App.tsx. Network IDs and public service URLs live in app/src/fuelConfig.ts.

Source-of-truth hierarchy ​

From strongest to weakest:

  1. Fuel Credit contract state is authoritative for loans, pool accounting, ownership, and oracle configuration.
  2. Confirmed Fuel transaction receipts prove that requested state changes executed.
  3. D1 is a query projection of verified contract state. It makes account lookup and aggregate statistics efficient but cannot alter financial truth.
  4. The application UI displays contract or indexed state and submits wallet transactions.
  5. Browser storage is not used for public testnet loan discovery. Legacy simulation helpers remain internal test fixtures only.

The oracle is not a loan database and does not determine borrower terms. It signs only current price and timestamp data bound to the chain and contract.

Major components ​

Fuel Credit contract ​

contracts/fuel-credit/src/main.sw

The Sway contract owns the lending state machine:

  • permissionless pool funding;
  • owner-only idle withdrawals, ownership transfer, and oracle rotation;
  • fixed 6, 9, and 12-month terms;
  • upfront FUEL fee collection;
  • USDC principal issuance;
  • partial and full repayment strictly before maturity without collateral release;
  • borrower-only collateral claims at or after maturity for fully repaid loans;
  • terminal maturity and permissionless default finalization;
  • per-loan reads through get_loan(id);
  • aggregate accounting through pool_stats().

Its ABI is consumed by the app and deployment tools. Contract rules win if documentation or an indexed value disagrees.

V9 managed-strategy accounting ​

The isolated V9 contract adds an owner/manager split without changing borrower loan semantics. The owner proposes managers and approves strategies; the pending manager must accept; the manager may deploy only free USDC, FUEL, or stFUEL to approved identities. Locked borrower collateral is excluded. Authenticated strategy returns reduce tracked exposure and record excess as yield; bounded manager write-downs record losses. capital_accounting(), asset_liability(), and strategy_state() expose onchain balance, borrower collateral owed, free liquidity, loan receivables, exposure, yield, and loss for audit. Strategy exposure is capital at risk, not a liability.

Application ​

app/src/App.tsx

The React application:

  • builds and displays quotes;
  • connects Burner, Fuel Wallet, or Fuelet;
  • requests oracle attestations;
  • submits open, repay, maturity-claim, and finalize transactions through the connected wallet;
  • reports confirmed transaction IDs to the indexer as refresh hints;
  • loads complete account loan history from the indexer;
  • displays active loans refreshed from Fuel;
  • renders contract-reconciled global statistics.

Burner, Fuel Wallet, and Fuelet all use real indexed testnet state when Testnet is selected.

Test assets and faucet ​

contracts/test-asset/src/main.sw · contracts/test-faucet/src/main.sw

Testnet uses valueless tFUEL and tUSDC. The public faucet mints only tFUEL. The lending contract pool holds tUSDC, sends it to borrowers when loans open, and receives it back through repayments.

There is intentionally no mainnet test-asset path.

Price oracle ​

workers/oracle/src/index.ts · workers/oracle/wrangler.jsonc

A singleton PriceCache Durable Object refreshes its signed attestation once per minute using both a persistent alarm and a cron trigger. It:

  1. refreshes its market observation at most once every ten minutes using keyless CoinGecko, then the exact Bitget bid/ask midpoint, then the exact Gate bid/ask midpoint;
  2. validates the selected provider's timestamp and retains the observation durably;
  3. reads the latest Fuel block timestamp every minute;
  4. constructs, signs, and durably stores a domain-, chain-, and contract-bound attestation using that cached observation.

Public attestation requests only read that durable record. A missing or older-than-90-second signed record fails closed; user traffic never calls a price provider. Cron initializes the cache and the Durable Object alarm maintains it. Normal operation makes about 4,320 primary price requests per 30-day month regardless of user volume; Bitget and Gate are contacted only when an earlier source fails. Because the market observation is intentionally reused between provider refreshes, a valid freshly signed attestation can contain a market price approaching ten minutes old; this is an explicit availability/cost trade-off rather than proof of minute-level market freshness.

The oracle key is a Cloudflare secret and is separate from the owner/deployer wallet. The oracle has no transaction funds or pool authority.

The same Worker currently owns the testnet.api.fuel.credit custom hostname and forwards indexer routes through a private Cloudflare service binding.

Loan indexer ​

workers/indexer/src/index.ts · workers/indexer/wrangler.jsonc

The indexer provides account discovery and global statistics without reading every Fuel block.

Every five minutes it:

  1. resumes at the persisted contiguous loan-ID cursor;
  2. calls get_loan(id) until the first absent ID;
  3. inserts or updates discovered loans;
  4. rereads active loans;
  5. calls pool_stats();
  6. stores verified projections in D1.

Closed loans are terminal, so they do not require periodic reads. Active loans are also reread synchronously when their borrower requests My Loans.

See Loan indexing for schema, retry, deployment, and API details.

WASM-free Fuel reader ​

workers/indexer/src/fuel-reader.ts · workers/indexer/src/fuel-reader.test.ts

The complete Fuels SDK dynamically compiles WebAssembly and cannot run in the Cloudflare Worker runtime used here. The indexer therefore uses a narrow read-only adapter:

  • ABI-specific unsigned transaction templates;
  • raw Fuel GraphQL assembleTx requests;
  • fixed-width loan-ID patching;
  • contract-ID verification;
  • explicit decoding of RETURN_DATA for Option<Loan> and PoolStats.

Templates are pinned to this deployed contract and ABI. A new contract, ABI, chain, or incompatible Fuel transaction format requires regeneration and live parity testing before the indexer can be activated.

D1 ​

workers/indexer/migrations/0001_init.sql

D1 stores:

  • the next undiscovered loan ID;
  • current loan snapshots;
  • transaction reports;
  • verified observed receipts;
  • current protocol statistics.

Money values are stored as base-unit strings. Unique composite keys prevent duplicate records when queues retry:

text
loan:              chain_id + contract_id + loan_id
transaction report: chain_id + transaction_id
observed receipt:   chain_id + transaction_id + receipt_index

Queue, dead-letter queue, cron, and coordinator ​

The Worker bindings and schedule are defined in workers/indexer/wrangler.jsonc.

  • Cron requests discovery and reconciliation every five minutes.
  • Queue performs bounded asynchronous reads and updates with retry delivery.
  • Dead-letter queue retains jobs that exceed their retry allowance.
  • Durable Object coordinator prevents overlapping scheduler invocations from enqueueing the same cycle concurrently.
  • D1 uniqueness constraints provide final idempotency because Queue delivery is at least once.

None of these components holds a wallet or can submit a contract mutation.

Request flows ​

Opening a loan ​

text
App → oracle attestation → wallet confirmation → FuelCredit.open_loan
    → confirmed receipt → transaction report → indexer verification
    → loan discovery → D1 → My Loans / Stats

The transaction report accelerates indexing but is only a hint. Scheduled discovery finds the loan if the browser closes before reporting it.

Loading My Loans ​

text
App → GET /v1/accounts/:address/loans
    → D1 account lookup
    → synchronous get_loan() reads for active positions
    → D1 updates
    → refreshed account response

This works from a new browser because discovery is account-based in D1 rather than browser-local storage.

Repayment or finalization ​

text
Wallet transaction → confirmed receipt → POST transaction ID
    → verify successful Fuel receipt involves configured contract
    → enqueue reconciliation → reread affected active state

Duplicate reports are harmless because transaction IDs are unique in D1.

Loading protocol statistics ​

text
App → GET /v1/stats → live pool_stats() read
    + D1 loan aggregates → reconciliation comparison → response

Contract pool totals provide global financial truth. D1 supplies borrower counts, loan listings, and maturity groupings. A mismatch returns degraded freshness instead of pretending the projection is authoritative.

Public testnet API ​

Live base URL:

text
https://testnet.api.fuel.credit/v1
RoutePurpose
GET /healthOracle availability and configured contract
GET /attestationCurrent signed FUEL price attestation
GET /indexer/healthIndexer activation and cursor status
GET /accounts/:address/loansAccount history with active-loan refresh
GET /loans/:loanIdIndexed loan lookup
GET /statsContract-reconciled protocol statistics
POST /transactions/reportSubmit a confirmed transaction ID as a refresh hint

The deployment manifest is deployments/testnet.json. Testnet deployment logic is scripts/testnet.ts.

Testnet and mainnet isolation ​

Testnet and mainnet use separate:

  • contracts and deployment manifests;
  • owner and oracle custody;
  • Workers and activation flags;
  • D1 databases;
  • queues and dead-letter queues;
  • sync cursors;
  • raw-reader templates and parity tests;
  • monitoring and operational runbooks.

The public mainnet oracle/indexer endpoint is:

text
https://fuel-credit-mainnet-oracle.nick-j-alexander.workers.dev/v1

The contract, verified foundation custody, monitored oracle, and isolated indexer are active. Pools remain empty and the borrower app remains disabled until exact funding, a small funded lifecycle smoke, reconciliation, and explicit public activation approval.

Security boundaries ​

  • Wallets sign all user transactions; Workers never custody user keys.
  • The indexer cannot fund pools, repay, finalize, withdraw, transfer ownership, or rotate the oracle.
  • Transaction reports are untrusted hints and are checked against Fuel RPC.
  • Oracle attestations are limited to price and timestamp.
  • Public browser access is restricted to the configured application origin.
  • Queue retries cannot duplicate canonical loan, transaction, or receipt records.
  • Contract reads remain authoritative if D1 is delayed or unavailable.

Authority and recovery roles are documented in Protocol control structure.

Glossary ​

Active loan A loan with contract status 1. It can be repaid before maturity and is periodically refreshed by the indexer.

Attestation A recoverable oracle signature over the current price and timestamp, bound to the domain, chain, and contract.

Base units Integer token amounts: 9 decimals for FUEL/tFUEL and 6 decimals for USDC/tUSDC. D1 stores these as strings.

Contract reconciliation Comparing D1-derived totals with pool_stats() from the contract. A match yields reconciled freshness.

D1 projection A query-optimized copy of verified contract state. It is replaceable and is not financial authority.

Dead-letter queue (DLQ) The queue that retains repeatedly failing indexing jobs for operator inspection.

Delinquent loan An active loan finalized at or after maturity. Repayment is permanently closed and remaining collateral becomes vault-owned.

Discovery Finding new contiguous loan IDs by calling get_loan() from the stored cursor until the first missing ID.

Durable Object coordinator A per-chain/contract synchronization lock that prevents overlapping cron cycles.

Freshness Indexer confidence returned with data. reconciled means D1 aggregates match contract pool accounting; degraded means they do not.

Hydration Reading the complete Loan value for a newly discovered loan ID and writing its D1 snapshot.

Indexer The read-only Worker system that discovers loans, refreshes active state, verifies reports, and serves query APIs.

Maturity The terminal repayment deadline. There is no grace period or post-maturity cure. Fully repaid borrowers may claim collateral at or after this time.

Pool Contract-held liquidity and accounting from which principal is issued and to which repayment returns.

Raw reader The small WASM-free adapter that executes ABI-specific read transactions through vanilla Fuel GraphQL.

Reconciliation Rereading mutable contract state and replacing the corresponding D1 projection.

Sync cursor The next contiguous loan ID the indexer has not yet discovered.

tFUEL / tUSDC Permissionlessly mintable, valueless testnet assets. They are not mainnet FUEL or USDC.

Transaction report A client-submitted transaction ID used to request faster synchronization. It has no authority until Fuel RPC verification succeeds.

Open-source infrastructure on Fuel.