@l.x/prices 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,145 @@
1
+ import { Ether, Token } from '@luxamm/sdk-core'
2
+ import type { TokenIdentifier } from '@l.x/prices'
3
+ import {
4
+ createPriceKey,
5
+ createPriceKeyFromToken,
6
+ filterValidTokens,
7
+ isCurrency,
8
+ isTokenIdentifier,
9
+ normalizeToken,
10
+ parsePriceKey,
11
+ toSubscriptionParams,
12
+ } from '@l.x/prices'
13
+ import { describe, expect, it } from 'vitest'
14
+
15
+ const WETH_ADDRESS = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'
16
+ const USDC_ADDRESS = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'
17
+
18
+ describe('tokenIdentifier utilities', () => {
19
+ describe('isCurrency', () => {
20
+ it('returns true for Token instances', () => {
21
+ const token = new Token(1, WETH_ADDRESS, 18, 'WETH', 'Wrapped Ether')
22
+ expect(isCurrency(token)).toBe(true)
23
+ })
24
+
25
+ it('returns false for TokenIdentifier', () => {
26
+ const identifier: TokenIdentifier = { chainId: 1, address: WETH_ADDRESS }
27
+ expect(isCurrency(identifier)).toBe(false)
28
+ })
29
+ })
30
+
31
+ describe('isTokenIdentifier', () => {
32
+ it('returns true for TokenIdentifier objects', () => {
33
+ const identifier: TokenIdentifier = { chainId: 1, address: WETH_ADDRESS }
34
+ expect(isTokenIdentifier(identifier)).toBe(true)
35
+ })
36
+
37
+ it('returns false for Token instances', () => {
38
+ const token = new Token(1, WETH_ADDRESS, 18, 'WETH', 'Wrapped Ether')
39
+ expect(isTokenIdentifier(token)).toBe(false)
40
+ })
41
+ })
42
+
43
+ describe('normalizeToken', () => {
44
+ it('normalizes TokenIdentifier with lowercase address', () => {
45
+ const identifier: TokenIdentifier = { chainId: 1, address: WETH_ADDRESS }
46
+ const result = normalizeToken(identifier)
47
+ expect(result).toEqual({
48
+ chainId: 1,
49
+ address: WETH_ADDRESS.toLowerCase(),
50
+ })
51
+ })
52
+
53
+ it('normalizes native currency to zero address', () => {
54
+ const eth = Ether.onChain(1)
55
+ const result = normalizeToken(eth)
56
+ expect(result).toEqual({
57
+ chainId: 1,
58
+ address: '0x0000000000000000000000000000000000000000',
59
+ })
60
+ })
61
+
62
+ it('normalizes Token instance', () => {
63
+ const token = new Token(1, WETH_ADDRESS, 18, 'WETH', 'Wrapped Ether')
64
+ const result = normalizeToken(token)
65
+ expect(result).toEqual({
66
+ chainId: 1,
67
+ address: WETH_ADDRESS.toLowerCase(),
68
+ })
69
+ })
70
+ })
71
+
72
+ describe('createPriceKey', () => {
73
+ it('creates key with format chainId-address', () => {
74
+ const key = createPriceKey(1, WETH_ADDRESS)
75
+ expect(key).toBe(`1-${WETH_ADDRESS.toLowerCase()}`)
76
+ })
77
+
78
+ it('lowercases address', () => {
79
+ const key = createPriceKey(1, '0xABC')
80
+ expect(key).toBe('1-0xabc')
81
+ })
82
+ })
83
+
84
+ describe('createPriceKeyFromToken', () => {
85
+ it('creates key from TokenIdentifier', () => {
86
+ const identifier: TokenIdentifier = { chainId: 1, address: WETH_ADDRESS }
87
+ const key = createPriceKeyFromToken(identifier)
88
+ expect(key).toBe(`1-${WETH_ADDRESS.toLowerCase()}`)
89
+ })
90
+
91
+ it('creates key from Token', () => {
92
+ const token = new Token(1, WETH_ADDRESS, 18, 'WETH', 'Wrapped Ether')
93
+ const key = createPriceKeyFromToken(token)
94
+ expect(key).toBe(`1-${WETH_ADDRESS.toLowerCase()}`)
95
+ })
96
+ })
97
+
98
+ describe('parsePriceKey', () => {
99
+ it('parses key back to TokenIdentifier', () => {
100
+ const key = `1-${WETH_ADDRESS.toLowerCase()}`
101
+ const result = parsePriceKey(key)
102
+ expect(result).toEqual({
103
+ chainId: 1,
104
+ address: WETH_ADDRESS.toLowerCase(),
105
+ })
106
+ })
107
+
108
+ it('returns null for missing address', () => {
109
+ expect(parsePriceKey('1')).toBeNull()
110
+ })
111
+
112
+ it('returns null for invalid chainId', () => {
113
+ expect(parsePriceKey('abc-0x123')).toBeNull()
114
+ })
115
+
116
+ it('returns null for empty key', () => {
117
+ expect(parsePriceKey('')).toBeNull()
118
+ })
119
+ })
120
+
121
+ describe('toSubscriptionParams', () => {
122
+ it('converts to subscription params format', () => {
123
+ const identifier: TokenIdentifier = { chainId: 1, address: WETH_ADDRESS }
124
+ const params = toSubscriptionParams(identifier)
125
+ expect(params).toEqual({
126
+ chainId: 1,
127
+ tokenAddress: WETH_ADDRESS.toLowerCase(),
128
+ })
129
+ })
130
+ })
131
+
132
+ describe('filterValidTokens', () => {
133
+ it('filters out invalid addresses', () => {
134
+ const tokens: TokenIdentifier[] = [
135
+ { chainId: 1, address: WETH_ADDRESS },
136
+ { chainId: 1, address: 'invalid' },
137
+ { chainId: 1, address: USDC_ADDRESS },
138
+ ]
139
+ const result = filterValidTokens(tokens)
140
+ expect(result).toHaveLength(2)
141
+ expect(result[0]?.address).toBe(WETH_ADDRESS.toLowerCase())
142
+ expect(result[1]?.address).toBe(USDC_ADDRESS.toLowerCase())
143
+ })
144
+ })
145
+ })
@@ -0,0 +1,111 @@
1
+ import type { Currency, Token } from '@luxamm/sdk-core'
2
+ import type { PriceKey, TokenIdentifier, TokenInput, TokenSubscriptionParams } from '@l.x/prices'
3
+ import { isEVMAddress } from 'utilities/src/addresses/evm/evm'
4
+
5
+ /** Address that represents native currencies on ETH, Arbitrum, etc. */
6
+ const DEFAULT_NATIVE_ADDRESS = '0x0000000000000000000000000000000000000000'
7
+
8
+ /**
9
+ * Type guard to check if input is a Currency object (from @luxamm/sdk-core).
10
+ * Currency objects have isNative and isToken properties from the SDK.
11
+ */
12
+ export function isCurrency(token: TokenInput): token is Currency {
13
+ return (
14
+ typeof token === 'object' &&
15
+ 'chainId' in token &&
16
+ typeof (token as Currency).chainId === 'number' &&
17
+ // Currency from SDK always has isNative property (true for native, false for tokens)
18
+ 'isNative' in token &&
19
+ typeof (token as Currency).isNative === 'boolean'
20
+ )
21
+ }
22
+
23
+ /**
24
+ * Type guard to check if input is a TokenIdentifier.
25
+ */
26
+ export function isTokenIdentifier(token: TokenInput): token is TokenIdentifier {
27
+ return (
28
+ typeof token === 'object' &&
29
+ 'chainId' in token &&
30
+ 'address' in token &&
31
+ typeof (token as TokenIdentifier).chainId === 'number' &&
32
+ typeof (token as TokenIdentifier).address === 'string' &&
33
+ !('isNative' in token)
34
+ )
35
+ }
36
+
37
+ /**
38
+ * Normalizes any token input to a TokenIdentifier.
39
+ */
40
+ export function normalizeToken(token: TokenInput): TokenIdentifier {
41
+ if (isTokenIdentifier(token)) {
42
+ return {
43
+ chainId: token.chainId,
44
+ address: token.address.toLowerCase(),
45
+ }
46
+ }
47
+
48
+ // It's a Currency
49
+ const currency = token as Currency
50
+ if (currency.isNative) {
51
+ return {
52
+ chainId: currency.chainId,
53
+ address: DEFAULT_NATIVE_ADDRESS,
54
+ }
55
+ }
56
+
57
+ // It's a Token
58
+ const tokenCurrency = currency as Token
59
+ return {
60
+ chainId: tokenCurrency.chainId,
61
+ address: tokenCurrency.address.toLowerCase(),
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Creates a unique price key from chainId and address.
67
+ * Format matches CurrencyId convention: "chainId-address"
68
+ */
69
+ export function createPriceKey(chainId: number, address: string): PriceKey {
70
+ return `${chainId}-${address.toLowerCase()}`
71
+ }
72
+
73
+ /**
74
+ * Creates a price key from a token input.
75
+ */
76
+ export function createPriceKeyFromToken(token: TokenInput): PriceKey {
77
+ const { chainId, address } = normalizeToken(token)
78
+ return createPriceKey(chainId, address)
79
+ }
80
+
81
+ /**
82
+ * Parses a price key back to chainId and address.
83
+ * Returns null if the key is malformed (missing chainId or address).
84
+ */
85
+ export function parsePriceKey(key: PriceKey): TokenIdentifier | null {
86
+ const [chainIdStr, address] = key.split('-')
87
+ const chainId = Number(chainIdStr)
88
+
89
+ if (Number.isNaN(chainId) || !address) {
90
+ return null
91
+ }
92
+
93
+ return { chainId, address }
94
+ }
95
+
96
+ /**
97
+ * Converts a TokenIdentifier to the format expected by the subscription API.
98
+ */
99
+ export function toSubscriptionParams(token: TokenIdentifier): TokenSubscriptionParams {
100
+ return {
101
+ chainId: token.chainId,
102
+ tokenAddress: token.address.toLowerCase(),
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Filters tokens to only those valid for subscription.
108
+ */
109
+ export function filterValidTokens(tokens: TokenInput[]): TokenIdentifier[] {
110
+ return tokens.map(normalizeToken).filter((t) => isEVMAddress(t.address))
111
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "extends": "../../config/tsconfig/app.json",
3
+ "include": [
4
+ "src/**/*.ts",
5
+ "src/**/*.tsx",
6
+ "src/**/*.json",
7
+ "src/global.d.ts"
8
+ ],
9
+ "exclude": [
10
+ "src/**/*.spec.ts",
11
+ "src/**/*.spec.tsx",
12
+ "src/**/*.test.ts",
13
+ "src/**/*.test.tsx"
14
+ ],
15
+ "compilerOptions": {
16
+ "noEmit": false,
17
+ "emitDeclarationOnly": true,
18
+ "types": ["node", "vitest/globals"],
19
+ "paths": {}
20
+ },
21
+ "references": [
22
+ {
23
+ "path": "../utilities"
24
+ },
25
+ {
26
+ "path": "../websocket"
27
+ },
28
+ {
29
+ "path": "../api"
30
+ },
31
+ {
32
+ "path": "../eslint-config"
33
+ }
34
+ ]
35
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "preserveSymlinks": true
5
+ },
6
+ "include": ["**/*.ts", "**/*.tsx", "**/*.json"],
7
+ "exclude": ["node_modules"]
8
+ }
@@ -0,0 +1,22 @@
1
+ import { defineConfig } from 'vitest/config'
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ environment: 'node',
6
+ server: {
7
+ deps: {
8
+ inline: ['@tanstack/react-query', 'partysocket'],
9
+ },
10
+ },
11
+ coverage: {
12
+ exclude: [
13
+ '**/__generated__/**',
14
+ '**/node_modules/**',
15
+ '**/dist/**',
16
+ '**/*.config.*',
17
+ '**/scripts/**',
18
+ '**/.eslintrc.*',
19
+ ],
20
+ },
21
+ },
22
+ })
package/index.d.ts DELETED
@@ -1 +0,0 @@
1
- export * from '@luxexchange/prices';
package/index.js DELETED
@@ -1 +0,0 @@
1
- module.exports = require('@luxexchange/prices');