Skip to content

feat: adds market insights logic in ai-digest-ctrl#7930

Open
zone-live wants to merge 12 commits intomainfrom
TSA-100-handle-market-insights-in-AiDigestController
Open

feat: adds market insights logic in ai-digest-ctrl#7930
zone-live wants to merge 12 commits intomainfrom
TSA-100-handle-market-insights-in-AiDigestController

Conversation

@zone-live
Copy link
Contributor

@zone-live zone-live commented Feb 13, 2026

Explanation

Extended AiDigestController with fetchMarketInsights and clearMarketInsights actions that search for AI generated market insights by asset. The service currently returns mock data but when the API is live, the real fetch to it will be added.

References

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've communicated my changes to consumers by updating changelogs for packages I've changed
  • I've introduced breaking changes in this PR and have prepared draft pull requests for clients and consumer packages to resolve them

Note

Medium Risk
Introduces new persisted controller state and a new network-facing API path with CAIP validation and cache eviction logic; issues could affect state persistence or API error handling but are covered by unit tests.

Overview
Adds Market Insights support to AiDigestController by introducing a persisted marketInsights cache in state plus new fetchMarketInsights (CAIP-19 validated, TTL + max-size eviction, clears cache on null/404) and clearMarketInsights APIs, and wires fetchMarketInsights into the messenger action handlers.

Extends AiDigestService with searchDigests(caipAssetType) to call GET /digests?caipAssetType=... (returning null on 404), adds the corresponding MarketInsights* types and a new INVALID_CAIP_ASSET_TYPE error constant, and updates tests/changelog/deps (adds @metamask/utils).

Written by Cursor Bugbot for commit 16624fa. This will update automatically on new commits. Configure here.

@zone-live
Copy link
Contributor Author

@metamaskbot publish-preview

@github-actions
Copy link
Contributor

Preview builds have been published. See these instructions for more information about preview builds.

Expand for full list of packages and versions.
{
  "@metamask-previews/account-tree-controller": "4.1.1-preview-f1938a560",
  "@metamask-previews/accounts-controller": "36.0.0-preview-f1938a560",
  "@metamask-previews/address-book-controller": "7.0.1-preview-f1938a560",
  "@metamask-previews/ai-controllers": "0.0.0-preview-f1938a560",
  "@metamask-previews/analytics-controller": "1.0.0-preview-f1938a560",
  "@metamask-previews/analytics-data-regulation-controller": "0.0.0-preview-f1938a560",
  "@metamask-previews/announcement-controller": "8.0.0-preview-f1938a560",
  "@metamask-previews/app-metadata-controller": "2.0.0-preview-f1938a560",
  "@metamask-previews/approval-controller": "8.0.0-preview-f1938a560",
  "@metamask-previews/assets-controller": "1.0.0-preview-f1938a560",
  "@metamask-previews/assets-controllers": "99.3.2-preview-f1938a560",
  "@metamask-previews/base-controller": "9.0.0-preview-f1938a560",
  "@metamask-previews/bridge-controller": "66.1.1-preview-f1938a560",
  "@metamask-previews/bridge-status-controller": "66.0.2-preview-f1938a560",
  "@metamask-previews/build-utils": "3.0.4-preview-f1938a560",
  "@metamask-previews/chain-agnostic-permission": "1.4.0-preview-f1938a560",
  "@metamask-previews/claims-controller": "0.4.2-preview-f1938a560",
  "@metamask-previews/composable-controller": "12.0.0-preview-f1938a560",
  "@metamask-previews/connectivity-controller": "0.1.0-preview-f1938a560",
  "@metamask-previews/controller-utils": "11.18.0-preview-f1938a560",
  "@metamask-previews/core-backend": "5.1.1-preview-f1938a560",
  "@metamask-previews/delegation-controller": "2.0.1-preview-f1938a560",
  "@metamask-previews/earn-controller": "11.1.0-preview-f1938a560",
  "@metamask-previews/eip-5792-middleware": "2.1.0-preview-f1938a560",
  "@metamask-previews/eip-7702-internal-rpc-middleware": "0.1.0-preview-f1938a560",
  "@metamask-previews/eip1193-permission-middleware": "1.0.3-preview-f1938a560",
  "@metamask-previews/ens-controller": "19.0.2-preview-f1938a560",
  "@metamask-previews/error-reporting-service": "3.0.1-preview-f1938a560",
  "@metamask-previews/eth-block-tracker": "15.0.1-preview-f1938a560",
  "@metamask-previews/eth-json-rpc-middleware": "23.1.0-preview-f1938a560",
  "@metamask-previews/eth-json-rpc-provider": "6.0.0-preview-f1938a560",
  "@metamask-previews/foundryup": "1.0.1-preview-f1938a560",
  "@metamask-previews/gas-fee-controller": "26.0.2-preview-f1938a560",
  "@metamask-previews/gator-permissions-controller": "1.1.2-preview-f1938a560",
  "@metamask-previews/json-rpc-engine": "10.2.2-preview-f1938a560",
  "@metamask-previews/json-rpc-middleware-stream": "8.0.8-preview-f1938a560",
  "@metamask-previews/keyring-controller": "25.1.0-preview-f1938a560",
  "@metamask-previews/logging-controller": "7.0.1-preview-f1938a560",
  "@metamask-previews/message-manager": "14.1.0-preview-f1938a560",
  "@metamask-previews/messenger": "0.3.0-preview-f1938a560",
  "@metamask-previews/multichain-account-service": "7.0.0-preview-f1938a560",
  "@metamask-previews/multichain-api-middleware": "1.2.6-preview-f1938a560",
  "@metamask-previews/multichain-network-controller": "3.0.3-preview-f1938a560",
  "@metamask-previews/multichain-transactions-controller": "7.0.1-preview-f1938a560",
  "@metamask-previews/name-controller": "9.0.0-preview-f1938a560",
  "@metamask-previews/network-controller": "29.0.0-preview-f1938a560",
  "@metamask-previews/network-enablement-controller": "4.1.0-preview-f1938a560",
  "@metamask-previews/notification-services-controller": "22.0.0-preview-f1938a560",
  "@metamask-previews/permission-controller": "12.2.0-preview-f1938a560",
  "@metamask-previews/permission-log-controller": "5.0.0-preview-f1938a560",
  "@metamask-previews/perps-controller": "0.0.0-preview-f1938a560",
  "@metamask-previews/phishing-controller": "16.2.0-preview-f1938a560",
  "@metamask-previews/polling-controller": "16.0.2-preview-f1938a560",
  "@metamask-previews/preferences-controller": "22.1.0-preview-f1938a560",
  "@metamask-previews/profile-metrics-controller": "3.0.1-preview-f1938a560",
  "@metamask-previews/profile-sync-controller": "27.1.0-preview-f1938a560",
  "@metamask-previews/ramps-controller": "8.0.0-preview-f1938a560",
  "@metamask-previews/rate-limit-controller": "7.0.0-preview-f1938a560",
  "@metamask-previews/remote-feature-flag-controller": "4.0.0-preview-f1938a560",
  "@metamask-previews/sample-controllers": "4.0.2-preview-f1938a560",
  "@metamask-previews/seedless-onboarding-controller": "8.0.0-preview-f1938a560",
  "@metamask-previews/selected-network-controller": "26.0.2-preview-f1938a560",
  "@metamask-previews/shield-controller": "5.0.1-preview-f1938a560",
  "@metamask-previews/signature-controller": "39.0.2-preview-f1938a560",
  "@metamask-previews/storage-service": "1.0.0-preview-f1938a560",
  "@metamask-previews/subscription-controller": "6.0.0-preview-f1938a560",
  "@metamask-previews/transaction-controller": "62.17.0-preview-f1938a560",
  "@metamask-previews/transaction-pay-controller": "14.0.0-preview-f1938a560",
  "@metamask-previews/user-operation-controller": "41.0.2-preview-f1938a560"
}

@zone-live zone-live marked this pull request as ready for review February 18, 2026 10:36
@zone-live zone-live requested review from a team as code owners February 18, 2026 10:36
@zone-live
Copy link
Contributor Author

@metamaskbot publish-preview

@github-actions
Copy link
Contributor

Preview builds have been published. See these instructions for more information about preview builds.

Expand for full list of packages and versions.
{
  "@metamask-previews/account-tree-controller": "4.1.1-preview-a196307b6",
  "@metamask-previews/accounts-controller": "36.0.0-preview-a196307b6",
  "@metamask-previews/address-book-controller": "7.0.1-preview-a196307b6",
  "@metamask-previews/ai-controllers": "0.0.0-preview-a196307b6",
  "@metamask-previews/analytics-controller": "1.0.0-preview-a196307b6",
  "@metamask-previews/analytics-data-regulation-controller": "0.0.0-preview-a196307b6",
  "@metamask-previews/announcement-controller": "8.0.0-preview-a196307b6",
  "@metamask-previews/app-metadata-controller": "2.0.0-preview-a196307b6",
  "@metamask-previews/approval-controller": "8.0.0-preview-a196307b6",
  "@metamask-previews/assets-controller": "2.0.0-preview-a196307b6",
  "@metamask-previews/assets-controllers": "99.4.0-preview-a196307b6",
  "@metamask-previews/base-controller": "9.0.0-preview-a196307b6",
  "@metamask-previews/bridge-controller": "67.0.0-preview-a196307b6",
  "@metamask-previews/bridge-status-controller": "67.0.0-preview-a196307b6",
  "@metamask-previews/build-utils": "3.0.4-preview-a196307b6",
  "@metamask-previews/chain-agnostic-permission": "1.4.0-preview-a196307b6",
  "@metamask-previews/claims-controller": "0.4.2-preview-a196307b6",
  "@metamask-previews/client-controller": "0.0.0-preview-a196307b6",
  "@metamask-previews/composable-controller": "12.0.0-preview-a196307b6",
  "@metamask-previews/connectivity-controller": "0.1.0-preview-a196307b6",
  "@metamask-previews/controller-utils": "11.18.0-preview-a196307b6",
  "@metamask-previews/core-backend": "5.1.1-preview-a196307b6",
  "@metamask-previews/delegation-controller": "2.0.1-preview-a196307b6",
  "@metamask-previews/earn-controller": "11.1.0-preview-a196307b6",
  "@metamask-previews/eip-5792-middleware": "2.1.0-preview-a196307b6",
  "@metamask-previews/eip-7702-internal-rpc-middleware": "0.1.0-preview-a196307b6",
  "@metamask-previews/eip1193-permission-middleware": "1.0.3-preview-a196307b6",
  "@metamask-previews/ens-controller": "19.0.2-preview-a196307b6",
  "@metamask-previews/error-reporting-service": "3.0.1-preview-a196307b6",
  "@metamask-previews/eth-block-tracker": "15.0.1-preview-a196307b6",
  "@metamask-previews/eth-json-rpc-middleware": "23.1.0-preview-a196307b6",
  "@metamask-previews/eth-json-rpc-provider": "6.0.0-preview-a196307b6",
  "@metamask-previews/foundryup": "1.0.1-preview-a196307b6",
  "@metamask-previews/gas-fee-controller": "26.0.2-preview-a196307b6",
  "@metamask-previews/gator-permissions-controller": "2.0.0-preview-a196307b6",
  "@metamask-previews/json-rpc-engine": "10.2.2-preview-a196307b6",
  "@metamask-previews/json-rpc-middleware-stream": "8.0.8-preview-a196307b6",
  "@metamask-previews/keyring-controller": "25.1.0-preview-a196307b6",
  "@metamask-previews/logging-controller": "7.0.1-preview-a196307b6",
  "@metamask-previews/message-manager": "14.1.0-preview-a196307b6",
  "@metamask-previews/messenger": "0.3.0-preview-a196307b6",
  "@metamask-previews/multichain-account-service": "7.0.0-preview-a196307b6",
  "@metamask-previews/multichain-api-middleware": "1.2.6-preview-a196307b6",
  "@metamask-previews/multichain-network-controller": "3.0.3-preview-a196307b6",
  "@metamask-previews/multichain-transactions-controller": "7.0.1-preview-a196307b6",
  "@metamask-previews/name-controller": "9.0.0-preview-a196307b6",
  "@metamask-previews/network-controller": "29.0.0-preview-a196307b6",
  "@metamask-previews/network-enablement-controller": "4.1.0-preview-a196307b6",
  "@metamask-previews/notification-services-controller": "22.0.0-preview-a196307b6",
  "@metamask-previews/permission-controller": "12.2.0-preview-a196307b6",
  "@metamask-previews/permission-log-controller": "5.0.0-preview-a196307b6",
  "@metamask-previews/perps-controller": "0.0.0-preview-a196307b6",
  "@metamask-previews/phishing-controller": "16.2.0-preview-a196307b6",
  "@metamask-previews/polling-controller": "16.0.2-preview-a196307b6",
  "@metamask-previews/preferences-controller": "22.1.0-preview-a196307b6",
  "@metamask-previews/profile-metrics-controller": "3.0.1-preview-a196307b6",
  "@metamask-previews/profile-sync-controller": "27.1.0-preview-a196307b6",
  "@metamask-previews/ramps-controller": "8.0.0-preview-a196307b6",
  "@metamask-previews/rate-limit-controller": "7.0.0-preview-a196307b6",
  "@metamask-previews/remote-feature-flag-controller": "4.0.0-preview-a196307b6",
  "@metamask-previews/sample-controllers": "4.0.2-preview-a196307b6",
  "@metamask-previews/seedless-onboarding-controller": "8.0.0-preview-a196307b6",
  "@metamask-previews/selected-network-controller": "26.0.2-preview-a196307b6",
  "@metamask-previews/shield-controller": "5.0.1-preview-a196307b6",
  "@metamask-previews/signature-controller": "39.0.3-preview-a196307b6",
  "@metamask-previews/storage-service": "1.0.0-preview-a196307b6",
  "@metamask-previews/subscription-controller": "6.0.0-preview-a196307b6",
  "@metamask-previews/transaction-controller": "62.17.0-preview-a196307b6",
  "@metamask-previews/transaction-pay-controller": "15.1.0-preview-a196307b6",
  "@metamask-previews/user-operation-controller": "41.0.2-preview-a196307b6"
}

@zone-live zone-live changed the title feat: adds market insights logic feat: adds market insights logic in ai-digest-ctrl Feb 18, 2026
*/
export type MarketInsightsEntry = {
/** CAIP-19 asset identifier */
caip19Id: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WYT of using the type CaipAssetType?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yap, will update 👍🏼


export type AiDigestControllerState = {
digests: Record<string, DigestEntry>;
marketInsights: Record<string, MarketInsightsEntry>;
Copy link
Contributor

@xavier-brochard xavier-brochard Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we actually need to manage cache ourselves? 🤔

The API returns responses with a header 'Cache-Control': 'public, max-age=600', so we get cache management at browser-level out of the box (assuming there's a browser in mobile. Is that a thing?)

Copy link
Contributor Author

@zone-live zone-live Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah we're not sure if its a thing for mobile, and since it didn't hurt to add this simple management, its there. So I would keep controller-level cache, likely in a RN app the fetch is not browser cached like in the web since app behavior changes between iOS and Android.
View it as an other layer of control, for max entries, persisted state, TTL, etc.

async fetchMarketInsights(
caip19Id: string,
): Promise<MarketInsightsReport | null> {
const existing = this.state.marketInsights[caip19Id];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should ensure that caip19Id is valid (using isCaipAssetType). This will avoid an unnecssary call to the API in case an invalid caip asset type is passed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well pointed, will update 👍🏼

* @returns The market insights report, or `null` if none exists.
*/
async fetchMarketInsights(
caip19Id: string,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think of naming it caipAssetType? It would avoid confusion with caip asset id

Comment on lines 43 to 46
export type AiDigestControllerClearMarketInsightsAction = {
type: `${typeof controllerName}:clearMarketInsights`;
handler: AiDigestController['clearMarketInsights'];
};
Copy link
Contributor

@xavier-brochard xavier-brochard Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does we really need to expose an action to clear the internal state? 🤔
It's a leaky abstraction

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, we don't, will update 👍🏼

const data: MarketInsightsApiResponse = await response.json();

// API currently returns an envelope with the report under `digest`.
// Keep backward compatibility if the report is returned directly.
Copy link
Contributor

@xavier-brochard xavier-brochard Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need ensure backward compat here. The feature is not live, so it just feels like free tech debt as makes the code and type MarketInsightsApiResponse unnecessarily complex. WYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had the same doubt :D and you just confirmed it, so yeah, I'll remove it. 👍🏼

Comment on lines +54 to +55
const response = await fetch(
`${this.#baseUrl}/digests?caipAssetType=${encodeURIComponent(caip19Id)}`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add some response schema validation here, or leave it for the frontend?

An argument in favour of leaving it to the frontend is that any changes in the API would just be forwarded by core, and only the frontend would need to be updated — since core isn't checking the schema at runtime, it wouldn't throw an error.

That said, core is using the MarketInsightsReport type, so a breaking API change would still require a change in core anyway. Even though the integration wouldn't break at runtime, we'd be operating with incorrect static types, which kind of undermines the benefit of skipping validation here.

Comment on lines 23 to 43
async fetchDigest(assetId: string): Promise<DigestData> {
const response = await fetch(
`${this.#baseUrl}/digests/assets/${encodeURIComponent(assetId)}/latest`,
);

if (!response.ok) {
throw new Error(
`${AiDigestControllerErrorMessage.API_REQUEST_FAILED}: ${response.status}`,
);
}

const data: DigestData = await response.json();

if (!data.success) {
throw new Error(
data.error ?? AiDigestControllerErrorMessage.API_RETURNED_ERROR,
);
}

return data;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can delete this right @zone-live ? We're only using searchDigests?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was added by Devin, but meant as just a placeholder I think, so yes... we can always add it back later if neded.

@socket-security
Copy link

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​metamask/​utils@​11.10.0981009493100

View full report

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

* @param caip19Id - The CAIP-19 identifier of the asset.
* @returns The market insights report, or `null` if none exists (404).
*/
async searchDigests(caip19Id: string): Promise<MarketInsightsReport | null> {
Copy link
Contributor

@joaosantos15 joaosantos15 Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: I think this function should be renamed to searchDigest (singular) .

The issue is that the endpoint suggests this returns a collection [] which is then filtered by the query parameter, but actually we're only using a query parameter because of CAIP19 not being safe to pass as a URL parameter and the endpoint always returns a single object, not a collection.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, it's not really a NIT, I think it's important but not a blocker, we can fix this later.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants