Skip to content
Open
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
8 changes: 8 additions & 0 deletions .changeset/tidy-pandas-gather.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@domain/entity-asset": minor
"@domain/entity-interest-rate": minor
"@domain/api-assets-data": minor
"@features/platform-assets-data": minor
---

Scaffold the DDD packages that will receive the dada-client code: assets and interest-rate entities, the assets-data API client and its app-facing platform layer
67 changes: 67 additions & 0 deletions domain/api/assets-data/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# @domain/api-assets-data

> [!CAUTION]
> **Status: UNSTABLE** — Being migrated out of `live-common`; the API is still being assembled.

Domain API client for **DADA** — Dynamic Assets Data Aggregator — the service that returns
aggregated crypto asset metadata, market data and interest rates.

One DADA response carries six collections in a single payload:

| Collection | Owned by |
| --- | --- |
| `cryptoAssets` | `@domain/entity-asset` |
| `networks` | `@domain/entity-asset` |
| `currenciesOrder` | `@domain/entity-asset` |
| `interestRates` | `@domain/entity-interest-rate` |
| `cryptoOrTokenCurrencies` | `@domain/entity-currency` (via `@domain/api-currency-token`) |
| `markets` | nobody yet — see below |

This package owns the cross-entity response contract, the requests, the transformations and the
RTK Query endpoints. It does not own UI or app composition.

## Why the wire types live here

`ApiAsset`, `ApiTokenCurrency`, `ApiCryptoCurrency` and `RawApiResponse` describe DADA's
**transport format**, so they belong to the API package. The entity packages own only canonical
shapes. `AssetsData` — the transformed aggregate — is this package's return contract, composed of
those entities.

This mirrors `domain/api/market-sentiment` + `domain/entity/market-sentiment`, where the entity
owns the canonical schema and the API owns the wire schema plus `transforms.ts`.

## `markets` is deliberately loosely typed

There is no `domain/entity/market`, and the `markets` collection keeps its existing type,
`PartialMarketItemResponse` = `Partial<MarketItemResponse>`.

Creating a real market entity would roughly double the migration and pull in a large surface owned
by another team. Because every field of a `Partial<>` is optional there is also nothing meaningful
to validate, so `markets` is excluded from the response-validation work.

### Accepted debt

`dadaIdToMarketId()` and the market item type are **copied** from
`libs/ledger-live-common/src/market`, not imported: a `domain/*` package must not import legacy
`libs/*`. Since every field is optional, future divergence from the original will **never** produce
a type error. Revisit when a real market entity exists.

## Invariants owned here

Both are load-bearing and neither can produce a type error, so do not "clean them up":

1. **`convertApiAssets` is lenient by design.** It silently drops tokens whose parent chain is
unknown, and *synthesises* a currency (placeholder `color`, empty `explorerViews`) for cryptos
missing from the local registry rather than dropping them. Assets DADA knows about but the local
CAL does not still render.
2. **`getChunkedAssetsData` succeeds if _any_ chunk resolves.** Requests are chunked by currency id;
partial results are returned rather than failing the whole query.

When response validation is added, it must use per-item `safeParse`-and-drop, never a top-level
`parse()`. DADA is a live aggregator whose shape evolves, and one unmodelled field failing the whole
response would blank out Market, Portfolio and the asset selector.

## Status

Scaffolded and empty. Code arrives from `libs/ledger-live-common/src/dada-client` in `LIVE-35226`.
Tracking epic: `LIVE-35223`.
20 changes: 20 additions & 0 deletions domain/api/assets-data/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module.exports = {
testEnvironment: "node",
roots: ["<rootDir>/src"],
testMatch: ["**/*.test.ts"],
transform: {
"^.+\\.(t|j)sx?$": [
"@swc/jest",
{
jsc: {
target: "esnext",
},
},
],
},
coverageReporters: ["json", ["lcov", { file: "lcov.info", projectRoot: "../../../" }], "text"],
reporters: [
"default",
["jest-sonar", { outputName: "sonar-executionTests-report.xml", reportedFilePath: "absolute" }],
],
};
29 changes: 29 additions & 0 deletions domain/api/assets-data/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "@domain/api-assets-data",
"version": "0.1.0",
"private": true,
"description": "Domain API client for DADA (Dynamic Assets Data Aggregator): wire contract, RTK Query endpoints and response transforms",
"main": "src/index.ts",
"types": "src/index.ts",
"exports": {
".": "./src/index.ts",
"./package.json": "./package.json"
},
"sideEffects": false,
"scripts": {
"typecheck": "tsc --noEmit",
"test": "jest --passWithNoTests",
"test:watch": "jest --watch",
"coverage": "jest --coverage --passWithNoTests",
"unimported": "pnpm knip --directory ../../.. -W domain/api/assets-data"
},
"devDependencies": {
"@jest/globals": "catalog:",
"@swc/core": "catalog:",
"@swc/jest": "catalog:",
"@types/jest": "catalog:",
"@types/node": "catalog:",
"jest": "catalog:",
"typescript": "catalog:"
}
}
8 changes: 8 additions & 0 deletions domain/api/assets-data/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "@domain/api-assets-data",
"targets": {
"build": {
"executor": "nx:noop"
}
}
}
1 change: 1 addition & 0 deletions domain/api/assets-data/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {};
13 changes: 13 additions & 0 deletions domain/api/assets-data/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"lib": ["ES2022"],
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"noEmit": true,
"types": ["jest"]
},
"include": ["src/**/*"],
"exclude": ["node_modules", "lib"]
}
23 changes: 23 additions & 0 deletions domain/entity/asset/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# @domain/entity-asset

> [!CAUTION]
> **Status: UNSTABLE** — Being migrated out of `live-common`; the API is still being assembled.

Domain entity for aggregated assets. Owns the canonical shapes for the asset-level collections
returned by DADA:

| Type | Describes |
| --- | --- |
| `CryptoAssetMeta` | A meta-currency grouping: id, ticker, name and its per-network asset ids |
| `NetworkInfo` | A network's id and display name |
| `CurrenciesOrder` | The server-provided ordering: sort key, direction and ordered meta-currency ids |

Pure entity package: runtime schemas, inferred types, defaults and mocks. **No network calls, no
feature state.** Requests, transformations and RTK Query endpoints belong to
`@domain/api-assets-data`; the wire-format types belong there too, since they describe DADA's
transport shape rather than a canonical one.

## Status

Scaffolded and empty. Types arrive from `libs/ledger-live-common/src/dada-client/entities` in
`LIVE-35226`. Tracking epic: `LIVE-35223`.
20 changes: 20 additions & 0 deletions domain/entity/asset/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module.exports = {
testEnvironment: "node",
roots: ["<rootDir>/src"],
testMatch: ["**/*.test.ts"],
transform: {
"^.+\\.(t|j)sx?$": [
"@swc/jest",
{
jsc: {
target: "esnext",
},
},
],
},
coverageReporters: ["json", ["lcov", { file: "lcov.info", projectRoot: "../../../" }], "text"],
reporters: [
"default",
["jest-sonar", { outputName: "sonar-executionTests-report.xml", reportedFilePath: "absolute" }],
],
};
28 changes: 28 additions & 0 deletions domain/entity/asset/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "@domain/entity-asset",
"version": "0.1.0",
"private": true,
"description": "Domain entity for aggregated assets: crypto-asset metadata, network info and currency ordering",
"main": "src/index.ts",
"types": "src/index.ts",
"exports": {
".": "./src/index.ts",
"./package.json": "./package.json"
},
"sideEffects": false,
"scripts": {
"typecheck": "tsc --noEmit",
"test": "jest --passWithNoTests",
"test:watch": "jest --watch",
"coverage": "jest --coverage --passWithNoTests",
"unimported": "pnpm knip --directory ../../.. -W domain/entity/asset"
},
"devDependencies": {
"@jest/globals": "catalog:",
"@swc/core": "catalog:",
"@swc/jest": "catalog:",
"@types/jest": "catalog:",
"jest": "catalog:",
"typescript": "catalog:"
}
}
8 changes: 8 additions & 0 deletions domain/entity/asset/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "@domain/entity-asset",
"targets": {
"build": {
"executor": "nx:noop"
}
}
}
1 change: 1 addition & 0 deletions domain/entity/asset/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {};
13 changes: 13 additions & 0 deletions domain/entity/asset/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"lib": ["ES2022"],
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"noEmit": true,
"types": ["jest"]
},
"include": ["src/**/*"],
"exclude": ["node_modules", "lib"]
}
24 changes: 24 additions & 0 deletions domain/entity/interest-rate/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# @domain/entity-interest-rate

> [!CAUTION]
> **Status: UNSTABLE** — Being migrated out of `live-common`; the API is still being assembled.

Domain entity for asset interest rates. Owns the canonical rate shape and the set of rate types the
apps understand:

| Type | Describes |
| --- | --- |
| `InterestRate` | A rate for one currency: `currencyId`, `rate`, `type` and `fetchAt` |
| `ApyType` | The recognised rate kinds — `NRR`, `APY`, `APR` |

`ApyType` is an allowlist, and it is enforced at the consumer boundary: a rate arriving with any
other `type` is dropped rather than displayed. That filtering behaviour is characterized in the
consuming hook's tests.

Pure entity package: runtime schemas, inferred types, defaults and mocks. **No network calls, no
feature state.**

## Status

Scaffolded and empty. Types arrive from `libs/ledger-live-common/src/dada-client` in `LIVE-35226`.
Tracking epic: `LIVE-35223`.
20 changes: 20 additions & 0 deletions domain/entity/interest-rate/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module.exports = {
testEnvironment: "node",
roots: ["<rootDir>/src"],
testMatch: ["**/*.test.ts"],
transform: {
"^.+\\.(t|j)sx?$": [
"@swc/jest",
{
jsc: {
target: "esnext",
},
},
],
},
coverageReporters: ["json", ["lcov", { file: "lcov.info", projectRoot: "../../../" }], "text"],
reporters: [
"default",
["jest-sonar", { outputName: "sonar-executionTests-report.xml", reportedFilePath: "absolute" }],
],
};
28 changes: 28 additions & 0 deletions domain/entity/interest-rate/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "@domain/entity-interest-rate",
"version": "0.1.0",
"private": true,
"description": "Domain entity for asset interest rates: the canonical rate shape and its APY type",
"main": "src/index.ts",
"types": "src/index.ts",
"exports": {
".": "./src/index.ts",
"./package.json": "./package.json"
},
"sideEffects": false,
"scripts": {
"typecheck": "tsc --noEmit",
"test": "jest --passWithNoTests",
"test:watch": "jest --watch",
"coverage": "jest --coverage --passWithNoTests",
"unimported": "pnpm knip --directory ../../.. -W domain/entity/interest-rate"
},
"devDependencies": {
"@jest/globals": "catalog:",
"@swc/core": "catalog:",
"@swc/jest": "catalog:",
"@types/jest": "catalog:",
"jest": "catalog:",
"typescript": "catalog:"
}
}
8 changes: 8 additions & 0 deletions domain/entity/interest-rate/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "@domain/entity-interest-rate",
"targets": {
"build": {
"executor": "nx:noop"
}
}
}
1 change: 1 addition & 0 deletions domain/entity/interest-rate/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {};
13 changes: 13 additions & 0 deletions domain/entity/interest-rate/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"lib": ["ES2022"],
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"noEmit": true,
"types": ["jest"]
},
"include": ["src/**/*"],
"exclude": ["node_modules", "lib"]
}
45 changes: 45 additions & 0 deletions features/platform/assets-data/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# @features/platform-assets-data

> [!CAUTION]
> **Status: UNSTABLE** — Being migrated out of `live-common`; the API is still being assembled.

App-facing runtime for aggregated assets data. Wraps `@domain/api-assets-data` in React hooks and
owns the cache selectors, asset-discovery helpers and currency-selection logic.

This lives in `features/platform` rather than `features/flow` because several unrelated flows consume
it — Market, Portfolio, Global Search and the asset/network selectors — so it is a capability shared
across flows, not one journey's internals.

## Public API is hooks-only

Consumers get hooks plus the types those hooks return. The RTK Query endpoint objects, the cache
selectors and the `assetsDataApi` instance stay internal.

The one exception is the app composition root, which must register the API's reducer and middleware
in the store.

## `reducerPath` must stay `"assetsDataApi"`

The cache selectors read `state.assetsDataApi?.queries` **by string**, and Storybook stories preload
that same key directly. Renaming `reducerPath` produces **no type error** and silently returns
`undefined` for every market and interest-rate lookup.

## Cache selectors scan every cache entry

`createCurrencyDataSelector` walks RTK Query's internal cache layout:

```text
state.assetsDataApi.queries → entry.data.pages → page[collection][currencyId]
```

Two consequences, both characterized in the test suite and **described, not endorsed**:

- It has no access to the query args of the entries it walks, so a value cached by one query is
served to callers of any other.
- `pages` is typed `Array<Record<string, unknown>>`, so nothing in this traversal can produce a type
error. Tests are the only safety net here.

## Status

Scaffolded and empty. Code arrives from `libs/ledger-live-common/src/dada-client` in `LIVE-35227`.
Tracking epic: `LIVE-35223`.
Loading
Loading