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
3 changes: 3 additions & 0 deletions src/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ export function buildOpenApiSpec(): Record<string, unknown> {
"/api/v1/liquidity/entries": {
get: { summary: "List raw per-anchor liquidity entries" },
},
"/api/v1/liquidity/anchors/{anchor}": {
get: { summary: "List raw liquidity entries for a single anchor" },
},
"/api/v1/liquidity/{asset}": {
get: { summary: "Read the aggregated pool for one asset" },
},
Expand Down
11 changes: 11 additions & 0 deletions src/repositories/liquidityRepository.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,17 @@ describe("LiquidityRepository", () => {
expect(repo.byAsset("EURC")).toHaveLength(1);
});

it("filters entries by anchor", () => {
const repo = new LiquidityRepository();
repo.upsert(entry("anchorA", "USDC", 100));
repo.upsert(entry("anchorB", "USDC", 50));
repo.upsert(entry("anchorA", "EURC", 75));

expect(repo.byAnchor("anchorA")).toHaveLength(2);
expect(repo.byAnchor("anchorB")).toHaveLength(1);
expect(repo.byAnchor("anchorC")).toHaveLength(0);
});

it("aggregates pools per asset", () => {
const repo = new LiquidityRepository();
repo.upsert(entry("anchorA", "USDC", 100));
Expand Down
5 changes: 5 additions & 0 deletions src/repositories/liquidityRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ export class LiquidityRepository extends InMemoryRepository<string, LiquidityEnt
return this.listAll().filter((e) => e.asset === asset);
}

/** Returns all entries for a given anchor. */
byAnchor(anchor: string): LiquidityEntry[] {
return this.listAll().filter((e) => e.anchor === anchor);
}

/** Returns every stored entry. */
all(): LiquidityEntry[] {
return this.listAll();
Expand Down
33 changes: 33 additions & 0 deletions src/routes/liquidity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,4 +171,37 @@ describe("liquidity routes", () => {
expect(res.body.error.code).toBe("BAD_REQUEST");
}
});

it("lists entries by anchor via GET /anchors/:anchor", async () => {
const app = createApp();
await request(app)
.post("/api/v1/liquidity")
.send({ anchor: "anchorA", asset: "USDC", amount: 500 });
await request(app)
.post("/api/v1/liquidity")
.send({ anchor: "anchorB", asset: "USDC", amount: 300 });
await request(app)
.post("/api/v1/liquidity")
.send({ anchor: "anchorA", asset: "EURC", amount: 150 });

const res = await request(app).get("/api/v1/liquidity/anchors/anchorA");

expect(res.status).toBe(200);
expect(res.body.entries).toHaveLength(2);

const assets = res.body.entries.map((e: any) => e.asset).sort();
expect(assets).toEqual(["EURC", "USDC"]);
});

it("does not shadow /:asset with /anchors/:anchor", async () => {
const app = createApp();
// Insert an asset named "anchors" just to be sure it can still be fetched
await request(app)
.post("/api/v1/liquidity")
.send({ anchor: "anchorA", asset: "ANCHORS", amount: 500 });

const res = await request(app).get("/api/v1/liquidity/ANCHORS");
expect(res.status).toBe(200);
expect(res.body.total).toBe(500);
});
});
5 changes: 5 additions & 0 deletions src/routes/liquidity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ export function liquidityRouter(service: LiquidityService): Router {
res.json(service.removeEntry(req.params.anchor, req.params.asset));
});

// Read the raw liquidity entries for a single anchor.
router.get("/anchors/:anchor", (req: Request, res: Response) => {
res.json({ entries: service.listByAnchor(req.params.anchor) });
});

// Read the aggregated pool for a single asset.
router.get("/:asset", (req: Request, res: Response) => {
res.json(service.getPool(req.params.asset));
Expand Down
15 changes: 15 additions & 0 deletions src/services/liquidityService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,19 @@ describe("LiquidityService", () => {
expect.objectContaining({ status: 404, code: "NOT_FOUND" }),
);
});

it("lists entries by anchor", () => {
const service = makeService();
service.addLiquidity({ anchor: "anchorA", asset: "USDC", amount: 100 });
service.addLiquidity({ anchor: "anchorB", asset: "USDC", amount: 50 });
service.addLiquidity({ anchor: "anchorA", asset: "EURC", amount: 75 });

const entriesA = service.listByAnchor("anchorA");
expect(entriesA).toHaveLength(2);
expect(entriesA.map(e => e.asset).sort()).toEqual(["EURC", "USDC"]);

const entriesB = service.listByAnchor("anchorB");
expect(entriesB).toHaveLength(1);
expect(entriesB[0].asset).toBe("USDC");
});
});
6 changes: 6 additions & 0 deletions src/services/liquidityService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,10 @@ export class LiquidityService {
listEntries(): LiquidityEntry[] {
return this.repo.all();
}

/** Returns all raw liquidity entries for a given anchor. */
listByAnchor(anchorInput: unknown): LiquidityEntry[] {
const anchor = requireString(anchorInput, "anchor");
return this.repo.byAnchor(anchor);
}
}