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
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ export const earnYieldCatalogAtom = Atom.family((key: YieldCatalogKey) => {
const source = enrichedYieldDirectoryResourceAtom.foreground(
new YieldDirectoryKey({
network: key.network,
token: key.token,
types: undefined,
yieldIds: key.yieldIds,
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,19 @@ export class AvailableYieldCategoriesKey extends Data.TaggedClass(
export class YieldCatalogKey extends Data.TaggedClass("YieldCatalogKey")<{
category: DashboardYieldCategory | null;
network: Network;
token: string | null;
yieldIds: ReadonlyArray<YieldId>;
}> {
constructor(input: {
readonly category: DashboardYieldCategory | null;
readonly network: Network;
readonly token?: string | null;
readonly yieldIds: ReadonlyArray<YieldId>;
}) {
super({
...input,
category: input.category,
network: input.network,
token: input.token ?? null,
yieldIds: [...new Set(input.yieldIds)].sort(),
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ const readYieldCatalog = (
new YieldCatalogKey({
category: null,
network: selectedToken.token.network,
token: selectedToken.token.address || selectedToken.token.symbol,
yieldIds: selectedToken.availableYields,
})
)
Expand Down
36 changes: 24 additions & 12 deletions packages/widget/src/resources/yield-directory/yield-directory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ import { YieldResourceSource } from "../../services/api/resource-sources";
import { withApiResourcePolicy } from "../../shared/effect/api-resource";
import {
API_MAX_PAGE_SIZE,
loadAllPages,
loadAllPagesByIdChunks,
YIELD_IDS_CHUNK_SIZE,
} from "../../shared/effect/pagination";
import { makePresentableResourceFamily } from "../resource-failure-presentation";
import type { YieldProviderError } from "../yield-provider/index";
Expand All @@ -27,18 +29,21 @@ type YieldType = EarnYield["mechanics"]["type"];

export class YieldDirectoryKey extends Data.TaggedClass("YieldDirectoryKey")<{
readonly network: Network | null;
readonly token: string | null;
readonly types: ReadonlyArray<YieldType>;
readonly yieldIds: ReadonlyArray<YieldId>;
}> {
constructor(input: {
readonly network?: Network | null;
readonly token?: string | null;
readonly types?: ReadonlyArray<YieldType>;
readonly yieldIds: ReadonlyArray<YieldId>;
readonly yieldIds?: ReadonlyArray<YieldId>;
}) {
super({
network: input.network ?? null,
token: input.token ?? null,
types: [...new Set(input.types ?? [])].sort(),
yieldIds: [...new Set(input.yieldIds)].sort(),
yieldIds: [...new Set(input.yieldIds ?? [])].sort(),
});
}
}
Expand Down Expand Up @@ -69,7 +74,7 @@ const yieldDirectoryCanonicalAtom = Atom.family((key: YieldDirectoryKey) =>
appRuntime
.atom(() =>
Effect.gen(function* () {
if (key.yieldIds?.length === 0) {
if (!key.token && key.yieldIds?.length === 0) {
return {
items: [],
missingYieldIds: [],
Expand All @@ -82,18 +87,25 @@ const yieldDirectoryCanonicalAtom = Atom.family((key: YieldDirectoryKey) =>
limit: API_MAX_PAGE_SIZE,
offset,
...(key.network ? { network: key.network } : {}),
...(key.token ? { token: key.token } : {}),
...(key.types.length > 0 ? { types: key.types } : {}),
...(yieldIds ? { yieldIds } : {}),
...(yieldIds && yieldIds.length > 0 ? { yieldIds } : {}),
});

const items = yield* loadAllPagesByIdChunks({
chunkSize: API_MAX_PAGE_SIZE,
concurrency: CONCURRENCY,
fetchPage: ({ ids, offset }) => fetchPage(offset, ids),
getItemId: (yieldModel) => yieldModel.id,
ids: key.yieldIds,
pageSize: API_MAX_PAGE_SIZE,
});
const items = key.token
? yield* loadAllPages({
concurrency: CONCURRENCY,
fetchPage: (offset) => fetchPage(offset),
pageSize: API_MAX_PAGE_SIZE,
})
: yield* loadAllPagesByIdChunks({
chunkSize: YIELD_IDS_CHUNK_SIZE,
concurrency: CONCURRENCY,
fetchPage: ({ ids, offset }) => fetchPage(offset, ids),
getItemId: (yieldModel) => yieldModel.id,
ids: key.yieldIds,
pageSize: API_MAX_PAGE_SIZE,
});
const returnedIds = new Set(items.map((yieldModel) => yieldModel.id));

return {
Expand Down
1 change: 1 addition & 0 deletions packages/widget/src/services/api/resource-sources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export type YieldDirectoryRequest = {
readonly limit: number;
readonly network?: Network;
readonly offset: number;
readonly token?: string;
readonly types?: ReadonlyArray<EarnYield["mechanics"]["type"]>;
readonly yieldIds?: ReadonlyArray<YieldId>;
};
Expand Down
1 change: 1 addition & 0 deletions packages/widget/src/services/api/yield-resource-source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const toYieldDirectoryParams = (request: YieldDirectoryRequest) => ({
limit: request.limit,
...(request.network ? { network: request.network } : {}),
offset: request.offset,
...(request.token ? { token: request.token } : {}),
...(request.types ? { types: [...request.types] } : {}),
...(request.yieldIds ? { yieldIds: [...request.yieldIds] } : {}),
});
Expand Down
1 change: 1 addition & 0 deletions packages/widget/src/shared/effect/pagination.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type PaginationMetadata = {
};

export const API_MAX_PAGE_SIZE = 100;
export const YIELD_IDS_CHUNK_SIZE = 20;

export const getNextPageOffset = ({
limit,
Expand Down
3 changes: 3 additions & 0 deletions packages/widget/tests/atoms/atom-family-key-identity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,18 @@ describe("atom family key identity", () => {
const first = new YieldCatalogKey({
category: null,
network: "ethereum",
token: "ETH",
yieldIds: [secondYieldId, firstYieldId, secondYieldId],
});
const equivalent = new YieldCatalogKey({
category: null,
network: "ethereum",
token: "ETH",
yieldIds: [firstYieldId, secondYieldId],
});

expect(first.yieldIds).toEqual([firstYieldId, secondYieldId]);
expect(first.token).toBe("ETH");
expect(earnYieldCatalogAtom(first)).toBe(earnYieldCatalogAtom(equivalent));
});

Expand Down
45 changes: 42 additions & 3 deletions packages/widget/tests/resources/yield-directory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { describe, expect, it, vi } from "@effect/vitest";
import { Cause, Deferred, Effect, Layer, Option } from "effect";
import { AsyncResult, Atom, AtomRegistry } from "effect/unstable/reactivity";
import { appRuntime } from "../../src/app/runtime/app-runtime";
import { YieldId } from "../../src/domain/identity/identifiers";
import {
enrichedYieldDirectoryResourceAtom,
YieldDirectoryError,
Expand Down Expand Up @@ -77,13 +78,51 @@ describe("Yield Directory resource", () => {
registry.get(yieldDirectoryResourceAtom(equivalent))
).items
).toHaveLength(101);
expect(listYields).toHaveBeenCalledTimes(2);
expect(listYields).toHaveBeenCalledTimes(6);
expect(listYields.mock.calls.map(([request]) => request.offset)).toEqual([
0, 0,
0, 0, 0, 0, 0, 0,
]);
expect(
listYields.mock.calls.map(([request]) => request.yieldIds?.length)
).toEqual([100, 1]);
).toEqual([20, 20, 20, 20, 20, 1]);
});

it("loads yields by token using standard pagination without yieldIds in request", () => {
const yields = Array.from({ length: 150 }, (_, index) =>
makeYield(`yield-${index}`)
);
const listYields = vi.fn((request: YieldDirectoryRequest) => {
expect(request.yieldIds).toBeUndefined();
expect(request.token).toBe("ETH");
expect(request.network).toBe("ethereum");

return Effect.succeed({
items: yields.slice(request.offset, request.offset + request.limit),
limit: request.limit,
offset: request.offset,
total: yields.length,
});
});
const registry = makeRegistry(
YieldResourceSource.of({ listYields } as never)
);
const missingId = YieldId.make("yield-999");
const key = new YieldDirectoryKey({
network: "ethereum",
token: "ETH",
yieldIds: [yields[0]!.id, yields[149]!.id, missingId],
});

const result = AsyncResult.getOrThrow(
registry.get(yieldDirectoryResourceAtom(key))
);

expect(result.items).toHaveLength(150);
expect(result.missingYieldIds).toEqual([missingId]);
expect(listYields).toHaveBeenCalledTimes(2);
expect(listYields.mock.calls.map(([request]) => request.offset)).toEqual([
0, 100,
]);
});

it("skips empty ID sets and distinguishes explicit directories", () => {
Expand Down
Loading