> For the complete documentation index, see [llms.txt](https://docs.usegimbal.app/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.usegimbal.app/architecture/platform.md).

# How the platform is built

What runs behind usegimbal.app/platform, from its six routes and the Privy to Supabase bridge to the schema, the typed-data signing, on-chain settlement and the instrument-panel design.

Nothing on the platform is hardcoded. Every market, price, parameter, request, offer, loan and event on screen is read from Supabase as the page renders, so what a user sees is the state of the book rather than a snapshot of it. The platform is Gimbal's reference front-end and relayer in a single application, reachable at usegimbal.app/platform: Next.js 16 on the App Router, React 19 and TypeScript, Tailwind CSS 4, Privy for sign-in, Supabase (Postgres) for the shared order book and loan registry, and viem for typed-data hashing and settlement. The screen is an instrument panel, dark and quiet, organised around live readouts.

## The six routes

| Path                  | Access                           | What it shows                                                                                                                                                                                                                                                          |
| --------------------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `/platform`           | Public                           | Telemetry: outstanding principal, open capacity and demand, liquidations; a table of markets showing each tier's limits, prices and a live indicative rate; the open requests                                                                                          |
| `/platform/borrow`    | Sign-in required                 | A request form asking for collateral, principal, a rate ceiling, a term and a launch window in hours, with LTV, health factor and origination fee computed as you type; the user's own requests, each with how much of it the book currently covers; the settle button |
| `/platform/lend`      | Public, with more once signed in | The open requests; a form for standing or targeted offers and their flags; the user's offers, what capacity each has left, and cancellation                                                                                                                            |
| `/platform/positions` | Sign-in required                 | Loans taken (debt, accrued interest, health factor, repay, top up) and slices held (principal, APR, accrued, health, maturity)                                                                                                                                         |
| `/platform/explorer`  | Public                           | The Explorer: the loan registry, each loan's syndicate and flight log, concentration by token against the exposure caps, and the live parameter set                                                                                                                    |
| `/platform/settings`  | Sign-in required                 | Profile and the attestations issued to the user's wallet, role by role                                                                                                                                                                                                 |

Four product terms recur. Telemetry names the live readouts of the protocol. The Explorer is the loan explorer anyone may open. A flight log is one loan's event history, each entry pointing at its Blockscout transaction. The launch window is a borrow request's fill deadline. [Reading the Explorer](/guides/explorer.md) walks through the explorer.

## Sign-in

Privy signs users in by email, Google or any EVM wallet. For a user who arrives without one, an embedded Robinhood Chain wallet is created, and it signs each request and offer from then on.

## Joining Privy to Supabase

Supabase has never heard of Privy, so the server joins the two:

1. Anything public, meaning prices, markets, parameters, the book, loans, slices and events, the browser fetches itself using the anon key on its own.
2. Whatever is done on a user's behalf, whether fetching a profile or attestation list, submitting a request or an offer, cancelling, settling, repaying or topping up, is sent as a `POST` to `/api/platform/db`, carrying the Privy access token in the `Authorization` header.
3. The route checks the token with `@privy-io/server-auth`, answering 401 to a missing or invalid one and 400 to an unknown operation. It validates the arguments in `src/lib/platform/ops.ts`, then performs the operation through a Supabase client holding the service-role key, which attaches the verified user id as the `x-gimbal-actor` header. `app_user_id()` in the schema accepts that header only when the JWT role is `service_role`, which means the `security definer` functions run their ownership checks against the actual user.

That key never reaches a browser, and the route refuses to start if an anon or publishable key has been put in its place. Row-level security continues to bound anon reads, and writes continue to pass through the functions in the database that apply the matching rules.

## The schema

`supabase/schema.sql` gives each on-chain structure a table:

| Table                            | Writer                                    | On chain                                                                                                                |
| -------------------------------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `profiles`                       | The user, own row only                    | Nothing                                                                                                                 |
| `markets`                        | Service role                              | Token and tier settings in `PolicyBoard`                                                                                |
| `oracle_prices`                  | Service role, from a keeper or a cron job | `PriceSentinel.quote()`; the table is append-only and the `market_latest_prices` view returns the newest row per market |
| `protocol_params`                | Service role                              | `PolicyBoard` parameters, plus the addresses under `deployment`                                                         |
| `attestations`                   | Service role, as the KYC issuer           | `AttestationLedger`                                                                                                     |
| `borrow_requests`                | Borrowers, over the API route             | Request side of the relayer's book                                                                                      |
| `offers`                         | Lenders, over the API route               | Offer side of the relayer's book, each row a signed EIP-712 message                                                     |
| `loans`, `slices`, `loan_events` | Database functions and nothing else       | `TermDesk` state                                                                                                        |

`protocol_params` is keyed `terms`, `fees`, `loan`, `auction`, `refinance`, `tiers`, `chain` and `deployment`. A `protocol_stats` view feeds the Telemetry figures, and `has_role(wallet, role)` answers eligibility from the mirrored attestations.

Every state change runs as a `security definer` function: `cancel_request`, `cancel_offer`, `settle_request`, `record_collateral_added` and `record_repayment`, with `loan_debt()` computing what is owed. Keeping the transition in the database is what lets it apply the rules `TermDesk` applies on chain, in the same way: expiry, term, the APR ceiling, tier or token scope, remaining capacity, the minimum fill and full coverage.

## Signing

Offers and requests are EIP-712 typed data with the primary types `LendOffer` and `BorrowRequest`. Struct layout and domain (`Gimbal`, version `1`, the chain id, `TermDesk` as verifying contract) match `contracts/src/libraries/OfferHash.sol`, so a relayer, or the desk itself, can verify a stored message later; the typed-data hash is saved next to the signature for that purpose. A standing offer scoped to a whole tier carries the tier sentinel `address(uint160(tier))` as its collateral token. Flags are bits: `SELF_LIQUIDATE` (1), `NO_CLOSED_MARKET_LIQUIDATION` (2) and `PARK_IDLE` (4). Signing runs through Privy's `signTypedData` and costs no gas.

## Settling

The `protocol_params` row keyed `deployment` holds the contract addresses under `chainId`, `TermDesk`, `CollateralAuction`, `RolloverAuction`, `PriceSentinel`, `RoleRegistry` and `LenderSlice`, filled from `contracts/deployments/4663.json`. With a `TermDesk` address in place, settling a request means an actual `originate` call goes on chain via viem's `writeContract`, signed by the user's Privy wallet, and its hash is written onto the loan row. Without one, the match is written to the book only, and the panel says so. Matching is greedy by APR, cheapest offer first, exactly as the relayer documentation lays out; see [Offers, requests and origination](/mechanics/offers-and-origination.md) and [Services off the chain](/architecture/services.md).

## The design system

The marketing site and the platform draw on one design system and read as one product. Grounds are cool and pale: Snow (`#f7f8fb`) for the page, White for cards, Frost (`#f0f2f7`) for tinted panels and nested blocks, with Mist (`#e2e6ee`) on borders, rules and the dot grid. Text is Ink (`#12161f`) for headings and primary copy, Slate (`#4b5261`) for body text and Stone (`#8b91a0`) for overlines. There is one accent, Cobalt (`#2b4fd8`), taken from the deep blue band of the mark and used at five intensities for actions, links, active states, badges and accented cards. Three colours exist for meaning alone: Ember (`#e08a3c`) for warnings and margin calls, Moss (`#3f9f6a`) for healthy figures and Coral (`#d45a4a`) for liquidation and loss. No decorative gradient exists.

Fraunces at weight 400 sets display text: page headings, the wordmark and the large figures, with an italic in cobalt for the one accented phrase in a heading. Plus Jakarta Sans sets everything else, including panel titles at 600, labels, buttons, tables and every number, address, hash and countdown with tabular figures, because the numbers are what each screen exists to show. Panels are white cards with 20 px corners and a hairline of Mist, nested blocks use Frost at 14 px, badges are 6 px, inputs 10 px, and the primary call to action is 16 px with a cobalt glow beneath it. Behind a hero sit two slow washes of cobalt under a fine grain and a faint dot grid; the only drawn ornament is a pair of hairline rings echoing the mark. On a phone the platform's four daily pages sit in a blurred bottom bar. Motion stays slow: washes drift, a card floats by a few pixels, a figure counts up when its page loads, and nothing bounces or flashes. Both applications share the `.card`, `.surface`, `.btn` and `.input` primitives from `src/app/globals.css`. [Brand and channels](/resources/brand.md) has the wider system.

## Configuration

Setup lives in `supabase/README.md` and the `.env.example` at the repository root: `NEXT_PUBLIC_PRIVY_APP_ID` and `PRIVY_APP_SECRET`, `NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_ANON_KEY` and `SUPABASE_SERVICE_ROLE_KEY`, plus the chain (`NEXT_PUBLIC_CHAIN_ID=4663`, name, RPC and explorer). If a public variable is missing, the platform renders a setup notice that names it instead of failing quietly.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.usegimbal.app/architecture/platform.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
