-
-
Notifications
You must be signed in to change notification settings - Fork 287
feat(perps): layer HyperLiquid annotation names & keyword search (TAT-3338) #9086
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
abretonc7s
wants to merge
9
commits into
main
Choose a base branch
from
TAT-3338-feat-perps-annotation-market-names
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 2 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
decec17
feat(perps-controller): layer HyperLiquid annotation names & keyword …
abretonc7s 418b567
docs(perps-controller): fill in PR number in changelog for #9086
abretonc7s 87e1f38
feat(perps-controller): wire perpConciseAnnotations into market data …
abretonc7s 6887228
docs(perps-controller): document why HYPERLIQUID_ASSET_NAMES is required
abretonc7s 263d447
Merge remote-tracking branch 'origin/main' into TAT-3338-feat-perps-a…
abretonc7s 1d785df
fix(perps-controller): match keywords in filterMarketsByQuery (#9086)
abretonc7s 5c5a5ed
Merge remote-tracking branch 'origin/main' into TAT-3338-feat-perps-a…
abretonc7s 58149b3
fix(perps-controller): oxfmt formatting and move #9086 changelog entr…
abretonc7s 13463fb
Merge remote-tracking branch 'origin/main' into TAT-3338-feat-perps-a…
abretonc7s File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
115 changes: 115 additions & 0 deletions
115
packages/perps-controller/src/utils/marketAnnotations.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| /** | ||
| * HyperLiquid perp-annotation resolution (TAT-3338). | ||
| * | ||
| * HyperLiquid exposes optional, deployer-set annotations for perpetual assets via | ||
| * the `perpConciseAnnotations` info endpoint (one bulk call returning a | ||
| * `[coin, { category, displayName?, keywords? }]` tuple per asset). These provide | ||
| * a `displayName` (a frontend-friendly name to use instead of the raw L1 ticker) | ||
| * and `keywords` (search hints). | ||
| * | ||
| * Annotations are optional and deployer-controlled, so they do not replace the | ||
| * curated, first-party {@link HYPERLIQUID_ASSET_NAMES} map — they layer beneath | ||
| * it. Name resolution precedence is: | ||
| * | ||
| * curated map > annotation `displayName` > raw ticker symbol | ||
| * | ||
| * The raw-symbol fallback is applied downstream by | ||
| * {@link getHyperLiquidAssetName} (which returns the symbol for any key absent | ||
| * from the supplied name map), so these helpers only need to merge the curated | ||
| * map over the annotation display names. | ||
| * | ||
| * Portable: no platform- or SDK-specific imports. The input type mirrors the | ||
| * `@nktkas/hyperliquid` `PerpConciseAnnotationsResponse` shape so the provider | ||
| * can pass the SDK response through directly, while keeping this module | ||
| * dependency-free and unit-testable. | ||
| */ | ||
| import { HYPERLIQUID_ASSET_NAMES } from '../constants/hyperLiquidConfig'; | ||
|
|
||
| /** | ||
| * A single concise annotation for a perpetual asset, mirroring the | ||
| * `@nktkas/hyperliquid` `perpConciseAnnotations` entry value. | ||
| */ | ||
| export type PerpConciseAnnotation = { | ||
| /** Classification category assigned to the perpetual. */ | ||
| category: string; | ||
| /** Display name for frontends to use instead of the L1 name (optional). */ | ||
| displayName?: string; | ||
| /** Keywords used as hints to match against searches (optional). */ | ||
| keywords?: string[]; | ||
| }; | ||
|
|
||
| /** | ||
| * A `[coin, annotation]` tuple as returned by `perpConciseAnnotations`. | ||
| */ | ||
| export type PerpConciseAnnotationEntry = [ | ||
| coin: string, | ||
| annotation: PerpConciseAnnotation, | ||
| ]; | ||
|
|
||
| /** | ||
| * Build a `symbol → human-readable name` map from concise annotations, with the | ||
| * curated map taking precedence. | ||
| * | ||
| * Annotation display names fill in only where the curated map has no entry, so | ||
| * first-party curated names always win. Symbols present in neither map are | ||
| * omitted, leaving the downstream {@link getHyperLiquidAssetName} symbol fallback | ||
| * to apply. The result is suitable to pass as the `assetNames` argument of | ||
| * `transformMarketData`. | ||
| * | ||
| * @param annotations - Concise annotations (e.g. the `perpConciseAnnotations` | ||
| * response). When undefined/empty, the curated map is returned unchanged. | ||
| * @param curatedNames - Curated first-party names that override annotations | ||
| * (defaults to the bundled {@link HYPERLIQUID_ASSET_NAMES}). | ||
| * @returns A merged name map where curated entries override annotation display | ||
| * names. | ||
| */ | ||
| export function mergeAssetNamesWithAnnotations( | ||
| annotations: PerpConciseAnnotationEntry[] | undefined, | ||
| curatedNames: Record<string, string> = HYPERLIQUID_ASSET_NAMES, | ||
| ): Record<string, string> { | ||
| if (!annotations?.length) { | ||
| return { ...curatedNames }; | ||
| } | ||
|
|
||
| const merged: Record<string, string> = {}; | ||
| for (const [coin, annotation] of annotations) { | ||
| const displayName = annotation?.displayName?.trim(); | ||
| if (displayName) { | ||
| merged[coin] = displayName; | ||
| } | ||
| } | ||
|
|
||
| // Curated names override annotation display names (first-party wins). | ||
| return { ...merged, ...curatedNames }; | ||
| } | ||
|
|
||
| /** | ||
| * Build a `symbol → keywords` map from concise annotations. | ||
| * | ||
| * Only assets with at least one non-empty keyword are included. The result is | ||
| * suitable to pass as the `assetKeywords` argument of `transformMarketData`, | ||
| * which surfaces them on `PerpsMarketData.keywords` for ranked search. | ||
| * | ||
| * @param annotations - Concise annotations (e.g. the `perpConciseAnnotations` | ||
| * response). | ||
| * @returns A map of asset symbol to its trimmed, non-empty keywords. | ||
| */ | ||
| export function extractAssetKeywords( | ||
| annotations: PerpConciseAnnotationEntry[] | undefined, | ||
| ): Record<string, string[]> { | ||
| const result: Record<string, string[]> = {}; | ||
| if (!annotations?.length) { | ||
| return result; | ||
| } | ||
|
|
||
| for (const [coin, annotation] of annotations) { | ||
| const keywords = annotation?.keywords | ||
| ?.map((keyword) => keyword.trim()) | ||
| .filter((keyword) => keyword.length > 0); | ||
| if (keywords?.length) { | ||
| result[coin] = keywords; | ||
| } | ||
| } | ||
|
|
||
| return result; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
89 changes: 89 additions & 0 deletions
89
packages/perps-controller/tests/src/utils/marketAnnotations.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| import { HYPERLIQUID_ASSET_NAMES } from '../../../src/constants/hyperLiquidConfig'; | ||
| import type { PerpConciseAnnotationEntry } from '../../../src/utils/marketAnnotations'; | ||
| import { | ||
| mergeAssetNamesWithAnnotations, | ||
| extractAssetKeywords, | ||
| } from '../../../src/utils/marketAnnotations'; | ||
|
|
||
| describe('mergeAssetNamesWithAnnotations', () => { | ||
| it('returns a copy of the curated map when there are no annotations', () => { | ||
| expect(mergeAssetNamesWithAnnotations(undefined)).toStrictEqual( | ||
| HYPERLIQUID_ASSET_NAMES, | ||
| ); | ||
| expect(mergeAssetNamesWithAnnotations([])).toStrictEqual( | ||
| HYPERLIQUID_ASSET_NAMES, | ||
| ); | ||
| // Must be a copy, not the shared reference. | ||
| expect(mergeAssetNamesWithAnnotations(undefined)).not.toBe( | ||
| HYPERLIQUID_ASSET_NAMES, | ||
| ); | ||
| }); | ||
|
|
||
| it('lets curated names win over annotation display names', () => { | ||
| const curated = { BTC: 'Bitcoin' }; | ||
| const annotations: PerpConciseAnnotationEntry[] = [ | ||
| ['BTC', { category: 'crypto', displayName: 'Bitcoin (annotation)' }], | ||
| ]; | ||
| expect(mergeAssetNamesWithAnnotations(annotations, curated)).toStrictEqual({ | ||
| BTC: 'Bitcoin', | ||
| }); | ||
| }); | ||
|
|
||
| it('fills gaps from annotation display names where curated has no entry', () => { | ||
| const curated = { BTC: 'Bitcoin' }; | ||
| const annotations: PerpConciseAnnotationEntry[] = [ | ||
| ['flx:DOGE', { category: 'crypto', displayName: 'Dogecoin' }], | ||
| ]; | ||
| expect(mergeAssetNamesWithAnnotations(annotations, curated)).toStrictEqual({ | ||
| BTC: 'Bitcoin', | ||
| 'flx:DOGE': 'Dogecoin', | ||
| }); | ||
| }); | ||
|
|
||
| it('ignores annotations with a missing or blank display name', () => { | ||
| const annotations: PerpConciseAnnotationEntry[] = [ | ||
| ['NOPE', { category: 'crypto' }], | ||
| ['BLANK', { category: 'crypto', displayName: ' ' }], | ||
| ['OK', { category: 'crypto', displayName: ' Trimmed ' }], | ||
| ]; | ||
| expect(mergeAssetNamesWithAnnotations(annotations, {})).toStrictEqual({ | ||
| OK: 'Trimmed', | ||
| }); | ||
| }); | ||
|
|
||
| it('defaults the curated map to the bundled HYPERLIQUID_ASSET_NAMES', () => { | ||
| const annotations: PerpConciseAnnotationEntry[] = [ | ||
| ['BTC', { category: 'crypto', displayName: 'Should not override' }], | ||
| ]; | ||
| const result = mergeAssetNamesWithAnnotations(annotations); | ||
| expect(result.BTC).toBe(HYPERLIQUID_ASSET_NAMES.BTC); | ||
| }); | ||
| }); | ||
|
|
||
| describe('extractAssetKeywords', () => { | ||
| it('returns an empty map when there are no annotations', () => { | ||
| expect(extractAssetKeywords(undefined)).toStrictEqual({}); | ||
| expect(extractAssetKeywords([])).toStrictEqual({}); | ||
| }); | ||
|
|
||
| it('collects trimmed, non-empty keywords per asset', () => { | ||
| const annotations: PerpConciseAnnotationEntry[] = [ | ||
| ['BTC', { category: 'crypto', keywords: [' digital gold ', 'btc'] }], | ||
| ]; | ||
| expect(extractAssetKeywords(annotations)).toStrictEqual({ | ||
| BTC: ['digital gold', 'btc'], | ||
| }); | ||
| }); | ||
|
|
||
| it('omits assets with no keywords or only blank keywords', () => { | ||
| const annotations: PerpConciseAnnotationEntry[] = [ | ||
| ['NONE', { category: 'crypto' }], | ||
| ['EMPTY', { category: 'crypto', keywords: [] }], | ||
| ['BLANK', { category: 'crypto', keywords: [' ', ''] }], | ||
| ['OK', { category: 'crypto', keywords: ['valid'] }], | ||
| ]; | ||
| expect(extractAssetKeywords(annotations)).toStrictEqual({ | ||
| OK: ['valid'], | ||
| }); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.