Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions features/offer-liability-headroom/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Offer Liability and Capacity Audit

Diagnostic snapshot of an account's Horizon offers next to balance and liability fields.

## Behavior

1. Validate a G-address (reject secret keys).
2. Fetch `/accounts/{id}` and `/accounts/{id}/offers` on the selected network.
3. Normalize buying/selling asset identities and exact `price_r` rationals.
4. Pair offers with balance rows; warn on unmatched assets or ledger skew.
5. Never imply that estimates guarantee execution.

## Data boundaries

- Configured Horizon networks only.
- Amounts and prices stay in strings / `BigInt` rationals.
- No offer mutation.

## Design decisions

- Large ledger skew between the two reads fails as `inconsistent_snapshot`.
- Small skew is shown as a warning so the UI stays usable.
- Approximate decimals are display-only; calculations keep the fraction.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { describe, expect, it } from "vitest";
import { renderFeature, screen } from "@/core/testing/render";
import { withMswHandlers } from "@/core/testing/msw";
import { resetHorizonClients } from "@/core/horizon/client";
import { OfferLiabilityHeadroomPanel } from "@/features/offer-liability-headroom/components/OfferLiabilityHeadroomPanel";
import { copy, errorCopy } from "@/features/offer-liability-headroom/copy";
import { handlers, rateLimitedHandler } from "@/features/offer-liability-headroom/msw/handlers";
import { accountId } from "@/features/offer-liability-headroom/fixtures/offerLiabilityHeadroom.fixture";

const server = withMswHandlers(...handlers);

describe("OfferLiabilityHeadroomPanel", () => {
it("shows empty state", () => {
renderFeature(<OfferLiabilityHeadroomPanel />);
expect(screen.getByText(copy.emptyTitle)).toBeInTheDocument();
});

it("renders offers and liabilities", async () => {
resetHorizonClients();
const { user } = renderFeature(<OfferLiabilityHeadroomPanel />, { network: "testnet" });
await user.type(screen.getByLabelText(copy.formLabel), accountId);
await user.click(screen.getByRole("button", { name: copy.submit }));
expect(await screen.findByText(copy.offersTitle)).toBeInTheDocument();
expect(screen.getByText(copy.liabilitiesTitle)).toBeInTheDocument();
});

it("explains invalid accounts", async () => {
const { user } = renderFeature(<OfferLiabilityHeadroomPanel />);
await user.type(screen.getByLabelText(copy.formLabel), "nope");
await user.click(screen.getByRole("button", { name: copy.submit }));
expect(await screen.findByText(errorCopy.invalid_account.title)).toBeInTheDocument();
});

it("explains rate limiting", async () => {
server.use(rateLimitedHandler);
resetHorizonClients();
const { user } = renderFeature(<OfferLiabilityHeadroomPanel />, { network: "testnet" });
await user.type(screen.getByLabelText(copy.formLabel), accountId);
await user.click(screen.getByRole("button", { name: copy.submit }));
expect(await screen.findByText(errorCopy.rate_limited.title)).toBeInTheDocument();
});
});
29 changes: 29 additions & 0 deletions features/offer-liability-headroom/__tests__/a11y.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { describe, it } from "vitest";
import { renderFeature, screen } from "@/core/testing/render";
import { expectNoAxeViolations } from "@/core/testing/axe";
import { withMswHandlers } from "@/core/testing/msw";
import { resetHorizonClients } from "@/core/horizon/client";
import { OfferLiabilityHeadroomPanel } from "@/features/offer-liability-headroom/components/OfferLiabilityHeadroomPanel";
import { copy } from "@/features/offer-liability-headroom/copy";
import { handlers } from "@/features/offer-liability-headroom/msw/handlers";
import { accountId } from "@/features/offer-liability-headroom/fixtures/offerLiabilityHeadroom.fixture";

withMswHandlers(...handlers);

describe("OfferLiabilityHeadroomPanel accessibility", () => {
it("has no WCAG A/AA violations idle", async () => {
const { container } = renderFeature(<OfferLiabilityHeadroomPanel />);
await expectNoAxeViolations(container);
});

it("has no WCAG A/AA violations after success", async () => {
resetHorizonClients();
const { container, user } = renderFeature(<OfferLiabilityHeadroomPanel />, {
network: "testnet"
});
await user.type(screen.getByLabelText(copy.formLabel), accountId);
await user.click(screen.getByRole("button", { name: copy.submit }));
await screen.findByText(copy.offersTitle);
await expectNoAxeViolations(container);
});
});
52 changes: 52 additions & 0 deletions features/offer-liability-headroom/__tests__/format.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { describe, expect, it } from "vitest";
import {
formatAssetLabel,
formatNetworkLabel,
formatOfferLine,
formatPrice
} from "@/features/offer-liability-headroom/lib/format";

describe("format helpers", () => {
it("labels networks and assets", () => {
expect(formatNetworkLabel("testnet")).toBe("Testnet");
expect(formatAssetLabel({ kind: "native", key: "native", label: "XLM (native)" })).toBe(
"XLM (native)"
);
});

it("formats exact prices with approximate preview", () => {
expect(
formatPrice({
numerator: "1",
denominator: "2",
display: "1/2",
approximate: "0.5"
})
).toBe("1/2 (≈ 0.5)");
});

it("formats offer lines", () => {
expect(
formatOfferLine({
id: "1",
selling: { kind: "native", key: "native", label: "XLM (native)" },
buying: {
kind: "credit",
key: "USDC:G",
code: "USDC",
issuer: "G",
label: "USDC"
},
amount: "25.0000000",
price: {
numerator: "1",
denominator: "2",
display: "1/2",
approximate: "0.5"
},
lastModifiedLedger: "1",
matchedBalance: true
})
).toContain("25.0000000");
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { describe, expect, it } from "vitest";
import {
buildLiabilityRows,
buildOfferRows
} from "@/features/offer-liability-headroom/lib/liability-audit";
import {
accountResponse,
offersResponse,
unmatchedOffersFixture
} from "@/features/offer-liability-headroom/fixtures/offerLiabilityHeadroom.fixture";

describe("liability-audit", () => {
it("builds balance rows with headroom estimates", () => {
const rows = buildLiabilityRows(accountResponse.balances);
expect(rows[0]).toMatchObject({
asset: { key: "native" },
availableToSellEstimate: "75"
});
});

it("matches offers to selling balance rows", () => {
const liabilities = buildLiabilityRows(accountResponse.balances);
const keys = new Set(liabilities.map((row) => row.asset.key));
const built = buildOfferRows(offersResponse._embedded.records, keys);
expect(built.malformed).toBe(false);
expect(built.offers[0]?.matchedBalance).toBe(true);
expect(built.offers[0]?.price.display).toBe("1/2");
});

it("flags unmatched selling assets", () => {
const liabilities = buildLiabilityRows(accountResponse.balances);
const keys = new Set(liabilities.map((row) => row.asset.key));
const built = buildOfferRows(unmatchedOffersFixture._embedded.records, keys);
expect(built.offers[0]?.matchedBalance).toBe(false);
});
});
33 changes: 33 additions & 0 deletions features/offer-liability-headroom/__tests__/offer-assets.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { describe, expect, it } from "vitest";
import { normalizeOfferAsset } from "@/features/offer-liability-headroom/lib/offer-assets";
import { issuerId } from "@/features/offer-liability-headroom/fixtures/offerLiabilityHeadroom.fixture";

describe("normalizeOfferAsset", () => {
it("normalizes native assets", () => {
expect(normalizeOfferAsset({ asset_type: "native" })).toEqual({
kind: "native",
key: "native",
label: "XLM (native)"
});
});

it("normalizes credit assets with uppercase codes", () => {
expect(
normalizeOfferAsset({
asset_type: "credit_alphanum4",
asset_code: "usdc",
asset_issuer: issuerId
})
).toMatchObject({
kind: "credit",
code: "USDC",
key: `USDC:${issuerId}`
});
});

it("rejects incomplete credit assets", () => {
expect(
normalizeOfferAsset({ asset_type: "credit_alphanum4", asset_code: "USDC" })
).toBeNull();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { describe, expect, it } from "vitest";
import { withMswHandlers } from "@/core/testing/msw";
import { resetHorizonClients } from "@/core/horizon/client";
import { runOfferLiabilityHeadroom } from "@/features/offer-liability-headroom/lib/offerLiabilityHeadroom";
import {
handlers,
inconsistentHandlerAccount,
inconsistentHandlerOffers,
malformedHandler,
rateLimitedHandler
} from "@/features/offer-liability-headroom/msw/handlers";
import {
accountId,
unknownAccountId
} from "@/features/offer-liability-headroom/fixtures/offerLiabilityHeadroom.fixture";

const server = withMswHandlers(...handlers);

describe("runOfferLiabilityHeadroom", () => {
it("loads account balances and offers", async () => {
resetHorizonClients();
const result = await runOfferLiabilityHeadroom({ accountId }, "testnet");
expect(result.ok).toBe(true);
if (!result.ok) return;
expect(result.value.offers).toHaveLength(1);
expect(result.value.liabilities.length).toBeGreaterThan(0);
});

it("maps account not found", async () => {
resetHorizonClients();
expect(await runOfferLiabilityHeadroom({ accountId: unknownAccountId }, "testnet")).toEqual({
ok: false,
code: "account_not_found"
});
});

it("maps malformed offers", async () => {
server.use(malformedHandler);
resetHorizonClients();
expect(await runOfferLiabilityHeadroom({ accountId }, "testnet")).toEqual({
ok: false,
code: "malformed_offer"
});
});

it("maps inconsistent snapshots", async () => {
server.use(inconsistentHandlerAccount, inconsistentHandlerOffers);
resetHorizonClients();
expect(await runOfferLiabilityHeadroom({ accountId }, "testnet")).toEqual({
ok: false,
code: "inconsistent_snapshot"
});
});

it("maps rate limiting", async () => {
server.use(rateLimitedHandler);
resetHorizonClients();
expect(await runOfferLiabilityHeadroom({ accountId }, "testnet")).toEqual({
ok: false,
code: "rate_limited"
});
});
});
29 changes: 29 additions & 0 deletions features/offer-liability-headroom/__tests__/rational-price.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { describe, expect, it } from "vitest";
import {
formatAmountString,
formatRationalApproximate,
formatRationalDisplay,
parseAmountString,
parseRationalPrice,
subtractAmounts
} from "@/features/offer-liability-headroom/lib/rational-price";

describe("rational-price", () => {
it("reduces and displays rationals exactly", () => {
const price = parseRationalPrice({ n: 2, d: 4 });
expect(price).toEqual({ n: 1n, d: 2n });
expect(formatRationalDisplay(1n, 2n)).toBe("1/2");
expect(formatRationalApproximate(1n, 2n)).toBe("0.5");
});

it("parses and subtracts amount strings without float drift", () => {
expect(parseAmountString("25.0000000")).toBe(250_000_000n);
expect(formatAmountString(250_000_000n)).toBe("25");
expect(subtractAmounts("100.0000000", "25.0000000")).toBe("75");
});

it("rejects invalid rationals and amounts", () => {
expect(parseRationalPrice({ n: 0, d: 1 })).toBeNull();
expect(parseAmountString("1.12345678")).toBeNull();
});
});
27 changes: 27 additions & 0 deletions features/offer-liability-headroom/__tests__/schema.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { describe, expect, it } from "vitest";
import { parseOfferLiabilityHeadroomInput } from "@/features/offer-liability-headroom/schema";
import { accountId } from "@/features/offer-liability-headroom/fixtures/offerLiabilityHeadroom.fixture";

describe("parseOfferLiabilityHeadroomInput", () => {
it("accepts a G-address", () => {
expect(parseOfferLiabilityHeadroomInput(accountId)).toEqual({
ok: true,
value: { accountId }
});
});

it("rejects empty, secret keys and malformed addresses", () => {
expect(parseOfferLiabilityHeadroomInput("")).toEqual({
ok: false,
code: "invalid_account"
});
expect(parseOfferLiabilityHeadroomInput("SAGH")).toEqual({
ok: false,
code: "invalid_account"
});
expect(parseOfferLiabilityHeadroomInput("not-an-address")).toEqual({
ok: false,
code: "invalid_account"
});
});
});
Loading
Loading