import { UserRequest, UserRequestEvent } from '../services/UserLending/types'; /** * The lending-request view model, as CODES. * * Lifted verbatim from kasu-ui's `derive-transaction-view.ts` + * `request-bundle.ts`, minus every word. kasu-ui's `deriveTransactionView` * produces a `TransactionView` that mixes derived FACTS (status code, amounts, * kind, ids, submission count, timestamps) with COPY (the status word, the * type label, the detail line beneath it, tooltip keys). Only the facts belong * in this layer: the copy stays in each application, where the design system, * the register and the visitor's language are. * * The branch ORDER below is the behaviour — cancelled beats forced beats * reallocated beats a live withdrawal remainder beats the resolved outcomes * beats pending. It reproduces `deriveTransactionView` check for check, so a * consumer that renders its own words on top of `statusCode` gets exactly the * row kasu-ui has been showing. */ /** * Stable status code for filters, tests and the caller's own status word. * Independent of any user-facing copy — kasu-ui's four-value vocabulary * (Queued / Processing / Completed / Cancelled) is a rendering of these seven * codes, not a replacement for them. */ export type RequestStatusCode = 'pending' | 'complete' | 'partial' | 'rejected' | 'reallocated' | 'cancelled' | 'forced'; /** * Direction of value flow into / out of the lender's balance. `neutral` * applies when the request was cancelled or fully rejected — nothing moved. */ export type RequestKind = 'inflow' | 'outflow' | 'neutral'; /** One derived request row. Every field is a number, a code or an id. */ export interface RequestState { /** `UserRequest.id`. */ id: string; /** * Id the loan contract is retrieved by — sourced from the `Initiated` * event (format `${requestId}-${index}`). The agreements upstream keys * contract metadata by EVENT id, not request id; passing `id` returns * "Signature data not found". Empty string when there is no `Initiated` * event (e.g. a withdrawal). */ contractId: string; /** Pool address, lowercased — the filter/join key. */ poolId: string; /** * Pool name exactly as it arrived. RAW: splitting it into a strategy name * and its borrower-type subheading (kasu-ui's `splitPoolName`) is a * display decision and stays in the application. */ poolName: string; /** * Tranche name exactly as the subgraph reports it. RAW: apps call * `getTrancheDisplayName(state.trancheName, { poolName: state.poolName })` * at the view boundary. A renamed value must never reach matching or * ranking code. */ trancheName: string; /** Tranche id — the resolve API re-renders a fixed-term contract against it. */ trancheId: string; /** Fixed-term configId, `'0'` for a variable-rate request. */ fixedTermConfigId: string; requestType: 'Deposit' | 'Withdrawal'; /** The subgraph status, untouched. Sticky — see `cycleClosed`. */ rawStatus: UserRequest['status']; statusCode: RequestStatusCode; kind: RequestKind; /** * The amount originally asked for, always positive. A cancelled request * has its on-request `requestedAmount` zeroed by the subgraph, so the * original is recovered from the `Initiated` event (see * `initiatedAmount`). */ requestedAmount: number; /** * The accepted figure as the subgraph reported it, or `null` when it * reported none. `null` is NOT zero: an absent figure means "not known", * and a caller that renders it as 0 states an outcome the chain has not * given. The branch logic below reads `null` as 0 — exactly what kasu-ui's * `num()` does — but the distinction survives into the row. */ acceptedAmount: number | null; /** * `assetAmount` of the `Initiated` event, or `null` when the request * carries no `Initiated` event. This is the cancelled-amount recovery: * the subgraph resets a cancelled request's `requestedAmount` to 0 (the * lender's effective balance is restored) and the original survives only * here. */ initiatedAmount: number | null; /** * The rejected figure the subgraph reported, parsed with the same `num` * rule as the rest: an absent or unparseable value is 0. Unlike * `acceptedAmount` this is NOT nullable — it feeds the partial-vs-complete * branch, where "no figure" and "nothing rejected" mean the same thing. */ rejectedAmount: number; /** * `assetAmount` of the reallocation event, or 0 when the request was not * reallocated. The amount that LEFT the requested tranche — which is not * necessarily `acceptedAmount`, because a partly-filled request can be * reallocated for only part of itself. */ reallocatedOutAmount: number; /** * RAW name of the tranche a reallocated deposit was accepted into, or * `null` when there was no reallocation. RAW for the same reason * `trancheName` is: the app calls `getTrancheDisplayName` on it at the * view boundary, and a renamed value must never reach matching code. */ reallocationTargetTrancheName: string | null; /** * The row's highest-resolution timestamp: the LATEST event, or * `request.timestamp` when the timeline is empty or older. Unix seconds. * * `firstSubmissionTimestamp` says when the lender asked; this says when * anything last happened to the request, which is what a "last updated" * column and a recency sort need. */ lastTimestamp: number; /** * The signed figure the row shows, chosen by the same branch logic * kasu-ui uses: positive for an inflow, negative for an outflow, 0 for a * cancelled or fully-rejected request. */ amount: number; /** * Submissions bundled into this dNFT-aggregate row (`Initiated` + * `Increased`). `> 1` is what drives kasu-ui's inline "(x2)". */ submissionCount: number; /** * Timestamp of the FIRST submission in the bundle, or `null` when the * bundle carries no submission event yet (the `Initiated` event has not * indexed). kasu-ui substitutes `request.timestamp` there; that fallback * is the application's to choose, so this layer reports the absence. */ firstSubmissionTimestamp: number | null; /** Has THIS request's cycle closed? — `!canCancel`. See `isCycleClosed`. */ cycleClosed: boolean; /** The SDK's per-pool cancel signal, passed through. */ canCancel: boolean; } /** * The submissions bundled into one dNFT-aggregate request row, input order * preserved — one loan agreement per submission. */ export declare function submissionEvents>(events: T[]): T[]; /** Count the submissions bundled into one dNFT-aggregate request row. */ export declare function countSubmissions(events: Pick[]): number; /** * Timestamp of the FIRST submission in the bundle. Falls back to `fallback` * when the `Initiated` event has not indexed yet — the caller decides what * that is (kasu-ui passes the request's own timestamp). */ export declare function firstSubmissionTimestamp(events: Pick[], fallback: number): number; /** * The LATEST event timestamp, or `fallback` when the timeline is empty — or * when every event predates it, because `fallback` seeds the reduction. That * seeding is deliberate and is kasu-ui's behaviour: `request.timestamp` is a * fact about the request, and an event indexed with an earlier clock must not * make the row look older than the request itself. */ export declare function lastEventTimestamp(events: Pick[], fallback: number): number; /** * Has THIS request's cycle closed? — the single open/closed signal behind the * status vocabulary and behind Cancel. * * `request.canCancel` is the SDK's `isCancelable(status, poolId)` — * `status !== 'Processed' && !isLendingPoolClearingPending(pool)`. It is * per-POOL and reads the same condition the contract enforces on the cancel * call — but it reads it ONCE, when the request was fetched. Nothing about * this value is live, so a client that holds a request across a cycle close * must refetch before acting on it. * * The raw subgraph `status` must NOT feed this: `'Processing'` is a STICKY * historical marker set on the first partial fill and never reset, so gating * on it would freeze a partly-filled request in Processing forever. A global * settlement clock is equally wrong here — it is blind to whether THIS * request's pool is already clearing. */ export declare function isCycleClosed(request: Pick): boolean; /** * Convert a `UserRequest` into a `RequestState`. Pure — no clock, no network, * no copy. * * BRANCH ORDER (this IS the behaviour; it reproduces kasu-ui's * `deriveTransactionView` check for check): * * 1. a `Cancelled` event → `cancelled`, neutral, 0 * 2. a withdrawal with a `Forced` event → `forced`, outflow, −accepted * 3. a reallocated deposit → `reallocated`, inflow, +accepted * 4. a withdrawal partly filled with a LIVE * remainder (cycle still open) → `partial`, outflow, −accepted * 5. resolved (`status === 'Processed'`): * withdrawal, partly filled → `partial`, outflow, −accepted * withdrawal, fully filled → `complete`, outflow, −accepted * deposit, nothing accepted → `rejected`, neutral, 0 * deposit, part rejected → `partial`, inflow, +accepted * deposit, fully accepted → `complete`, inflow, +accepted * 6. otherwise → `pending`, ±requested * * `cancelled` is reachable ONLY from branch 1 — a Cancelled EVENT. No * processing or resolved state can derive it. * * Branch 4 is checked BEFORE the resolved branch and gates on `isCycleClosed` * (i.e. `canCancel`), never on the sticky raw status: a withdrawal that was * partly filled returns to the queue with a live Cancel, and reading the raw * status would strand it. * * WHAT THE APPLICATION STILL OWNS: the status word and the detail line beneath * it; the tranche display rename (`getTrancheDisplayName` on `trancheName` * and on `reallocationTargetTrancheName`, both of which are RAW here); the * pool-name split; the amount format. The "view loan agreement" affordance is * a fact, and it follows from two fields already here — * `requestType === 'Deposit' && statusCode !== 'cancelled' && statusCode !== 'rejected'` * — because neither a cancelled nor a fully-rejected deposit ever issued one, * and withdrawals sign no agreement at all. */ export declare function deriveRequestState(request: UserRequest): RequestState;